Skip to content

Commit

Permalink
Fleshed out input system, added camera controls (keyboard and basic t…
Browse files Browse the repository at this point in the history
…ouchpad) #1
  • Loading branch information
aeplay committed Jun 22, 2016
1 parent 79bebde commit 991f989
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 29 deletions.
3 changes: 3 additions & 0 deletions citybound/Cargo.toml
Expand Up @@ -3,6 +3,9 @@ name = "citybound"
version = "0.1.0"
authors = ["Anselm Eickhoff <anselm.eickhoff@gmail.com>"]

[dependencies]
nalgebra = "*"

[dependencies.world_record]
path = "../world_record/"

Expand Down
113 changes: 113 additions & 0 deletions 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<InputCommand>),
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::<InputCommand>::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<Vec<InputCommand>> {
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<InputCommand> {
let mut inputs = Vec::<InputCommand>::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;
}
47 changes: 25 additions & 22 deletions 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;
Expand All @@ -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::<Vec<input::InputCommand>>();
let (renderer_to_simulation, from_renderer) = channel::<()>();
let (to_renderer, from_simulation) = channel::<monet::Scene>();

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::<models::State>::new(
PathBuf::from("savegames/dev"),
vec! [Box::new(steps::tick)],
vec! [Box::new(input_step), Box::new(steps::tick)],
vec! [Box::new(renderer_listener)]
);

Expand All @@ -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);
}
}
50 changes: 45 additions & 5 deletions 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)]
Expand All @@ -25,9 +27,9 @@ pub struct Car {
}

pub struct Lane {
start: [f32; 2],
direction: [f32; 2],
end: [f32; 2],
start: Point2<f32>,
direction: Point2<f32>,
end: Point2<f32>,
next: LinkedList<LaneConnection>,
previous: LinkedList<LaneConnection>,
first_overlap: LinkedList<LaneOverlap>
Expand Down Expand Up @@ -80,6 +82,34 @@ pub struct Plan {
lanes_to_destroy: LinkedList<LanePlanEntry>
}

#[derive(Copy, Clone)]
pub struct Eye {
pub target: Point2<f32>,
pub azimuth: f32,
pub inclination: f32,
pub distance: f32
}

impl Eye {
pub fn direction(&self) -> Vector3<f32> {
let inclined_vector = Rotation3::<f32>::new(Vector3::x() * -self.inclination).rotate(&Vector3::<f32>::y());
Rotation3::new(Vector3::z() * self.azimuth).rotate(&inclined_vector)
}

pub fn direction_2d(&self) -> Vector2<f32> {
Rotation2::new(Vector1::new(-self.azimuth)).rotate(&Vector2::y())
}

pub fn right_direction_2d(&self) -> Vector2<f32> {
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<Core, u32>,
pub cars: FutureRecordCollection<Car>,
Expand All @@ -89,7 +119,8 @@ pub struct State {
pub lane_overlap_groups: FutureRecordCollection<LaneOverlapGroup>,
pub intersections: FutureRecordCollection<Intersection>,
pub lane_plan_entries: FutureRecordCollection<LanePlanEntry>,
pub plans: FutureRecordCollection<Plan>
pub plans: FutureRecordCollection<Plan>,
pub ui_state: UIState
}

impl FutureState for State {
Expand All @@ -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
}
}
}
}

Expand All @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions 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::<f32>::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
}
1 change: 0 additions & 1 deletion 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,
Expand Down
2 changes: 1 addition & 1 deletion monet/src/lib.rs
Expand Up @@ -35,7 +35,7 @@ impl Thing {
}

pub struct Scene {
eye: Eye,
pub eye: Eye,
pub things: HashMap<&'static str, Thing>,
pub debug_text: String
}
Expand Down

0 comments on commit 991f989

Please sign in to comment.