From f3c25b63702a85354c694bc3bc0abcc1434b064f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 25 Oct 2023 21:49:38 -0400 Subject: [PATCH] Rotate camera with mouse --- examples/testbed/src/free_fly_camera.rs | 155 ++++++++++++++---------- examples/testbed/src/main.rs | 4 +- src/ecs/components/free_fly_camera.rs | 28 +++++ src/ecs/components/mod.rs | 3 +- src/game.rs | 31 ++--- src/input.rs | 2 - src/render/window.rs | 49 +++++++- 7 files changed, 175 insertions(+), 97 deletions(-) create mode 100644 src/ecs/components/free_fly_camera.rs diff --git a/examples/testbed/src/free_fly_camera.rs b/examples/testbed/src/free_fly_camera.rs index 846f527..590c850 100644 --- a/examples/testbed/src/free_fly_camera.rs +++ b/examples/testbed/src/free_fly_camera.rs @@ -1,14 +1,21 @@ use std::ops::Deref; -use edict::{World, Component}; -use lyra_engine::{math::{Vec3, Angle, Quat}, input::{InputButtons, KeyCode, MouseMotion}, ecs::{SimpleSystem, components::camera::CameraComponent, EventQueue}, game::Game, plugin::Plugin}; +use edict::{Component, World}; +use lyra_engine::{ + ecs::{components::camera::CameraComponent, EventQueue, SimpleSystem}, + game::Game, + input::{InputButtons, KeyCode, MouseMotion}, + math::{Angle, Quat, Vec3}, + plugin::Plugin, +}; use tracing::debug; #[derive(Clone, Component)] pub struct FreeFlyCamera { pub speed: f32, pub look_speed: f32, - pub look_with_keys: bool + pub mouse_sensitivity: f32, + pub look_with_keys: bool, } impl Default for FreeFlyCamera { @@ -16,17 +23,19 @@ impl Default for FreeFlyCamera { Self { speed: 0.07, look_speed: 0.01, - look_with_keys: false + mouse_sensitivity: 0.03, + look_with_keys: false, } } } impl FreeFlyCamera { - pub fn new(speed: f32, look_speed: f32, look_with_keys: bool) -> Self { + pub fn new(speed: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool) -> Self { Self { speed, look_speed, - look_with_keys + mouse_sensitivity, + look_with_keys, } } } @@ -37,77 +46,93 @@ impl SimpleSystem for FreeFlyCameraController { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { let mut camera_rot = Vec3::default(); - let keys = world.get_resource::>() + let events = world + .get_resource_mut::() + .and_then(|q| q.read_events::()); + + let keys = world + .get_resource::>() .map(|r| r.deref().clone()); - if keys.is_none() { - return Ok(()); - } - let keys = keys.unwrap(); - if keys.is_pressed(KeyCode::Left) { - camera_rot.y += 1.0; + if let Some(keys) = keys.as_ref() { + if keys.is_pressed(KeyCode::Left) { + camera_rot.y += 1.0; + } + + if keys.is_pressed(KeyCode::Right) { + camera_rot.y -= 1.0; + } + + if keys.is_pressed(KeyCode::Up) { + camera_rot.x += 1.0; + } + + if keys.is_pressed(KeyCode::Down) { + camera_rot.x -= 1.0; + } + + if keys.is_pressed(KeyCode::E) { + camera_rot.z -= 1.0; + } + + if keys.is_pressed(KeyCode::Q) { + camera_rot.z += 1.0; + } } - if keys.is_pressed(KeyCode::Right) { - camera_rot.y -= 1.0; - } - - if keys.is_pressed(KeyCode::Up) { - camera_rot.x += 1.0; - } - - if keys.is_pressed(KeyCode::Down) { - camera_rot.x -= 1.0; - } - - if keys.is_pressed(KeyCode::E) { - camera_rot.z -= 1.0; - } - - if keys.is_pressed(KeyCode::Q) { - camera_rot.z += 1.0; - } - - let camera_rot = camera_rot.normalize(); - - for (cam, fly) in world.query_mut::<(&mut CameraComponent, &mut FreeFlyCamera)>().iter_mut() { + for (cam, fly) in world + .query_mut::<(&mut CameraComponent, &mut FreeFlyCamera)>() + .iter_mut() + { let forward = cam.transform.forward(); let left = cam.transform.left(); let up = cam.transform.up(); - let mut velocity = Vec3::ZERO; + // handle camera movement + if let Some(keys) = keys.as_ref() { + let mut velocity = Vec3::ZERO; + if keys.is_pressed(KeyCode::A) { + velocity -= left; + } - if keys.is_pressed(KeyCode::A) { - velocity -= left; - } - - if keys.is_pressed(KeyCode::D) { - velocity += left; - } - - if keys.is_pressed(KeyCode::W) { - velocity += forward; - } - - if keys.is_pressed(KeyCode::S) { - velocity -= forward; + if keys.is_pressed(KeyCode::D) { + velocity += left; + } + + if keys.is_pressed(KeyCode::W) { + velocity += forward; + } + + if keys.is_pressed(KeyCode::S) { + velocity -= forward; + } + + if keys.is_pressed(KeyCode::C) { + velocity += up; + } + + if keys.is_pressed(KeyCode::Z) { + velocity -= up; + } + + if velocity != Vec3::ZERO { + cam.transform.translation += velocity.normalize() * fly.speed; + } } - if keys.is_pressed(KeyCode::C) { - velocity += up; - } - - if keys.is_pressed(KeyCode::Z) { - velocity -= up; + // handle camera rotation + if let Some(mut events) = events.clone() { + while let Some(motion) = events.pop_front() { + camera_rot.x -= motion.delta.y * fly.mouse_sensitivity; + camera_rot.y -= motion.delta.x * fly.mouse_sensitivity; + } } - if velocity != Vec3::ZERO { - cam.transform.translation += velocity.normalize() * fly.speed; - } - - if !camera_rot.is_nan() { - let look_velocity = camera_rot.normalize() * fly.look_speed; - cam.transform.rotation *= Quat::from_rotation_x(look_velocity.x) * Quat::from_rotation_y(look_velocity.y) * Quat::from_rotation_z(look_velocity.z); + if camera_rot != Vec3::ZERO { + let look_velocity = camera_rot * fly.look_speed; + cam.transform.rotation *= Quat::from_rotation_x(look_velocity.x) + * Quat::from_rotation_y(look_velocity.y) + * Quat::from_rotation_z(look_velocity.z); } } @@ -119,4 +144,4 @@ impl Plugin for FreeFlyCameraController { fn setup(&self, game: &mut Game) { game.with_system("free_fly_camera_controller", FreeFlyCameraController, &[]); } -} \ No newline at end of file +} diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 38bfec0..b7ab30b 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -23,11 +23,11 @@ pub const INDICES: &[u16] = &[ #[async_std::main] async fn main() { let setup_sys = |world: &mut World| -> anyhow::Result<()> { - /* { + { let mut window_options = world.get_resource_mut::>().unwrap(); window_options.cursor_grab = CursorGrabMode::Confined; window_options.cursor_visible = false; - } */ + } let mut resman = world.get_resource_mut::().unwrap(); //let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); diff --git a/src/ecs/components/free_fly_camera.rs b/src/ecs/components/free_fly_camera.rs new file mode 100644 index 0000000..9b24679 --- /dev/null +++ b/src/ecs/components/free_fly_camera.rs @@ -0,0 +1,28 @@ +use edict::Component; + +use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode}; + +#[derive(Clone, Component)] +pub struct FreeFlyCamera { + pub transform: Transform, + pub fov: Angle, + pub mode: CameraProjectionMode, + pub speed: f32, +} + +impl Default for FreeFlyCamera { + fn default() -> Self { + Self::new() + } +} + +impl FreeFlyCamera { + pub fn new() -> Self { + Self { + transform: Transform::default(), + fov: Angle::Degrees(45.0), + mode: CameraProjectionMode::Perspective, + speed: 1.5, + } + } +} \ No newline at end of file diff --git a/src/ecs/components/mod.rs b/src/ecs/components/mod.rs index 4a474dd..6976828 100755 --- a/src/ecs/components/mod.rs +++ b/src/ecs/components/mod.rs @@ -1,4 +1,5 @@ pub mod mesh; pub mod model; pub mod transform; -pub mod camera; \ No newline at end of file +pub mod camera; +pub mod free_fly_camera; \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index cc6f6aa..038aa42 100755 --- a/src/game.rs +++ b/src/game.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, collections::VecDeque}; use async_std::task::block_on; -use tracing::{info, error, Level}; +use tracing::{info, error, Level, debug}; use tracing_appender::non_blocking; use tracing_subscriber::{ layer::SubscriberExt, @@ -12,7 +12,7 @@ use tracing_subscriber::{ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}}; -use crate::{render::renderer::{Renderer, BasicRenderer}, input_event::InputEvent, ecs::{SimpleSystem, SystemDispatcher, EventQueue, Events}, plugin::Plugin}; +use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input_event::InputEvent, ecs::{SimpleSystem, SystemDispatcher, EventQueue, Events}, plugin::Plugin, change_tracker::Ct}; pub struct Controls<'a> { pub world: &'a mut edict::World, @@ -91,21 +91,6 @@ impl GameLoop { Some(ControlFlow::Exit) }, - // TODO: Create system for this? or maybe merge into input system, idk - InputEvent::CursorEntered { .. } => { - let state = self.world.with_resource(WindowState::new); - state.is_cursor_inside_window = true; - - None - }, - - InputEvent::CursorLeft { .. } => { - let state = self.world.with_resource(WindowState::new); - state.is_cursor_inside_window = false; - - None - } - _ => { //debug!("Got unhandled input event: \"{:?}\"", event); @@ -122,16 +107,20 @@ impl GameLoop { *control_flow = ControlFlow::Poll; match event { Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta } } => { + //debug!("motion: {delta:?}"); // convert a MouseMotion event to an InputEvent // make sure that the mouse is inside the window and the mouse has focus before reporting mouse motion - let trigger = matches!(self.world.get_resource::(), Some(window_state) - if window_state.is_focused && window_state.is_cursor_inside_window); + /* let trigger = matches!(self.world.get_resource::(), Some(window_state) + if window_state.is_focused && window_state.is_cursor_inside_window); */ + + let trigger = matches!(self.world.get_resource::>(), Some(window) + if window.focused && window.cursor_inside_window); if trigger { - let event_queue = self.world.with_resource(Events::::new); + let mut event_queue = self.world.get_resource_mut::().unwrap(); let input_event = InputEvent::MouseMotion { device_id, delta, }; - event_queue.push_back(input_event); + event_queue.trigger_event(input_event); } }, Event::WindowEvent { diff --git a/src/input.rs b/src/input.rs index b17a729..46e61c5 100755 --- a/src/input.rs +++ b/src/input.rs @@ -258,8 +258,6 @@ impl InputSystem { delta: Vec2::new(delta.0 as f32, delta.1 as f32) }; - debug!("delta: {:?}", delta); - event_queue.trigger_event(delta); }, InputEvent::CursorMoved { position, .. } => { diff --git a/src/render/window.rs b/src/render/window.rs index cd147d6..82700c5 100644 --- a/src/render/window.rs +++ b/src/render/window.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, collections::VecDeque}; use glam::{Vec2, IVec2}; use tracing::{warn, error}; @@ -6,7 +6,7 @@ use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, Ph pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel}; -use crate::{plugin::Plugin, change_tracker::Ct}; +use crate::{plugin::Plugin, change_tracker::Ct, ecs::EventQueue, input_event::InputEvent}; #[derive(Default, Clone)] pub enum WindowMode { @@ -171,6 +171,12 @@ pub struct WindowOptions { /// Change the window level. /// This is just a hint to the OS, and the system could ignore it. pub level: WindowLevel, + + /// Get/set the window's focused state. + pub focused: bool, + + /// Get whether or not the cursor is inside the window. + pub cursor_inside_window: bool, } impl Default for WindowOptions { @@ -198,6 +204,8 @@ impl Default for WindowOptions { title: "Lyra Engine Game".to_string(), icon: None, level: WindowLevel::Normal, + focused: false, + cursor_inside_window: false, } } } @@ -252,10 +260,11 @@ fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result Ok(()) } -/// if the window is set to confine the cursor, and the cursor is invisible, -/// set the cursor position to the center of the screen. +/// if the window is set to confine the cursor, the cursor is invisible, +/// and the window is focused, set the cursor position to the center of the screen. fn center_mouse(window: &Window, options: &WindowOptions) { - if options.cursor_grab == CursorGrabMode::Confined && !options.cursor_visible { + if options.cursor_grab == CursorGrabMode::Confined && !options.cursor_visible + && options.focused { let size = window.inner_size(); let middle = PhysicalPosition { x: size.width / 2, @@ -269,11 +278,14 @@ fn window_updater_system(world: &mut edict::World) -> anyhow::Result<()> { if let (Some(window), Some(opts)) = (world.get_resource::>(), world.get_resource::>()) { // if the options changed, update the window if opts.peek_changed() { - drop(opts); // avoid attempting to get a RefMut when we already have a Ref out. + drop(opts); // drop the Ref, we're about to get a RefMut // now we can get it mutable, this will trigger the ChangeTracker, so it will be reset at the end of this scope. let mut opts = world.get_resource_mut::>().unwrap(); + if opts.focused { + window.focus_window(); + } window.set_content_protected(opts.content_protected); set_cursor_grab(&window, &mut opts.cursor_grab)?; match window.set_cursor_hittest(opts.cursor_hittest) { @@ -324,6 +336,31 @@ fn window_updater_system(world: &mut edict::World) -> anyhow::Result<()> { center_mouse(&window, &opts); } else { + drop(opts); // drop the Ref, we're about to get a RefMut + let mut opts = world.get_resource_mut::>().unwrap(); + + if let Some(event_queue) = world.get_resource_mut::() { + if let Some(mut events) = event_queue.read_events::() { + while let Some(ev) = events.pop_front() { + match ev { + InputEvent::CursorEntered { .. } => { + opts.cursor_inside_window = true; + }, + InputEvent::CursorLeft { .. } => { + opts.cursor_inside_window = false; + }, + _ => {}, + } + } + } + } + + // update the stored state of the window to match the actual window + + opts.focused = window.has_focus(); + + opts.reset(); + center_mouse(&window, &opts); } }