diff --git a/Cargo.lock b/Cargo.lock index b75daea..bc40061 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,11 +1748,20 @@ dependencies = [ "hecs", "raidillon_core", "raidillon_ecs", + "raidillon_input", "raidillon_render", "raidillon_ui", "winit", ] +[[package]] +name = "raidillon_input" +version = "0.1.0" +dependencies = [ + "glam", + "winit", +] + [[package]] name = "raidillon_render" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 67268bd..27dcb24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ members = [ "raidillon_render", "raidillon_ui", "raidillon_game", + "raidillon_input", ] diff --git a/raidillon_game/Cargo.toml b/raidillon_game/Cargo.toml index 5c88109..a5aad2f 100644 --- a/raidillon_game/Cargo.toml +++ b/raidillon_game/Cargo.toml @@ -14,3 +14,4 @@ raidillon_ecs = { path = "../raidillon_ecs" } raidillon_ui = { path = "../raidillon_ui" } raidillon_core = { path = "../raidillon_core" } hecs = "0.10.5" +raidillon_input = { path = "../raidillon_input" } diff --git a/raidillon_game/src/main.rs b/raidillon_game/src/main.rs index fc9d741..17d665c 100644 --- a/raidillon_game/src/main.rs +++ b/raidillon_game/src/main.rs @@ -5,6 +5,17 @@ use raidillon_core::Time; use raidillon_ecs::Transform; use raidillon_render::{Camera, GliumRenderer, gltf_loader, ECSRenderer}; use raidillon_ui::Gui; +use raidillon_input::{Input, FPSCameraController}; +use winit::keyboard::KeyCode; +use winit::window::CursorGrabMode; + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +enum Action { + MoveForward, + MoveBackward, + MoveLeft, + MoveRight, +} fn main() -> Result<()> { let event_loop = glium::winit::event_loop::EventLoop::builder() @@ -26,6 +37,17 @@ fn main() -> Result<()> { // Dear ImGui integration let mut gui = Gui::new(&display, &window)?; + let mut input = Input::::new(); + input.map_key(KeyCode::KeyW, Action::MoveForward); + input.map_key(KeyCode::KeyS, Action::MoveBackward); + input.map_key(KeyCode::KeyA, Action::MoveLeft); + input.map_key(KeyCode::KeyD, Action::MoveRight); + + let mut camera_controller = FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0)); + + let mut cursor_grabbed = false; + let mut attempted_initial_grab = false; + let mut time = Time::new(); let object_ent = { @@ -66,6 +88,8 @@ fn main() -> Result<()> { gui.handle_event(&window, &event); + input.handle_event(&event); + match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => el.exit(), @@ -110,6 +134,51 @@ fn main() -> Result<()> { }, Event::AboutToWait => { time.tick(); + + if !attempted_initial_grab { + attempted_initial_grab = true; + if window + .set_cursor_grab(CursorGrabMode::Confined) + .or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)) + .is_ok() + { + window.set_cursor_visible(false); + cursor_grabbed = true; + } + } + + { + let dt = time.delta_seconds(); + camera_controller.update( + &input, + dt, + cursor_grabbed, + (Action::MoveForward, Action::MoveBackward, Action::MoveLeft, Action::MoveRight), + ); + + if input.key_pressed(KeyCode::Escape) { + if cursor_grabbed { + let _ = window.set_cursor_grab(CursorGrabMode::None); + window.set_cursor_visible(true); + cursor_grabbed = false; + } else if window + .set_cursor_grab(CursorGrabMode::Confined) + .or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)) + .is_ok() + { + window.set_cursor_visible(false); + cursor_grabbed = true; + } + } + + if let Ok(mut cam) = ecsr.world.query_one_mut::<&mut Camera>(camera_ent) { + cam.eye = camera_controller.position; + cam.center = camera_controller.position + camera_controller.front(); + } + } + + input.end_frame(); + gui.prepare_frame(&window); window.request_redraw(); } diff --git a/raidillon_input/Cargo.toml b/raidillon_input/Cargo.toml new file mode 100644 index 0000000..1f1700d --- /dev/null +++ b/raidillon_input/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "raidillon_input" +version = "0.1.0" +edition = "2021" + +[dependencies] +winit = "0.30" +glam = "0.30.4" \ No newline at end of file diff --git a/raidillon_input/src/camera.rs b/raidillon_input/src/camera.rs new file mode 100644 index 0000000..00b4fea --- /dev/null +++ b/raidillon_input/src/camera.rs @@ -0,0 +1,73 @@ +use glam::Vec3; +use std::hash::Hash; + +use super::Input; + +#[derive(Debug, Clone)] +pub struct FPSCameraController { + pub position: Vec3, + yaw: f32, + pitch: f32, + pub speed: f32, + pub sensitivity: f32, +} + +impl FPSCameraController { + pub fn new(position: Vec3) -> Self { + Self { + position, + yaw: -90.0, + pitch: 0.0, + speed: 3.0, + sensitivity: 0.1, + } + } + + pub fn update(&mut self, + input: &Input, + dt: f32, + mouse_enabled: bool, + actions: (A, A, A, A)) + where + A: Copy + Eq + Hash, + { + let (forward, backward, left, right) = actions; + + // Mouse look + if mouse_enabled { + let (dx, dy) = input.mouse_delta(); + self.yaw += dx as f32 * self.sensitivity; + self.pitch -= dy as f32 * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + } + + // Movement + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + let frame_speed = self.speed * dt; + + if input.action_held(forward) { + self.position += front * frame_speed; + } + if input.action_held(backward) { + self.position -= front * frame_speed; + } + if input.action_held(left) { + self.position -= right_vec * frame_speed; + } + if input.action_held(right) { + self.position += right_vec * frame_speed; + } + } + + pub fn front(&self) -> Vec3 { + let yaw_rad = self.yaw.to_radians(); + let pitch_rad = self.pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ) + .normalize() + } +} \ No newline at end of file diff --git a/raidillon_input/src/lib.rs b/raidillon_input/src/lib.rs new file mode 100644 index 0000000..25f5a95 --- /dev/null +++ b/raidillon_input/src/lib.rs @@ -0,0 +1,107 @@ +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + +use winit::event::{DeviceEvent, ElementState, Event, WindowEvent}; +use winit::keyboard::{KeyCode, PhysicalKey}; + +pub mod camera; +pub use camera::FPSCameraController; + +pub struct Input { + pressed_keys: HashSet, + pressed_once: HashSet, + + keymap: HashMap, + pressed_actions: HashSet, + pressed_actions_once: HashSet, + + mouse_delta: (f64, f64), +} + +impl Input { + pub fn new() -> Self { + Self { + pressed_keys: HashSet::new(), + pressed_once: HashSet::new(), + keymap: HashMap::new(), + pressed_actions: HashSet::new(), + pressed_actions_once: HashSet::new(), + mouse_delta: (0.0, 0.0), + } + } + + pub fn map_key(&mut self, key: KeyCode, action: A) { + self.keymap.insert(key, action); + } + + pub fn clear_keymap(&mut self) { + self.keymap.clear(); + self.pressed_actions.clear(); + self.pressed_actions_once.clear(); + } + + pub fn handle_event(&mut self, event: &Event) { + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { event, .. } => { + let key_code = match event.physical_key { + PhysicalKey::Code(code) => code, + _ => return, + }; + + match event.state { + ElementState::Pressed => { + self.pressed_keys.insert(key_code); + self.pressed_once.insert(key_code); + + if let Some(&action) = self.keymap.get(&key_code) { + self.pressed_actions.insert(action); + self.pressed_actions_once.insert(action); + } + } + ElementState::Released => { + self.pressed_keys.remove(&key_code); + + if let Some(&action) = self.keymap.get(&key_code) { + self.pressed_actions.remove(&action); + } + } + } + } + _ => {} + }, + Event::DeviceEvent { event, .. } => match event { + DeviceEvent::MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + } + _ => {} + }, + _ => {} + } + } + + pub fn key_held(&self, key: KeyCode) -> bool { + self.pressed_keys.contains(&key) + } + pub fn key_pressed(&self, key: KeyCode) -> bool { + self.pressed_once.contains(&key) + } + + pub fn action_held(&self, action: A) -> bool { + self.pressed_actions.contains(&action) + } + pub fn action_pressed(&self, action: A) -> bool { + self.pressed_actions_once.contains(&action) + } + + pub fn mouse_delta(&self) -> (f64, f64) { + self.mouse_delta + } + + pub fn end_frame(&mut self) { + self.mouse_delta = (0.0, 0.0); + self.pressed_once.clear(); + self.pressed_actions_once.clear(); + } +} \ No newline at end of file