diff --git a/citybound/Cargo.toml b/citybound/Cargo.toml index c522d17b..92d37a3e 100644 --- a/citybound/Cargo.toml +++ b/citybound/Cargo.toml @@ -3,6 +3,9 @@ name = "citybound" version = "0.1.0" authors = ["Anselm Eickhoff "] +[dependencies] +nalgebra = "*" + [dependencies.world_record] path = "../world_record/" diff --git a/citybound/src/input.rs b/citybound/src/input.rs new file mode 100644 index 00000000..90b0ed8c --- /dev/null +++ b/citybound/src/input.rs @@ -0,0 +1,113 @@ +use ::monet::glium::glutin::{Event, ElementState, VirtualKeyCode as Key, MouseScrollDelta}; +use ::monet::glium; +use ::std::f32::consts::PI; + +pub enum InputCommand { + RotateEye(f32), + TiltEye(f32), + MoveEyeForwards(f32), + MoveEyeSideways(f32), + LetEyeApproach(f32) +} + +pub enum InputResult { + ContinueWithInputCommands(Vec), + Exit +} + +#[derive(Default)] +pub struct InputState { + rotating_eye_left: bool, + rotating_eye_right: bool, + tilting_eye_up: bool, + tilting_eye_down: bool, + moving_eye_forward: bool, + moving_eye_backward: bool, + moving_eye_left: bool, + moving_eye_right: bool, + eye_approaching: bool, + eye_receding: bool +} + +pub fn interpret_events (events: glium::backend::glutin_backend::PollEventsIter, input_state: &mut InputState) -> InputResult { + let mut immediate_inputs = Vec::::new(); + for event in events { + match event { + Event::KeyboardInput(_, _, Some(Key::Escape)) | + Event::Closed => return InputResult::Exit, + _ => match interpret_event(event, input_state) { + Some(mut input) => immediate_inputs.append(&mut input), + None => {} + }, + } + } + + let mut inputs = immediate_inputs; + inputs.append(&mut recurring_inputs(input_state)); + + InputResult::ContinueWithInputCommands(inputs) +} + +fn interpret_event (event: Event, input_state: &mut InputState) -> Option> { + match event { + Event::KeyboardInput(element_state, _, Some(key_code)) => { + let pressed = element_state == ElementState::Pressed; + match key_code { + Key::Q => {input_state.rotating_eye_left = pressed; None}, + Key::E => {input_state.rotating_eye_right = pressed; None}, + Key::R => {input_state.tilting_eye_up = pressed; None}, + Key::F => {input_state.tilting_eye_down = pressed; None}, + Key::W => {input_state.moving_eye_forward = pressed; None}, + Key::S => {input_state.moving_eye_backward = pressed; None}, + Key::A => {input_state.moving_eye_left = pressed; None}, + Key::D => {input_state.moving_eye_right = pressed; None}, + Key::T => {input_state.eye_approaching = pressed; None}, + Key::G => {input_state.eye_receding = pressed; None} + _ => None + }}, + Event::MouseWheel(MouseScrollDelta::PixelDelta(x, y), _) => Some(vec![ + InputCommand::MoveEyeForwards(y * 0.005), InputCommand::MoveEyeSideways(x * -0.005) + ]), + _ => None + } +} + +fn recurring_inputs (input_state: &InputState) -> Vec { + let mut inputs = Vec::::new(); + if input_state.rotating_eye_left {inputs.push(InputCommand::RotateEye(-0.02))}; + if input_state.rotating_eye_right {inputs.push(InputCommand::RotateEye(0.02))}; + if input_state.tilting_eye_up {inputs.push(InputCommand::TiltEye(0.02))}; + if input_state.tilting_eye_down {inputs.push(InputCommand::TiltEye(-0.02))}; + if input_state.moving_eye_forward {inputs.push(InputCommand::MoveEyeForwards(0.05))}; + if input_state.moving_eye_backward {inputs.push(InputCommand::MoveEyeForwards(-0.05))}; + if input_state.moving_eye_right {inputs.push(InputCommand::MoveEyeSideways(0.05))}; + if input_state.moving_eye_left {inputs.push(InputCommand::MoveEyeSideways(-0.05))}; + if input_state.eye_approaching {inputs.push(InputCommand::LetEyeApproach(0.05))}; + if input_state.eye_receding {inputs.push(InputCommand::LetEyeApproach(-0.05))}; + inputs +} + +pub fn apply_input_command (command: InputCommand, past: &::models::State, future: &mut ::models::State) { + let eye = past.ui_state.eye; + let mut new_eye = future.ui_state.eye; + match command { + InputCommand::RotateEye(amount) => { + new_eye.azimuth += amount; + }, + InputCommand::TiltEye(amount) => { + new_eye.inclination += amount; + new_eye.inclination = new_eye.inclination.max(0.0).min(PI / 2.1); + }, + InputCommand::MoveEyeForwards(amount) => { + new_eye.target += eye.direction_2d() * amount; + }, + InputCommand::MoveEyeSideways(amount) => { + new_eye.target += eye.right_direction_2d() * amount; + }, + InputCommand::LetEyeApproach(amount) => { + new_eye.distance -= amount; + new_eye.distance = new_eye.distance.max(0.3); + } + } + future.ui_state.eye = new_eye; +} \ No newline at end of file diff --git a/citybound/src/main.rs b/citybound/src/main.rs index 047bbad2..e2d38fb0 100644 --- a/citybound/src/main.rs +++ b/citybound/src/main.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] extern crate world_record; extern crate monet; +extern crate nalgebra; use std::path::PathBuf; use std::thread; @@ -11,31 +12,34 @@ use monet::glium::glutin; mod models; mod steps; mod simulation; -#[path = "../resources/car.rs"] -mod car; +mod renderer; +mod input; fn main() { - let (to_simulation, from_renderer) = channel::<()>(); + let (input_to_simulation, from_input) = channel::>(); + let (renderer_to_simulation, from_renderer) = channel::<()>(); let (to_renderer, from_simulation) = channel::(); + let input_step = move |past: &models::State, future: &mut models::State| { + loop {match from_input.try_recv() { + Ok(inputs) => for input in inputs { + input::apply_input_command(input, past, future) + }, + Err(_) => {break} + }} + }; + let renderer_listener = move |past: &models::State, future: &models::State| { match from_renderer.try_recv() { - Ok(_) => { - println!("creating renderer state..."); - let mut scene = monet::Scene::new(); - scene.things.insert("car", car::create()); - scene.debug_text = format!("Simulation frame: {}", past.core.header.ticks); - to_renderer.send(scene).unwrap(); - }, + Ok(_) => {to_renderer.send(renderer::render(past, future)).unwrap();}, Err(_) => {} - }; - + }; }; thread::Builder::new().name("simulation".to_string()).spawn(|| { let mut simulation = simulation::Simulation::::new( PathBuf::from("savegames/dev"), - vec! [Box::new(steps::tick)], + vec! [Box::new(input_step), Box::new(steps::tick)], vec! [Box::new(renderer_listener)] ); @@ -52,21 +56,20 @@ fn main() { .with_vsync().build_glium().unwrap(); let renderer = monet::Renderer::new(&window); + let mut input_state = input::InputState::default(); 'main: loop { - // loop over events - for event in window.poll_events() { - match event { - glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) | - glutin::Event::Closed => break 'main, - _ => {}, + match input::interpret_events(window.poll_events(), &mut input_state) { + input::InputResult::Exit => break 'main, + input::InputResult::ContinueWithInputCommands(inputs) => { + input_to_simulation.send(inputs).unwrap() } } - - to_simulation.send(()).unwrap(); + + renderer_to_simulation.send(()).unwrap(); let scene = from_simulation.recv().unwrap(); println!("rendering..."); - renderer.draw(scene) + renderer.draw(scene); } } \ No newline at end of file diff --git a/citybound/src/models.rs b/citybound/src/models.rs index e4917259..f7472c48 100644 --- a/citybound/src/models.rs +++ b/citybound/src/models.rs @@ -1,5 +1,7 @@ use world_record::{ID, FutureState, FutureRecordCollection, GrowableBuffer}; use std::path::PathBuf; +use std::f32::consts::PI; +use nalgebra::{Point2, Vector1, Vector2, Vector3, Rotation3, Rotation2, Rotate}; // use std::ops::Range; #[derive(Default, Clone)] @@ -25,9 +27,9 @@ pub struct Car { } pub struct Lane { - start: [f32; 2], - direction: [f32; 2], - end: [f32; 2], + start: Point2, + direction: Point2, + end: Point2, next: LinkedList, previous: LinkedList, first_overlap: LinkedList @@ -80,6 +82,34 @@ pub struct Plan { lanes_to_destroy: LinkedList } +#[derive(Copy, Clone)] +pub struct Eye { + pub target: Point2, + pub azimuth: f32, + pub inclination: f32, + pub distance: f32 +} + +impl Eye { + pub fn direction(&self) -> Vector3 { + let inclined_vector = Rotation3::::new(Vector3::x() * -self.inclination).rotate(&Vector3::::y()); + Rotation3::new(Vector3::z() * self.azimuth).rotate(&inclined_vector) + } + + pub fn direction_2d(&self) -> Vector2 { + Rotation2::new(Vector1::new(-self.azimuth)).rotate(&Vector2::y()) + } + + pub fn right_direction_2d(&self) -> Vector2 { + Rotation2::new(Vector1::new(-PI / 2.0)).rotate(&self.direction_2d()) + } +} + +#[derive(Copy, Clone)] +pub struct UIState { + pub eye: Eye +} + pub struct State { pub core: GrowableBuffer, pub cars: FutureRecordCollection, @@ -89,7 +119,8 @@ pub struct State { pub lane_overlap_groups: FutureRecordCollection, pub intersections: FutureRecordCollection, pub lane_plan_entries: FutureRecordCollection, - pub plans: FutureRecordCollection + pub plans: FutureRecordCollection, + pub ui_state: UIState } impl FutureState for State { @@ -103,7 +134,15 @@ impl FutureState for State { lane_overlap_groups: FutureRecordCollection::new(path.join("lane_overlap_groups")), intersections: FutureRecordCollection::new(path.join("intersections")), lane_plan_entries: FutureRecordCollection::new(path.join("lane_plan_entries")), - plans: FutureRecordCollection::new(path.join("plans")) + plans: FutureRecordCollection::new(path.join("plans")), + ui_state: UIState{ + eye: Eye{ + target: Point2::new(0.0, 0.0), + azimuth: PI / 4.0, + inclination: PI / 4.0, + distance: 7.0 + } + } } } @@ -117,6 +156,7 @@ impl FutureState for State { self.intersections.overwrite_with(&other.intersections); self.lane_plan_entries.overwrite_with(&other.lane_plan_entries); self.plans.overwrite_with(&other.plans); + self.ui_state = other.ui_state; } fn materialize(&mut self) { diff --git a/citybound/src/renderer.rs b/citybound/src/renderer.rs new file mode 100644 index 00000000..6470f6ab --- /dev/null +++ b/citybound/src/renderer.rs @@ -0,0 +1,27 @@ +#[path = "../resources/car.rs"] +mod car; + +extern crate nalgebra; +use nalgebra::{Vector3, Point3}; + +// coordinate system: +// Z = UP +// Y = NORTH +// X = EAST + +pub fn render (past: &::models::State, future: &::models::State) -> ::monet::Scene { + println!("creating renderer state..."); + let mut scene = ::monet::Scene::new(); + scene.things.insert("car", car::create()); + scene.debug_text = format!("Simulation frame: {}", past.core.header.ticks); + + let eye = past.ui_state.eye; + scene.eye.target = Point3::new(eye.target.x, eye.target.y, 0.0); + scene.eye.up = Vector3::::z(); + scene.eye.position = scene.eye.target + eye.distance * Vector3::new( + eye.inclination.cos() * -eye.azimuth.sin(), + eye.inclination.cos() * -eye.azimuth.cos(), + eye.inclination.sin() + ); + scene +} \ No newline at end of file diff --git a/citybound/src/simulation.rs b/citybound/src/simulation.rs index 4d21eeb0..be853a6d 100644 --- a/citybound/src/simulation.rs +++ b/citybound/src/simulation.rs @@ -1,7 +1,6 @@ use world_record::{FutureState}; use std::time::{Duration, Instant}; use std::path::PathBuf; -use std::thread; struct TimingInfo { target_ticks_per_second: u32, diff --git a/monet/src/lib.rs b/monet/src/lib.rs index 1a60f7f3..bd107a2c 100644 --- a/monet/src/lib.rs +++ b/monet/src/lib.rs @@ -35,7 +35,7 @@ impl Thing { } pub struct Scene { - eye: Eye, + pub eye: Eye, pub things: HashMap<&'static str, Thing>, pub debug_text: String }