diff --git a/Cargo.lock b/Cargo.lock index 25cb3cb..e1b6adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.30.5" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" +checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" [[package]] name = "glium" @@ -1444,6 +1444,7 @@ dependencies = [ name = "raidillon_engine" version = "0.1.0" dependencies = [ + "glam", "hecs", "indexmap", "raidillon_assets", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 726ac44..98202e4 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -10,3 +10,4 @@ raidillon_platform = { path = "../platform" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" +glam = "0.30.8" \ No newline at end of file diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 82b1f91..68f17e0 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -5,11 +5,13 @@ use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; use raidillon_core::DebugUIBuffer; use raidillon_core::engine::EngineTrait; +use crate::input::InputState; pub struct Engine { pub scene_manager: SceneManager, pub system_manager: SystemManager, debug_ui_buffer: Rc>, + input_state: Rc>, } impl EngineTrait for Engine { @@ -21,6 +23,7 @@ impl EngineTrait for Engine { scene_manager, system_manager, debug_ui_buffer: Rc::new(RefCell::new(DebugUIBuffer::new())), + input_state: Default::default(), } } @@ -35,6 +38,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; // Engine Loading Stage 2: load world @@ -50,6 +54,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { @@ -62,6 +67,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { @@ -70,10 +76,13 @@ impl EngineTrait for Engine { } fn handle_event(&mut self, platform_context: PlatformContext) { + self.input_state.borrow_mut().handle_event(&platform_context.current_event); + let mut ctx = SystemContext { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { diff --git a/engine/src/input.rs b/engine/src/input.rs new file mode 100644 index 0000000..27eb6fc --- /dev/null +++ b/engine/src/input.rs @@ -0,0 +1,69 @@ +use std::collections::HashSet; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::{KeyCode, PhysicalKey}; + +/// A utility to help with buffering input. +/// Meant to be plugged into systems. +#[derive(Default, Clone, Debug)] +pub struct InputState { + held_keys: HashSet, + held_mouse: HashSet, +} + +impl InputState { + fn new() -> Self { + Default::default() + } + + pub fn handle_event(&mut self, event: &Event<()>) { + if let Event::WindowEvent { event, .. } = event { + match event { + // Keyboard + WindowEvent::KeyboardInput { event: key_event, .. } => { + if let PhysicalKey::Code(code) = key_event.physical_key { + match key_event.state { + ElementState::Pressed => { + self.held_keys.insert(code); + } + ElementState::Released => { + self.held_keys.remove(&code); + } + } + } + } + + // Mouse + WindowEvent::MouseInput { state, button, .. } => { + match state { + ElementState::Pressed => { + self.held_mouse.insert(*button); + } + ElementState::Released => { + self.held_mouse.remove(button); + } + } + } + + WindowEvent::Focused(focused) => { + if !*focused { + self.clear(); + } + } + _ => {} + } + } + } + + pub fn key_held(&self, code: KeyCode) -> bool { + self.held_keys.contains(&code) + } + + pub fn mouse_held(&self, button: MouseButton) -> bool { + self.held_mouse.contains(&button) + } + + pub fn clear(&mut self) { + self.held_keys.clear(); + self.held_mouse.clear(); + } +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index b3a2dc8..18ce5fd 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,4 +1,6 @@ pub mod engine; pub mod system; +mod input; +pub mod systems; pub use crate::engine::Engine; diff --git a/engine/src/system.rs b/engine/src/system.rs index 1ab49bf..b863a75 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -5,12 +5,13 @@ use raidillon_platform::PlatformContext; use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; +use crate::input::InputState; pub struct SystemContext<'a> { - // TODO: time delta etc. pub scene: &'a mut Scene, pub platform_context: PlatformContext, pub debug_ui_buffer: Rc>, + pub input_state: Rc>, } pub trait System { diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs new file mode 100644 index 0000000..cbe14b6 --- /dev/null +++ b/engine/src/systems/fps_camera.rs @@ -0,0 +1,114 @@ +use crate::system::{System, SystemContext}; +use glam::{Quat, Vec3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::PhysicalKey; +use winit::window::CursorGrabMode; +use raidillon_assets::model_path; +use raidillon_platform::Camera; + +pub struct FPSCameraSystem { + mouse_delta: (f64, f64), + mouse_enabled: bool, + position: Vec3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, +} + +impl Default for FPSCameraSystem { + fn default() -> Self { + Self { + mouse_delta: Default::default(), + mouse_enabled: Default::default(), + position: Vec3::new(0.0, 0.0, 2.0), + yaw: -90.0, + pitch: 0.0, + speed: 3.0, + sensitivity: 0.1, + } + } +} + +impl System for FPSCameraSystem { + fn load_world(&mut self, ctx: &mut SystemContext) { + ctx.scene.world.spawn((Camera { + eye: Vec3::new(0.0, 0.0, 2.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: ctx.platform_context.frame_width / ctx.platform_context.frame_height, + znear: 0.1, + zfar: 100.0, + },)); + } + + fn handle_event(&mut self, ctx: &mut SystemContext) { + let event2 = ctx.platform_context.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event} => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + }, + _ => {} + } + }, + Event::WindowEvent { event, .. } => match event { + WindowEvent::MouseInput { state, button, .. } => { + if button == MouseButton::Right { + // blood and tear + let window = ctx.platform_context.window.lock().unwrap(); + match state { + ElementState::Pressed => { + if window + .set_cursor_grab(CursorGrabMode::Confined) + .or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)) + .is_ok() + { + window.set_cursor_visible(false); + self.mouse_enabled = true; + } + } + ElementState::Released => { + let _ = window.set_cursor_grab(CursorGrabMode::None); + window.set_cursor_visible(true); + self.mouse_enabled = false; + } + } + } + } + _ => {}, + }, + _ => {}, + } + } + + fn frame_update(&mut self, ctx: &mut SystemContext) { + if self.mouse_enabled { + self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; + self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + } + + ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + camera.eye = self.position; + camera.center = self.position + self.front(); + }); + self.mouse_delta = (0.0, 0.0); + } +} + +impl FPSCameraSystem { + 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() + } +} diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs new file mode 100644 index 0000000..8489571 --- /dev/null +++ b/engine/src/systems/mod.rs @@ -0,0 +1 @@ +pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index bea5fe0..b7c45e0 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -10,12 +10,56 @@ use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] use raidillon_glium::GliumPlatform; -use winit::event::{Event, WindowEvent}; +use winit::event::{ElementState, Event, WindowEvent}; +use winit::event::DeviceEvent::MouseMotion; +use winit::keyboard::{KeyCode, PhysicalKey}; +use raidillon_engine::systems::fps_camera::FPSCameraSystem; const TEST_GLTF: &str = "pink-monkey.gltf"; const MAIN_SCENE_ID: &str = "main_scene"; +#[derive(Default)] +struct InputTestSystem { + mouse_delta: (f64, f64), +} + +impl System for InputTestSystem { + fn handle_event(&mut self, ctx: &mut SystemContext) { + let event2 = ctx.platform_context.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event} => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + println!("UPDATED mouse delta: ({},{})", self.mouse_delta.0, self.mouse_delta.1); + }, + _ => {} + } + }, + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { device_id, event, is_synthetic} => { + let PhysicalKey::Code(key_code) = event.physical_key else { + return; + }; + + match event.state { + ElementState::Pressed => { + println!("Pressed Key: {:?}", key_code); + } + ElementState::Released => { + println!("Released Key: {:?}", key_code); + } + } + } + _ => {}, + } + _ => {}, + } + } +} + #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { @@ -42,16 +86,6 @@ impl System for RenderingTestSystem { fn load_world(&mut self, ctx: &mut SystemContext) { self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - ctx.scene.world.spawn((Camera { - eye: Vec3::new(0.0, 0.0, 2.0), - center: Vec3::ZERO, - up: Vec3::Y, - fovy: 60_f32.to_radians(), - aspect: ctx.platform_context.frame_width / ctx.platform_context.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - let mut am = ctx.platform_context.asset_manager.borrow_mut(); am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); @@ -72,6 +106,20 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", ctx.platform_context.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", ctx.platform_context.time_ctx.fixed_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); + + let input = ctx.input_state.borrow(); + if input.key_held(KeyCode::KeyW) { + dbg_ui.text("W".to_owned()); + } + if input.key_held(KeyCode::KeyA) { + dbg_ui.text("A".to_owned()); + } + if input.key_held(KeyCode::KeyS) { + dbg_ui.text("S".to_owned()); + } + if input.key_held(KeyCode::KeyD) { + dbg_ui.text("D".to_owned()); + } } fn fixed_update(&mut self, ctx: &mut SystemContext) { @@ -85,8 +133,10 @@ impl System for RenderingTestSystem { fn main() { let mut engine = Engine::new(); // Define systems + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + // engine.system_manager.add::(); // Set up the scene let main_scene = Scene::new( @@ -102,7 +152,7 @@ fn main() { engine, "Raidillon".to_string(), 2560, - 1440, + 1080, ); platform.run() }; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index f03cd96..9418ab1 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; @@ -8,7 +9,7 @@ use glium::winit::event_loop::EventLoop; use glium::winit::window::Window; use glium::Surface; use crate::system::{RenderingContext, RenderingSystemManager}; -use winit::event::{Event, WindowEvent}; +use winit::event::{DeviceEvent, Event, WindowEvent}; use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; @@ -17,10 +18,11 @@ use crate::render::debug_ui::ImguiBridge; use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; +use winit::event::DeviceEvent::MouseMotion; pub struct GliumPlatform> { event_loop: EventLoop<()>, - window: Window, + window: Arc>, display: Display, rendering_system_manager: RenderingSystemManager, asset_manager: ModelManagerRef, @@ -45,10 +47,12 @@ impl> Platform for GliumPlatfor let time_cfg = time::Config::default(); let time = time::Time::new(time_cfg); + let window = Arc::new(Mutex::new(window)); + // Install rendering systems in order - rendering_system_manager.add::(&display, &window); - rendering_system_manager.add::(&display, &window); - rendering_system_manager.add::(&display, &window); + rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone()); Self { event_loop, @@ -62,20 +66,24 @@ impl> Platform for GliumPlatfor } fn run(mut self) { - let (w, h): (u32, u32) = self.window.inner_size().into(); + let (w, h): (u32, u32) = match self.window.lock() { + Ok(window) => window.inner_size().into(), + Err(_) => (0, 0), // fallback values + }; let ctx = PlatformContext { current_event: Event::AboutToWait, asset_manager: self.asset_manager.clone(), frame_width: w as f32, frame_height: h as f32, time_ctx: self.construct_time_ctx(), + window: self.window.clone(), }; self.engine.initialize(ctx.clone()); let _ = &self.event_loop.run(move |event, el| { self.rendering_system_manager .systems .values_mut() - .for_each(|system| system.handle_event(&mut self.window, event.clone())); + .for_each(|system| system.handle_event(self.window.clone(), event.clone())); let mut ctx2 = ctx.clone(); ctx2.current_event = event.clone(); @@ -95,7 +103,7 @@ impl> Platform for GliumPlatfor scene: scene_mut, target: &mut target, asset_manager: self.asset_manager.clone(), - window: &mut self.window, + window: self.window.clone(), debug_ui_buffer, env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; @@ -129,8 +137,8 @@ impl> Platform for GliumPlatfor self.rendering_system_manager .systems .values_mut() - .for_each(|system| system.prepare_frame(&mut self.window)); - self.window.request_redraw(); + .for_each(|system| system.prepare_frame(self.window.clone())); + self.window.lock().unwrap().request_redraw(); } _ => {}, } diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 8551ecd..663b2dd 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::sync::{Arc, Mutex}; use glium::{uniform, Display, Program, Surface}; use glium::glutin::surface::WindowSurface; use glium::texture::{RawImage2d, SrgbTexture2d}; @@ -20,7 +21,7 @@ pub struct BasicMeshRenderingSystem { } impl RenderingSystem for BasicMeshRenderingSystem { - fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + fn initialize(display: &Display, _window: Arc>) -> Self { const VERT_SRC: &str = include_shader!("gl_textured.vert"); const FRAG_SRC: &str = include_shader!("gl_textured.frag"); diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs index ff29fe1..be4ff0d 100644 --- a/glium_platform/src/render/debug_ui.rs +++ b/glium_platform/src/render/debug_ui.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, Mutex}; use std::time::Instant; use glium::Display; use glium::glutin::surface::WindowSurface; @@ -19,17 +20,19 @@ pub struct ImguiBridge { } impl RenderingSystem for ImguiBridge { - fn handle_event(&mut self, window: &mut Window, event: Event<()>) { - self.platform.handle_event(self.imgui.io_mut(), window, &event); + fn handle_event(&mut self, window: Arc>, event: Event<()>) { + let window = window.lock().unwrap(); + self.platform.handle_event(self.imgui.io_mut(), &*window, &event); } - fn prepare_frame(&mut self, window: &mut Window) { + fn prepare_frame(&mut self, window: Arc>) { self.rendered_this_frame = false; let now = Instant::now(); self.imgui.io_mut().update_delta_time(now - self.last_frame); self.last_frame = now; + let window = window.lock().unwrap(); self.platform - .prepare_frame(self.imgui.io_mut(), window) + .prepare_frame(self.imgui.io_mut(), &*window) .expect("Failed to prepare frame"); } @@ -40,7 +43,10 @@ impl RenderingSystem for ImguiBridge { let ui = self.imgui.frame(); ctx.debug_ui_buffer.borrow().write_buffer(&ui); - self.platform.prepare_render(&ui, ctx.window); + { + let window = ctx.window.lock().unwrap(); + self.platform.prepare_render(&ui, &*window); + } let draw_data = self.imgui.render(); if draw_data.total_vtx_count == 0 && draw_data.total_idx_count == 0 { return; @@ -49,11 +55,12 @@ impl RenderingSystem for ImguiBridge { self.renderer.render(ctx.target, draw_data).expect("imgui rendering failed"); } - fn initialize(display: &Display, window: &Window) -> Self { + fn initialize(display: &Display, window: Arc>) -> Self { let mut imgui = ImguiContext::create(); imgui.set_ini_filename(None); let mut platform = WinitPlatform::new(&mut imgui); - platform.attach_window(imgui.io_mut(), window, HiDpiMode::Default); + let window = window.lock().unwrap(); + platform.attach_window(imgui.io_mut(), &*window, HiDpiMode::Default); imgui.fonts().add_font(&[imgui::FontSource::DefaultFontData { config: None }]); let renderer = ImguiGliumRenderer::new(&mut imgui, display).unwrap(); diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 49ea594..ac22210 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::cell::RefCell; +use std::sync::{Arc, Mutex}; use glium::{Display, Program, Surface, VertexBuffer, IndexBuffer, implement_vertex}; use glium::glutin::surface::WindowSurface; use glium::index::PrimitiveType; @@ -108,7 +109,7 @@ impl SkyboxRenderingSystem { } impl RenderingSystem for SkyboxRenderingSystem { - fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + fn initialize(display: &Display, _window: Arc>) -> Self { const VERT_SRC: &str = include_shader!("skybox.vert"); const FRAG_SRC: &str = include_shader!("skybox.frag"); let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 891fec0..3460bc3 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -1,6 +1,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; @@ -12,7 +13,7 @@ use glam::Vec3; pub struct RenderingContext<'a> { pub scene: &'a Scene, pub target: &'a mut Frame, - pub window: &'a mut glium::winit::window::Window, + pub window: Arc>, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: Rc>, pub env_light_dir: Vec3, @@ -23,13 +24,13 @@ pub struct RenderingContext<'a> { pub trait RenderingSystem { fn handle_event( &mut self, - _window: &mut glium::winit::window::Window, + _window: Arc>, _event: winit::event::Event<()>, ) { } - fn prepare_frame(&mut self, _window: &mut glium::winit::window::Window) {} + fn prepare_frame(&mut self, _window: Arc>) {} fn render(&mut self, ctx: &mut RenderingContext); - fn initialize(display: &Display, window: &glium::winit::window::Window) -> Self + fn initialize(display: &Display, window: Arc>) -> Self where Self: Sized; } @@ -45,7 +46,7 @@ impl RenderingSystemManager { } } - pub fn add(&mut self, display: &Display, window: &glium::winit::window::Window) + pub fn add(&mut self, display: &Display, window: Arc>) where R: RenderingSystem + 'static, { diff --git a/platform/src/context.rs b/platform/src/context.rs index bea1cb1..892b69e 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, Mutex}; use winit::event::Event; use raidillon_assets::ModelManagerRef; @@ -8,6 +9,7 @@ pub struct PlatformContext { pub frame_width: f32, pub frame_height: f32, pub time_ctx: TimeContext, + pub window: Arc>, } #[derive(Clone)]