From 75fd59a5049f557564029f98e5fd8a85be83ac26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20Osmano=C4=9Flu?= Date: Wed, 10 Sep 2025 13:21:35 +0300 Subject: [PATCH 01/45] Refactor system registration to use TypeIds --- engine/src/engine.rs | 10 ++++----- engine/src/system.rs | 25 +++++++++++---------- game/src/main.rs | 29 ++++++++++++------------ glium_platform/src/platform.rs | 41 ++++++++++++---------------------- glium_platform/src/system.rs | 36 ++++++++++++++++++----------- 5 files changed, 68 insertions(+), 73 deletions(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index ea6a894..171d631 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -1,12 +1,10 @@ use std::cell::RefCell; use std::rc::Rc; -use winit::event::Event; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; -use raidillon_assets::{ModelManager, ModelManagerRef}; use raidillon_core::context::PlatformContext; use raidillon_core::DebugUIBuffer; -use raidillon_core::engine::{EngineTrait}; +use raidillon_core::engine::EngineTrait; pub struct Engine { pub scene_manager: SceneManager, @@ -28,7 +26,7 @@ impl EngineTrait for Engine { /// Initialize systems, load the world. fn initialize(&mut self, platform_context: PlatformContext) { // Engine Loading Stage 1: initialize systems - for (system_id, system) in self.system_manager.systems.iter_mut() { + for system in self.system_manager.systems.values_mut() { system.initialize(); } @@ -39,7 +37,7 @@ impl EngineTrait for Engine { }; // Engine Loading Stage 2: load world - for (system_id, system) in self.system_manager.systems.iter_mut() { + for system in self.system_manager.systems.values_mut() { system.load_world(&mut ctx); } } @@ -53,7 +51,7 @@ impl EngineTrait for Engine { debug_ui_buffer: self.debug_ui_buffer.clone(), }; - for (system_id, system) in self.system_manager.systems.iter_mut() { + for system in self.system_manager.systems.values_mut() { system.update(&mut ctx); } } diff --git a/engine/src/system.rs b/engine/src/system.rs index cd4c1d4..1e62e52 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -1,9 +1,9 @@ +use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; -use raidillon_core::scene::Scene; use indexmap::IndexMap; -use winit::event::Event; use raidillon_core::context::PlatformContext; +use raidillon_core::scene::Scene; use raidillon_core::DebugUIBuffer; pub struct SystemContext<'a> { @@ -17,26 +17,27 @@ pub trait System { /// Initialize the system. fn initialize(&mut self) {} /// Spawn the first entities of the world. - fn load_world(&mut self, ctx: &mut SystemContext) {} - fn update(&mut self, ctx: &mut SystemContext) {} + fn load_world(&mut self, _ctx: &mut SystemContext) {} + fn update(&mut self, _ctx: &mut SystemContext) {} } -pub type SystemID = &'static str; pub struct SystemManager { - pub systems: IndexMap>, + pub systems: IndexMap>, } impl SystemManager { pub fn new() -> Self { - let systems = IndexMap::default(); - Self { systems } + Self { + systems: IndexMap::default(), + } } - pub fn add_system(&mut self, id: SystemID, system: Box) { - self.systems.insert(id, system); + pub fn add(&mut self) { + self.systems + .insert(TypeId::of::(), Box::new(S::default())); } - pub fn remove_system(&mut self, id: SystemID) { - self.systems.shift_remove(&id); + pub fn remove(&mut self) { + self.systems.shift_remove(&TypeId::of::()); } } \ No newline at end of file diff --git a/game/src/main.rs b/game/src/main.rs index a02b359..396d962 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,4 +1,3 @@ -use std::path::Path; use glam::{Quat, Vec3}; use raidillon_engine::{Engine, system::System}; use raidillon_engine::system::SystemContext; @@ -10,31 +9,32 @@ use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] use raidillon_glium::GliumPlatform; -use raidillon_core::DebugUIBuffer; use winit::event::{Event, WindowEvent}; const TEST_GLTF: &str = "pink-monkey.gltf"; -const RENDERING_TEST_SYSTEM: &str = "rendering_test_system"; - -const UPDATE_ASPECT_RATIO_SYSTEM: &str = "update_aspect_ratio_system"; - const MAIN_SCENE_ID: &str = "main_scene"; +#[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { fn initialize(&mut self) {} - fn load_world(&mut self, ctx: &mut SystemContext) {} + fn load_world(&mut self, _ctx: &mut SystemContext) {} fn update(&mut self, ctx: &mut SystemContext) { // FIXME: Need an event handler rework for systems. match &ctx.platform_context.current_event { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(sz) => { - let _ = ctx.scene.world.query_mut::<&mut Camera>().into_iter().map(|mut cam| { - cam.1.aspect = sz.width as f32 / sz.height as f32; - }); + let _ = ctx + .scene + .world + .query_mut::<&mut Camera>() + .into_iter() + .map(|(_, cam)| { + cam.aspect = sz.width as f32 / sz.height as f32; + }); } _ => {} }, @@ -43,6 +43,7 @@ impl System for UpdateAspectRatioSystem { } } +#[derive(Default)] struct RenderingTestSystem; impl System for RenderingTestSystem { fn initialize(&mut self) {} @@ -82,10 +83,8 @@ impl System for RenderingTestSystem { fn main() { let mut engine = Engine::new(); // Define systems - // engine.system_manager.add_system("spawn_chunks".to_string(), ChunkSystem); - // engine.system_manager.add_system("movement".to_string(), MovementSystem); - engine.system_manager.add_system(RENDERING_TEST_SYSTEM, Box::new(RenderingTestSystem)); - engine.system_manager.add_system(UPDATE_ASPECT_RATIO_SYSTEM, Box::new(UpdateAspectRatioSystem)); + engine.system_manager.add::(); + engine.system_manager.add::(); // Set up the scene let main_scene = Scene::new( @@ -97,7 +96,7 @@ fn main() { #[cfg(feature = "glium")] { - let mut platform = GliumPlatform::initialize( + let platform = GliumPlatform::initialize( engine, "Raidillon".to_string(), 1920, diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 34864c3..7f4320f 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,25 +1,19 @@ use std::cell::RefCell; use std::rc::Rc; -use std::str::FromStr; -use std::sync::{Arc, RwLock}; use raidillon_platform::{Platform, PlatformContext}; +use glium::backend::glutin::Display; +use glium::backend::glutin::SimpleWindowBuilder; +use glium::glutin::surface::WindowSurface; use glium::winit::event_loop::EventLoop; use glium::winit::window::Window; -use glium::backend::glutin::Display; -use glium::glutin::surface::WindowSurface; -use glium::backend::glutin::SimpleWindowBuilder; use glium::Surface; -use crate::system::{RenderingSystemManager, RenderingSystem, RenderingContext, SystemID}; +use crate::system::{RenderingContext, RenderingSystemManager}; use winit::event::{Event, WindowEvent}; -use raidillon_assets::{ModelManager, ModelManagerRef}; +use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; -use raidillon_engine::Engine; -use crate::{GliumAssetManager}; use crate::render::debug_ui::ImguiBridge; use crate::render::BasicMeshRenderingSystem; - -pub const MESH_RENDERER: &str = "mesh_renderer"; -pub const DEBUG_UI_RENDERER: &str = "debug_ui_renderer"; +use crate::GliumAssetManager; pub struct GliumPlatform { event_loop: EventLoop<()>, @@ -45,15 +39,8 @@ impl Platform for GliumPlatform { let mut rendering_system_manager = RenderingSystemManager::new(); // Install rendering systems - rendering_system_manager.add_system( - MESH_RENDERER, - Box::new(BasicMeshRenderingSystem::initialize(&display, &window)) - ); - - rendering_system_manager.add_system( - DEBUG_UI_RENDERER, - Box::new(ImguiBridge::initialize(&display, &window)), - ); + rendering_system_manager.add::(&display, &window); + rendering_system_manager.add::(&display, &window); Self { event_loop, @@ -77,8 +64,8 @@ impl Platform for GliumPlatform { let _ = &self.event_loop.run(move |event, el| { self.rendering_system_manager .systems - .iter_mut() - .for_each(|(_, system)| system.handle_event(&mut self.window, event.clone())); + .values_mut() + .for_each(|system| system.handle_event(&mut self.window, event.clone())); match event { Event::WindowEvent { event, .. } => match event { @@ -100,8 +87,8 @@ impl Platform for GliumPlatform { self.rendering_system_manager .systems - .iter_mut() - .for_each(|(_, system)| system.render(&mut context)); + .values_mut() + .for_each(|system| system.render(&mut context)); target.finish().unwrap(); } _ => {}, @@ -112,8 +99,8 @@ impl Platform for GliumPlatform { self.engine.update(ctx2); self.rendering_system_manager .systems - .iter_mut() - .for_each(|(_, system)| system.prepare_frame(&mut self.window)); + .values_mut() + .for_each(|system| system.prepare_frame(&mut self.window)); self.window.request_redraw(); } _ => {}, diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 6a31a2d..7f1f55e 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -1,12 +1,12 @@ +use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; -use raidillon_core::scene::Scene; +use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; -use indexmap::IndexMap; use raidillon_assets::ModelManagerRef; use raidillon_core::DebugUIBuffer; -use crate::GliumAssetManager; +use raidillon_core::scene::Scene; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -19,16 +19,21 @@ pub struct RenderingContext<'a> { /// The internal "rendering system" trait of glium_platform. /// This is unrelated to the main System trait in core. pub trait RenderingSystem { - fn handle_event(&mut self, window: &mut glium::winit::window::Window, event: winit::event::Event<()>) {} - fn prepare_frame(&mut self, window: &mut glium::winit::window::Window) {} + fn handle_event( + &mut self, + _window: &mut glium::winit::window::Window, + _event: winit::event::Event<()>, + ) { + } + fn prepare_frame(&mut self, _window: &mut glium::winit::window::Window) {} fn render(&mut self, ctx: &mut RenderingContext); - fn initialize(display: &Display, window: &glium::winit::window::Window) -> Self where Self: Sized; + fn initialize(display: &Display, window: &glium::winit::window::Window) -> Self + where + Self: Sized; } -pub type SystemID = &'static str; - pub struct RenderingSystemManager { - pub systems: IndexMap>, + pub systems: IndexMap>, } impl RenderingSystemManager { @@ -37,11 +42,16 @@ impl RenderingSystemManager { systems: IndexMap::default(), } } - pub fn add_system(&mut self, id: SystemID, system: Box) { - self.systems.insert(id, system); + + pub fn add(&mut self, display: &Display, window: &glium::winit::window::Window) + where + R: RenderingSystem + 'static, + { + let system = R::initialize(display, window); + self.systems.insert(TypeId::of::(), Box::new(system)); } - pub fn remove_system(&mut self, id: SystemID) { - self.systems.shift_remove(&id); + pub fn remove(&mut self) { + self.systems.shift_remove(&TypeId::of::()); } } From 9905ffd26bb2de051f477cc9665778f5090c5ced Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 18 Sep 2025 00:32:12 +0300 Subject: [PATCH 02/45] Add event handler methods to the engine structure --- core/src/engine.rs | 1 + engine/src/engine.rs | 12 ++++++++++++ engine/src/system.rs | 3 ++- game/src/main.rs | 34 ++++++++++------------------------ glium_platform/src/platform.rs | 4 ++++ 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/core/src/engine.rs b/core/src/engine.rs index 4c73815..53aeef1 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -8,6 +8,7 @@ pub trait EngineTrait { fn new() -> Self; fn initialize(&mut self, platform_context: PlatformContext); fn update(&mut self, platform_context: PlatformContext); + fn handle_event(&mut self, platform_context: PlatformContext); fn current_scene_mut(&mut self) -> &mut Scene; fn get_debug_ui_buffer(&self) -> Rc>; fn reset_debug_ui_buffer(&mut self); diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 171d631..bf3b692 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -56,6 +56,18 @@ impl EngineTrait for Engine { } } + fn handle_event(&mut self, platform_context: PlatformContext) { + let mut ctx = SystemContext { + scene: self.scene_manager.current_mut(), + platform_context, + debug_ui_buffer: self.debug_ui_buffer.clone(), + }; + + for system in self.system_manager.systems.values_mut() { + system.handle_event(&mut ctx); + } + } + // pub fn build_system_context(&mut self) -> SystemContext { // SystemContext { // scene: self.scene_manager.current_mut(), diff --git a/engine/src/system.rs b/engine/src/system.rs index 1e62e52..b11d8fd 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -18,6 +18,7 @@ pub trait System { fn initialize(&mut self) {} /// Spawn the first entities of the world. fn load_world(&mut self, _ctx: &mut SystemContext) {} + fn handle_event(&mut self, _ctx: &mut SystemContext) {} fn update(&mut self, _ctx: &mut SystemContext) {} } @@ -40,4 +41,4 @@ impl SystemManager { pub fn remove(&mut self) { self.systems.shift_remove(&TypeId::of::()); } -} \ No newline at end of file +} diff --git a/game/src/main.rs b/game/src/main.rs index 396d962..126c2e4 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -18,27 +18,16 @@ const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { - fn initialize(&mut self) {} - - fn load_world(&mut self, _ctx: &mut SystemContext) {} - - fn update(&mut self, ctx: &mut SystemContext) { - // FIXME: Need an event handler rework for systems. - match &ctx.platform_context.current_event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(sz) => { - let _ = ctx - .scene - .world - .query_mut::<&mut Camera>() - .into_iter() - .map(|(_, cam)| { - cam.aspect = sz.width as f32 / sz.height as f32; - }); - } - _ => {} - }, - _ => {} + fn handle_event(&mut self, ctx: &mut SystemContext) { + if let Event::WindowEvent { event: WindowEvent::Resized(sz), .. } = + &ctx.platform_context.current_event + { + ctx.scene.world + .query_mut::<&mut Camera>() + .into_iter() + .for_each(|(_, cam)| { + cam.aspect = sz.width as f32 / sz.height as f32; + }); } } } @@ -73,9 +62,6 @@ impl System for RenderingTestSystem { } fn update(&mut self, ctx: &mut SystemContext) { - // if let Some(mut debug_ui) = ctx.platform_context.imgui_ui.as_ref().map(|ui| ui.borrow_mut()) { - // debug_ui.text("Hello World!"); - // } ctx.debug_ui_buffer.borrow_mut().text("Hello World!".to_owned()); } } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 7f4320f..4848c7c 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -66,6 +66,10 @@ impl Platform for GliumPlatform { .systems .values_mut() .for_each(|system| system.handle_event(&mut self.window, event.clone())); + + let mut ctx2 = ctx.clone(); + ctx2.current_event = event.clone(); + self.engine.handle_event(ctx2); match event { Event::WindowEvent { event, .. } => match event { From 369bbd19c7329bc624f12e02acabbfa1b166d622 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 22 Sep 2025 19:45:30 +0300 Subject: [PATCH 03/45] Move PlatformContext to the raidillon_platform crate --- core/src/engine.rs | 8 ++++---- core/src/lib.rs | 1 - engine/src/engine.rs | 3 ++- engine/src/system.rs | 2 +- glium_platform/src/platform.rs | 4 ++-- {core => platform}/src/context.rs | 7 ++----- platform/src/lib.rs | 4 +++- platform/src/platform.rs | 2 +- 8 files changed, 15 insertions(+), 16 deletions(-) rename {core => platform}/src/context.rs (62%) diff --git a/core/src/engine.rs b/core/src/engine.rs index 53aeef1..9aafdf5 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -1,14 +1,14 @@ use std::cell::RefCell; use std::rc::Rc; -use crate::context::PlatformContext; use crate::DebugUIBuffer; use crate::scene::Scene; pub trait EngineTrait { + type PlatformCtx: Clone; fn new() -> Self; - fn initialize(&mut self, platform_context: PlatformContext); - fn update(&mut self, platform_context: PlatformContext); - fn handle_event(&mut self, platform_context: PlatformContext); + fn initialize(&mut self, platform_context: Self::PlatformCtx); + fn update(&mut self, platform_context: Self::PlatformCtx); + fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; fn get_debug_ui_buffer(&self) -> Rc>; fn reset_debug_ui_buffer(&mut self); diff --git a/core/src/lib.rs b/core/src/lib.rs index b6fc074..ee279f9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,5 +1,4 @@ pub mod engine; pub mod scene; -pub mod context; pub mod debug_ui; pub use debug_ui::*; \ No newline at end of file diff --git a/engine/src/engine.rs b/engine/src/engine.rs index bf3b692..38b6657 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::rc::Rc; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; -use raidillon_core::context::PlatformContext; +use raidillon_platform::PlatformContext; use raidillon_core::DebugUIBuffer; use raidillon_core::engine::EngineTrait; @@ -13,6 +13,7 @@ pub struct Engine { } impl EngineTrait for Engine { + type PlatformCtx = PlatformContext; fn new() -> Self { let scene_manager = SceneManager::new(); let system_manager = SystemManager::new(); diff --git a/engine/src/system.rs b/engine/src/system.rs index b11d8fd..49b6c44 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -2,7 +2,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; use indexmap::IndexMap; -use raidillon_core::context::PlatformContext; +use raidillon_platform::PlatformContext; use raidillon_core::scene::Scene; use raidillon_core::DebugUIBuffer; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 4848c7c..97a94a1 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -15,7 +15,7 @@ use crate::render::debug_ui::ImguiBridge; use crate::render::BasicMeshRenderingSystem; use crate::GliumAssetManager; -pub struct GliumPlatform { +pub struct GliumPlatform> { event_loop: EventLoop<()>, window: Window, display: Display, @@ -24,7 +24,7 @@ pub struct GliumPlatform { engine: E, } -impl Platform for GliumPlatform { +impl> Platform for GliumPlatform { fn initialize(mut engine: E, title: String, width: u32, height: u32) -> Self { let event_loop = glium::winit::event_loop::EventLoop::builder() .build() diff --git a/core/src/context.rs b/platform/src/context.rs similarity index 62% rename from core/src/context.rs rename to platform/src/context.rs index f1cdffe..fb5ec65 100644 --- a/core/src/context.rs +++ b/platform/src/context.rs @@ -1,8 +1,5 @@ -use std::cell::RefCell; -use std::rc::Rc; -use std::sync::Arc; use winit::event::Event; -use raidillon_assets::{ModelManagerRef, ModelManager}; +use raidillon_assets::ModelManagerRef; #[derive(Clone)] pub struct PlatformContext { @@ -10,4 +7,4 @@ pub struct PlatformContext { pub asset_manager: ModelManagerRef, pub frame_width: f32, pub frame_height: f32, -} +} \ No newline at end of file diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 3ff1a65..962c05f 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -1,6 +1,8 @@ pub mod platform; mod camera; +mod event; +pub mod context; pub use platform::Platform; pub use camera::Camera; -pub use raidillon_core::context::PlatformContext; +pub use context::PlatformContext; diff --git a/platform/src/platform.rs b/platform/src/platform.rs index 44b799f..d840723 100644 --- a/platform/src/platform.rs +++ b/platform/src/platform.rs @@ -2,7 +2,7 @@ use raidillon_assets::ModelManager; use raidillon_core::engine::EngineTrait; use crate::PlatformContext; -pub trait Platform { +pub trait Platform> { /// Initialize platform. fn initialize(engine: E, title: String, width: u32, height: u32) -> Self; fn run(self); From 50d07ffea59f2729fa9ea754610524fb407d9227 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 23 Sep 2025 21:06:24 +0300 Subject: [PATCH 04/45] Optimize imports --- platform/src/platform.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platform/src/platform.rs b/platform/src/platform.rs index d840723..8884c46 100644 --- a/platform/src/platform.rs +++ b/platform/src/platform.rs @@ -1,6 +1,5 @@ -use raidillon_assets::ModelManager; -use raidillon_core::engine::EngineTrait; use crate::PlatformContext; +use raidillon_core::engine::EngineTrait; pub trait Platform> { /// Initialize platform. From 59963cdec142fe2ba2b0cde485a1764470106984 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 23 Sep 2025 21:11:09 +0300 Subject: [PATCH 05/45] Add pre-commit-config, run pre-commit on all files --- .pre-commit-config.yaml | 11 +++++++++++ core/src/lib.rs | 2 +- engine/src/engine.rs | 2 +- engine/src/lib.rs | 2 +- glium_platform/src/platform.rs | 2 +- glium_platform/src/render/mod.rs | 2 +- platform/src/context.rs | 2 +- platform/src/event.rs | 2 +- 8 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1927e4f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + files: "\\.rs$" + - id: end-of-file-fixer + files: "\\.rs$" + - id: mixed-line-ending + args: [--fix=lf] + files: "\\.rs$" diff --git a/core/src/lib.rs b/core/src/lib.rs index ee279f9..f4b8d0c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,4 @@ pub mod engine; pub mod scene; pub mod debug_ui; -pub use debug_ui::*; \ No newline at end of file +pub use debug_ui::*; diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 38b6657..5d6af36 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -79,7 +79,7 @@ impl EngineTrait for Engine { self.scene_manager.current_mut() } - fn get_debug_ui_buffer(&self) -> Rc> { + fn get_debug_ui_buffer(&self) -> Rc> { self.debug_ui_buffer.clone() } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index e17307d..b3a2dc8 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,4 +1,4 @@ pub mod engine; pub mod system; -pub use crate::engine::Engine; \ No newline at end of file +pub use crate::engine::Engine; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 97a94a1..97c1535 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -66,7 +66,7 @@ impl> Platform for GliumPlatfor .systems .values_mut() .for_each(|system| system.handle_event(&mut self.window, event.clone())); - + let mut ctx2 = ctx.clone(); ctx2.current_event = event.clone(); self.engine.handle_event(ctx2); diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index e98554f..c31a0d9 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,4 +1,4 @@ mod basic; pub mod debug_ui; -pub use basic::BasicMeshRenderingSystem; \ No newline at end of file +pub use basic::BasicMeshRenderingSystem; diff --git a/platform/src/context.rs b/platform/src/context.rs index fb5ec65..45e8fa4 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -7,4 +7,4 @@ pub struct PlatformContext { pub asset_manager: ModelManagerRef, pub frame_width: f32, pub frame_height: f32, -} \ No newline at end of file +} diff --git a/platform/src/event.rs b/platform/src/event.rs index 82ce8b6..bb27077 100644 --- a/platform/src/event.rs +++ b/platform/src/event.rs @@ -4,7 +4,7 @@ pub enum PlatformEvent { /// The platform has requested to close the app. CloseRequested, - + /// Platform event loop is about to block and wait for new /// new events. AboutToWait From 5e8897c2717ec6a975bab35edbd255fea5a9c5c9 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 23 Sep 2025 21:16:06 +0300 Subject: [PATCH 06/45] Optimize imports --- engine/src/system.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/src/system.rs b/engine/src/system.rs index 49b6c44..31708b3 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -1,10 +1,10 @@ +use indexmap::IndexMap; +use raidillon_core::scene::Scene; +use raidillon_core::DebugUIBuffer; +use raidillon_platform::PlatformContext; use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; -use indexmap::IndexMap; -use raidillon_platform::PlatformContext; -use raidillon_core::scene::Scene; -use raidillon_core::DebugUIBuffer; pub struct SystemContext<'a> { // TODO: time delta etc. From 84ab3a26b16bad457d787cc57385cb95a78b5219 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 24 Sep 2025 23:20:51 +0300 Subject: [PATCH 07/45] Timing Module Update - Implement a new timing module - Utilize the new timing module in glium platform implementation for frame limiting and fixed engine updates timing. --- core/src/engine.rs | 3 +- core/src/lib.rs | 2 + core/src/time.rs | 144 +++++++++++++++++++++++++++++++++ engine/src/engine.rs | 16 +++- engine/src/system.rs | 3 +- game/src/main.rs | 17 +++- glium_platform/src/platform.rs | 37 ++++++++- platform/src/context.rs | 8 ++ platform/src/lib.rs | 2 +- 9 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 core/src/time.rs diff --git a/core/src/engine.rs b/core/src/engine.rs index 9aafdf5..80d2a69 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -7,7 +7,8 @@ pub trait EngineTrait { type PlatformCtx: Clone; fn new() -> Self; fn initialize(&mut self, platform_context: Self::PlatformCtx); - fn update(&mut self, platform_context: Self::PlatformCtx); + fn frame_update(&mut self, platform_context: Self::PlatformCtx); + fn fixed_update(&mut self, platform_context: Self::PlatformCtx); fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; fn get_debug_ui_buffer(&self) -> Rc>; diff --git a/core/src/lib.rs b/core/src/lib.rs index f4b8d0c..4b7d531 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,6 @@ pub mod engine; pub mod scene; pub mod debug_ui; +pub mod time; + pub use debug_ui::*; diff --git a/core/src/time.rs b/core/src/time.rs new file mode 100644 index 0000000..c47466d --- /dev/null +++ b/core/src/time.rs @@ -0,0 +1,144 @@ +use std::thread; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct Config { + pub target_frame_hz: Option, + pub target_update_hz: f64, + pub max_updates_per_frame: u32, + pub max_accumulated_steps: u32, + pub sleep_tolerance: Duration, +} + +impl Default for Config { + fn default() -> Self { + Self { + target_frame_hz: Some(144.0), + target_update_hz: 60.0, + max_updates_per_frame: 5, + max_accumulated_steps: 8, + sleep_tolerance: Duration::from_micros(500), + } + } +} + +#[derive(Debug)] +pub struct Time { + cfg: Config, + last_instant: Instant, + next_frame_due: Instant, + frame_interval: Option, + fixed_dt: Duration, + + // tracking + frame_dt: Duration, + accumulator: Duration, + + // counters + pub frame_count: u64, + pub update_count: u64, +} + +pub struct TickPlan { + /// How many fixed updates to run this frame + pub updates: u32, + /// Interpolation factor for rendering between previous/next sim states + pub alpha: f32, + /// Measured last frame delta (seconds) + pub frame_dt: f32, + /// Fixed timestep (seconds) + pub fixed_dt: f32, +} + +impl Time { + pub fn new(cfg: Config) -> Self { + let now = Instant::now(); + let frame_interval = cfg.target_frame_hz.map(|hz| Duration::from_secs_f64(1.0 / hz)); + let fixed_dt = Duration::from_secs_f64(1.0 / cfg.target_update_hz); + Self { + cfg, + last_instant: now, + next_frame_due: now, + frame_interval, + fixed_dt, + frame_dt: Duration::ZERO, + accumulator: Duration::ZERO, + frame_count: 0, + update_count: 0, + } + } + + pub fn reconfigure(&mut self, cfg: Config) { + self.cfg = cfg.clone(); + self.frame_interval = cfg.target_frame_hz.map(|hz| Duration::from_secs_f64(1.0 / hz)); + self.fixed_dt = Duration::from_secs_f64(1.0 / cfg.target_update_hz); + } + + pub fn begin_frame_blocking(&mut self) -> TickPlan { + // 1) If there's a frame cap, block until next frame deadline + if let Some(interval) = self.frame_interval { + let mut now = Instant::now(); + if now < self.next_frame_due { + // Sleep most of the remainder, then spin the last tiny bit for precision + let total_remaining = self.next_frame_due - now; + if total_remaining > self.cfg.sleep_tolerance { + let sleep_for = total_remaining - self.cfg.sleep_tolerance; + thread::sleep(sleep_for); + } + // Short spin-wait for precision + while Instant::now() < self.next_frame_due { + std::hint::spin_loop(); + } + now = self.next_frame_due; + } + self.next_frame_due = self.next_frame_due + interval; + // In case we fell far behind (e.g., debugger pause), resync. + if self.next_frame_due < now { + self.next_frame_due = now + interval; + } + } + + // 2) Measure frame dt + let now = Instant::now(); + self.frame_dt = now.saturating_duration_since(self.last_instant); + self.last_instant = now; + self.frame_count += 1; + + // 3) Accumulate for fixed updates + self.accumulator += self.frame_dt; + + // Clamp accumulator to avoid doing a huge number of updates after a stall + let max_accumulated = self.fixed_dt * self.cfg.max_accumulated_steps; + if self.accumulator > max_accumulated { + self.accumulator = max_accumulated; + } + + // 4) Determine how many updates to run this frame + let mut updates = 0u32; + while self.accumulator >= self.fixed_dt && updates < self.cfg.max_updates_per_frame { + self.accumulator -= self.fixed_dt; + updates += 1; + self.update_count += 1; + } + + // 5) Compute interpolation factor for rendering (0..1) + let alpha = if self.fixed_dt.is_zero() { + 1.0 + } else { + (self.accumulator.as_secs_f32() / self.fixed_dt.as_secs_f32()).clamp(0.0, 1.0) + }; + + TickPlan { + updates, + alpha, + frame_dt: self.frame_dt.as_secs_f32(), + fixed_dt: self.fixed_dt.as_secs_f32(), + } + } + + pub fn frame_dt_seconds(&self) -> f32 { self.frame_dt.as_secs_f32() } + pub fn fixed_dt_seconds(&self) -> f32 { self.fixed_dt.as_secs_f32() } + pub fn alpha(&self) -> f32 { + if self.fixed_dt.is_zero() { 1.0 } else { (self.accumulator.as_secs_f32() / self.fixed_dt.as_secs_f32()).clamp(0.0, 1.0) } + } +} diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 5d6af36..82b1f91 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -44,7 +44,7 @@ impl EngineTrait for Engine { } /// Update the engine - fn update(&mut self, platform_context: PlatformContext) { + fn frame_update(&mut self, platform_context: PlatformContext) { self.debug_ui_buffer.borrow_mut().reset_buffer(); let mut ctx = SystemContext { scene: self.scene_manager.current_mut(), @@ -53,7 +53,19 @@ impl EngineTrait for Engine { }; for system in self.system_manager.systems.values_mut() { - system.update(&mut ctx); + system.frame_update(&mut ctx); + } + } + + fn fixed_update(&mut self, platform_context: PlatformContext) { + let mut ctx = SystemContext { + scene: self.scene_manager.current_mut(), + platform_context, + debug_ui_buffer: self.debug_ui_buffer.clone(), + }; + + for system in self.system_manager.systems.values_mut() { + system.fixed_update(&mut ctx); } } diff --git a/engine/src/system.rs b/engine/src/system.rs index 31708b3..1ab49bf 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -19,7 +19,8 @@ pub trait System { /// Spawn the first entities of the world. fn load_world(&mut self, _ctx: &mut SystemContext) {} fn handle_event(&mut self, _ctx: &mut SystemContext) {} - fn update(&mut self, _ctx: &mut SystemContext) {} + fn fixed_update(&mut self, _ctx: &mut SystemContext) {} + fn frame_update(&mut self, _ctx: &mut SystemContext) {} } pub struct SystemManager { diff --git a/game/src/main.rs b/game/src/main.rs index 126c2e4..b1b5c2c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,3 +1,4 @@ +use std::fmt::format; use glam::{Quat, Vec3}; use raidillon_engine::{Engine, system::System}; use raidillon_engine::system::SystemContext; @@ -34,8 +35,8 @@ impl System for UpdateAspectRatioSystem { #[derive(Default)] struct RenderingTestSystem; + impl System for RenderingTestSystem { - fn initialize(&mut self) {} fn load_world(&mut self, ctx: &mut SystemContext) { ctx.scene.world.spawn((Camera { eye: Vec3::new(0.0, 0.0, 2.0), @@ -61,9 +62,19 @@ impl System for RenderingTestSystem { )); } - fn update(&mut self, ctx: &mut SystemContext) { - ctx.debug_ui_buffer.borrow_mut().text("Hello World!".to_owned()); + fn frame_update(&mut self, ctx: &mut SystemContext) { + let mut dbg_ui = ctx.debug_ui_buffer.borrow_mut(); + dbg_ui.text("Hello World!".to_owned()); + 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)); } + + fn fixed_update(&mut self, ctx: &mut SystemContext) { + ctx.scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { + t.rotation *= Quat::from_rotation_y(10.0 * ctx.platform_context.time_ctx.fixed_dt); + }); + } + } fn main() { diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 97c1535..16df321 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -use raidillon_platform::{Platform, PlatformContext}; +use raidillon_platform::{Platform, PlatformContext, TimeContext}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; use glium::glutin::surface::WindowSurface; @@ -11,6 +11,8 @@ use crate::system::{RenderingContext, RenderingSystemManager}; use winit::event::{Event, WindowEvent}; use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; +use raidillon_core::time; +use raidillon_core::time::Time; use crate::render::debug_ui::ImguiBridge; use crate::render::BasicMeshRenderingSystem; use crate::GliumAssetManager; @@ -22,6 +24,7 @@ pub struct GliumPlatform> { rendering_system_manager: RenderingSystemManager, asset_manager: ModelManagerRef, engine: E, + time: time::Time, } impl> Platform for GliumPlatform { @@ -38,6 +41,9 @@ impl> Platform for GliumPlatfor let asset_manager: ModelManagerRef = Rc::new(RefCell::new(Box::new(GliumAssetManager::new(Box::new(display.clone()))))); let mut rendering_system_manager = RenderingSystemManager::new(); + let time_cfg = time::Config::default(); + let time = time::Time::new(time_cfg); + // Install rendering systems rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); @@ -49,6 +55,7 @@ impl> Platform for GliumPlatfor rendering_system_manager, asset_manager, engine, + time, } } @@ -59,6 +66,7 @@ impl> Platform for GliumPlatfor asset_manager: self.asset_manager.clone(), frame_width: w as f32, frame_height: h as f32, + time_ctx: self.construct_time_ctx(), }; self.engine.initialize(ctx.clone()); let _ = &self.event_loop.run(move |event, el| { @@ -98,9 +106,22 @@ impl> Platform for GliumPlatfor _ => {}, }, Event::AboutToWait => { + let plan = self.time.begin_frame_blocking(); + let mut ctx2 = ctx.clone(); - ctx2.current_event = event.clone(); - self.engine.update(ctx2); + ctx2.time_ctx = TimeContext { + frame_dt: self.time.frame_dt_seconds(), + fixed_dt: self.time.fixed_dt_seconds(), + alpha: self.time.alpha(), + }; + ctx2.current_event = Event::AboutToWait; + + for _ in 0..plan.updates { + self.engine.fixed_update(ctx2.clone()); + } + + self.engine.frame_update(ctx2.clone()); + self.rendering_system_manager .systems .values_mut() @@ -112,3 +133,13 @@ impl> Platform for GliumPlatfor }); } } + +impl> GliumPlatform { + fn construct_time_ctx(&self) -> TimeContext { + TimeContext { + frame_dt: self.time.frame_dt_seconds(), + fixed_dt: self.time.fixed_dt_seconds(), + alpha: self.time.alpha(), + } + } +} diff --git a/platform/src/context.rs b/platform/src/context.rs index 45e8fa4..bea1cb1 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -7,4 +7,12 @@ pub struct PlatformContext { pub asset_manager: ModelManagerRef, pub frame_width: f32, pub frame_height: f32, + pub time_ctx: TimeContext, +} + +#[derive(Clone)] +pub struct TimeContext { + pub frame_dt: f32, + pub fixed_dt: f32, + pub alpha: f32, } diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 962c05f..8875d35 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -5,4 +5,4 @@ pub mod context; pub use platform::Platform; pub use camera::Camera; -pub use context::PlatformContext; +pub use context::{PlatformContext, TimeContext}; From f34a9b01a0b855b2147b17c57e142a7cb9fba4cb Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 25 Sep 2025 13:01:02 +0300 Subject: [PATCH 08/45] Add imgui slider command to the debug ui buffer implementation --- core/src/debug_ui.rs | 10 ++++++++++ game/src/main.rs | 9 +++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/src/debug_ui.rs b/core/src/debug_ui.rs index 18d846f..df3b9f1 100644 --- a/core/src/debug_ui.rs +++ b/core/src/debug_ui.rs @@ -1,6 +1,7 @@ pub enum UICommand { Text(String), Separator, + SliderF32 { label: String, min: f32, max: f32, value: std::rc::Rc> }, } pub struct DebugUIBuffer { @@ -21,6 +22,11 @@ impl DebugUIBuffer { self.cmds.push(UICommand::Separator); } + pub fn slider_f32>(&mut self, label: T, min: f32, max: f32, value: std::rc::Rc>) { + self.cmds.push(UICommand::SliderF32 { label: label.into(), min, max, value }); + } + // End of commands + pub fn write_buffer(&self, ui: &imgui::Ui) { for cmd in &self.cmds { match cmd { @@ -30,6 +36,10 @@ impl DebugUIBuffer { UICommand::Separator => { ui.separator(); } + UICommand::SliderF32 { label, min, max, value } => { + let mut v = value.borrow_mut(); + ui.slider_config(label.as_str(), *min, *max).build(&mut *v); + } } } } diff --git a/game/src/main.rs b/game/src/main.rs index b1b5c2c..05ae11e 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -34,10 +34,14 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem; +struct RenderingTestSystem { + rotation_speed: std::rc::Rc>, +} 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, @@ -67,11 +71,12 @@ impl System for RenderingTestSystem { dbg_ui.text("Hello World!".to_owned()); 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()); } fn fixed_update(&mut self, ctx: &mut SystemContext) { ctx.scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(10.0 * ctx.platform_context.time_ctx.fixed_dt); + t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * ctx.platform_context.time_ctx.fixed_dt); }); } From 44489f9fe356d1357d96652b50ab1a8db848dc90 Mon Sep 17 00:00:00 2001 From: reo Date: Fri, 26 Sep 2025 16:52:14 +0300 Subject: [PATCH 09/45] Move models to git LFS --- .gitattributes | 1 + assets/models/monkey.bin | Bin 68720 -> 130 bytes assets/models/monkey.gltf | 107 +----------------------- assets/models/pink-monkey.bin | Bin 68720 -> 130 bytes assets/models/pink-monkey.gltf | 124 +--------------------------- assets/models/tree.bin | Bin 13584 -> 130 bytes assets/models/tree.gltf | 143 +-------------------------------- assets/models/tree_texture.png | Bin 845 -> 128 bytes 8 files changed, 10 insertions(+), 365 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7dc2605 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +assets/models/* filter=lfs diff=lfs merge=lfs -text diff --git a/assets/models/monkey.bin b/assets/models/monkey.bin index e7992f36d75135cc5966e2abe94e5937420da413..56f4aa06d5161846423e6a1fd1ce39019a203efd 100644 GIT binary patch literal 130 zcmWN?K@!3s3;@78uiyigKp`dl4TMyfQRxWw;OliSd&zsW{?c{MbL__4+q^xFvHY)} zyl{V-aSEHus@{qoHMxMhBex1BPsGuS7ZT3QjIfpv8A9?A1-4j%1+j=inH?1jAOij% MHQHA+#?68H0qdA2e*gdg literal 68720 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 diff --git a/assets/models/monkey.gltf b/assets/models/monkey.gltf index d3e2438..2651b01 100644 --- a/assets/models/monkey.gltf +++ b/assets/models/monkey.gltf @@ -1,104 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.4.56", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"Suzanne" - } - ], - "meshes":[ - { - "name":"Suzanne", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3 - } - ] - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":1966, - "max":[ - 1.3671875, - 0.984375, - 0.8515625 - ], - "min":[ - -1.3671875, - -0.984375, - -0.8515625 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":1966, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":1966, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":2904, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":23592, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":23592, - "byteOffset":23592, - "target":34962 - }, - { - "buffer":0, - "byteLength":15728, - "byteOffset":47184, - "target":34962 - }, - { - "buffer":0, - "byteLength":5808, - "byteOffset":62912, - "target":34963 - } - ], - "buffers":[ - { - "byteLength":68720, - "uri":"monkey.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:6513b26c81eb59be818a8ddc0331eccd2e54d8c2e9b0d7edb01b491a16cdc421 +size 1333 diff --git a/assets/models/pink-monkey.bin b/assets/models/pink-monkey.bin index 2db45966ba391e44f67a7dcc92cd338340566448..627ca3e113752425edd688115646fdf9ceb9a10e 100644 GIT binary patch literal 130 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 avGu<>G5hAoEVHk!#K);QG(HZ^t^5H`4fe$V diff --git a/assets/models/pink-monkey.gltf b/assets/models/pink-monkey.gltf index a27e895..d4c6adf 100644 --- a/assets/models/pink-monkey.gltf +++ b/assets/models/pink-monkey.gltf @@ -1,121 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.0.44", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"Suzanne" - } - ], - "materials":[ - { - "doubleSided":true, - "name":"Material.001", - "pbrMetallicRoughness":{ - "baseColorFactor":[ - 0.8002749681472778, - 0, - 0.07706787437200546, - 1 - ], - "metallicFactor":0, - "roughnessFactor":0.5 - } - } - ], - "meshes":[ - { - "name":"Suzanne", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3, - "material":0 - } - ] - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":1966, - "max":[ - 1.3671875, - 0.984375, - 0.8515625 - ], - "min":[ - -1.3671875, - -0.984375, - -0.8515625 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":1966, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":1966, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":2904, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":23592, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":23592, - "byteOffset":23592, - "target":34962 - }, - { - "buffer":0, - "byteLength":15728, - "byteOffset":47184, - "target":34962 - }, - { - "buffer":0, - "byteLength":5808, - "byteOffset":62912, - "target":34963 - } - ], - "buffers":[ - { - "byteLength":68720, - "uri":"pink-monkey.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:a955cb6bcdcaed40dee676c707ba562f93ca960806107c6e9f328764be49a303 +size 1613 diff --git a/assets/models/tree.bin b/assets/models/tree.bin index e7826210d4c2a288d9825d6987ca7d963ef294b5..225ddd1884035a2c5d098123da7d4e5ca48c572d 100644 GIT binary patch literal 130 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 A3jhEB diff --git a/assets/models/tree.gltf b/assets/models/tree.gltf index 51bf9ed..58b4fb9 100644 --- a/assets/models/tree.gltf +++ b/assets/models/tree.gltf @@ -1,140 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.4.56", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"tree01", - "scale":[ - 0.009999999776482582, - 0.009999999776482582, - 0.009999999776482582 - ] - } - ], - "materials":[ - { - "name":"Mat", - "pbrMetallicRoughness":{ - "baseColorTexture":{ - "index":0 - }, - "metallicFactor":0 - } - } - ], - "meshes":[ - { - "name":"tree01_1", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3, - "material":0 - } - ] - } - ], - "textures":[ - { - "sampler":0, - "source":0 - } - ], - "images":[ - { - "mimeType":"image/png", - "name":"tree_texture", - "uri":"tree_texture.png" - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":395, - "max":[ - 123.4296646118164, - 509.0864562988281, - 168.36563110351562 - ], - "min":[ - -184.9799346923828, - 0, - -155.37196350097656 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":395, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":395, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":471, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":4740, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":4740, - "byteOffset":4740, - "target":34962 - }, - { - "buffer":0, - "byteLength":3160, - "byteOffset":9480, - "target":34962 - }, - { - "buffer":0, - "byteLength":942, - "byteOffset":12640, - "target":34963 - } - ], - "samplers":[ - { - "magFilter":9729, - "minFilter":9987 - } - ], - "buffers":[ - { - "byteLength":13584, - "uri":"tree.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:7f5b405308b025a6fdc8e5dad59d374350415338102219f5a31562064d3624c8 +size 1846 diff --git a/assets/models/tree_texture.png b/assets/models/tree_texture.png index f5726d41ea881c58cf74e16442f6e7856ad98742..1d2807bd98744fa735b0b5ac83e02b7ac3c1be8f 100644 GIT binary patch literal 128 zcmWN_$qmCG5J1s8RnS0;*}>hg%mkz)SQ+d?st>2H_iy-8A0OW4Jn0bP+2`Yhw5_%) zZ)Lm%olwLr+31y1OUk-K7Y)_tJ%F@YO3cyu;0kJtMY&LePD@6*jHW~h3NfSi#zER) L9Y*~JJQ&OVsp==K literal 845 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911L)MWvCLk0$B5l7=R`)GBhwSI502( z#RSL@CQB1Gn;9NBk>>&o!x#> Date: Fri, 26 Sep 2025 22:44:43 +0300 Subject: [PATCH 10/45] Implement HDRI skybox support --- .gitattributes | 1 + Cargo.lock | 114 +++++++++++++- assets/exr/citrus_orchard_road_puresky_4k.exr | 3 + assets/exr/qwantani_sunset_puresky_2k.exr | 3 + assets/shaders/skybox.frag | 1 + game/src/main.rs | 4 +- glium_platform/Cargo.toml | 2 + glium_platform/src/platform.rs | 8 +- glium_platform/src/render/basic.rs | 10 +- glium_platform/src/render/mod.rs | 2 + glium_platform/src/render/skybox.rs | 149 ++++++++++++++++++ glium_platform/src/system.rs | 2 + 12 files changed, 282 insertions(+), 17 deletions(-) create mode 100644 assets/exr/citrus_orchard_road_puresky_4k.exr create mode 100644 assets/exr/qwantani_sunset_puresky_2k.exr create mode 100644 glium_platform/src/render/skybox.rs diff --git a/.gitattributes b/.gitattributes index 7dc2605..7182511 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ assets/models/* filter=lfs diff=lfs merge=lfs -text +assets/exr/* filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index 3665a1b..25cb3cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -319,12 +325,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "cursor-icon" version = "1.2.0" @@ -384,6 +415,21 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -603,6 +649,16 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -636,12 +692,14 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "image" -version = "0.25.6" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", + "exr", + "moxcms", "num-traits", "png", "zune-core", @@ -769,6 +827,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.174" @@ -864,6 +928,16 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1267,11 +1341,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" -version = "0.17.16" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "crc32fast", "fdeflate", "flate2", @@ -1310,6 +1384,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +dependencies = [ + "num-traits", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -1388,9 +1471,11 @@ name = "raidillon_glium" version = "0.1.0" dependencies = [ "anyhow", + "exr", "glam", "glium", "gltf", + "image", "imgui", "imgui-glium-renderer", "imgui-winit-support", @@ -1419,6 +1504,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2408,6 +2503,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.20" diff --git a/assets/exr/citrus_orchard_road_puresky_4k.exr b/assets/exr/citrus_orchard_road_puresky_4k.exr new file mode 100644 index 0000000..c57cb53 --- /dev/null +++ b/assets/exr/citrus_orchard_road_puresky_4k.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8aa6cc5bb4a5a8f5fa12870cefdd6a6b600072454add837dca45e340c1549d30 +size 70720809 diff --git a/assets/exr/qwantani_sunset_puresky_2k.exr b/assets/exr/qwantani_sunset_puresky_2k.exr new file mode 100644 index 0000000..86cd3d7 --- /dev/null +++ b/assets/exr/qwantani_sunset_puresky_2k.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f738ddd35a5e1a291eb45b30eea73b4d296dc4a05a412ae2f187ef5e95dc076a +size 18335204 diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag index 8418e54..864be50 100644 --- a/assets/shaders/skybox.frag +++ b/assets/shaders/skybox.frag @@ -17,6 +17,7 @@ vec2 sample_spherical_map(vec3 v) { void main() { vec2 uv = sample_spherical_map(normalize(direction)); + uv.y = 1.0 - uv.y; vec3 color = texture(equirect, uv).rgb; frag_color = vec4(color, 1.0); } diff --git a/game/src/main.rs b/game/src/main.rs index 05ae11e..bea5fe0 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -101,8 +101,8 @@ fn main() { let platform = GliumPlatform::initialize( engine, "Raidillon".to_string(), - 1920, - 1080, + 2560, + 1440, ); platform.run() }; diff --git a/glium_platform/Cargo.toml b/glium_platform/Cargo.toml index 0c0ecc2..6e51be6 100644 --- a/glium_platform/Cargo.toml +++ b/glium_platform/Cargo.toml @@ -18,3 +18,5 @@ indexmap = "2.10.0" imgui = "0.12.0" imgui-winit-support = "0.13.0" imgui-glium-renderer = "0.13.0" +exr = "1.73.0" +image = { version = "0.25.8", default-features = false, features = ["exr"] } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 16df321..f03cd96 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -14,8 +14,9 @@ use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; use crate::render::debug_ui::ImguiBridge; -use crate::render::BasicMeshRenderingSystem; +use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; use crate::GliumAssetManager; +use glam::Vec3; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -44,7 +45,8 @@ impl> Platform for GliumPlatfor let time_cfg = time::Config::default(); let time = time::Time::new(time_cfg); - // Install rendering systems + // Install rendering systems in order + rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); @@ -95,12 +97,14 @@ impl> Platform for GliumPlatfor asset_manager: self.asset_manager.clone(), window: &mut self.window, debug_ui_buffer, + env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; self.rendering_system_manager .systems .values_mut() .for_each(|system| system.render(&mut context)); + target.finish().unwrap(); } _ => {}, diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 0f1db93..8551ecd 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -55,14 +55,8 @@ impl RenderingSystem for BasicMeshRenderingSystem { } }; - // Direction from the light source (0,+Y) towards the scene. - let light_dir: Vec3 = Vec3::new(0.0, -1.0, 0.0).normalize(); - - // let asset_manager = ctx.asset_manager.borrow(); - // let any_ref: &dyn Any = &**asset_manager; - // if let Some(glium_manager) = any_ref.downcast_ref::() { - // &glium_manager.models; - // } + // Use HDR-derived environment light direction if provided, otherwise default to downward + let light_dir: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; let asset_manager = ctx.asset_manager.borrow(); diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index c31a0d9..f49e308 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,4 +1,6 @@ mod basic; pub mod debug_ui; +mod skybox; pub use basic::BasicMeshRenderingSystem; +pub use skybox::SkyboxRenderingSystem; diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs new file mode 100644 index 0000000..49ea594 --- /dev/null +++ b/glium_platform/src/render/skybox.rs @@ -0,0 +1,149 @@ +use std::path::PathBuf; +use std::rc::Rc; +use std::cell::RefCell; +use glium::{Display, Program, Surface, VertexBuffer, IndexBuffer, implement_vertex}; +use glium::glutin::surface::WindowSurface; +use glium::index::PrimitiveType; +use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; +use glium::uniform; +use glam::{Mat4, Vec2, Vec3}; +use raidillon_assets::include_shader; +use crate::system::RenderingContext; +use crate::RenderingSystem; + +#[derive(Copy, Clone)] +struct SkyboxVertex { position: [f32; 3] } +implement_vertex!(SkyboxVertex, position); + +pub struct SkyboxRenderingSystem { + program: Program, + quad_vb: VertexBuffer, + quad_ib: IndexBuffer, + /// Equirectangular HDR image, tonemapped to sRGB for skybox view + equirect_srgb: SrgbTexture2d, + /// Dominant light direction estimated from HDRI + light_dir: Vec3, +} + +impl SkyboxRenderingSystem { + fn build_cube(display: &Display) -> (VertexBuffer, IndexBuffer) { + // Unit cube centered at origin + let p = &[ + [-1.0, -1.0, -1.0], [ 1.0, -1.0, -1.0], [ 1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], // back + [-1.0, -1.0, 1.0], [ 1.0, -1.0, 1.0], [ 1.0, 1.0, 1.0], [-1.0, 1.0, 1.0], // front + ]; + let verts = vec![ + SkyboxVertex { position: p[0] }, SkyboxVertex { position: p[1] }, SkyboxVertex { position: p[2] }, SkyboxVertex { position: p[3] }, // back + SkyboxVertex { position: p[4] }, SkyboxVertex { position: p[5] }, SkyboxVertex { position: p[6] }, SkyboxVertex { position: p[7] }, // front + ]; + let idx: [u16; 36] = [ + // back face + 0,1,2, 2,3,0, + // front face + 4,6,5, 6,4,7, + // left face + 0,3,7, 7,4,0, + // right face + 1,5,6, 6,2,1, + // bottom face + 0,4,5, 5,1,0, + // top face + 3,2,6, 6,7,3, + ]; + ( + VertexBuffer::new(display, &verts).unwrap(), + IndexBuffer::new(display, PrimitiveType::TrianglesList, &idx).unwrap(), + ) + } + + fn load_hdr_equirect_and_analyze(display: &Display, path: &std::path::Path) -> (SrgbTexture2d, Vec3) { + // Use image crate to decode EXR as f32 RGB + let dyn_img = image::ImageReader::open(path).expect("open exr").with_guessed_format().expect("guess format").decode().expect("decode exr"); + let hdr = dyn_img.to_rgb32f(); + let (width, height) = hdr.dimensions(); + let width = width as usize; let height = height as usize; + let mut dir_accum = Vec3::ZERO; + let mut weight_sum = 0.0f32; + for y in 0..height { + let v = (y as f32 + 0.5) / height as f32; + let theta = (v - 0.5) * std::f32::consts::PI; + let lat_weight = theta.cos().max(0.0); + for x in 0..width { + let u = (x as f32 + 0.5) / width as f32; + let phi = (u - 0.5) * 2.0 * std::f32::consts::PI; + let px = hdr.get_pixel(x as u32, y as u32).0; + let rgb = Vec3::new(px[0], px[1], px[2]); + let lum = 0.2126*rgb.x + 0.7152*rgb.y + 0.0722*rgb.z; + if lum > 0.0 { + let dir = Vec3::new(phi.cos()*theta.cos(), theta.sin(), phi.sin()*theta.cos()); + let w = lum * lat_weight; + dir_accum += dir * w; + weight_sum += w; + } + } + } + let mut light_dir = if weight_sum > 0.0 { dir_accum / weight_sum } else { Vec3::new(0.0, -1.0, 0.0) }; + if light_dir.length_squared() < 1e-6 { light_dir = Vec3::new(0.0,-1.0,0.0); } + light_dir = light_dir.normalize(); + + // Tonemap to sRGB + let mut srgb_bytes = Vec::with_capacity(width*height*4); + for y in 0..height { + for x in 0..width { + let px = hdr.get_pixel(x as u32, y as u32).0; + let mapped = Vec3::new(px[0], px[1], px[2]) / (Vec3::new(px[0], px[1], px[2]) + Vec3::ONE); + let srgb = mapped.powf(1.0/2.2); + srgb_bytes.extend_from_slice(&[ + (srgb.x.clamp(0.0,1.0)*255.0) as u8, + (srgb.y.clamp(0.0,1.0)*255.0) as u8, + (srgb.z.clamp(0.0,1.0)*255.0) as u8, + 255u8, + ]); + } + } + let raw = RawImage2d::from_raw_rgba(srgb_bytes, (width as u32, height as u32)); + let tex = SrgbTexture2d::new(display, raw).unwrap(); + (tex, light_dir) + } +} + +impl RenderingSystem for SkyboxRenderingSystem { + fn initialize(display: &Display, _window: &glium::winit::window::Window) -> 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(); + let (quad_vb, quad_ib) = Self::build_cube(display); + + // Load EXR from assets/exr + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let path = std::path::Path::new(manifest_dir).join("../assets/exr/qwantani_sunset_puresky_2k.exr"); + let (equirect_srgb, light_dir) = Self::load_hdr_equirect_and_analyze(display, &path); + Self { program, quad_vb, quad_ib, equirect_srgb, light_dir } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + // Provide view and projection without translation for skybox + let cam = match ctx.scene.world.query::<&raidillon_platform::Camera>().iter().next() { + Some((_, cam)) => *cam, + None => return, + }; + let mut view = cam.view(); + // remove translation from view matrix (only orientation) + view.col_mut(3).x = 0.0; view.col_mut(3).y = 0.0; view.col_mut(3).z = 0.0; + let uniforms = uniform! { + view: view.to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + equirect: &self.equirect_srgb, + }; + let params = glium::DrawParameters { depth: glium::Depth { test: glium::draw_parameters::DepthTest::IfLessOrEqual, write: false, ..Default::default() }, ..Default::default() }; + ctx.target.draw(&self.quad_vb, &self.quad_ib, &self.program, &uniforms, ¶ms).ok(); + + // Share light direction with following passes + ctx.env_light_dir = self.light_dir; + } +} + +// Provide a getter for light direction for other systems +impl SkyboxRenderingSystem { + pub fn light_direction(&self) -> Vec3 { self.light_dir } +} diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 7f1f55e..891fec0 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -7,6 +7,7 @@ use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; use raidillon_core::DebugUIBuffer; use raidillon_core::scene::Scene; +use glam::Vec3; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -14,6 +15,7 @@ pub struct RenderingContext<'a> { pub window: &'a mut glium::winit::window::Window, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: Rc>, + pub env_light_dir: Vec3, } /// The internal "rendering system" trait of glium_platform. From 46c8c3281907ed42d45b6a2c0eef41732b533635 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 28 Sep 2025 01:31:14 +0300 Subject: [PATCH 11/45] Huge input update, FPS Camera controls system Long day. I now store winit::window::Window in a mutex. --- Cargo.lock | 5 +- engine/Cargo.toml | 1 + engine/src/engine.rs | 9 ++ engine/src/input.rs | 69 ++++++++++++++++ engine/src/lib.rs | 2 + engine/src/system.rs | 3 +- engine/src/systems/fps_camera.rs | 114 ++++++++++++++++++++++++++ engine/src/systems/mod.rs | 1 + game/src/main.rs | 74 ++++++++++++++--- glium_platform/src/platform.rs | 28 ++++--- glium_platform/src/render/basic.rs | 3 +- glium_platform/src/render/debug_ui.rs | 21 +++-- glium_platform/src/render/skybox.rs | 3 +- glium_platform/src/system.rs | 11 +-- platform/src/context.rs | 2 + 15 files changed, 307 insertions(+), 39 deletions(-) create mode 100644 engine/src/input.rs create mode 100644 engine/src/systems/fps_camera.rs create mode 100644 engine/src/systems/mod.rs 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)] From 7732280229cedbeaa8771c0b487a87eb51cec823 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 29 Sep 2025 16:04:40 +0300 Subject: [PATCH 12/45] Add WASD keys to FPSDebugCamera system, rename to FPSDebugCameraSystem --- engine/src/systems/fps_camera.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index cbe14b6..3cc0a3d 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -2,12 +2,12 @@ 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::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::Camera; -pub struct FPSCameraSystem { +pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), mouse_enabled: bool, position: Vec3, @@ -17,7 +17,7 @@ pub struct FPSCameraSystem { sensitivity: f32, } -impl Default for FPSCameraSystem { +impl Default for FPSDebugCameraSystem { fn default() -> Self { Self { mouse_delta: Default::default(), @@ -31,7 +31,7 @@ impl Default for FPSCameraSystem { } } -impl System for FPSCameraSystem { +impl System for FPSDebugCameraSystem { fn load_world(&mut self, ctx: &mut SystemContext) { ctx.scene.world.spawn((Camera { eye: Vec3::new(0.0, 0.0, 2.0), @@ -93,15 +93,32 @@ impl System for FPSCameraSystem { self.pitch = self.pitch.clamp(-89.0, 89.0); } + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + let input = ctx.input_state.borrow_mut(); + + if input.key_held(KeyCode::KeyW) { + self.position += front * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.position -= front * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.position -= right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.position += right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { camera.eye = self.position; - camera.center = self.position + self.front(); + camera.center = self.position + front; }); self.mouse_delta = (0.0, 0.0); } } -impl FPSCameraSystem { +impl FPSDebugCameraSystem { pub fn front(&self) -> Vec3 { let yaw_rad = self.yaw.to_radians(); let pitch_rad = self.pitch.to_radians(); From 6e42d94b4416c62fdfed4a11056fc2b215b1c033 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 29 Sep 2025 16:05:54 +0300 Subject: [PATCH 13/45] Remove the older debug system, rename FPSDebugCameraSystem --- game/src/main.rs | 59 ++---------------------------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/game/src/main.rs b/game/src/main.rs index b7c45e0..d5a826b 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -13,53 +13,12 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_engine::systems::fps_camera::FPSCameraSystem; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; 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 { @@ -106,20 +65,6 @@ 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) { @@ -133,7 +78,7 @@ 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::(); // engine.system_manager.add::(); From ef055a1bda1f627c8e28fc51196e425e99980c53 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 15 Oct 2025 22:33:04 +0300 Subject: [PATCH 14/45] Replace contexts with resources - Implements a new macro to generate code for a new structure: TypeMap - TypeMaps are wrappers for HashMaps that use TypeIDs as keys. - Refactor the entire codebase to use the new resource structures. - This commit is the first step towards getting rid of "god context objects everywhere". --- Cargo.toml | 2 +- core/src/engine.rs | 5 +- core/src/lib.rs | 3 +- core/src/scene.rs | 16 +++- core/src/utils/managers.rs | 44 ++++++++++ core/src/utils/mod.rs | 2 + core/src/utils/typemap.rs | 122 ++++++++++++++++++++++++++ engine/src/engine.rs | 85 ++++++++---------- engine/src/lib.rs | 2 + engine/src/resources.rs | 3 + engine/src/system.rs | 9 +- engine/src/systems/fps_camera.rs | 35 +++++--- game/src/main.rs | 37 ++++---- glium_platform/src/platform.rs | 7 +- glium_platform/src/render/debug_ui.rs | 2 +- glium_platform/src/system.rs | 6 +- 16 files changed, 287 insertions(+), 93 deletions(-) create mode 100644 core/src/utils/managers.rs create mode 100644 core/src/utils/mod.rs create mode 100644 core/src/utils/typemap.rs create mode 100644 engine/src/resources.rs diff --git a/Cargo.toml b/Cargo.toml index 102844c..23332d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ members = [ "asset", "game", "ecs", - "engine" + "engine", ] diff --git a/core/src/engine.rs b/core/src/engine.rs index 80d2a69..b70a945 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -11,7 +11,8 @@ pub trait EngineTrait { fn fixed_update(&mut self, platform_context: Self::PlatformCtx); fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; - fn get_debug_ui_buffer(&self) -> Rc>; + fn current_scene(&self) -> &Scene; + fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; fn reset_debug_ui_buffer(&mut self); - fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, Rc>); + // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Self::Scene, &DebugUIBuffer); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 4b7d531..238d5a6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,7 @@ pub mod engine; -pub mod scene; pub mod debug_ui; pub mod time; +pub mod utils; +pub mod scene; pub use debug_ui::*; diff --git a/core/src/scene.rs b/core/src/scene.rs index 9d1a7d4..1a075bf 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -1,19 +1,31 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; +use crate::{define_typemap, DebugUIBuffer}; pub struct Scene { pub title: String, pub world: hecs::World, pub skybox_texture_path: Option, + pub resources: SceneResources, } +define_typemap!(SceneResources,); + impl Scene { pub fn new(title: String, skybox_texture_path: Option) -> Self { - Self { + let mut s = Self { title, world: hecs::World::new(), skybox_texture_path, - } + resources: SceneResources::new(), + }; + s.load_default_resources(); + s + } + + pub fn load_default_resources(&mut self) { + let dbg = DebugUIBuffer::new(); + self.resources.insert(dbg); } } diff --git a/core/src/utils/managers.rs b/core/src/utils/managers.rs new file mode 100644 index 0000000..aac2ce7 --- /dev/null +++ b/core/src/utils/managers.rs @@ -0,0 +1,44 @@ +/// Unused as of now. +#[macro_export] +macro_rules! create_manager { + ($manager_name:ident, $trait_name:ident) => { + pub struct $manager_name { + systems: ::indexmap::IndexMap<::std::any::TypeId, Box>, + } + + impl $manager_name { + pub fn new() -> Self { + Self { + systems: ::indexmap::IndexMap::default(), + } + } + + pub fn add(&mut self) { + self.systems + .insert(::std::any::TypeId::of::(), Box::new(S::default())); + } + + pub fn remove(&mut self) { + self.systems.shift_remove(&::std::any::TypeId::of::()); + } + + pub fn for_each_value(&self, mut f: F) + where + F: FnMut(&dyn $trait_name), + { + for value in self.systems.values() { + f(value.as_ref()); + } + } + + pub fn for_each_value_mut(&mut self, mut f: F) + where + F: FnMut(&mut dyn $trait_name), + { + for value in self.systems.values_mut() { + f(value.as_mut()); + } + } + } + }; +} diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs new file mode 100644 index 0000000..4a5eaf5 --- /dev/null +++ b/core/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod typemap; +mod managers; diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs new file mode 100644 index 0000000..88d3dc5 --- /dev/null +++ b/core/src/utils/typemap.rs @@ -0,0 +1,122 @@ +#[macro_export] +macro_rules! define_typemap { + ($name:ident, $($trait_bound:tt)*) => { + pub struct $name { + map: std::collections::HashMap>, + } + + impl $name { + pub fn new() -> Self { + Self { + map: std::collections::HashMap::new(), + } + } + + pub fn insert(&mut self, value: T) -> Option> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + let type_id = std::any::TypeId::of::(); + self.map + .insert(type_id, Box::new(value)) + .and_then(|boxed| boxed.downcast().ok()) + } + + pub fn get(&self) -> Option<&T> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .get(&std::any::TypeId::of::()) + .and_then(|any| any.downcast_ref::()) + } + + pub fn get_mut(&mut self) -> Option<&mut T> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .get_mut(&std::any::TypeId::of::()) + .and_then(|any| any.downcast_mut::()) + } + + pub fn remove(&mut self) -> Option> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .remove(&std::any::TypeId::of::()) + .and_then(|boxed| boxed.downcast().ok()) + } + + pub fn len(&self) -> usize { + self.map.len() + } + + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + pub fn clear(&mut self) { + self.map.clear(); + } + + pub fn contains(&self) -> bool + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map.contains_key(&std::any::TypeId::of::()) + } + } + + impl Default for $name { + fn default() -> Self { + Self::new() + } + } + }; +} + +// pub struct TypeMap { +// map: HashMap>, +// } +// +// impl TypeMap { +// pub fn new() -> Self { +// Self { +// map: HashMap::new(), +// } +// } +// +// pub fn insert(&mut self, value: T) -> Option> { +// let type_id = TypeId::of::(); +// self.map +// .insert(type_id, Box::new(value)) +// .and_then(|boxed| boxed.downcast().ok()) +// } +// +// pub fn get(&self) -> Option<&T> +// where +// T: Any + 'static, +// { +// self.map.get(&TypeId::of::()) +// .and_then(|any| any.downcast_ref::()) +// } +// +// pub fn get_mut(&mut self) -> Option<&mut T> +// where +// T: Any + 'static, +// { +// self.map.get_mut(&TypeId::of::()).and_then(|any| any.downcast_mut::()) +// } +// +// pub fn remove(&mut self) -> Option> { +// unimplemented!() +// } +// } +// +// impl Default for TypeMap { +// fn default() -> Self { +// Self::new() +// } +// } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 68f17e0..7c366dc 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -3,15 +3,22 @@ use std::rc::Rc; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{define_typemap, DebugUIBuffer}; use raidillon_core::engine::EngineTrait; use crate::input::InputState; +use crate::resources::EngineResources; pub struct Engine { pub scene_manager: SceneManager, pub system_manager: SystemManager, - debug_ui_buffer: Rc>, - input_state: Rc>, + pub resources: EngineResources, +} + +impl Engine { + fn load_default_resources(&mut self) { + let input = InputState::default(); + self.resources.insert(input); + } } impl EngineTrait for Engine { @@ -19,12 +26,13 @@ impl EngineTrait for Engine { fn new() -> Self { let scene_manager = SceneManager::new(); let system_manager = SystemManager::new(); - Self { + let mut s = Self { scene_manager, system_manager, - debug_ui_buffer: Rc::new(RefCell::new(DebugUIBuffer::new())), - input_state: Default::default(), - } + resources: EngineResources::new(), + }; + s.load_default_resources(); + s } /// Initialize systems, load the world. @@ -33,60 +41,35 @@ impl EngineTrait for Engine { for system in self.system_manager.systems.values_mut() { system.initialize(); } - - 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(), - }; - + self.resources.insert(platform_context); // Engine Loading Stage 2: load world for system in self.system_manager.systems.values_mut() { - system.load_world(&mut ctx); + system.load_world(&mut self.resources, &mut self.scene_manager.current_mut()); } } /// Update the engine fn frame_update(&mut self, platform_context: PlatformContext) { - self.debug_ui_buffer.borrow_mut().reset_buffer(); - 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(), - }; + self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.frame_update(&mut ctx); + system.frame_update(&mut self.resources, &mut self.scene_manager.current_mut()); } } fn fixed_update(&mut self, platform_context: PlatformContext) { - 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(), - }; - + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.fixed_update(&mut ctx); + system.fixed_update(&mut self.resources, &mut self.scene_manager.current_mut()); } } 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(), - }; - + // self.input_state.borrow_mut().handle_event(&platform_context.current_event); + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.handle_event(&mut ctx); + system.handle_event(&mut self.resources, &mut self.scene_manager.current_mut()); } } @@ -100,16 +83,20 @@ impl EngineTrait for Engine { self.scene_manager.current_mut() } - fn get_debug_ui_buffer(&self) -> Rc> { - self.debug_ui_buffer.clone() + fn current_scene(&self) -> &Scene { + self.scene_manager.current() + } + + fn get_debug_ui_buffer(&self) -> &DebugUIBuffer { + self.scene_manager.current().resources.get::().unwrap() } fn reset_debug_ui_buffer(&mut self) { - self.debug_ui_buffer.borrow_mut().reset_buffer(); + self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); } - fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, Rc>) { - let (sm, dub) = (&mut self.scene_manager, &mut self.debug_ui_buffer); - (sm.current_mut(), dub.clone()) - } + // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, &DebugUIBuffer) { + // let (sm, dub) = (&mut self.scene_manager, self.scene_manager.current().resources.get::().unwrap()); + // (sm.current_mut(), dub) + // } } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 18ce5fd..9a87556 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -2,5 +2,7 @@ pub mod engine; pub mod system; mod input; pub mod systems; +mod resources; pub use crate::engine::Engine; +pub use crate::resources::EngineResources; diff --git a/engine/src/resources.rs b/engine/src/resources.rs new file mode 100644 index 0000000..cd4be0b --- /dev/null +++ b/engine/src/resources.rs @@ -0,0 +1,3 @@ +use raidillon_core::define_typemap; + +define_typemap!(EngineResources,); diff --git a/engine/src/system.rs b/engine/src/system.rs index b863a75..55cccb1 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -6,6 +6,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; use crate::input::InputState; +use crate::resources::EngineResources; pub struct SystemContext<'a> { pub scene: &'a mut Scene, @@ -18,10 +19,10 @@ pub trait System { /// Initialize the system. fn initialize(&mut self) {} /// Spawn the first entities of the world. - fn load_world(&mut self, _ctx: &mut SystemContext) {} - fn handle_event(&mut self, _ctx: &mut SystemContext) {} - fn fixed_update(&mut self, _ctx: &mut SystemContext) {} - fn frame_update(&mut self, _ctx: &mut SystemContext) {} + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {} } pub struct SystemManager { diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 3cc0a3d..6bd99f7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -5,7 +5,10 @@ use winit::event::{ElementState, Event, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; +use crate::input::InputState; +use crate::resources::EngineResources; +use raidillon_core::scene::Scene; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -32,20 +35,22 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, ctx: &mut SystemContext) { - ctx.scene.world.spawn((Camera { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + 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, + aspect: pctx.frame_width / pctx.frame_height, znear: 0.1, zfar: 100.0, },)); } - fn handle_event(&mut self, ctx: &mut SystemContext) { - let event2 = ctx.platform_context.current_event.clone(); + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + let event2 = pctx.current_event.clone(); match event2 { Event::DeviceEvent { device_id, event} => { match event { @@ -60,7 +65,7 @@ impl System for FPSDebugCameraSystem { WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Right { // blood and tear - let window = ctx.platform_context.window.lock().unwrap(); + let window = pctx.window.lock().unwrap(); match state { ElementState::Pressed => { if window @@ -86,7 +91,10 @@ impl System for FPSDebugCameraSystem { } } - fn frame_update(&mut self, ctx: &mut SystemContext) { + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); + if self.mouse_enabled { self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; @@ -95,22 +103,21 @@ impl System for FPSDebugCameraSystem { let front = self.front(); let right_vec = front.cross(Vec3::Y).normalize(); - let input = ctx.input_state.borrow_mut(); if input.key_held(KeyCode::KeyW) { - self.position += front * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position += front * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyS) { - self.position -= front * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position -= front * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyA) { - self.position -= right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyD) { - self.position += right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; } - ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { camera.eye = self.position; camera.center = self.position + front; }); diff --git a/game/src/main.rs b/game/src/main.rs index d5a826b..acedefd 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,8 +1,8 @@ use std::fmt::format; use glam::{Quat, Vec3}; -use raidillon_engine::{Engine, system::System}; +use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; -use raidillon_platform::{Platform, Camera}; +use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; use raidillon_ecs::components::ModelHandle; @@ -13,6 +13,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; +use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; const TEST_GLTF: &str = "pink-monkey.gltf"; @@ -22,11 +23,13 @@ const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { - fn handle_event(&mut self, ctx: &mut SystemContext) { + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + if let Event::WindowEvent { event: WindowEvent::Resized(sz), .. } = - &ctx.platform_context.current_event + pctx.current_event { - ctx.scene.world + scene.world .query_mut::<&mut Camera>() .into_iter() .for_each(|(_, cam)| { @@ -42,14 +45,16 @@ struct RenderingTestSystem { } impl System for RenderingTestSystem { - fn load_world(&mut self, ctx: &mut SystemContext) { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - let mut am = ctx.platform_context.asset_manager.borrow_mut(); + let mut am = pctx.asset_manager.borrow_mut(); am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - ctx.scene.world.spawn(( + scene.world.spawn(( Transform { translation: Vec3::new(0.0, 0.0, 0.0), rotation: Quat::IDENTITY, @@ -59,17 +64,19 @@ impl System for RenderingTestSystem { )); } - fn frame_update(&mut self, ctx: &mut SystemContext) { - let mut dbg_ui = ctx.debug_ui_buffer.borrow_mut(); + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap().clone(); + let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); - 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.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); + dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - fn fixed_update(&mut self, ctx: &mut SystemContext) { - ctx.scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * ctx.platform_context.time_ctx.fixed_dt); + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { + t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); }); } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 9418ab1..db8e56e 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -98,9 +98,12 @@ impl> Platform for GliumPlatfor WindowEvent::RedrawRequested => { let mut target = self.display.draw(); target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0); - let (scene_mut, debug_ui_buffer) = self.engine.scene_and_debug_ui_buffer_mut(); + let (scene, debug_ui_buffer) = ( + self.engine.current_scene(), + self.engine.get_debug_ui_buffer(), + ); let mut context = RenderingContext { - scene: scene_mut, + scene, target: &mut target, asset_manager: self.asset_manager.clone(), window: self.window.clone(), diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs index be4ff0d..ac4a06e 100644 --- a/glium_platform/src/render/debug_ui.rs +++ b/glium_platform/src/render/debug_ui.rs @@ -41,7 +41,7 @@ impl RenderingSystem for ImguiBridge { self.rendered_this_frame = true; let ui = self.imgui.frame(); - ctx.debug_ui_buffer.borrow().write_buffer(&ui); + ctx.debug_ui_buffer.write_buffer(&ui); { let window = ctx.window.lock().unwrap(); diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 3460bc3..51a292f 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,7 +6,7 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{define_typemap, DebugUIBuffer}; use raidillon_core::scene::Scene; use glam::Vec3; @@ -15,7 +15,7 @@ pub struct RenderingContext<'a> { pub target: &'a mut Frame, pub window: Arc>, pub asset_manager: ModelManagerRef, - pub debug_ui_buffer: Rc>, + pub debug_ui_buffer: &'a DebugUIBuffer, pub env_light_dir: Vec3, } @@ -35,6 +35,8 @@ pub trait RenderingSystem { Self: Sized; } +// define_typemap!(RenderingSystemManager, RenderingSystem); + pub struct RenderingSystemManager { pub systems: IndexMap>, } From b86bbdd237706dfea4c9e5db182d23d83daab8ed Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 16:11:53 +0300 Subject: [PATCH 15/45] fix spacing --- game/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/src/main.rs b/game/src/main.rs index acedefd..4e0ee6e 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -65,8 +65,9 @@ impl System for RenderingTestSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap().clone(); + let pctx = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); + dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); @@ -75,6 +76,7 @@ impl System for RenderingTestSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); }); From 1a48e58a1cfca6baea4cd60437abfca69fe0d6dd Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 16:12:18 +0300 Subject: [PATCH 16/45] Implement get_many. I believe the repeated code can be generated with procedural macros, but I don't need that right now. I'll do it if I ever end up needing this. Right now I need to start making the game itself more than anything. --- core/src/utils/typemap.rs | 436 +++++++++++++++++++++++++++++-- engine/src/systems/fps_camera.rs | 3 +- 2 files changed, 408 insertions(+), 31 deletions(-) diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs index 88d3dc5..bd28a66 100644 --- a/core/src/utils/typemap.rs +++ b/core/src/utils/typemap.rs @@ -7,9 +7,7 @@ macro_rules! define_typemap { impl $name { pub fn new() -> Self { - Self { - map: std::collections::HashMap::new(), - } + Self { map: std::collections::HashMap::new() } } pub fn insert(&mut self, value: T) -> Option> @@ -17,49 +15,33 @@ macro_rules! define_typemap { T: std::any::Any + 'static + $($trait_bound)*, { let type_id = std::any::TypeId::of::(); - self.map - .insert(type_id, Box::new(value)) - .and_then(|boxed| boxed.downcast().ok()) + self.map.insert(type_id, Box::new(value)).and_then(|b| b.downcast().ok()) } pub fn get(&self) -> Option<&T> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .get(&std::any::TypeId::of::()) - .and_then(|any| any.downcast_ref::()) + self.map.get(&std::any::TypeId::of::())?.downcast_ref::() } pub fn get_mut(&mut self) -> Option<&mut T> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .get_mut(&std::any::TypeId::of::()) - .and_then(|any| any.downcast_mut::()) + self.map.get_mut(&std::any::TypeId::of::())?.downcast_mut::() } pub fn remove(&mut self) -> Option> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .remove(&std::any::TypeId::of::()) - .and_then(|boxed| boxed.downcast().ok()) + self.map.remove(&std::any::TypeId::of::())?.downcast().ok() } - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - pub fn clear(&mut self) { - self.map.clear(); - } + pub fn len(&self) -> usize { self.map.len() } + pub fn is_empty(&self) -> bool { self.map.is_empty() } + pub fn clear(&mut self) { self.map.clear(); } pub fn contains(&self) -> bool where @@ -67,11 +49,407 @@ macro_rules! define_typemap { { self.map.contains_key(&std::any::TypeId::of::()) } + + // --- tuple-based multi-get API --- + pub fn get_many<'a, T>(&'a self) -> Option<::Output<'a>> + where + T: __tm_get::GetTuple, + { + ::get_from(&self.map) + } + + pub fn get_many_mut<'a, T>(&'a mut self) -> Option<::Output<'a>> + where + T: __tm_get::GetTupleMut, + { + ::get_from_mut(&mut self.map) + } } - impl Default for $name { - fn default() -> Self { - Self::new() + impl Default for $name { fn default() -> Self { Self::new() } } + + // Put helper traits/impls in a private module to avoid name clashes. + mod __tm_get { + use std::any::{Any, TypeId}; + use std::collections::HashMap; + + pub trait GetTuple { + type Output<'a>; + fn get_from<'a>(map: &'a HashMap>) -> Option>; + } + + pub trait GetTupleMut { + type Output<'a>; + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option>; + } + + // Manual implementations for arities 1..=8. + impl GetTuple for (A,) + where + A: Any + 'static, + { + type Output<'a> = (&'a A,); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a,)) + } + } + + impl GetTupleMut for (A,) + where + A: Any + 'static, + { + type Output<'a> = (&'a mut A,); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + Some((a,)) + } + } + } + + impl GetTuple for (A, B) + where + A: Any + 'static, + B: Any + 'static, + { + type Output<'a> = (&'a A, &'a B); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] { return None; } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b)) + } + } + + impl GetTupleMut for (A, B) + where + A: Any + 'static, + B: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] { return None; } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + Some((a, b)) + } + } + } + + impl GetTuple for (A, B, C) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c)) + } + } + + impl GetTupleMut for (A, B, C) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + Some((a, b, c)) + } + } + } + + impl GetTuple for (A, B, C, D) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d)) + } + } + + impl GetTupleMut for (A, B, C, D) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + Some((a, b, c, d)) + } + } + } + + impl GetTuple for (A, B, C, D, E) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e)) + } + } + + impl GetTupleMut for (A, B, C, D, E) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + Some((a, b, c, d, e)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + Some((a, b, c, d, e, f)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F, G) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + let g = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f, g)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F, G) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F, &'a mut G); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + let g = (&mut *ptr_g).downcast_mut::()?; + Some((a, b, c, d, e, f, g)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F, G, H) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + H: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + let g = map.get(&TypeId::of::())?.downcast_ref::()?; + let h = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f, g, h)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F, G, H) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + H: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F, &'a mut G, &'a mut H); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + let g = (&mut *ptr_g).downcast_mut::()?; + let h = (&mut *ptr_h).downcast_mut::()?; + Some((a, b, c, d, e, f, g, h)) + } + } } } }; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 6bd99f7..0d98577 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -92,8 +92,7 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - let input = res.get::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; From 54967067bdf034e05280c59147ec297071b902d2 Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 17:01:23 +0300 Subject: [PATCH 17/45] Enable InputState event handler in engine --- engine/src/engine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 7c366dc..a5c8a1d 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -66,7 +66,7 @@ impl EngineTrait for Engine { } fn handle_event(&mut self, platform_context: PlatformContext) { - // self.input_state.borrow_mut().handle_event(&platform_context.current_event); + self.resources.get_mut::().unwrap().handle_event(&platform_context.current_event); self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { system.handle_event(&mut self.resources, &mut self.scene_manager.current_mut()); From 5f3da6ab99b7a3e8e86a440e212a1ee11910726e Mon Sep 17 00:00:00 2001 From: reo Date: Fri, 17 Oct 2025 23:18:18 +0300 Subject: [PATCH 18/45] Add FPS counter to the debug UI --- game/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/game/src/main.rs b/game/src/main.rs index 4e0ee6e..ad230cf 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -71,6 +71,7 @@ impl System for RenderingTestSystem { dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); + dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } From 8b5a6167ebfd06c345edb13db62cfea95ba4a9cf Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 19 Oct 2025 17:40:51 +0300 Subject: [PATCH 19/45] Physics Support - NEW CRATE: raidillon_physics. - Added new models to be able to test the physics support. - Added a new system "PhysicsSystem" to apply physics calculations to the ECS world. - NEW COMPONENT: RigidBodyComponent --- Cargo.lock | 521 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + assets/models/plane.glb | 3 + assets/models/sphere.glb | 3 + ecs/Cargo.toml | 1 + ecs/src/components.rs | 3 + game/Cargo.toml | 2 + game/src/main.rs | 75 +++--- game/src/systems/mod.rs | 3 + game/src/systems/physics.rs | 34 +++ physics/Cargo.toml | 9 + physics/src/lib.rs | 3 + physics/src/physics.rs | 82 ++++++ 13 files changed, 697 insertions(+), 43 deletions(-) create mode 100644 assets/models/plane.glb create mode 100644 assets/models/sphere.glb create mode 100644 game/src/systems/mod.rs create mode 100644 game/src/systems/physics.rs create mode 100644 physics/Cargo.toml create mode 100644 physics/src/lib.rs create mode 100644 physics/src/physics.rs diff --git a/Cargo.lock b/Cargo.lock index e1b6adb..525e6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.0" @@ -64,7 +70,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -79,6 +85,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -130,6 +145,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit_field" version = "0.10.3" @@ -198,7 +219,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -393,12 +414,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -455,6 +497,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -521,6 +575,96 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + [[package]] name = "glam" version = "0.30.8" @@ -659,6 +803,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -673,6 +826,30 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] [[package]] name = "hecs" @@ -784,7 +961,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -849,6 +1026,12 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.9" @@ -888,6 +1071,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.5" @@ -938,6 +1131,49 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.8", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.9.0" @@ -950,7 +1186,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -968,6 +1204,56 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -975,6 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1269,6 +1556,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -1301,6 +1597,40 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry3d" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.0", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "smallvec", + "spade", + "static_assertions", + "thiserror 2.0.17", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1384,6 +1714,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.24" @@ -1436,15 +1785,16 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", + "rapier3d", ] [[package]] name = "raidillon_engine" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "hecs", "indexmap", "raidillon_assets", @@ -1457,13 +1807,15 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "raidillon_ecs", "raidillon_engine", "raidillon_glium", + "raidillon_physics", "raidillon_platform", + "rapier3d", "winit", ] @@ -1473,7 +1825,7 @@ version = "0.1.0" dependencies = [ "anyhow", "exr", - "glam", + "glam 0.30.8", "glium", "gltf", "image", @@ -1489,22 +1841,62 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_physics" +version = "0.1.0" +dependencies = [ + "glam 0.30.8", + "raidillon_ecs", + "rapier3d", +] + [[package]] name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "winit", ] +[[package]] +name = "rapier3d" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd27b8eb36d0833fa0f2aea40164fabfad0fc34b9932286ae9e84f3452f5364" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash", + "simba", + "static_assertions", + "thiserror 2.0.17", + "wide", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon-core" version = "1.13.0" @@ -1533,12 +1925,35 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1577,6 +1992,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1649,6 +2073,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1681,7 +2118,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1701,12 +2138,36 @@ dependencies = [ "serde", ] +[[package]] +name = "spade" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +dependencies = [ + "hashbrown 0.15.4", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strict-num" version = "0.1.1" @@ -1730,7 +2191,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1744,6 +2214,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1808,6 +2289,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1929,7 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.0.8", "scoped-tls", "smallvec", @@ -2051,6 +2538,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 23332d0..dd544ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "game", "ecs", "engine", + "physics", ] diff --git a/assets/models/plane.glb b/assets/models/plane.glb new file mode 100644 index 0000000..e755152 --- /dev/null +++ b/assets/models/plane.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b54f00f327a5edc76c03f83d4efa2a4400215d7ca9c961b38537e7f836bca710 +size 1240 diff --git a/assets/models/sphere.glb b/assets/models/sphere.glb new file mode 100644 index 0000000..ea99045 --- /dev/null +++ b/assets/models/sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78583ec562098f7fa4eaf95c6e21b7d1ced53416eb4c71351e246d53f93aa0a3 +size 24744 diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index 0e1efb5..a47173d 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] glam = "0.30.5" raidillon_assets = { path = "../asset" } +rapier3d = "0.30.1" diff --git a/ecs/src/components.rs b/ecs/src/components.rs index 758f0f4..fa30cc2 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -15,3 +15,6 @@ impl Transform { } pub struct ModelHandle(pub ModelID); + +#[derive(Copy, Clone)] +pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); diff --git a/game/Cargo.toml b/game/Cargo.toml index 484e434..9bb72c4 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -13,6 +13,8 @@ raidillon_platform = { path = "../platform" } raidillon_assets = { path = "../asset" } raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } +raidillon_physics = { path = "../physics" } raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" +rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index ad230cf..8ddce6a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,11 +1,14 @@ +mod systems; use std::fmt::format; use glam::{Quat, Vec3}; +use rapier3d::dynamics::RigidBodyType; +use rapier3d::prelude::ColliderBuilder; use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::ModelHandle; +use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -15,9 +18,11 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use crate::systems::PhysicsSystem; -const TEST_GLTF: &str = "pink-monkey.gltf"; - +const TEST_GLTF: &str = "sphere.glb"; +const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] @@ -40,28 +45,45 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem { - rotation_speed: std::rc::Rc>, -} +struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); - self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - - let mut am = pctx.asset_manager.borrow_mut(); - - am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - - scene.world.spawn(( - Transform { - translation: Vec3::new(0.0, 0.0, 0.0), - rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), - }, - ModelHandle(TEST_GLTF), - )); + // Spawn Sphere + { + let tr = Transform { + translation: Vec3::new(0.0, 5.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + let collider = ColliderBuilder::ball(1.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(TEST_GLTF), + RigidBodyComponent(rb_handle), + )); + } + // Spawn Plane + { + let tr = Transform { + translation: Vec3::new(0.0, 0.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(10.0, 1.0, 10.0), + }; + let collider = ColliderBuilder::cuboid(10.0, 0.01, 10.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Fixed, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(PLANE_GLTF, &model_path(PLANE_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(PLANE_GLTF), + RigidBodyComponent(rb_handle), + )); + } } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { @@ -72,22 +94,13 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); - dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - - fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - - scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); - }); - } - } 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::(); diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs new file mode 100644 index 0000000..0ab4645 --- /dev/null +++ b/game/src/systems/mod.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use physics::PhysicsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs new file mode 100644 index 0000000..c5d4807 --- /dev/null +++ b/game/src/systems/physics.rs @@ -0,0 +1,34 @@ +use raidillon_core::scene::Scene; +use raidillon_ecs::components::RigidBodyComponent; +use raidillon_ecs::Transform; +use raidillon_engine::EngineResources; +use raidillon_engine::system::System; +use raidillon_physics::Physics; +use raidillon_platform::PlatformContext; + +#[derive(Default)] +pub struct PhysicsSystem; + +impl System for PhysicsSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let p = Physics::default(); + res.insert(p); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); + physics.step(pctx.time_ctx.fixed_dt); + + let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); + for (_ent, (tr, rb_component)) in query.iter() { + if let Some(body) = physics.get_rigid_body(rb_component.0) { + let pos = body.position(); + let translation = Physics::rapier_translation_to_glam(&pos.translation.vector); + let rotation = Physics::rapier_rotation_to_glam(&pos.rotation); + tr.translation = translation; + tr.rotation = rotation; + } + } + } +} diff --git a/physics/Cargo.toml b/physics/Cargo.toml new file mode 100644 index 0000000..dc34e98 --- /dev/null +++ b/physics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "raidillon_physics" +version = "0.1.0" +edition = "2024" + +[dependencies] +rapier3d = "0.30.1" +raidillon_ecs = { path = "../ecs" } +glam = "0.30.8" \ No newline at end of file diff --git a/physics/src/lib.rs b/physics/src/lib.rs new file mode 100644 index 0000000..a5dafc6 --- /dev/null +++ b/physics/src/lib.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use crate::physics::Physics; diff --git a/physics/src/physics.rs b/physics/src/physics.rs new file mode 100644 index 0000000..009984c --- /dev/null +++ b/physics/src/physics.rs @@ -0,0 +1,82 @@ +use glam::{Quat, Vec3}; +use rapier3d::na::{Quaternion, UnitQuaternion}; +use rapier3d::prelude::*; +use raidillon_ecs::Transform; + +/// Tiny wrapper around rapier3d. +pub struct Physics { + rigid_body_set: RigidBodySet, + collider_set: ColliderSet, + physics_pipeline: PhysicsPipeline, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + ccd_solver: CCDSolver, + gravity: Vector, + integration_parameters: IntegrationParameters, +} + +impl Default for Physics { + fn default() -> Self { + Self { + gravity: vector![0.0, -9.81, 0.0], + rigid_body_set: Default::default(), + collider_set: Default::default(), + physics_pipeline: Default::default(), + island_manager: Default::default(), + broad_phase: Default::default(), + narrow_phase: Default::default(), + impulse_joint_set: Default::default(), + multibody_joint_set: Default::default(), + ccd_solver: Default::default(), + integration_parameters: Default::default(), + } + } +} + +impl Physics { + pub fn step(&mut self, dt: f32) { + self.integration_parameters.dt = dt; + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigid_body_set, + &mut self.collider_set, + &mut self.impulse_joint_set, + &mut self.multibody_joint_set, + &mut self.ccd_solver, + &(), + &(), + ); + } + + pub fn add_rigid_body(&mut self, kind: RigidBodyType, transform: Transform, collider: Collider) -> RigidBodyHandle { + let rb = RigidBodyBuilder::new(kind) + .translation(vector![transform.translation.x, transform.translation.y, transform.translation.z]) + .build(); + let rb_handle = self.rigid_body_set.insert(rb); + self.collider_set.insert_with_parent(collider, rb_handle, &mut self.rigid_body_set); + rb_handle + } + + pub fn get_rigid_body(&self, handle: RigidBodyHandle) -> Option<&RigidBody> { + self.rigid_body_set.get(handle) + } + + pub fn get_rigid_body_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { + self.rigid_body_set.get_mut(handle) + } + + pub fn rapier_translation_to_glam(v: &Vector) -> Vec3 { + Vec3::new(v.x, v.y, v.z) + } + + pub fn rapier_rotation_to_glam(r: &UnitQuaternion) -> Quat { + Quat::from_xyzw(r.i, r.j, r.k, r.w) + } +} From 713d865dd7799c59c2a5ec018258ce00d260fd1d Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 26 Oct 2025 18:29:59 +0300 Subject: [PATCH 20/45] MASSIVE Kinematic Character Controller Update - NEW kinematic character controller powered by rapier3d at kinematic_character_controller.rs - NEW camera modes. The ability to switch between the free debug camera and new character controller. - NEW keybinds system to support the camera mode swap --- Cargo.lock | 2 + ecs/src/components.rs | 10 + engine/Cargo.toml | 1 + engine/src/lib.rs | 1 + engine/src/systems/fps_camera.rs | 29 +-- game/Cargo.toml | 1 + game/src/main.rs | 35 ++- game/src/systems/keybinds.rs | 52 +++++ .../systems/kinematic_character_controller.rs | 200 ++++++++++++++++++ game/src/systems/mod.rs | 4 + game/src/systems/physics.rs | 6 +- physics/src/physics.rs | 8 +- 12 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 game/src/systems/keybinds.rs create mode 100644 game/src/systems/kinematic_character_controller.rs diff --git a/Cargo.lock b/Cargo.lock index 525e6ad..91f50f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1799,6 +1799,7 @@ dependencies = [ "indexmap", "raidillon_assets", "raidillon_core", + "raidillon_ecs", "raidillon_platform", "winit", ] @@ -1808,6 +1809,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "glam 0.30.8", + "hecs", "raidillon_assets", "raidillon_core", "raidillon_ecs", diff --git a/ecs/src/components.rs b/ecs/src/components.rs index fa30cc2..3c20ec7 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -18,3 +18,13 @@ pub struct ModelHandle(pub ModelID); #[derive(Copy, Clone)] pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Copy, Clone)] +pub struct CharacterBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum CameraMode { + #[default] + Kinematic, + Debug, +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 98202e4..79a2de9 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" raidillon_assets = { path = "../asset" } raidillon_core = { path = "../core" } raidillon_platform = { path = "../platform" } +raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 9a87556..a7ad724 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -6,3 +6,4 @@ mod resources; pub use crate::engine::Engine; pub use crate::resources::EngineResources; +pub use input::InputState; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 0d98577..5ff9cc7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -9,6 +9,7 @@ use raidillon_platform::{Camera, PlatformContext}; use crate::input::InputState; use crate::resources::EngineResources; use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -35,20 +36,10 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - 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: pctx.frame_width / pctx.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - } - fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let pctx = res.get::().unwrap(); let event2 = pctx.current_event.clone(); match event2 { @@ -92,6 +83,9 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { @@ -134,4 +128,13 @@ impl FPSDebugCameraSystem { yaw_rad.sin() * pitch_rad.cos(), ).normalize() } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Debug + } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9bb72c4..9579d22 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -18,3 +18,4 @@ raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" +hecs = "0.10.5" diff --git a/game/src/main.rs b/game/src/main.rs index 8ddce6a..a5a3d9c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,14 +1,14 @@ mod systems; use std::fmt::format; use glam::{Quat, Vec3}; -use rapier3d::dynamics::RigidBodyType; +use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources}; +use raidillon_engine::{Engine, system::System, EngineResources, InputState}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -18,8 +18,9 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; -use crate::systems::PhysicsSystem; +use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -50,7 +51,7 @@ struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); // Spawn Sphere { @@ -59,7 +60,10 @@ impl System for RenderingTestSystem { rotation: Quat::IDENTITY, scale: Vec3::new(1.0, 1.0, 1.0), }; - let collider = ColliderBuilder::ball(1.0).build(); + let collider = ColliderBuilder::ball(1.0) + .restitution(0.7) + .restitution_combine_rule(CoefficientCombineRule::Max) + .build(); let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); scene.world.spawn(( @@ -84,16 +88,32 @@ impl System for RenderingTestSystem { RigidBodyComponent(rb_handle), )); } + + scene.world.spawn((Camera { + eye: Vec3::new(0.0, 2.0, 3.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: pctx.frame_width / pctx.frame_height, + znear: 0.1, + zfar: 100.0}, + CameraMode::default(), + )); } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + dbg_ui.text(format!("Character POS: {}", tr.translation)); + } } } @@ -101,7 +121,8 @@ 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::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs new file mode 100644 index 0000000..6bc3e70 --- /dev/null +++ b/game/src/systems/keybinds.rs @@ -0,0 +1,52 @@ +use winit::keyboard::KeyCode; +use raidillon_core::DebugUIBuffer; +use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use raidillon_platform::Camera; + +#[derive(Default)] +pub struct KeybindsSystem; + +impl System for KeybindsSystem { + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let input = res.get::().unwrap(); + + if input.key_held(KeyCode::F5) { + self.toggle_camera_mode(scene); + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let dbg_ui = scene.resources.get_mut::().unwrap(); + + dbg_ui.text("F5 to switch camera".to_owned()); + + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + } +} + +impl KeybindsSystem { + fn toggle_camera_mode(&mut self, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut Camera, &mut CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .into_iter() + .next() + .unwrap(); + + match *cam_mode { + CameraMode::Kinematic => { + *cam_mode = CameraMode::Debug; + } + CameraMode::Debug => { + *cam_mode = CameraMode::Kinematic; + } + } + } +} diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs new file mode 100644 index 0000000..4608e67 --- /dev/null +++ b/game/src/systems/kinematic_character_controller.rs @@ -0,0 +1,200 @@ +use glam::{Quat, Vec3}; +use hecs::Entity; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::vector; +use raidillon_core::scene::Scene; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::math::Isometry; +use rapier3d::na::{Isometry3, Vector3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::KeyCode; +use winit::window::CursorGrabMode; +use raidillon_core::DebugUIBuffer; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; +use raidillon_ecs::Transform; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use raidillon_platform::{Camera, PlatformContext}; + +#[derive(Default)] +pub struct KinematicCharacterController { + character_controller: RapierKinematicCharacterController, + character_collider: ColliderBuilder, + + desired_movement: Vec3, + last_position: Vector3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, + mouse_delta: (f64, f64), + + vertical_velocity: f32, + gravity: f32, + max_fall_speed: f32, +} + +impl System for KinematicCharacterController { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // create the rigid body, add it to the body set + let p = scene.resources.get_mut::().expect("Physics missing"); + let rb = RigidBodyBuilder::kinematic_position_based().build(); + let rb_handle = p.rigid_body_set.insert(rb); + self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); + let tr = Transform { + translation: Vec3::new(0.0, 2.0, 3.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + self.last_position = vector![ + tr.translation.x, + tr.translation.y, + tr.translation.z, + ]; + scene.world.spawn(( + tr, + CharacterBodyComponent(rb_handle), + )); + + self.speed = 5.0; + self.sensitivity = 0.1; + self.gravity = -9.81; + self.max_fall_speed = -50.0; + self.vertical_velocity = 0.0; + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + + let pctx = res.get::().unwrap(); + let event2 = pctx.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; + }, + _ => {} + } + }, + _ => {}, + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + 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); + + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } + + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + self.mouse_delta = (0.0, 0.0); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let p = scene.resources.get_mut::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + let (ch_ent, (ch_tr, ch_component)) = scene + .world + .query_mut::<(&mut Transform, &mut CharacterBodyComponent)>() + .into_iter() + .next() + .expect("no character entity in world"); + + let query_pipeline = p.broad_phase.as_query_pipeline( + p.narrow_phase.query_dispatcher(), + &p.rigid_body_set, + &p.collider_set, + QueryFilter::default().exclude_rigid_body(ch_component.0), + ); + + self.vertical_velocity = (self.vertical_velocity + self.gravity * pctx.time_ctx.fixed_dt) + .max(self.max_fall_speed); + let mut total_displacement = self.desired_movement; + total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + + let corrected_movement = self.character_controller.move_shape( + pctx.time_ctx.fixed_dt, + &query_pipeline, + &*self.character_collider.shape, + &Isometry3::from(self.last_position), + vector![total_displacement.x, total_displacement.y, total_displacement.z], + |_| {}, + ); + + // update character rigid body with the new translation. + if let Some(body) = p.get_rigid_body_mut(ch_component.0) { + self.last_position = vector![ + self.last_position.x + corrected_movement.translation.x, + self.last_position.y + corrected_movement.translation.y, + self.last_position.z + corrected_movement.translation.z, + ]; + body.set_next_kinematic_position(Isometry3::from(self.last_position)); + ch_tr.translation = Physics::rapier_translation_to_glam(&self.last_position); + // reset vertical velocity if grounded + if corrected_movement.grounded { + self.vertical_velocity = 0.0; + } + } + + self.desired_movement = Vec3::ZERO; + } +} + +impl KinematicCharacterController { + 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() + } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Kinematic + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 0ab4645..f975aff 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,3 +1,7 @@ mod physics; +mod kinematic_character_controller; +mod keybinds; pub use physics::PhysicsSystem; +pub use kinematic_character_controller::KinematicCharacterController; +pub use keybinds::KeybindsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index c5d4807..a77c75c 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -6,20 +6,22 @@ use raidillon_engine::system::System; use raidillon_physics::Physics; use raidillon_platform::PlatformContext; +/// Do physics calculations and apply to world. #[derive(Default)] pub struct PhysicsSystem; impl System for PhysicsSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let p = Physics::default(); - res.insert(p); + scene.resources.insert(p); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); physics.step(pctx.time_ctx.fixed_dt); + // apply calculations to dynamic bodies let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); for (_ent, (tr, rb_component)) in query.iter() { if let Some(body) = physics.get_rigid_body(rb_component.0) { diff --git a/physics/src/physics.rs b/physics/src/physics.rs index 009984c..5eff699 100644 --- a/physics/src/physics.rs +++ b/physics/src/physics.rs @@ -5,12 +5,12 @@ use raidillon_ecs::Transform; /// Tiny wrapper around rapier3d. pub struct Physics { - rigid_body_set: RigidBodySet, - collider_set: ColliderSet, + pub rigid_body_set: RigidBodySet, + pub collider_set: ColliderSet, physics_pipeline: PhysicsPipeline, island_manager: IslandManager, - broad_phase: DefaultBroadPhase, - narrow_phase: NarrowPhase, + pub broad_phase: DefaultBroadPhase, + pub narrow_phase: NarrowPhase, impulse_joint_set: ImpulseJointSet, multibody_joint_set: MultibodyJointSet, ccd_solver: CCDSolver, From 96a59b68fa23ddbf927e3d5540f7b2fe8efb8b22 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:35:55 +0300 Subject: [PATCH 21/45] Move debug_camera to the game crate, activate FPSDebugCameraSystem --- engine/src/systems/mod.rs | 1 - game/src/main.rs | 3 ++- .../fps_camera.rs => game/src/systems/debug_camera.rs | 8 ++++---- game/src/systems/mod.rs | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) rename engine/src/systems/fps_camera.rs => game/src/systems/debug_camera.rs (96%) diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs index 8489571..e69de29 100644 --- a/engine/src/systems/mod.rs +++ b/engine/src/systems/mod.rs @@ -1 +0,0 @@ -pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index a5a3d9c..99eb0e2 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -17,7 +17,7 @@ use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; @@ -123,6 +123,7 @@ fn main() { engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/engine/src/systems/fps_camera.rs b/game/src/systems/debug_camera.rs similarity index 96% rename from engine/src/systems/fps_camera.rs rename to game/src/systems/debug_camera.rs index 5ff9cc7..13cb971 100644 --- a/engine/src/systems/fps_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,4 +1,4 @@ -use crate::system::{System, SystemContext}; +use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; @@ -6,8 +6,8 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::{Camera, PlatformContext}; -use crate::input::InputState; -use crate::resources::EngineResources; +use raidillon_engine::InputState; +use raidillon_engine::EngineResources; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; @@ -29,7 +29,7 @@ impl Default for FPSDebugCameraSystem { position: Vec3::new(0.0, 0.0, 2.0), yaw: -90.0, pitch: 0.0, - speed: 3.0, + speed: 8.0, sensitivity: 0.1, } } diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index f975aff..2a387b1 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,6 +1,7 @@ mod physics; mod kinematic_character_controller; mod keybinds; +pub mod debug_camera; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; From ded85dcd72c9e70adf6301249807215efb02122c Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:36:38 +0300 Subject: [PATCH 22/45] Optimize imports of kinematic_character_controller.rs --- game/src/systems/kinematic_character_controller.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 4608e67..e3fc099 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,21 +1,16 @@ use glam::{Quat, Vec3}; -use hecs::Entity; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; use raidillon_core::scene::Scene; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; -use rapier3d::math::Isometry; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; -use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::event::Event; use winit::keyboard::KeyCode; -use winit::window::CursorGrabMode; -use raidillon_core::DebugUIBuffer; use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; use raidillon_ecs::Transform; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; use raidillon_physics::Physics; use raidillon_platform::{Camera, PlatformContext}; From e88ce258ce43ca48fcf82bbde46aea742a1e33b1 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:37:49 +0300 Subject: [PATCH 23/45] Tweak keybinds.rs to make camera mode toggle smoother --- game/src/systems/keybinds.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 6bc3e70..e701dad 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -4,17 +4,22 @@ use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; #[derive(Default)] -pub struct KeybindsSystem; +pub struct KeybindsSystem { + camera_toggle_held: bool, +} impl System for KeybindsSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let input = res.get::().unwrap(); - if input.key_held(KeyCode::F5) { + if self.camera_toggle_held { return } self.toggle_camera_mode(scene); + self.camera_toggle_held = true; + } else { + self.camera_toggle_held = false; } } From ce24354f3b71ecf597f4f9800ff33305e8f77f88 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:14:12 +0300 Subject: [PATCH 24/45] egui support - New Resource, EguiQueue: Utility to queue egui ui builders - New Rendering System, EguiRenderer: Utilizes a modified egui_glium library to render egui UI - Adjusted RenderingSystem trait and RenderingContext structure to provide event_loop and egui_queue. - Various minor adjusments --- Cargo.lock | 770 ++++++++++++++++++++++---- core/Cargo.toml | 3 +- core/src/egui_queue.rs | 22 + core/src/engine.rs | 3 - core/src/lib.rs | 2 + engine/Cargo.toml | 3 +- engine/src/engine.rs | 5 - game/Cargo.toml | 1 + game/src/main.rs | 32 +- glium_platform/Cargo.toml | 7 +- glium_platform/src/platform.rs | 17 +- glium_platform/src/render/basic.rs | 4 +- glium_platform/src/render/debug_ui.rs | 75 --- glium_platform/src/render/egui.rs | 46 ++ glium_platform/src/render/mod.rs | 3 +- glium_platform/src/render/skybox.rs | 3 +- glium_platform/src/system.rs | 11 +- platform/src/context.rs | 3 + 18 files changed, 806 insertions(+), 204 deletions(-) create mode 100644 core/src/egui_queue.rs delete mode 100644 glium_platform/src/render/debug_ui.rs create mode 100644 glium_platform/src/render/egui.rs diff --git a/Cargo.lock b/Cargo.lock index 91f50f1..3e9b382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -59,7 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.1", + "bitflags 2.10.0", "cc", "cesu8", "jni", @@ -94,6 +94,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -165,9 +185,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block2" @@ -186,9 +206,23 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -214,7 +248,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "log", "polling", "rustix 0.38.44", @@ -222,18 +256,43 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "calloop" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" +dependencies = [ + "bitflags 2.10.0", + "polling", + "rustix 1.0.8", + "slab", + "tracing", +] + [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", ] +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.3", + "rustix 1.0.8", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.2.31" @@ -278,6 +337,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "combine" version = "4.6.7" @@ -307,6 +375,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -320,7 +398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -333,7 +411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -395,8 +473,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -426,12 +515,84 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecolor" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084980ebede2fb1ad6c4f54285b3e489052ef2b6aa4016e4c19349417adc75c5" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75645894de4ca1695ab3ab7201c7953bb95c1725aafeefa6822dc901ad2a81b" +dependencies = [ + "ahash", + "bitflags 2.10.0", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "egui-winit" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4772ed5f16fa8ec2ba295e58f62b58ee83fcf49e67ec13d2b7ddf4e9a2dea34e" +dependencies = [ + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glium" +version = "0.31.1" +source = "git+https://github.com/reo6/egui_glium.git#66dbadcb16b3e6c827847eaa51a0ca9bd296d768" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "egui-winit", + "glium", + "log", + "raw-window-handle", + "winit", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "emath" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e561352ae95c22ad179fb56c38d6e6eecd86cf4925cf5c70e738dd01df9b620" +dependencies = [ + "bytemuck", +] + [[package]] name = "ena" version = "0.14.3" @@ -441,6 +602,30 @@ dependencies = [ "log", ] +[[package]] +name = "epaint" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a909ce8cee63e8350fb0c251ad39277a5b24f19add60787e84a3b3ab3f2bd83a" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9649446c23368ae138716910e3e28143995691b598fbb9de16b42b0722cbcc" + [[package]] name = "equivalent" version = "1.0.2" @@ -457,6 +642,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "exr" version = "1.73.0" @@ -472,6 +663,26 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -536,6 +747,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -673,9 +893,9 @@ checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" [[package]] name = "glium" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a3028d1f135b5395e6e4336916b424bc5dd2b38c6e378ce2704e4b8f4a617ed" +checksum = "8cf52ce4f5ce1501bb056627f35484a819e812d1d93f0f3da413676421b1bbe0" dependencies = [ "backtrace", "fnv", @@ -733,7 +953,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg_aliases", "cgl", "dispatch2", @@ -741,7 +961,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.1", + "objc2 0.6.3", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -867,6 +1087,108 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.8" @@ -879,6 +1201,7 @@ dependencies = [ "moxcms", "num-traits", "png", + "tiff", "zune-core", "zune-jpeg", ] @@ -896,16 +1219,6 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "imgui-glium-renderer" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781e53a393214a132c89c62e6e4b817645ad38832d3e334b2ad543f2c3df5106" -dependencies = [ - "glium", - "imgui", -] - [[package]] name = "imgui-sys" version = "0.12.0" @@ -918,16 +1231,6 @@ dependencies = [ "mint", ] -[[package]] -name = "imgui-winit-support" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7fcccfa9efab56c94274c0fec9939bb14149342b49e6a425883a5b7dda6a3f" -dependencies = [ - "imgui", - "winit", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -1038,7 +1341,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", "redox_syscall 0.5.17", ] @@ -1056,20 +1359,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] -name = "lock_api" -version = "0.4.13" +name = "litemap" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "matrixmultiply" @@ -1180,7 +1488,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "jni-sys", "log", "ndk-sys", @@ -1204,6 +1512,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num-bigint" version = "0.4.6" @@ -1304,9 +1618,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", ] @@ -1317,7 +1631,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "libc", "objc2 0.5.2", @@ -1333,9 +1647,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.1", ] @@ -1345,7 +1660,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1369,7 +1684,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1377,13 +1692,26 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -1422,7 +1750,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "dispatch", "libc", @@ -1435,8 +1763,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -1458,7 +1797,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1470,7 +1809,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1493,7 +1832,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-cloud-kit", @@ -1525,7 +1864,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1576,9 +1915,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1586,15 +1925,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.17", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -1605,7 +1944,7 @@ checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" dependencies = [ "approx", "arrayvec", - "bitflags 2.9.1", + "bitflags 2.10.0", "downcast-rs 2.0.2", "either", "ena", @@ -1675,7 +2014,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crc32fast", "fdeflate", "flate2", @@ -1696,6 +2035,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -1742,6 +2090,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -1774,6 +2128,7 @@ version = "0.1.0" name = "raidillon_core" version = "0.1.0" dependencies = [ + "egui", "hecs", "imgui", "indexmap", @@ -1794,6 +2149,7 @@ dependencies = [ name = "raidillon_engine" version = "0.1.0" dependencies = [ + "egui", "glam 0.30.8", "hecs", "indexmap", @@ -1808,6 +2164,7 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ + "egui", "glam 0.30.8", "hecs", "raidillon_assets", @@ -1826,14 +2183,13 @@ name = "raidillon_glium" version = "0.1.0" dependencies = [ "anyhow", + "egui", + "egui_glium", "exr", "glam 0.30.8", "glium", "gltf", "image", - "imgui", - "imgui-glium-renderer", - "imgui-winit-support", "indexmap", "raidillon_assets", "raidillon_core", @@ -1871,7 +2227,7 @@ dependencies = [ "approx", "arrayvec", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.10.0", "downcast-rs 2.0.2", "log", "nalgebra", @@ -1924,7 +2280,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] @@ -1962,7 +2318,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1975,7 +2331,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2033,7 +2389,7 @@ dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "tiny-skia", ] @@ -2112,9 +2468,9 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.1", - "calloop", - "calloop-wayland-source", + "bitflags 2.10.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", @@ -2131,6 +2487,44 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.10.0", + "calloop 0.14.3", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.0.8", + "thiserror 2.0.17", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -2187,6 +2581,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2227,6 +2632,20 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -2252,6 +2671,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -2275,6 +2704,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] @@ -2309,12 +2739,29 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "version_check" version = "0.9.5" @@ -2431,7 +2878,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -2443,7 +2890,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cursor-icon", "wayland-backend", ] @@ -2465,19 +2912,45 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-scanner", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2490,7 +2963,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2540,6 +3013,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.3", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wide" version = "0.7.33" @@ -2565,6 +3060,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.45.0" @@ -2653,7 +3154,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2853,13 +3354,13 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "bytemuck", - "calloop", + "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -2878,7 +3379,7 @@ dependencies = [ "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "smol_str", "tracing", "unicode-segmentation", @@ -2911,9 +3412,15 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "x11-dl" version = "2.21.0" @@ -2958,7 +3465,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "dlib", "log", "once_cell", @@ -2977,6 +3484,29 @@ version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -2997,6 +3527,60 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/core/Cargo.toml b/core/Cargo.toml index fc6b73f..a8d650d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,4 +8,5 @@ hecs = "0.10.5" indexmap = "2.10.0" raidillon_assets = { path = "../asset" } winit = "0.30.12" -imgui = "0.12.0" \ No newline at end of file +imgui = "0.12.0" +egui = "0.33.2" \ No newline at end of file diff --git a/core/src/egui_queue.rs b/core/src/egui_queue.rs new file mode 100644 index 0000000..fbbb789 --- /dev/null +++ b/core/src/egui_queue.rs @@ -0,0 +1,22 @@ +pub struct EguiQueue { + pub queue_vec: Vec>, +} + +impl EguiQueue { + pub fn new() -> Self { + Self { queue_vec: Vec::new() } + } + + pub fn queue(&mut self, func: impl FnOnce(&egui::Context) + Send + 'static) { + self.queue_vec.push(Box::new(func)); + } + + pub fn clear(&mut self) { + self.queue_vec.clear() + } + + pub fn run(&mut self, ctx: &egui::Context) { + self.queue_vec.drain(..).for_each(|func| func(ctx)); + self.clear(); + } +} diff --git a/core/src/engine.rs b/core/src/engine.rs index b70a945..1308e20 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -1,5 +1,3 @@ -use std::cell::RefCell; -use std::rc::Rc; use crate::DebugUIBuffer; use crate::scene::Scene; @@ -14,5 +12,4 @@ pub trait EngineTrait { fn current_scene(&self) -> &Scene; fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; fn reset_debug_ui_buffer(&mut self); - // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Self::Scene, &DebugUIBuffer); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 238d5a6..61f5286 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -3,5 +3,7 @@ pub mod debug_ui; pub mod time; pub mod utils; pub mod scene; +mod egui_queue; pub use debug_ui::*; +pub use egui_queue::EguiQueue; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 79a2de9..1264629 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -11,4 +11,5 @@ raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" -glam = "0.30.8" \ No newline at end of file +glam = "0.30.8" +egui = "0.33.2" \ No newline at end of file diff --git a/engine/src/engine.rs b/engine/src/engine.rs index a5c8a1d..398920c 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -94,9 +94,4 @@ impl EngineTrait for Engine { fn reset_debug_ui_buffer(&mut self) { self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); } - - // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, &DebugUIBuffer) { - // let (sm, dub) = (&mut self.scene_manager, self.scene_manager.current().resources.get::().unwrap()); - // (sm.current_mut(), dub) - // } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9579d22..f88ea43 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -19,3 +19,4 @@ glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" hecs = "0.10.5" +egui = "0.33.2" \ No newline at end of file diff --git a/game/src/main.rs b/game/src/main.rs index 99eb0e2..42ab2d3 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -16,7 +16,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{DebugUIBuffer, EguiQueue}; use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; @@ -102,18 +102,30 @@ impl System for RenderingTestSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - let input = res.get::().unwrap(); - let dbg_ui = scene.resources.get_mut::().unwrap(); - - dbg_ui.text("Hello World!".to_owned()); - dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); - dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); - dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + let ( + pctx, + input, + ) = res.get_many_mut::<( + PlatformContext, + InputState, + )>().unwrap(); + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - dbg_ui.text(format!("Character POS: {}", tr.translation)); + character_pos = tr.translation; } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {}", character_pos)); + }); + }); + } } diff --git a/glium_platform/Cargo.toml b/glium_platform/Cargo.toml index 6e51be6..109fc57 100644 --- a/glium_platform/Cargo.toml +++ b/glium_platform/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] anyhow = "1.0.98" glam = "0.30.5" -glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] } +glium = { version = "0.36.0", features = ["glutin_backend", "simple_window_builder"] } gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transform"] } raidillon_platform = { path = "../platform" } raidillon_core = { path = "../core" } @@ -15,8 +15,7 @@ raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } winit = "0.30.12" indexmap = "2.10.0" -imgui = "0.12.0" -imgui-winit-support = "0.13.0" -imgui-glium-renderer = "0.13.0" exr = "1.73.0" image = { version = "0.25.8", default-features = false, features = ["exr"] } +egui = "0.33.2" +egui_glium = { version = "0.31.1", git = "https://github.com/reo6/egui_glium.git" } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index db8e56e..efc1543 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -14,11 +14,11 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; -use crate::render::debug_ui::ImguiBridge; -use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; +use crate::render::{BasicMeshRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; +use raidillon_core::EguiQueue; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -28,6 +28,7 @@ pub struct GliumPlatform> { asset_manager: ModelManagerRef, engine: E, time: time::Time, + egui_queue: Rc>, } impl> Platform for GliumPlatform { @@ -50,9 +51,11 @@ impl> Platform for GliumPlatfor let window = Arc::new(Mutex::new(window)); // Install rendering systems in order - rendering_system_manager.add::(&display, window.clone()); - rendering_system_manager.add::(&display, window.clone()); - rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + + let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); Self { event_loop, @@ -62,6 +65,7 @@ impl> Platform for GliumPlatfor asset_manager, engine, time, + egui_queue, } } @@ -77,6 +81,7 @@ impl> Platform for GliumPlatfor frame_height: h as f32, time_ctx: self.construct_time_ctx(), window: self.window.clone(), + egui_queue: self.egui_queue.clone(), }; self.engine.initialize(ctx.clone()); let _ = &self.event_loop.run(move |event, el| { @@ -105,9 +110,11 @@ impl> Platform for GliumPlatfor let mut context = RenderingContext { scene, target: &mut target, + display: &self.display, asset_manager: self.asset_manager.clone(), window: self.window.clone(), debug_ui_buffer, + egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 663b2dd..a55d865 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -9,6 +9,8 @@ use raidillon_assets::include_shader; pub use raidillon_platform::Camera; use glam::Vec3; use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; +use winit::event::Event; +use winit::event_loop::EventLoop; use raidillon_ecs::{Transform, ModelID}; use raidillon_ecs::components::ModelHandle; use crate::model::Model; @@ -21,7 +23,7 @@ pub struct BasicMeshRenderingSystem { } impl RenderingSystem for BasicMeshRenderingSystem { - fn initialize(display: &Display, _window: Arc>) -> Self { + fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> 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 deleted file mode 100644 index ac4a06e..0000000 --- a/glium_platform/src/render/debug_ui.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::sync::{Arc, Mutex}; -use std::time::Instant; -use glium::Display; -use glium::glutin::surface::WindowSurface; -use imgui::{Context as ImguiContext}; -use imgui_winit_support::{HiDpiMode, WinitPlatform}; -use imgui_glium_renderer::Renderer as ImguiGliumRenderer; -use winit::window::Window; -use winit::event::Event; -use glium::Frame; -use crate::RenderingSystem; -use crate::system::RenderingContext; - -pub struct ImguiBridge { - imgui: ImguiContext, - platform: WinitPlatform, - renderer: ImguiGliumRenderer, - last_frame: Instant, - rendered_this_frame: bool, -} - -impl RenderingSystem for ImguiBridge { - 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: 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) - .expect("Failed to prepare frame"); - } - - fn render(&mut self, ctx: &mut RenderingContext) { - if self.rendered_this_frame { return; } - self.rendered_this_frame = true; - - let ui = self.imgui.frame(); - ctx.debug_ui_buffer.write_buffer(&ui); - - { - 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; - } - - self.renderer.render(ctx.target, draw_data).expect("imgui rendering failed"); - } - - fn initialize(display: &Display, window: Arc>) -> Self { - let mut imgui = ImguiContext::create(); - imgui.set_ini_filename(None); - let mut platform = WinitPlatform::new(&mut imgui); - 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(); - - Self { - imgui, - platform, - renderer, - last_frame: Instant::now(), - rendered_this_frame: false, - } - } -} diff --git a/glium_platform/src/render/egui.rs b/glium_platform/src/render/egui.rs new file mode 100644 index 0000000..e754ba3 --- /dev/null +++ b/glium_platform/src/render/egui.rs @@ -0,0 +1,46 @@ +use std::sync::{Arc, Mutex}; +use egui::ViewportId; +use glium::{Display, Frame}; +use glium::glutin::surface::WindowSurface; +use winit::window::Window; +use crate::RenderingSystem; +use crate::system::RenderingContext; +use egui_glium::EguiGlium; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::EventLoop; + +pub struct EguiRenderer { + egui_glium: EguiGlium, +} + +impl RenderingSystem for EguiRenderer { + fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self + where + Self: Sized, + { + let window = window.lock().unwrap(); + let egui_glium = EguiGlium::new(ViewportId::ROOT, &display, &window, &event_loop); + + Self { egui_glium } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + let window = ctx.window.lock().unwrap(); + + self.egui_glium.run(&window, |egui_ctx| { + ctx.egui_queue.borrow_mut().run(egui_ctx); + }); + + self.egui_glium.paint(ctx.display, ctx.target); + } + + fn handle_event(&mut self, window: Arc>, event: Event<()>) { + let window = window.lock().unwrap(); + match event { + Event::WindowEvent { event, .. } => { + let _ = self.egui_glium.on_event(&window, &event); + } + _ => {}, + } + } +} diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index f49e308..fdfa1ea 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,6 +1,7 @@ mod basic; -pub mod debug_ui; mod skybox; +mod egui; pub use basic::BasicMeshRenderingSystem; pub use skybox::SkyboxRenderingSystem; +pub use egui::EguiRenderer; diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index ac22210..94832d0 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -8,6 +8,7 @@ use glium::index::PrimitiveType; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; use glium::uniform; use glam::{Mat4, Vec2, Vec3}; +use winit::event_loop::EventLoop; use raidillon_assets::include_shader; use crate::system::RenderingContext; use crate::RenderingSystem; @@ -109,7 +110,7 @@ impl SkyboxRenderingSystem { } impl RenderingSystem for SkyboxRenderingSystem { - fn initialize(display: &Display, _window: Arc>) -> Self { + fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> 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 51a292f..5d7f197 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,16 +6,19 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::{define_typemap, DebugUIBuffer}; +use raidillon_core::{define_typemap, DebugUIBuffer, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; +use winit::event_loop::EventLoop; pub struct RenderingContext<'a> { pub scene: &'a Scene, pub target: &'a mut Frame, pub window: Arc>, + pub display: &'a Display, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: &'a DebugUIBuffer, + pub egui_queue: Rc>, pub env_light_dir: Vec3, } @@ -30,7 +33,7 @@ pub trait RenderingSystem { } fn prepare_frame(&mut self, _window: Arc>) {} fn render(&mut self, ctx: &mut RenderingContext); - fn initialize(display: &Display, window: Arc>) -> Self + fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self where Self: Sized; } @@ -48,11 +51,11 @@ impl RenderingSystemManager { } } - pub fn add(&mut self, display: &Display, window: Arc>) + pub fn add(&mut self, display: &Display, window: Arc>, event_loop: &EventLoop<()>) where R: RenderingSystem + 'static, { - let system = R::initialize(display, window); + let system = R::initialize(display, window, event_loop); self.systems.insert(TypeId::of::(), Box::new(system)); } diff --git a/platform/src/context.rs b/platform/src/context.rs index 892b69e..b78ea94 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,6 +1,8 @@ +use std::{cell::RefCell, rc::Rc}; use std::sync::{Arc, Mutex}; use winit::event::Event; use raidillon_assets::ModelManagerRef; +use raidillon_core::EguiQueue; #[derive(Clone)] pub struct PlatformContext { @@ -10,6 +12,7 @@ pub struct PlatformContext { pub frame_height: f32, pub time_ctx: TimeContext, pub window: Arc>, + pub egui_queue: Rc>, } #[derive(Clone)] From 4b97bd98d2a99eb0c5e1c8cf35d35f81ec3f1e0f Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:22:09 +0300 Subject: [PATCH 25/45] Fix #9, add tests to typemap.rs --- core/src/utils/typemap.rs | 116 ++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs index bd28a66..c04c2c9 100644 --- a/core/src/utils/typemap.rs +++ b/core/src/utils/typemap.rs @@ -101,7 +101,7 @@ macro_rules! define_typemap { { type Output<'a> = (&'a mut A,); fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; Some((a,)) @@ -133,8 +133,8 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::()]; if ids[0] == ids[1] { return None; } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -170,9 +170,9 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -212,10 +212,10 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -259,11 +259,11 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -311,12 +311,12 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -368,13 +368,13 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -430,14 +430,14 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -498,3 +498,47 @@ macro_rules! define_typemap { // Self::new() // } // } + +#[cfg(test)] +mod tests { + use crate::define_typemap; + + define_typemap!(TestMap,); + + #[test] + fn get_test() { + let mut tm = TestMap::new(); + tm.insert::(42); + let v = tm.get::().expect("value should be present"); + assert_eq!(*v, 42); + } + + #[test] + fn get_many_test() { + let mut tm = TestMap::new(); + tm.insert::(1); + tm.insert::(-2); + tm.insert::(3.5); + + let (a, b, c) = tm + .get_many::<(u32, i32, f32)>() + .expect("all values should be present"); + assert_eq!((*a, *b, *c), (1, -2, 3.5)); + } + + #[test] + fn get_many_mut_test() { + let mut tm = TestMap::new(); + + tm.insert::(-2); + tm.insert::(1); + tm.insert::(3.5); + + let (a, b, c) = tm + .get_many_mut::<(i32, u32, f32)>() + .expect("all values should be present"); + *a = 4; + *b = 5; + *c = 6.5; + } +} From 13aefcc86fb7e45afc9534b6cfac36147b3c056a Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:34:48 +0300 Subject: [PATCH 26/45] Remove imgui from the entire project --- Cargo.lock | 38 -------------------------- core/Cargo.toml | 1 - core/src/debug_ui.rs | 50 ---------------------------------- core/src/engine.rs | 3 -- core/src/lib.rs | 2 -- core/src/scene.rs | 7 ++--- engine/src/engine.rs | 11 +------- engine/src/system.rs | 2 -- game/src/main.rs | 2 +- game/src/systems/keybinds.rs | 16 +++++++---- glium_platform/src/platform.rs | 6 +--- glium_platform/src/system.rs | 3 +- 12 files changed, 16 insertions(+), 125 deletions(-) delete mode 100644 core/src/debug_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 3e9b382..877142c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,12 +331,6 @@ dependencies = [ "libc", ] -[[package]] -name = "chlorine" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19" - [[package]] name = "clipboard-win" version = "5.4.1" @@ -1206,31 +1200,6 @@ dependencies = [ "zune-jpeg", ] -[[package]] -name = "imgui" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8addafa5cecf0515812226e806913814e02ce38d10215778082af5174abe5669" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "imgui-sys", - "mint", - "parking_lot", -] - -[[package]] -name = "imgui-sys" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead193f9f4b60398e8b8f4ab1483e2321640d87aeebdaa3e5f44c55633ccd804" -dependencies = [ - "cc", - "cfg-if", - "chlorine", - "mint", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -1423,12 +1392,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mint" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" - [[package]] name = "moxcms" version = "0.7.5" @@ -2130,7 +2093,6 @@ version = "0.1.0" dependencies = [ "egui", "hecs", - "imgui", "indexmap", "raidillon_assets", "winit", diff --git a/core/Cargo.toml b/core/Cargo.toml index a8d650d..9b3f82f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,5 +8,4 @@ hecs = "0.10.5" indexmap = "2.10.0" raidillon_assets = { path = "../asset" } winit = "0.30.12" -imgui = "0.12.0" egui = "0.33.2" \ No newline at end of file diff --git a/core/src/debug_ui.rs b/core/src/debug_ui.rs deleted file mode 100644 index df3b9f1..0000000 --- a/core/src/debug_ui.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub enum UICommand { - Text(String), - Separator, - SliderF32 { label: String, min: f32, max: f32, value: std::rc::Rc> }, -} - -pub struct DebugUIBuffer { - cmds: Vec, -} - -impl DebugUIBuffer { - pub fn new() -> DebugUIBuffer { - DebugUIBuffer { cmds: vec![] } - } - - // Commands - pub fn text(&mut self, text: String) { - self.cmds.push(UICommand::Text(text)); - } - - pub fn separator(&mut self) { - self.cmds.push(UICommand::Separator); - } - - pub fn slider_f32>(&mut self, label: T, min: f32, max: f32, value: std::rc::Rc>) { - self.cmds.push(UICommand::SliderF32 { label: label.into(), min, max, value }); - } - // End of commands - - pub fn write_buffer(&self, ui: &imgui::Ui) { - for cmd in &self.cmds { - match cmd { - UICommand::Text(s) => { - ui.text(s); - } - UICommand::Separator => { - ui.separator(); - } - UICommand::SliderF32 { label, min, max, value } => { - let mut v = value.borrow_mut(); - ui.slider_config(label.as_str(), *min, *max).build(&mut *v); - } - } - } - } - - pub fn reset_buffer(&mut self) { - self.cmds = vec![]; - } -} diff --git a/core/src/engine.rs b/core/src/engine.rs index 1308e20..5890063 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -1,4 +1,3 @@ -use crate::DebugUIBuffer; use crate::scene::Scene; pub trait EngineTrait { @@ -10,6 +9,4 @@ pub trait EngineTrait { fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; fn current_scene(&self) -> &Scene; - fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; - fn reset_debug_ui_buffer(&mut self); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 61f5286..759393d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,9 +1,7 @@ pub mod engine; -pub mod debug_ui; pub mod time; pub mod utils; pub mod scene; mod egui_queue; -pub use debug_ui::*; pub use egui_queue::EguiQueue; diff --git a/core/src/scene.rs b/core/src/scene.rs index 1a075bf..2f748c4 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use crate::{define_typemap, DebugUIBuffer}; +use crate::{define_typemap}; pub struct Scene { pub title: String, @@ -23,10 +23,7 @@ impl Scene { s } - pub fn load_default_resources(&mut self) { - let dbg = DebugUIBuffer::new(); - self.resources.insert(dbg); - } + pub fn load_default_resources(&mut self) {} } impl Scene {} diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 398920c..ec9841f 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; -use raidillon_core::{define_typemap, DebugUIBuffer}; +use raidillon_core::{define_typemap}; use raidillon_core::engine::EngineTrait; use crate::input::InputState; use crate::resources::EngineResources; @@ -50,7 +50,6 @@ impl EngineTrait for Engine { /// Update the engine fn frame_update(&mut self, platform_context: PlatformContext) { - self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { @@ -86,12 +85,4 @@ impl EngineTrait for Engine { fn current_scene(&self) -> &Scene { self.scene_manager.current() } - - fn get_debug_ui_buffer(&self) -> &DebugUIBuffer { - self.scene_manager.current().resources.get::().unwrap() - } - - fn reset_debug_ui_buffer(&mut self) { - self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); - } } diff --git a/engine/src/system.rs b/engine/src/system.rs index 55cccb1..420da1b 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -1,6 +1,5 @@ use indexmap::IndexMap; use raidillon_core::scene::Scene; -use raidillon_core::DebugUIBuffer; use raidillon_platform::PlatformContext; use std::any::TypeId; use std::cell::RefCell; @@ -11,7 +10,6 @@ use crate::resources::EngineResources; pub struct SystemContext<'a> { pub scene: &'a mut Scene, pub platform_context: PlatformContext, - pub debug_ui_buffer: Rc>, pub input_state: Rc>, } diff --git a/game/src/main.rs b/game/src/main.rs index 42ab2d3..e81d77c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -16,7 +16,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::{DebugUIBuffer, EguiQueue}; +use raidillon_core::{EguiQueue}; use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index e701dad..10bbbe8 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,5 +1,4 @@ use winit::keyboard::KeyCode; -use raidillon_core::DebugUIBuffer; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; @@ -24,16 +23,21 @@ impl System for KeybindsSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let dbg_ui = scene.resources.get_mut::().unwrap(); - - dbg_ui.text("F5 to switch camera".to_owned()); - + let pctx = res.get_mut::().unwrap(); let mut q = scene.world.query::<(&Camera, &CameraMode)>(); let (cam_ent, (cam, cam_mode)) = q .iter() .next() .unwrap(); - dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + + let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Camera").show(egui_ctx, |ui| { + ui.label("F5 to switch camera"); + ui.label(cam_mode_str); + }); + }); } } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index efc1543..1b1ba08 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -103,17 +103,13 @@ impl> Platform for GliumPlatfor WindowEvent::RedrawRequested => { let mut target = self.display.draw(); target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0); - let (scene, debug_ui_buffer) = ( - self.engine.current_scene(), - self.engine.get_debug_ui_buffer(), - ); + let scene = self.engine.current_scene(); let mut context = RenderingContext { scene, target: &mut target, display: &self.display, asset_manager: self.asset_manager.clone(), window: self.window.clone(), - debug_ui_buffer, egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 5d7f197..d94e9c3 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,7 +6,7 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::{define_typemap, DebugUIBuffer, EguiQueue}; +use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; use winit::event_loop::EventLoop; @@ -17,7 +17,6 @@ pub struct RenderingContext<'a> { pub window: Arc>, pub display: &'a Display, pub asset_manager: ModelManagerRef, - pub debug_ui_buffer: &'a DebugUIBuffer, pub egui_queue: Rc>, pub env_light_dir: Vec3, } From db1b427e2ae2f905f1b0413953370d97fb027f9a Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 19 Oct 2025 17:40:51 +0300 Subject: [PATCH 27/45] Physics Support - NEW CRATE: raidillon_physics. - Added new models to be able to test the physics support. - Added a new system "PhysicsSystem" to apply physics calculations to the ECS world. - NEW COMPONENT: RigidBodyComponent --- Cargo.lock | 521 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + assets/models/plane.glb | 3 + assets/models/sphere.glb | 3 + ecs/Cargo.toml | 1 + ecs/src/components.rs | 3 + game/Cargo.toml | 2 + game/src/main.rs | 75 +++--- game/src/systems/mod.rs | 3 + game/src/systems/physics.rs | 34 +++ physics/Cargo.toml | 9 + physics/src/lib.rs | 3 + physics/src/physics.rs | 82 ++++++ 13 files changed, 697 insertions(+), 43 deletions(-) create mode 100644 assets/models/plane.glb create mode 100644 assets/models/sphere.glb create mode 100644 game/src/systems/mod.rs create mode 100644 game/src/systems/physics.rs create mode 100644 physics/Cargo.toml create mode 100644 physics/src/lib.rs create mode 100644 physics/src/physics.rs diff --git a/Cargo.lock b/Cargo.lock index e1b6adb..525e6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.0" @@ -64,7 +70,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -79,6 +85,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -130,6 +145,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit_field" version = "0.10.3" @@ -198,7 +219,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -393,12 +414,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -455,6 +497,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -521,6 +575,96 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + [[package]] name = "glam" version = "0.30.8" @@ -659,6 +803,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -673,6 +826,30 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] [[package]] name = "hecs" @@ -784,7 +961,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -849,6 +1026,12 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.9" @@ -888,6 +1071,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.5" @@ -938,6 +1131,49 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.8", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.9.0" @@ -950,7 +1186,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -968,6 +1204,56 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -975,6 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1269,6 +1556,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -1301,6 +1597,40 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry3d" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.0", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "smallvec", + "spade", + "static_assertions", + "thiserror 2.0.17", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1384,6 +1714,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.24" @@ -1436,15 +1785,16 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", + "rapier3d", ] [[package]] name = "raidillon_engine" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "hecs", "indexmap", "raidillon_assets", @@ -1457,13 +1807,15 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "raidillon_ecs", "raidillon_engine", "raidillon_glium", + "raidillon_physics", "raidillon_platform", + "rapier3d", "winit", ] @@ -1473,7 +1825,7 @@ version = "0.1.0" dependencies = [ "anyhow", "exr", - "glam", + "glam 0.30.8", "glium", "gltf", "image", @@ -1489,22 +1841,62 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_physics" +version = "0.1.0" +dependencies = [ + "glam 0.30.8", + "raidillon_ecs", + "rapier3d", +] + [[package]] name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "winit", ] +[[package]] +name = "rapier3d" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd27b8eb36d0833fa0f2aea40164fabfad0fc34b9932286ae9e84f3452f5364" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash", + "simba", + "static_assertions", + "thiserror 2.0.17", + "wide", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon-core" version = "1.13.0" @@ -1533,12 +1925,35 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1577,6 +1992,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1649,6 +2073,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1681,7 +2118,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1701,12 +2138,36 @@ dependencies = [ "serde", ] +[[package]] +name = "spade" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +dependencies = [ + "hashbrown 0.15.4", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strict-num" version = "0.1.1" @@ -1730,7 +2191,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1744,6 +2214,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1808,6 +2289,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1929,7 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.0.8", "scoped-tls", "smallvec", @@ -2051,6 +2538,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 23332d0..dd544ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "game", "ecs", "engine", + "physics", ] diff --git a/assets/models/plane.glb b/assets/models/plane.glb new file mode 100644 index 0000000..e755152 --- /dev/null +++ b/assets/models/plane.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b54f00f327a5edc76c03f83d4efa2a4400215d7ca9c961b38537e7f836bca710 +size 1240 diff --git a/assets/models/sphere.glb b/assets/models/sphere.glb new file mode 100644 index 0000000..ea99045 --- /dev/null +++ b/assets/models/sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78583ec562098f7fa4eaf95c6e21b7d1ced53416eb4c71351e246d53f93aa0a3 +size 24744 diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index 0e1efb5..a47173d 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] glam = "0.30.5" raidillon_assets = { path = "../asset" } +rapier3d = "0.30.1" diff --git a/ecs/src/components.rs b/ecs/src/components.rs index 758f0f4..fa30cc2 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -15,3 +15,6 @@ impl Transform { } pub struct ModelHandle(pub ModelID); + +#[derive(Copy, Clone)] +pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); diff --git a/game/Cargo.toml b/game/Cargo.toml index 484e434..9bb72c4 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -13,6 +13,8 @@ raidillon_platform = { path = "../platform" } raidillon_assets = { path = "../asset" } raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } +raidillon_physics = { path = "../physics" } raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" +rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index ad230cf..8ddce6a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,11 +1,14 @@ +mod systems; use std::fmt::format; use glam::{Quat, Vec3}; +use rapier3d::dynamics::RigidBodyType; +use rapier3d::prelude::ColliderBuilder; use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::ModelHandle; +use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -15,9 +18,11 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use crate::systems::PhysicsSystem; -const TEST_GLTF: &str = "pink-monkey.gltf"; - +const TEST_GLTF: &str = "sphere.glb"; +const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] @@ -40,28 +45,45 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem { - rotation_speed: std::rc::Rc>, -} +struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); - self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - - let mut am = pctx.asset_manager.borrow_mut(); - - am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - - scene.world.spawn(( - Transform { - translation: Vec3::new(0.0, 0.0, 0.0), - rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), - }, - ModelHandle(TEST_GLTF), - )); + // Spawn Sphere + { + let tr = Transform { + translation: Vec3::new(0.0, 5.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + let collider = ColliderBuilder::ball(1.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(TEST_GLTF), + RigidBodyComponent(rb_handle), + )); + } + // Spawn Plane + { + let tr = Transform { + translation: Vec3::new(0.0, 0.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(10.0, 1.0, 10.0), + }; + let collider = ColliderBuilder::cuboid(10.0, 0.01, 10.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Fixed, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(PLANE_GLTF, &model_path(PLANE_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(PLANE_GLTF), + RigidBodyComponent(rb_handle), + )); + } } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { @@ -72,22 +94,13 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); - dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - - fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - - scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); - }); - } - } 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::(); diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs new file mode 100644 index 0000000..0ab4645 --- /dev/null +++ b/game/src/systems/mod.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use physics::PhysicsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs new file mode 100644 index 0000000..c5d4807 --- /dev/null +++ b/game/src/systems/physics.rs @@ -0,0 +1,34 @@ +use raidillon_core::scene::Scene; +use raidillon_ecs::components::RigidBodyComponent; +use raidillon_ecs::Transform; +use raidillon_engine::EngineResources; +use raidillon_engine::system::System; +use raidillon_physics::Physics; +use raidillon_platform::PlatformContext; + +#[derive(Default)] +pub struct PhysicsSystem; + +impl System for PhysicsSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let p = Physics::default(); + res.insert(p); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); + physics.step(pctx.time_ctx.fixed_dt); + + let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); + for (_ent, (tr, rb_component)) in query.iter() { + if let Some(body) = physics.get_rigid_body(rb_component.0) { + let pos = body.position(); + let translation = Physics::rapier_translation_to_glam(&pos.translation.vector); + let rotation = Physics::rapier_rotation_to_glam(&pos.rotation); + tr.translation = translation; + tr.rotation = rotation; + } + } + } +} diff --git a/physics/Cargo.toml b/physics/Cargo.toml new file mode 100644 index 0000000..dc34e98 --- /dev/null +++ b/physics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "raidillon_physics" +version = "0.1.0" +edition = "2024" + +[dependencies] +rapier3d = "0.30.1" +raidillon_ecs = { path = "../ecs" } +glam = "0.30.8" \ No newline at end of file diff --git a/physics/src/lib.rs b/physics/src/lib.rs new file mode 100644 index 0000000..a5dafc6 --- /dev/null +++ b/physics/src/lib.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use crate::physics::Physics; diff --git a/physics/src/physics.rs b/physics/src/physics.rs new file mode 100644 index 0000000..009984c --- /dev/null +++ b/physics/src/physics.rs @@ -0,0 +1,82 @@ +use glam::{Quat, Vec3}; +use rapier3d::na::{Quaternion, UnitQuaternion}; +use rapier3d::prelude::*; +use raidillon_ecs::Transform; + +/// Tiny wrapper around rapier3d. +pub struct Physics { + rigid_body_set: RigidBodySet, + collider_set: ColliderSet, + physics_pipeline: PhysicsPipeline, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + ccd_solver: CCDSolver, + gravity: Vector, + integration_parameters: IntegrationParameters, +} + +impl Default for Physics { + fn default() -> Self { + Self { + gravity: vector![0.0, -9.81, 0.0], + rigid_body_set: Default::default(), + collider_set: Default::default(), + physics_pipeline: Default::default(), + island_manager: Default::default(), + broad_phase: Default::default(), + narrow_phase: Default::default(), + impulse_joint_set: Default::default(), + multibody_joint_set: Default::default(), + ccd_solver: Default::default(), + integration_parameters: Default::default(), + } + } +} + +impl Physics { + pub fn step(&mut self, dt: f32) { + self.integration_parameters.dt = dt; + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigid_body_set, + &mut self.collider_set, + &mut self.impulse_joint_set, + &mut self.multibody_joint_set, + &mut self.ccd_solver, + &(), + &(), + ); + } + + pub fn add_rigid_body(&mut self, kind: RigidBodyType, transform: Transform, collider: Collider) -> RigidBodyHandle { + let rb = RigidBodyBuilder::new(kind) + .translation(vector![transform.translation.x, transform.translation.y, transform.translation.z]) + .build(); + let rb_handle = self.rigid_body_set.insert(rb); + self.collider_set.insert_with_parent(collider, rb_handle, &mut self.rigid_body_set); + rb_handle + } + + pub fn get_rigid_body(&self, handle: RigidBodyHandle) -> Option<&RigidBody> { + self.rigid_body_set.get(handle) + } + + pub fn get_rigid_body_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { + self.rigid_body_set.get_mut(handle) + } + + pub fn rapier_translation_to_glam(v: &Vector) -> Vec3 { + Vec3::new(v.x, v.y, v.z) + } + + pub fn rapier_rotation_to_glam(r: &UnitQuaternion) -> Quat { + Quat::from_xyzw(r.i, r.j, r.k, r.w) + } +} From f503c70a9ba89dc2730a9de5ec0761cb569ef9f0 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 26 Oct 2025 18:29:59 +0300 Subject: [PATCH 28/45] MASSIVE Kinematic Character Controller Update - NEW kinematic character controller powered by rapier3d at kinematic_character_controller.rs - NEW camera modes. The ability to switch between the free debug camera and new character controller. - NEW keybinds system to support the camera mode swap --- Cargo.lock | 2 + ecs/src/components.rs | 10 + engine/Cargo.toml | 1 + engine/src/lib.rs | 1 + engine/src/systems/fps_camera.rs | 29 +-- game/Cargo.toml | 1 + game/src/main.rs | 35 ++- game/src/systems/keybinds.rs | 52 +++++ .../systems/kinematic_character_controller.rs | 200 ++++++++++++++++++ game/src/systems/mod.rs | 4 + game/src/systems/physics.rs | 6 +- physics/src/physics.rs | 8 +- 12 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 game/src/systems/keybinds.rs create mode 100644 game/src/systems/kinematic_character_controller.rs diff --git a/Cargo.lock b/Cargo.lock index 525e6ad..91f50f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1799,6 +1799,7 @@ dependencies = [ "indexmap", "raidillon_assets", "raidillon_core", + "raidillon_ecs", "raidillon_platform", "winit", ] @@ -1808,6 +1809,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "glam 0.30.8", + "hecs", "raidillon_assets", "raidillon_core", "raidillon_ecs", diff --git a/ecs/src/components.rs b/ecs/src/components.rs index fa30cc2..3c20ec7 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -18,3 +18,13 @@ pub struct ModelHandle(pub ModelID); #[derive(Copy, Clone)] pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Copy, Clone)] +pub struct CharacterBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum CameraMode { + #[default] + Kinematic, + Debug, +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 98202e4..79a2de9 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" raidillon_assets = { path = "../asset" } raidillon_core = { path = "../core" } raidillon_platform = { path = "../platform" } +raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 9a87556..a7ad724 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -6,3 +6,4 @@ mod resources; pub use crate::engine::Engine; pub use crate::resources::EngineResources; +pub use input::InputState; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 0d98577..5ff9cc7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -9,6 +9,7 @@ use raidillon_platform::{Camera, PlatformContext}; use crate::input::InputState; use crate::resources::EngineResources; use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -35,20 +36,10 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - 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: pctx.frame_width / pctx.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - } - fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let pctx = res.get::().unwrap(); let event2 = pctx.current_event.clone(); match event2 { @@ -92,6 +83,9 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { @@ -134,4 +128,13 @@ impl FPSDebugCameraSystem { yaw_rad.sin() * pitch_rad.cos(), ).normalize() } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Debug + } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9bb72c4..9579d22 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -18,3 +18,4 @@ raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" +hecs = "0.10.5" diff --git a/game/src/main.rs b/game/src/main.rs index 8ddce6a..a5a3d9c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,14 +1,14 @@ mod systems; use std::fmt::format; use glam::{Quat, Vec3}; -use rapier3d::dynamics::RigidBodyType; +use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources}; +use raidillon_engine::{Engine, system::System, EngineResources, InputState}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -18,8 +18,9 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; -use crate::systems::PhysicsSystem; +use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -50,7 +51,7 @@ struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); // Spawn Sphere { @@ -59,7 +60,10 @@ impl System for RenderingTestSystem { rotation: Quat::IDENTITY, scale: Vec3::new(1.0, 1.0, 1.0), }; - let collider = ColliderBuilder::ball(1.0).build(); + let collider = ColliderBuilder::ball(1.0) + .restitution(0.7) + .restitution_combine_rule(CoefficientCombineRule::Max) + .build(); let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); scene.world.spawn(( @@ -84,16 +88,32 @@ impl System for RenderingTestSystem { RigidBodyComponent(rb_handle), )); } + + scene.world.spawn((Camera { + eye: Vec3::new(0.0, 2.0, 3.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: pctx.frame_width / pctx.frame_height, + znear: 0.1, + zfar: 100.0}, + CameraMode::default(), + )); } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + dbg_ui.text(format!("Character POS: {}", tr.translation)); + } } } @@ -101,7 +121,8 @@ 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::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs new file mode 100644 index 0000000..6bc3e70 --- /dev/null +++ b/game/src/systems/keybinds.rs @@ -0,0 +1,52 @@ +use winit::keyboard::KeyCode; +use raidillon_core::DebugUIBuffer; +use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use raidillon_platform::Camera; + +#[derive(Default)] +pub struct KeybindsSystem; + +impl System for KeybindsSystem { + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let input = res.get::().unwrap(); + + if input.key_held(KeyCode::F5) { + self.toggle_camera_mode(scene); + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let dbg_ui = scene.resources.get_mut::().unwrap(); + + dbg_ui.text("F5 to switch camera".to_owned()); + + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + } +} + +impl KeybindsSystem { + fn toggle_camera_mode(&mut self, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut Camera, &mut CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .into_iter() + .next() + .unwrap(); + + match *cam_mode { + CameraMode::Kinematic => { + *cam_mode = CameraMode::Debug; + } + CameraMode::Debug => { + *cam_mode = CameraMode::Kinematic; + } + } + } +} diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs new file mode 100644 index 0000000..4608e67 --- /dev/null +++ b/game/src/systems/kinematic_character_controller.rs @@ -0,0 +1,200 @@ +use glam::{Quat, Vec3}; +use hecs::Entity; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::vector; +use raidillon_core::scene::Scene; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::math::Isometry; +use rapier3d::na::{Isometry3, Vector3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::KeyCode; +use winit::window::CursorGrabMode; +use raidillon_core::DebugUIBuffer; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; +use raidillon_ecs::Transform; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use raidillon_platform::{Camera, PlatformContext}; + +#[derive(Default)] +pub struct KinematicCharacterController { + character_controller: RapierKinematicCharacterController, + character_collider: ColliderBuilder, + + desired_movement: Vec3, + last_position: Vector3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, + mouse_delta: (f64, f64), + + vertical_velocity: f32, + gravity: f32, + max_fall_speed: f32, +} + +impl System for KinematicCharacterController { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // create the rigid body, add it to the body set + let p = scene.resources.get_mut::().expect("Physics missing"); + let rb = RigidBodyBuilder::kinematic_position_based().build(); + let rb_handle = p.rigid_body_set.insert(rb); + self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); + let tr = Transform { + translation: Vec3::new(0.0, 2.0, 3.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + self.last_position = vector![ + tr.translation.x, + tr.translation.y, + tr.translation.z, + ]; + scene.world.spawn(( + tr, + CharacterBodyComponent(rb_handle), + )); + + self.speed = 5.0; + self.sensitivity = 0.1; + self.gravity = -9.81; + self.max_fall_speed = -50.0; + self.vertical_velocity = 0.0; + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + + let pctx = res.get::().unwrap(); + let event2 = pctx.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; + }, + _ => {} + } + }, + _ => {}, + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + 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); + + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } + + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + self.mouse_delta = (0.0, 0.0); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let p = scene.resources.get_mut::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + let (ch_ent, (ch_tr, ch_component)) = scene + .world + .query_mut::<(&mut Transform, &mut CharacterBodyComponent)>() + .into_iter() + .next() + .expect("no character entity in world"); + + let query_pipeline = p.broad_phase.as_query_pipeline( + p.narrow_phase.query_dispatcher(), + &p.rigid_body_set, + &p.collider_set, + QueryFilter::default().exclude_rigid_body(ch_component.0), + ); + + self.vertical_velocity = (self.vertical_velocity + self.gravity * pctx.time_ctx.fixed_dt) + .max(self.max_fall_speed); + let mut total_displacement = self.desired_movement; + total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + + let corrected_movement = self.character_controller.move_shape( + pctx.time_ctx.fixed_dt, + &query_pipeline, + &*self.character_collider.shape, + &Isometry3::from(self.last_position), + vector![total_displacement.x, total_displacement.y, total_displacement.z], + |_| {}, + ); + + // update character rigid body with the new translation. + if let Some(body) = p.get_rigid_body_mut(ch_component.0) { + self.last_position = vector![ + self.last_position.x + corrected_movement.translation.x, + self.last_position.y + corrected_movement.translation.y, + self.last_position.z + corrected_movement.translation.z, + ]; + body.set_next_kinematic_position(Isometry3::from(self.last_position)); + ch_tr.translation = Physics::rapier_translation_to_glam(&self.last_position); + // reset vertical velocity if grounded + if corrected_movement.grounded { + self.vertical_velocity = 0.0; + } + } + + self.desired_movement = Vec3::ZERO; + } +} + +impl KinematicCharacterController { + 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() + } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Kinematic + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 0ab4645..f975aff 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,3 +1,7 @@ mod physics; +mod kinematic_character_controller; +mod keybinds; pub use physics::PhysicsSystem; +pub use kinematic_character_controller::KinematicCharacterController; +pub use keybinds::KeybindsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index c5d4807..a77c75c 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -6,20 +6,22 @@ use raidillon_engine::system::System; use raidillon_physics::Physics; use raidillon_platform::PlatformContext; +/// Do physics calculations and apply to world. #[derive(Default)] pub struct PhysicsSystem; impl System for PhysicsSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let p = Physics::default(); - res.insert(p); + scene.resources.insert(p); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); physics.step(pctx.time_ctx.fixed_dt); + // apply calculations to dynamic bodies let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); for (_ent, (tr, rb_component)) in query.iter() { if let Some(body) = physics.get_rigid_body(rb_component.0) { diff --git a/physics/src/physics.rs b/physics/src/physics.rs index 009984c..5eff699 100644 --- a/physics/src/physics.rs +++ b/physics/src/physics.rs @@ -5,12 +5,12 @@ use raidillon_ecs::Transform; /// Tiny wrapper around rapier3d. pub struct Physics { - rigid_body_set: RigidBodySet, - collider_set: ColliderSet, + pub rigid_body_set: RigidBodySet, + pub collider_set: ColliderSet, physics_pipeline: PhysicsPipeline, island_manager: IslandManager, - broad_phase: DefaultBroadPhase, - narrow_phase: NarrowPhase, + pub broad_phase: DefaultBroadPhase, + pub narrow_phase: NarrowPhase, impulse_joint_set: ImpulseJointSet, multibody_joint_set: MultibodyJointSet, ccd_solver: CCDSolver, From 0af3b41293ef5663cb8752feaff53ae6fed347c5 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:35:55 +0300 Subject: [PATCH 29/45] Move debug_camera to the game crate, activate FPSDebugCameraSystem --- engine/src/systems/mod.rs | 1 - game/src/main.rs | 3 ++- .../fps_camera.rs => game/src/systems/debug_camera.rs | 8 ++++---- game/src/systems/mod.rs | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) rename engine/src/systems/fps_camera.rs => game/src/systems/debug_camera.rs (96%) diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs index 8489571..e69de29 100644 --- a/engine/src/systems/mod.rs +++ b/engine/src/systems/mod.rs @@ -1 +0,0 @@ -pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index a5a3d9c..99eb0e2 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -17,7 +17,7 @@ use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; @@ -123,6 +123,7 @@ fn main() { engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/engine/src/systems/fps_camera.rs b/game/src/systems/debug_camera.rs similarity index 96% rename from engine/src/systems/fps_camera.rs rename to game/src/systems/debug_camera.rs index 5ff9cc7..13cb971 100644 --- a/engine/src/systems/fps_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,4 +1,4 @@ -use crate::system::{System, SystemContext}; +use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; @@ -6,8 +6,8 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::{Camera, PlatformContext}; -use crate::input::InputState; -use crate::resources::EngineResources; +use raidillon_engine::InputState; +use raidillon_engine::EngineResources; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; @@ -29,7 +29,7 @@ impl Default for FPSDebugCameraSystem { position: Vec3::new(0.0, 0.0, 2.0), yaw: -90.0, pitch: 0.0, - speed: 3.0, + speed: 8.0, sensitivity: 0.1, } } diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index f975aff..2a387b1 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,6 +1,7 @@ mod physics; mod kinematic_character_controller; mod keybinds; +pub mod debug_camera; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; From 445d90b1a5ff2ca191c6a593a7026bb34207858a Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:36:38 +0300 Subject: [PATCH 30/45] Optimize imports of kinematic_character_controller.rs --- game/src/systems/kinematic_character_controller.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 4608e67..e3fc099 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,21 +1,16 @@ use glam::{Quat, Vec3}; -use hecs::Entity; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; use raidillon_core::scene::Scene; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; -use rapier3d::math::Isometry; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; -use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::event::Event; use winit::keyboard::KeyCode; -use winit::window::CursorGrabMode; -use raidillon_core::DebugUIBuffer; use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; use raidillon_ecs::Transform; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; use raidillon_physics::Physics; use raidillon_platform::{Camera, PlatformContext}; From c431ebf74522cdf6cee8d034c5f4d9ac5f2c75b3 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:37:49 +0300 Subject: [PATCH 31/45] Tweak keybinds.rs to make camera mode toggle smoother --- game/src/systems/keybinds.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 6bc3e70..e701dad 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -4,17 +4,22 @@ use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; #[derive(Default)] -pub struct KeybindsSystem; +pub struct KeybindsSystem { + camera_toggle_held: bool, +} impl System for KeybindsSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let input = res.get::().unwrap(); - if input.key_held(KeyCode::F5) { + if self.camera_toggle_held { return } self.toggle_camera_mode(scene); + self.camera_toggle_held = true; + } else { + self.camera_toggle_held = false; } } From 82f3b277325bc48bb53688b9220232596fe73f14 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:42:45 +0300 Subject: [PATCH 32/45] Regenerate cargo.lock --- Cargo.lock | 558 +++++++++++++++++++++++++---------------------------- 1 file changed, 268 insertions(+), 290 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 877142c..7791e88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,15 +14,15 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -81,9 +81,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -104,10 +104,10 @@ dependencies = [ "image", "log", "objc2 0.6.3", - "objc2-app-kit 0.3.1", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "windows-sys 0.60.2", @@ -146,9 +146,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -156,7 +156,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -238,9 +238,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "calloop" @@ -264,7 +264,7 @@ checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" dependencies = [ "bitflags 2.10.0", "polling", - "rustix 1.0.8", + "rustix 1.1.2", "slab", "tracing", ] @@ -288,17 +288,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ "calloop 0.14.3", - "rustix 1.0.8", + "rustix 1.1.2", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.31" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -312,9 +313,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -628,12 +629,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -644,9 +645,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "exr" -version = "1.73.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" dependencies = [ "bit_field", "half", @@ -687,10 +688,16 @@ dependencies = [ ] [[package]] -name = "flate2" -version = "1.1.2" +name = "find-msvc-tools" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -752,31 +759,31 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.1.2", + "windows-link", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gl_generator" @@ -881,9 +888,9 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" -version = "0.30.8" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" [[package]] name = "glium" @@ -956,9 +963,9 @@ dependencies = [ "glutin_wgl_sys", "libloading", "objc2 0.6.3", - "objc2-app-kit 0.3.1", + "objc2-app-kit 0.3.2", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "raw-window-handle", "wayland-sys", @@ -1009,12 +1016,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1037,9 +1045,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1185,9 +1193,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -1196,18 +1204,18 @@ dependencies = [ "num-traits", "png", "tiff", - "zune-core", - "zune-jpeg", + "zune-core 0.5.0", + "zune-jpeg 0.5.5", ] [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.0", ] [[package]] @@ -1246,9 +1254,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", @@ -1256,9 +1264,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1284,18 +1292,18 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1306,13 +1314,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", ] [[package]] @@ -1323,9 +1331,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1360,15 +1368,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -1394,9 +1402,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6" dependencies = [ "num-traits", "pxfm", @@ -1424,7 +1432,7 @@ dependencies = [ "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", - "glam 0.30.8", + "glam 0.30.9", "matrixmultiply", "nalgebra-macros", "num-complex", @@ -1543,9 +1551,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1553,9 +1561,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1606,15 +1614,15 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] @@ -1722,9 +1730,9 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", @@ -1836,9 +1844,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1851,9 +1859,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827" dependencies = [ "libredox", ] @@ -1894,16 +1902,16 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] name = "parry3d" -version = "0.25.1" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +checksum = "e99471b7b6870f7fe406d5611dd4b4c9b07aa3e5436b1d27e1515f9832bb0c6b" dependencies = [ "approx", "arrayvec", @@ -1935,9 +1943,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" @@ -1986,16 +1994,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] @@ -2009,18 +2017,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2046,9 +2054,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] @@ -2070,9 +2078,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2102,7 +2110,7 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_assets", "rapier3d", ] @@ -2112,7 +2120,7 @@ name = "raidillon_engine" version = "0.1.0" dependencies = [ "egui", - "glam 0.30.8", + "glam 0.30.9", "hecs", "indexmap", "raidillon_assets", @@ -2127,7 +2135,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "egui", - "glam 0.30.8", + "glam 0.30.9", "hecs", "raidillon_assets", "raidillon_core", @@ -2148,7 +2156,7 @@ dependencies = [ "egui", "egui_glium", "exr", - "glam 0.30.8", + "glam 0.30.9", "glium", "gltf", "image", @@ -2165,7 +2173,7 @@ dependencies = [ name = "raidillon_physics" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_ecs", "rapier3d", ] @@ -2174,7 +2182,7 @@ dependencies = [ name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_assets", "raidillon_core", "winit", @@ -2238,9 +2246,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.10.0", ] @@ -2289,22 +2297,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2357,18 +2365,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2377,14 +2395,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2414,9 +2433,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2462,7 +2481,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix 1.0.8", + "rustix 1.1.2", "thiserror 2.0.17", "wayland-backend", "wayland-client", @@ -2498,11 +2517,11 @@ dependencies = [ [[package]] name = "spade" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", "num-traits", "robust", "smallvec", @@ -2534,9 +2553,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2605,7 +2624,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg", + "zune-jpeg 0.4.21", ] [[package]] @@ -2645,18 +2664,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ "winnow", ] @@ -2691,9 +2723,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -2703,13 +2735,14 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2741,45 +2774,32 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -2790,9 +2810,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2800,22 +2820,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -2828,7 +2848,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs 1.2.1", - "rustix 1.0.8", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -2841,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.10.0", - "rustix 1.0.8", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -2863,7 +2883,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.2", "wayland-client", "xcursor", ] @@ -2957,9 +2977,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -2986,7 +3006,7 @@ dependencies = [ "log", "ndk-context", "objc2 0.6.3", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "url", "web-sys", ] @@ -3009,19 +3029,13 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -3061,7 +3075,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -3079,21 +3102,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3112,19 +3120,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3133,12 +3141,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3147,9 +3149,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3157,12 +3159,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3171,9 +3167,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3181,12 +3177,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3195,9 +3185,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -3207,9 +3197,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3217,12 +3207,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3231,9 +3215,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3241,12 +3225,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3255,9 +3233,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3265,12 +3243,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3279,9 +3251,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3289,12 +3261,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3303,9 +3269,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" @@ -3361,21 +3327,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.10.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -3396,24 +3359,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading", "once_cell", - "rustix 0.38.44", + "rustix 1.1.2", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" @@ -3442,9 +3405,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "yoke" @@ -3471,18 +3434,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -3549,6 +3512,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -3560,9 +3529,18 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ - "zune-core", + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" +dependencies = [ + "zune-core 0.5.0", ] From 47c3b2b11163e0842948ff351d2335f6abfa9cc5 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 19:32:56 +0300 Subject: [PATCH 33/45] New crate: raidillon_app, refactor game/main.rs to use the new crate --- Cargo.lock | 21 ++++-- Cargo.toml | 2 +- app/Cargo.toml | 16 +++++ app/src/lib.rs | 45 +++++++++++++ app/src/prelude.rs | 43 ++++++++++++ core/src/scene.rs | 2 +- game/Cargo.toml | 12 +--- game/src/main.rs | 65 +++++-------------- game/src/systems/debug_camera.rs | 8 +-- game/src/systems/keybinds.rs | 6 +- .../systems/kinematic_character_controller.rs | 8 +-- game/src/systems/physics.rs | 8 +-- 12 files changed, 140 insertions(+), 96 deletions(-) create mode 100644 app/Cargo.toml create mode 100644 app/src/lib.rs create mode 100644 app/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index 7791e88..ac3f862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,6 +2091,19 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "raidillon_app" +version = "0.1.0" +dependencies = [ + "raidillon_assets", + "raidillon_core", + "raidillon_ecs", + "raidillon_engine", + "raidillon_glium", + "raidillon_physics", + "raidillon_platform", +] + [[package]] name = "raidillon_assets" version = "0.1.0" @@ -2137,13 +2150,7 @@ dependencies = [ "egui", "glam 0.30.9", "hecs", - "raidillon_assets", - "raidillon_core", - "raidillon_ecs", - "raidillon_engine", - "raidillon_glium", - "raidillon_physics", - "raidillon_platform", + "raidillon_app", "rapier3d", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index dd544ab..3741214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ members = [ "game", "ecs", "engine", - "physics", + "physics", "app", ] diff --git a/app/Cargo.toml b/app/Cargo.toml new file mode 100644 index 0000000..081dae0 --- /dev/null +++ b/app/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "raidillon_app" +version = "0.1.0" +edition = "2024" + +[dependencies] +raidillon_engine = { path = "../engine" } +raidillon_platform = { path = "../platform" } +raidillon_assets = { path = "../asset" } +raidillon_ecs = { path = "../ecs" } +raidillon_physics = { path = "../physics" } +raidillon_glium = { path = "../glium_platform", optional = true } +raidillon_core = { path = "../core" } + +[features] +glium = ["raidillon_glium"] diff --git a/app/src/lib.rs b/app/src/lib.rs new file mode 100644 index 0000000..a5b875a --- /dev/null +++ b/app/src/lib.rs @@ -0,0 +1,45 @@ +pub mod prelude; + +pub use prelude::*; + +pub struct App { + pub engine: Option, +} + +impl App { + pub fn new() -> Self { + let engine = Engine::new(); + Self { engine: Some(engine) } + } + + pub fn add_system(&mut self) -> &mut Self { + self.engine.as_mut().unwrap().system_manager.add::(); + self + } + + pub fn add_scene(&mut self, id: SceneID, scene: Scene) -> &mut Self { + self.engine.as_mut().unwrap().scene_manager.add_scene(id, scene); + self + } + + pub fn set_active_scene(&mut self, id: SceneID) -> &mut Self { + self.engine.as_mut().unwrap().scene_manager.set_active_scene(id); + self + } + + pub fn run(&mut self, title: String, width: u32, height: u32) { + #[cfg(feature = "glium")] + { + let platform = GliumPlatform::initialize( + self.engine.take().unwrap(), + title, + width, + height, + ); + platform.run(); + } + + #[cfg(not(any(feature = "glium")))] + compile_error!("No platform feature enabled."); + } +} diff --git a/app/src/prelude.rs b/app/src/prelude.rs new file mode 100644 index 0000000..1304175 --- /dev/null +++ b/app/src/prelude.rs @@ -0,0 +1,43 @@ +pub use raidillon_engine::{ + Engine, + system::System, + EngineResources, + InputState, + system::SystemContext, +}; + +pub use raidillon_platform::{ + Platform, + Camera, + PlatformContext, + TimeContext, +}; + +pub use raidillon_assets::{ + ModelManagerRef, + model_path, +}; + +pub use raidillon_ecs::{ + components::{ + CameraMode, + CharacterBodyComponent, + ModelHandle, + RigidBodyComponent, + Transform, + }, +}; + +pub use raidillon_physics::Physics; + +pub use raidillon_core::{ + scene::{Scene, SceneID}, + EguiQueue, + engine::EngineTrait, +}; + +#[cfg(feature = "glium")] +pub use raidillon_glium::{ + GliumPlatform, + RenderingSystem, +}; diff --git a/core/src/scene.rs b/core/src/scene.rs index 2f748c4..e586be5 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -40,7 +40,7 @@ impl AsMut for Scene { } } -type SceneID = &'static str; +pub type SceneID = &'static str; pub struct SceneManager { scenes: HashMap, diff --git a/game/Cargo.toml b/game/Cargo.toml index 480aea4..0a28217 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -3,18 +3,8 @@ name = "raidillon_game" version = "0.1.0" edition = "2024" -[features] -default = ["glium"] -glium = ["raidillon_glium"] - [dependencies] -raidillon_core = { path = "../core" } -raidillon_platform = { path = "../platform" } -raidillon_assets = { path = "../asset" } -raidillon_ecs = { path = "../ecs" } -raidillon_engine = { path = "../engine" } -raidillon_physics = { path = "../physics" } -raidillon_glium = { path = "../glium_platform", optional = true } +raidillon_app = { path = "../app", features = ["glium"] } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index e81d77c..939e4dd 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,25 +1,11 @@ +use raidillon_app::prelude::*; + mod systems; -use std::fmt::format; use glam::{Quat, Vec3}; use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources, InputState}; -use raidillon_engine::system::SystemContext; -use raidillon_platform::{Platform, Camera, PlatformContext}; -use raidillon_assets::model_path; -use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; -use raidillon_ecs::Transform; -use raidillon_core::scene::Scene; -#[cfg(feature = "glium")] -use raidillon_glium::GliumPlatform; -use winit::event::{ElementState, Event, WindowEvent}; -use winit::event::DeviceEvent::MouseMotion; -use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::{EguiQueue}; +use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; -use raidillon_glium::RenderingSystem; -use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; @@ -46,9 +32,9 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem; +struct MainSystem; -impl System for RenderingTestSystem { +impl System for MainSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); let physics = scene.resources.get_mut::().expect("Physics missing"); @@ -130,35 +116,14 @@ 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::(); - engine.system_manager.add::(); - engine.system_manager.add::(); - // engine.system_manager.add::(); - - // Set up the scene - let main_scene = Scene::new( - MAIN_SCENE_ID.to_owned(), - None, - ); - engine.scene_manager.add_scene(MAIN_SCENE_ID, main_scene); - engine.scene_manager.set_active_scene(MAIN_SCENE_ID); - - #[cfg(feature = "glium")] - { - let platform = GliumPlatform::initialize( - engine, - "Raidillon".to_string(), - 2560, - 1080, - ); - platform.run() - }; - - #[cfg(not(any(feature = "glium")))] - compile_error!("No platform feature enabled."); + raidillon_app::App::new() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) + .set_active_scene(MAIN_SCENE_ID) + .run("Raidillon".to_string(), 2560, 1080); } diff --git a/game/src/systems/debug_camera.rs b/game/src/systems/debug_camera.rs index 13cb971..959af5c 100644 --- a/game/src/systems/debug_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,15 +1,9 @@ -use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; -use raidillon_assets::model_path; -use raidillon_platform::{Camera, PlatformContext}; -use raidillon_engine::InputState; -use raidillon_engine::EngineResources; -use raidillon_core::scene::Scene; -use raidillon_ecs::components::CameraMode; +use raidillon_app::prelude::*; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 10bbbe8..d8df7b7 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,9 +1,5 @@ use winit::keyboard::KeyCode; -use raidillon_core::scene::Scene; -use raidillon_ecs::components::CameraMode; -use raidillon_engine::{EngineResources, InputState}; -use raidillon_engine::system::System; -use raidillon_platform::{Camera, PlatformContext}; +use raidillon_app::prelude::*; #[derive(Default)] pub struct KeybindsSystem { diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index e3fc099..34de5b4 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,18 +1,12 @@ use glam::{Quat, Vec3}; use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; -use raidillon_core::scene::Scene; -use raidillon_engine::{EngineResources, InputState}; -use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::Event; use winit::keyboard::KeyCode; -use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; -use raidillon_ecs::Transform; -use raidillon_physics::Physics; -use raidillon_platform::{Camera, PlatformContext}; +use raidillon_app::prelude::*; #[derive(Default)] pub struct KinematicCharacterController { diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index a77c75c..f660526 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -1,10 +1,4 @@ -use raidillon_core::scene::Scene; -use raidillon_ecs::components::RigidBodyComponent; -use raidillon_ecs::Transform; -use raidillon_engine::EngineResources; -use raidillon_engine::system::System; -use raidillon_physics::Physics; -use raidillon_platform::PlatformContext; +use raidillon_app::prelude::*; /// Do physics calculations and apply to world. #[derive(Default)] From b17a7636d8daf649e63fc5f6f394afce937a8756 Mon Sep 17 00:00:00 2001 From: reo Date: Sat, 22 Nov 2025 14:15:53 +0300 Subject: [PATCH 34/45] Implement MenuSystem, reimplement the mouse grab and menu system, add a new common module in the game systems for common functions, various other fixes --- game/src/main.rs | 35 +++---- game/src/systems/common.rs | 34 +++++++ game/src/systems/debug_camera.rs | 95 +++++-------------- game/src/systems/keybinds.rs | 36 ++++--- .../systems/kinematic_character_controller.rs | 85 +++++++---------- game/src/systems/menu.rs | 81 ++++++++++++++++ game/src/systems/mod.rs | 3 + 7 files changed, 219 insertions(+), 150 deletions(-) create mode 100644 game/src/systems/common.rs create mode 100644 game/src/systems/menu.rs diff --git a/game/src/main.rs b/game/src/main.rs index 939e4dd..582e2d5 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -6,7 +6,8 @@ use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; -use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; +use crate::systems::common::should_draw_menu; +use crate::systems::{KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -96,22 +97,23 @@ impl System for MainSystem { InputState, )>().unwrap(); - let mut egui_queue = pctx.egui_queue.borrow_mut(); - let time_ctx = pctx.time_ctx.clone(); - let mut character_pos = Vec3::ZERO; - for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - character_pos = tr.translation; - } - egui_queue.queue(move |egui_ctx| { - egui::Window::new("Debug").show(egui_ctx, |ui| { - ui.label("Hello World!"); - ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); - ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); - ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); - ui.label(format!("Character POS: {}", character_pos)); + if should_draw_menu(scene) { + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + character_pos = tr.translation; + } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {}", character_pos)); + }); }); - }); - + } } } @@ -121,6 +123,7 @@ fn main() { .add_system::() .add_system::() .add_system::() + .add_system::() .add_system::() .add_system::() .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) diff --git a/game/src/systems/common.rs b/game/src/systems/common.rs new file mode 100644 index 0000000..8863f2e --- /dev/null +++ b/game/src/systems/common.rs @@ -0,0 +1,34 @@ +use glam::Vec3; +use raidillon_app::prelude::*; +use crate::systems::menu::MenuState; + +pub fn is_camera_mode_valid(scene: &mut Scene, mode: CameraMode) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == mode +} + +pub fn is_mouse_look_enabled(scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, mode) = q.iter().next().unwrap(); + *mode == MenuState::Closed +} + +pub fn camera_front(yaw: f32, pitch: f32) -> Vec3 { + let yaw_rad = yaw.to_radians(); + let pitch_rad = pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ).normalize() +} + +pub fn should_draw_menu(scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, mode) = q.iter().next().unwrap(); + *mode == MenuState::Open +} diff --git a/game/src/systems/debug_camera.rs b/game/src/systems/debug_camera.rs index 959af5c..b687e08 100644 --- a/game/src/systems/debug_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -5,9 +5,11 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_app::prelude::*; +use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled}; +use crate::systems::menu::MenuState; + pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), - mouse_enabled: bool, position: Vec3, yaw: f32, pitch: f32, @@ -19,7 +21,6 @@ impl Default for FPSDebugCameraSystem { 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, @@ -31,7 +32,7 @@ impl Default for FPSDebugCameraSystem { impl System for FPSDebugCameraSystem { fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { + if !(is_camera_mode_valid(scene, CameraMode::Debug) && is_mouse_look_enabled(scene)) { return } let pctx = res.get::().unwrap(); @@ -46,89 +47,43 @@ impl System for FPSDebugCameraSystem { _ => {} } }, - Event::WindowEvent { event, .. } => match event { - WindowEvent::MouseInput { state, button, .. } => { - if button == MouseButton::Right { - // blood and tear - let window = pctx.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, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); - if self.mouse_enabled { + if is_mouse_look_enabled(scene) { 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); } - let front = self.front(); + let front = camera_front(self.yaw, self.pitch); let right_vec = front.cross(Vec3::Y).normalize(); - if input.key_held(KeyCode::KeyW) { - self.position += front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyS) { - self.position -= front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyA) { - self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyD) { - self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; + if is_mouse_look_enabled(scene) { + if input.key_held(KeyCode::KeyW) { + self.position += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.position -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; + } } - scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { - camera.eye = self.position; - camera.center = self.position + front; - }); + if is_camera_mode_valid(scene, CameraMode::Debug) { + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + camera.eye = self.position; + camera.center = self.position + front; + }); + } self.mouse_delta = (0.0, 0.0); } } - -impl FPSDebugCameraSystem { - 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() - } - - fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); - *cam_mode == CameraMode::Debug - } -} diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index d8df7b7..5519969 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,6 +1,8 @@ use winit::keyboard::KeyCode; use raidillon_app::prelude::*; +use crate::systems::{common::should_draw_menu, menu::MenuState}; + #[derive(Default)] pub struct KeybindsSystem { camera_toggle_held: bool, @@ -19,21 +21,31 @@ impl System for KeybindsSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get_mut::().unwrap(); - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); + if should_draw_menu(scene) { + let pctx = res.get_mut::().unwrap(); + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); - let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, menu_state) = q + .iter() + .next() + .unwrap(); - pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { - egui::Window::new("Camera").show(egui_ctx, |ui| { - ui.label("F5 to switch camera"); - ui.label(cam_mode_str); + let menu_state_str = format!("Menu State: {:?}", menu_state); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Camera").show(egui_ctx, |ui| { + ui.label("F5 to switch camera"); + ui.label(cam_mode_str); + ui.label(menu_state_str) + }); }); - }); + } } } diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 34de5b4..41561a5 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -8,6 +8,9 @@ use winit::event::Event; use winit::keyboard::KeyCode; use raidillon_app::prelude::*; +use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled}; +use crate::systems::menu::MenuState; + #[derive(Default)] pub struct KinematicCharacterController { character_controller: RapierKinematicCharacterController, @@ -57,7 +60,7 @@ impl System for KinematicCharacterController { } fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { + if !is_camera_mode_valid(scene, CameraMode::Kinematic) { return } @@ -78,45 +81,44 @@ impl System for KinematicCharacterController { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } - let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); - - 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); - - let front = self.front(); + let front = camera_front(self.yaw, self.pitch); let right_vec = front.cross(Vec3::Y).normalize(); - if input.key_held(KeyCode::KeyW) { - self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyS) { - self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyA) { - self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyD) { - self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + if is_camera_mode_valid(scene, CameraMode::Kinematic) && is_mouse_look_enabled(scene) { + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + 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); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } } - let pos = Physics::rapier_translation_to_glam(&self.last_position); + if is_camera_mode_valid(scene, CameraMode::Kinematic) { + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + } - scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { - // INTERPOLATION NEEDED. - camera.eye = pos; - camera.center = pos + front; - }); self.mouse_delta = (0.0, 0.0); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } let p = scene.resources.get_mut::().unwrap(); let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); @@ -166,24 +168,3 @@ impl System for KinematicCharacterController { self.desired_movement = Vec3::ZERO; } } - -impl KinematicCharacterController { - 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() - } - - fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); - *cam_mode == CameraMode::Kinematic - } -} diff --git a/game/src/systems/menu.rs b/game/src/systems/menu.rs new file mode 100644 index 0000000..60350b7 --- /dev/null +++ b/game/src/systems/menu.rs @@ -0,0 +1,81 @@ +use egui::Id; +use raidillon_app::prelude::*; +use winit::{dpi::{LogicalPosition, Position}, keyboard::KeyCode, window::CursorGrabMode}; + +#[derive(Default)] +pub struct MenuSystem { + escape_key_held: bool, + /// Unoptimal solution to fix windows event delay + times_ran_initial_win_event: u32, +} + +#[derive(Default, Eq, PartialEq, Debug)] +pub enum MenuState { + Open, + #[default] + Closed, +} + +impl System for MenuSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + scene.world.spawn((MenuState::Closed,)); + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // Windows won't register some events in the first miliseconds after initialization + if self.times_ran_initial_win_event < 20 { + let window = res.get::().unwrap().window.lock().unwrap(); + window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); + window.set_cursor_visible(false); + self.times_ran_initial_win_event += 1 + } + + let mut egui_queue = res.get::().unwrap().egui_queue.borrow_mut(); + egui_queue.queue(|egui_ctx| { + egui::Area::new(Id::new("esc to pause")) + .anchor(egui::Align2::RIGHT_TOP, [-10.0, 10.0]) + .show(egui_ctx, |ui| { + ui.label( + egui::RichText::new("ESC to pause").size(24.0).color(egui::Color32::BLACK) + ); + }); + }); + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // The menu is toggled by pressing the escape key + let input = res.get::().unwrap(); + if input.key_held(KeyCode::Escape) { + if self.escape_key_held { return } + self.toggle_menu(res, scene); + self.escape_key_held = true; + } else { + self.escape_key_held = false; + } + } +} + +impl MenuSystem { + fn toggle_menu(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut MenuState)>(); + let (menu_ent, menu_state) = q + .into_iter() + .next() + .unwrap(); + + let window = res.get::().unwrap().window.lock().unwrap(); + + match *menu_state { + MenuState::Open => { + *menu_state = MenuState::Closed; + window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); + window.set_cursor_visible(false); + }, + MenuState::Closed => { + *menu_state = MenuState::Open; + window.set_cursor_grab(CursorGrabMode::None); + window.set_cursor_visible(true); + }, + } + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 2a387b1..2f23ed5 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,8 +1,11 @@ mod physics; mod kinematic_character_controller; mod keybinds; +mod menu; pub mod debug_camera; +pub mod common; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; +pub use menu::MenuSystem; From f5a16213fae1b05339b7372270e944a1c07a1535 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 9 Dec 2025 17:52:17 +0300 Subject: [PATCH 35/45] Add Settings with fullscreen and windowed options, a config file (settings.toml) to persist settings, fix a bug in platform code where innner window size wasn't updated on resize, various other tweaks --- Cargo.lock | 32 +++++++++ app/src/prelude.rs | 1 + game/src/main.rs | 35 +++++----- game/src/systems/display_settings.rs | 92 ++++++++++++++++++++++++ game/src/systems/mod.rs | 2 + glium_platform/src/platform.rs | 26 ++++++- platform/Cargo.toml | 2 + platform/src/context.rs | 4 +- platform/src/lib.rs | 1 + platform/src/settings.rs | 101 +++++++++++++++++++++++++++ 10 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 game/src/systems/display_settings.rs create mode 100644 platform/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index ac3f862..d36cc29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2192,6 +2192,8 @@ dependencies = [ "glam 0.30.9", "raidillon_assets", "raidillon_core", + "serde", + "toml", "winit", ] @@ -2413,6 +2415,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2669,6 +2680,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -2699,6 +2725,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tracing" version = "0.1.41" diff --git a/app/src/prelude.rs b/app/src/prelude.rs index 1304175..1171d0f 100644 --- a/app/src/prelude.rs +++ b/app/src/prelude.rs @@ -11,6 +11,7 @@ pub use raidillon_platform::{ Camera, PlatformContext, TimeContext, + settings::{Settings, WindowMode}, }; pub use raidillon_assets::{ diff --git a/game/src/main.rs b/game/src/main.rs index 582e2d5..b49445c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -7,7 +7,9 @@ use rapier3d::prelude::ColliderBuilder; use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; use crate::systems::common::should_draw_menu; -use crate::systems::{KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem}; +use crate::systems::{ + DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem +}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -97,23 +99,21 @@ impl System for MainSystem { InputState, )>().unwrap(); - if should_draw_menu(scene) { - let mut egui_queue = pctx.egui_queue.borrow_mut(); - let time_ctx = pctx.time_ctx.clone(); - let mut character_pos = Vec3::ZERO; - for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - character_pos = tr.translation; - } - egui_queue.queue(move |egui_ctx| { - egui::Window::new("Debug").show(egui_ctx, |ui| { - ui.label("Hello World!"); - ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); - ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); - ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); - ui.label(format!("Character POS: {}", character_pos)); - }); - }); + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + character_pos = tr.translation; } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {:.3}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {:.3}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {:.3}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {character_pos:.3}")); + }); + }); } } @@ -124,6 +124,7 @@ fn main() { .add_system::() .add_system::() .add_system::() + .add_system::() .add_system::() .add_system::() .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) diff --git a/game/src/systems/display_settings.rs b/game/src/systems/display_settings.rs new file mode 100644 index 0000000..6384d15 --- /dev/null +++ b/game/src/systems/display_settings.rs @@ -0,0 +1,92 @@ +use std::sync::{Arc, Mutex}; +use raidillon_app::prelude::*; +use crate::systems::common::should_draw_menu; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum SettingsTab { + Display, +} + +impl Default for SettingsTab { + fn default() -> Self { + SettingsTab::Display + } +} + +#[derive(Clone, Default)] +struct DisplaySettingsUiState { + selected_fullscreen_mode: WindowMode, + active_tab: SettingsTab, +} + +#[derive(Default)] +pub struct DisplaySettings { + ui_state: Arc>, +} + +impl System for DisplaySettings { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get_mut::().unwrap(); + + // sync the settings with UI state once + if let (Ok(settings_handle), Ok(mut state)) = (pctx.settings.read(), self.ui_state.lock()) { + state.selected_fullscreen_mode = settings_handle.display_settings.fullscreen_mode; + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if should_draw_menu(scene) { + let pctx = res.get_mut::().unwrap(); + let settings = pctx.settings.clone(); + let ui_state = self.ui_state.clone(); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Settings").default_open(false).show(egui_ctx, |ui| { + let mut state = ui_state.lock().unwrap(); + + ui.horizontal(|ui| { + ui.selectable_value(&mut state.active_tab, SettingsTab::Display, "Display Settings"); + }); + ui.separator(); + + match state.active_tab { + SettingsTab::Display => { + ui.label("Window Mode"); + egui::ComboBox::from_id_salt("window_mode") + .selected_text(window_mode_label(state.selected_fullscreen_mode)) + .show_ui(ui, |ui| { + for mode in [ + WindowMode::Windowed, + WindowMode::BorderlessFullscreen, + ] { + ui.selectable_value( + &mut state.selected_fullscreen_mode, + mode, + window_mode_label(mode), + ); + } + }); + + ui.add_space(8.0); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("Apply").clicked() { + if let Ok(mut settings_handle) = settings.write() { + settings_handle.display_settings.fullscreen_mode = state.selected_fullscreen_mode; + settings_handle.display_settings.dirty = true; + } + } + }); + } + } + }); + }); + } + } +} + +fn window_mode_label(mode: WindowMode) -> &'static str { + match mode { + WindowMode::Windowed => "Windowed", + WindowMode::BorderlessFullscreen => "Borderless Fullscreen", + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 2f23ed5..bcf77ca 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -4,8 +4,10 @@ mod keybinds; mod menu; pub mod debug_camera; pub mod common; +mod display_settings; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; pub use menu::MenuSystem; +pub use display_settings::DisplaySettings; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 1b1ba08..bb1e192 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; @@ -19,6 +19,7 @@ use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; use raidillon_core::EguiQueue; +use raidillon_platform::settings::{Settings, default_config_path}; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -29,6 +30,7 @@ pub struct GliumPlatform> { engine: E, time: time::Time, egui_queue: Rc>, + settings: Arc>, } impl> Platform for GliumPlatform { @@ -57,6 +59,12 @@ impl> Platform for GliumPlatfor let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); + let settings = Arc::new( + RwLock::new( + Settings::load_or_default(default_config_path()).unwrap() + ) + ); + Self { event_loop, window, @@ -66,6 +74,7 @@ impl> Platform for GliumPlatfor engine, time, egui_queue, + settings, } } @@ -82,9 +91,18 @@ impl> Platform for GliumPlatfor time_ctx: self.construct_time_ctx(), window: self.window.clone(), egui_queue: self.egui_queue.clone(), + settings: self.settings.clone(), }; self.engine.initialize(ctx.clone()); + self.settings.read().unwrap().display_settings.apply(&*self.window.lock().unwrap()); + let _ = &self.event_loop.run(move |event, el| { + let settings_handle = self.settings.read().unwrap(); + if settings_handle.display_settings.dirty { + settings_handle.display_settings.apply(&*self.window.lock().unwrap()); + } + drop(settings_handle); + self.rendering_system_manager .systems .values_mut() @@ -96,8 +114,14 @@ impl> Platform for GliumPlatfor match event { Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => { + if size.width > 0 && size.height > 0 { + self.display.resize((size.width, size.height)); + } + }, WindowEvent::CloseRequested => { // TODO: Run uninitialize on renderer and engine + self.settings.read().unwrap().save_to_file(default_config_path()); el.exit(); }, WindowEvent::RedrawRequested => { diff --git a/platform/Cargo.toml b/platform/Cargo.toml index 3b42bac..1b7b5b2 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -8,3 +8,5 @@ winit = "0.30.12" raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } glam = "0.30.5" +serde = "1.0.228" +toml = "0.9.8" diff --git a/platform/src/context.rs b/platform/src/context.rs index b78ea94..fdbe087 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,8 +1,9 @@ use std::{cell::RefCell, rc::Rc}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use winit::event::Event; use raidillon_assets::ModelManagerRef; use raidillon_core::EguiQueue; +use crate::settings::Settings; #[derive(Clone)] pub struct PlatformContext { @@ -13,6 +14,7 @@ pub struct PlatformContext { pub time_ctx: TimeContext, pub window: Arc>, pub egui_queue: Rc>, + pub settings: Arc>, } #[derive(Clone)] diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 8875d35..94c467b 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -2,6 +2,7 @@ pub mod platform; mod camera; mod event; pub mod context; +pub mod settings; pub use platform::Platform; pub use camera::Camera; diff --git a/platform/src/settings.rs b/platform/src/settings.rs new file mode 100644 index 0000000..f6d092f --- /dev/null +++ b/platform/src/settings.rs @@ -0,0 +1,101 @@ +use winit::dpi::LogicalSize; +use winit::window::{Fullscreen, Window}; +use serde::{Serialize, Deserialize}; +use std::error::Error; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +pub fn default_config_path() -> PathBuf { + let exe_path = std::env::current_exe().unwrap(); + let exe_dir = exe_path + .parent() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "executable has no parent")).unwrap(); + + exe_dir.join("settings.toml") +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum WindowMode { + BorderlessFullscreen, + #[default] + Windowed, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Settings { + pub display_settings: DisplaySettings, +} + +impl Settings { + pub fn load_from_file(path: impl AsRef) -> Result> { + let path = path.as_ref(); + let text = fs::read_to_string(path)?; + let settings: Settings = toml::from_str(&text)?; + Ok(settings) + } + + pub fn save_to_file(&self, path: impl AsRef) -> Result<(), Box> { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let toml_str = toml::to_string_pretty(self)?; + fs::write(path, toml_str)?; + Ok(()) + } + + pub fn load_or_default(path: impl AsRef) -> Result> { + let path = path.as_ref(); + + match fs::read_to_string(path) { + Ok(text) => { + let settings: Settings = toml::from_str(&text)?; + Ok(settings) + } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + let settings = Settings::default(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let toml_str = toml::to_string_pretty(&settings)?; + fs::write(path, toml_str)?; + Ok(settings) + } + Err(err) => Err(Box::new(err)), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct DisplaySettings { + pub fullscreen_mode: WindowMode, + #[serde(skip)] + pub dirty: bool, +} + +impl Default for DisplaySettings { + fn default() -> Self { + Self { + fullscreen_mode: WindowMode::Windowed, + dirty: false, + } + } +} + +impl DisplaySettings { + pub fn apply(&self, window: &Window) { + // apply fullscreen mode + match self.fullscreen_mode { + WindowMode::BorderlessFullscreen => { + let monitor = window.current_monitor().or_else(|| window.primary_monitor()); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); + } + WindowMode::Windowed => { + window.set_fullscreen(None); + }, + } + } +} From 8041c7e01d88a1997f2ca3c1ad4727b04ec12aa8 Mon Sep 17 00:00:00 2001 From: reo Date: Sat, 13 Dec 2025 18:23:52 +0300 Subject: [PATCH 36/45] Fix awkward mouse conflict issues between egui and the engine --- game/src/main.rs | 5 +++++ game/src/systems/menu.rs | 6 +++++- glium_platform/src/platform.rs | 8 +++++++- glium_platform/src/render/egui.rs | 32 +++++++++++++++++++++++++++++-- glium_platform/src/system.rs | 2 ++ platform/src/context.rs | 4 ++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/game/src/main.rs b/game/src/main.rs index b49445c..0f84666 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -106,6 +106,11 @@ impl System for MainSystem { character_pos = tr.translation; } egui_queue.queue(move |egui_ctx| { + // disable text selection on all labels. + egui_ctx.style_mut(|style| { + style.interaction.selectable_labels = false; + }); + egui::Window::new("Debug").show(egui_ctx, |ui| { ui.label("Hello World!"); ui.label(format!("Frame Delta: {:.3}", time_ctx.frame_dt)); diff --git a/game/src/systems/menu.rs b/game/src/systems/menu.rs index 60350b7..8a4b0d5 100644 --- a/game/src/systems/menu.rs +++ b/game/src/systems/menu.rs @@ -42,6 +42,7 @@ impl System for MenuSystem { }); } + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { // The menu is toggled by pressing the escape key let input = res.get::().unwrap(); @@ -63,18 +64,21 @@ impl MenuSystem { .next() .unwrap(); - let window = res.get::().unwrap().window.lock().unwrap(); + let pctx = res.get::().unwrap(); + let window = pctx.window.lock().unwrap(); match *menu_state { MenuState::Open => { *menu_state = MenuState::Closed; window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); window.set_cursor_visible(false); + pctx.should_egui_receive_input_events.set(false); }, MenuState::Closed => { *menu_state = MenuState::Open; window.set_cursor_grab(CursorGrabMode::None); window.set_cursor_visible(true); + pctx.should_egui_receive_input_events.set(true); }, } } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index bb1e192..b67ef77 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{RefCell, Cell}; use std::rc::Rc; use std::sync::{Arc, Mutex, RwLock}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; @@ -31,6 +31,8 @@ pub struct GliumPlatform> { time: time::Time, egui_queue: Rc>, settings: Arc>, + /// Used for [`raidillon_platform::context::PlatformContext::should_egui_receive_input_events`] + should_egui_receive_input_events: Rc>, } impl> Platform for GliumPlatform { @@ -64,6 +66,7 @@ impl> Platform for GliumPlatfor Settings::load_or_default(default_config_path()).unwrap() ) ); + let should_egui_receive_input_events = Rc::new(Cell::new(false)); Self { event_loop, @@ -75,6 +78,7 @@ impl> Platform for GliumPlatfor time, egui_queue, settings, + should_egui_receive_input_events, } } @@ -92,6 +96,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), settings: self.settings.clone(), + should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.engine.initialize(ctx.clone()); self.settings.read().unwrap().display_settings.apply(&*self.window.lock().unwrap()); @@ -136,6 +141,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), + should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.rendering_system_manager diff --git a/glium_platform/src/render/egui.rs b/glium_platform/src/render/egui.rs index e754ba3..3cd0583 100644 --- a/glium_platform/src/render/egui.rs +++ b/glium_platform/src/render/egui.rs @@ -8,9 +8,12 @@ use crate::system::RenderingContext; use egui_glium::EguiGlium; use winit::event::{Event, WindowEvent}; use winit::event_loop::EventLoop; +use std::cell::Cell; +use std::rc::Rc; pub struct EguiRenderer { egui_glium: EguiGlium, + should_egui_receive_input_events: Option>>, } impl RenderingSystem for EguiRenderer { @@ -21,10 +24,14 @@ impl RenderingSystem for EguiRenderer { let window = window.lock().unwrap(); let egui_glium = EguiGlium::new(ViewportId::ROOT, &display, &window, &event_loop); - Self { egui_glium } + Self { egui_glium: egui_glium, should_egui_receive_input_events: None } } fn render(&mut self, ctx: &mut RenderingContext) { + if self.should_egui_receive_input_events.is_none() { + self.should_egui_receive_input_events = Some(ctx.should_egui_receive_input_events.clone()); + } + let window = ctx.window.lock().unwrap(); self.egui_glium.run(&window, |egui_ctx| { @@ -38,7 +45,28 @@ impl RenderingSystem for EguiRenderer { let window = window.lock().unwrap(); match event { Event::WindowEvent { event, .. } => { - let _ = self.egui_glium.on_event(&window, &event); + let should_egui_receive_input_events = match self.should_egui_receive_input_events.as_ref() { + Some(v) => v.get(), + None => true, + }; + + let should_send_event = if should_egui_receive_input_events { + true + } else { + !matches!(event, + WindowEvent::KeyboardInput { .. } | + WindowEvent::ModifiersChanged(_) | + WindowEvent::CursorMoved { .. } | + WindowEvent::MouseInput { .. } | + WindowEvent::MouseWheel { .. } | + WindowEvent::Touch(_) | + WindowEvent::Ime(_) + ) + }; + + if should_send_event { + let _ = self.egui_glium.on_event(&window, &event); + } } _ => {}, } diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index d94e9c3..92fd553 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -10,6 +10,7 @@ use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; use winit::event_loop::EventLoop; +use std::cell::Cell; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -19,6 +20,7 @@ pub struct RenderingContext<'a> { pub asset_manager: ModelManagerRef, pub egui_queue: Rc>, pub env_light_dir: Vec3, + pub should_egui_receive_input_events: Rc> } /// The internal "rendering system" trait of glium_platform. diff --git a/platform/src/context.rs b/platform/src/context.rs index fdbe087..0990527 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use std::{cell::RefCell, rc::Rc}; use std::sync::{Arc, Mutex, RwLock}; use winit::event::Event; @@ -15,6 +16,9 @@ pub struct PlatformContext { pub window: Arc>, pub egui_queue: Rc>, pub settings: Arc>, + /// Sets whether or not egui will receive input events. + /// Added to prevent the mouse state conflict between the engine and egui. + pub should_egui_receive_input_events: Rc>, } #[derive(Clone)] From 73692b710e5814bd519b4e30fec10f14b97d32e8 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 15:53:54 +0300 Subject: [PATCH 37/45] Add debug wireframe rendering support --- app/src/prelude.rs | 3 + assets/shaders/debug_wireframe.frag | 9 +++ assets/shaders/debug_wireframe.vert | 14 ++++ game/src/main.rs | 4 +- game/src/systems/mod.rs | 2 + game/src/systems/physics_debug.rs | 35 +++++++++ glium_platform/src/platform.rs | 14 +++- glium_platform/src/render/debug_wireframe.rs | 80 ++++++++++++++++++++ glium_platform/src/render/mod.rs | 2 + glium_platform/src/system.rs | 2 + platform/src/context.rs | 61 ++++++++++++++- platform/src/lib.rs | 2 +- 12 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 assets/shaders/debug_wireframe.frag create mode 100644 assets/shaders/debug_wireframe.vert create mode 100644 game/src/systems/physics_debug.rs create mode 100644 glium_platform/src/render/debug_wireframe.rs diff --git a/app/src/prelude.rs b/app/src/prelude.rs index 1171d0f..fc9cdc1 100644 --- a/app/src/prelude.rs +++ b/app/src/prelude.rs @@ -11,6 +11,9 @@ pub use raidillon_platform::{ Camera, PlatformContext, TimeContext, + DebugWireframes, + DebugWireframesRef, + DebugWireframeVertex, settings::{Settings, WindowMode}, }; diff --git a/assets/shaders/debug_wireframe.frag b/assets/shaders/debug_wireframe.frag new file mode 100644 index 0000000..96d53b9 --- /dev/null +++ b/assets/shaders/debug_wireframe.frag @@ -0,0 +1,9 @@ +#version 330 core + +in vec4 v_color; + +out vec4 frag_color; + +void main() { + frag_color = v_color; +} diff --git a/assets/shaders/debug_wireframe.vert b/assets/shaders/debug_wireframe.vert new file mode 100644 index 0000000..7a53438 --- /dev/null +++ b/assets/shaders/debug_wireframe.vert @@ -0,0 +1,14 @@ +#version 330 core + +in vec3 position; +in vec4 color; + +uniform mat4 view; +uniform mat4 projection; + +out vec4 v_color; + +void main() { + v_color = color; + gl_Position = projection * view * vec4(position, 1.0); +} diff --git a/game/src/main.rs b/game/src/main.rs index 0f84666..3e1f318 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -8,7 +8,8 @@ use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; use crate::systems::common::should_draw_menu; use crate::systems::{ - DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem + DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem, + PhysicsDebugSystem, }; const TEST_GLTF: &str = "sphere.glb"; @@ -125,6 +126,7 @@ impl System for MainSystem { fn main() { raidillon_app::App::new() .add_system::() + .add_system::() .add_system::() .add_system::() .add_system::() diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index bcf77ca..08c54ab 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,4 +1,5 @@ mod physics; +mod physics_debug; mod kinematic_character_controller; mod keybinds; mod menu; @@ -7,6 +8,7 @@ pub mod common; mod display_settings; pub use physics::PhysicsSystem; +pub use physics_debug::PhysicsDebugSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; pub use menu::MenuSystem; diff --git a/game/src/systems/physics_debug.rs b/game/src/systems/physics_debug.rs new file mode 100644 index 0000000..33364d2 --- /dev/null +++ b/game/src/systems/physics_debug.rs @@ -0,0 +1,35 @@ +use raidillon_app::prelude::*; + +/// renders aabb wireframes for all physics colliders +#[derive(Default)] +pub struct PhysicsDebugSystem; + +impl System for PhysicsDebugSystem { + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + + let mut debug_wireframes = pctx.debug_wireframes.borrow_mut(); + if !debug_wireframes.enabled { + return; + } + + let physics = match scene.resources.get::() { + Some(p) => p, + None => return, + }; + + let color = [1.0, 0.0, 0.0, 1.0]; + + for (_, collider) in physics.collider_set.iter() { + let aabb = collider.compute_aabb(); + let min = aabb.mins; + let max = aabb.maxs; + + debug_wireframes.add_box( + [min.x, min.y, min.z], + [max.x, max.y, max.z], + color, + ); + } + } +} diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index b67ef77..8f00d92 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,7 +1,7 @@ use std::cell::{RefCell, Cell}; use std::rc::Rc; use std::sync::{Arc, Mutex, RwLock}; -use raidillon_platform::{Platform, PlatformContext, TimeContext}; +use raidillon_platform::{Platform, PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; use glium::glutin::surface::WindowSurface; @@ -14,7 +14,7 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; -use crate::render::{BasicMeshRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; +use crate::render::{BasicMeshRenderingSystem, DebugWireframeRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; @@ -31,7 +31,7 @@ pub struct GliumPlatform> { time: time::Time, egui_queue: Rc>, settings: Arc>, - /// Used for [`raidillon_platform::context::PlatformContext::should_egui_receive_input_events`] + debug_wireframes: DebugWireframesRef, should_egui_receive_input_events: Rc>, } @@ -57,6 +57,7 @@ impl> Platform for GliumPlatfor // Install rendering systems in order rendering_system_manager.add::(&display, window.clone(), &event_loop); rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); rendering_system_manager.add::(&display, window.clone(), &event_loop); let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); @@ -66,6 +67,7 @@ impl> Platform for GliumPlatfor Settings::load_or_default(default_config_path()).unwrap() ) ); + let debug_wireframes = Rc::new(RefCell::new(DebugWireframes::new())); let should_egui_receive_input_events = Rc::new(Cell::new(false)); Self { @@ -78,6 +80,7 @@ impl> Platform for GliumPlatfor time, egui_queue, settings, + debug_wireframes, should_egui_receive_input_events, } } @@ -96,6 +99,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), settings: self.settings.clone(), + debug_wireframes: self.debug_wireframes.clone(), should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.engine.initialize(ctx.clone()); @@ -140,6 +144,7 @@ impl> Platform for GliumPlatfor asset_manager: self.asset_manager.clone(), window: self.window.clone(), egui_queue: self.egui_queue.clone(), + debug_wireframes: self.debug_wireframes.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; @@ -149,6 +154,9 @@ impl> Platform for GliumPlatfor .values_mut() .for_each(|system| system.render(&mut context)); + // clear debug wireframes after rendering + self.debug_wireframes.borrow_mut().clear(); + target.finish().unwrap(); } _ => {}, diff --git a/glium_platform/src/render/debug_wireframe.rs b/glium_platform/src/render/debug_wireframe.rs new file mode 100644 index 0000000..2f259e9 --- /dev/null +++ b/glium_platform/src/render/debug_wireframe.rs @@ -0,0 +1,80 @@ +use std::sync::{Arc, Mutex}; +use glium::{Display, Program, Surface, VertexBuffer, implement_vertex}; +use glium::glutin::surface::WindowSurface; +use glium::index::PrimitiveType; +use glium::uniform; +use winit::event_loop::EventLoop; +use raidillon_assets::include_shader; +use crate::system::RenderingContext; +use crate::RenderingSystem; +pub use raidillon_platform::Camera; + +#[derive(Copy, Clone)] +struct DebugVertex { + position: [f32; 3], + color: [f32; 4], +} + +implement_vertex!(DebugVertex, position, color); + +/// renders debug wireframes from the shared buffer +pub struct DebugWireframeRenderingSystem { + program: Program, + params: glium::DrawParameters<'static>, +} + +impl RenderingSystem for DebugWireframeRenderingSystem { + fn initialize(display: &Display, _window: Arc>, _event_loop: &EventLoop<()>) -> Self { + const VERT_SRC: &str = include_shader!("debug_wireframe.vert"); + const FRAG_SRC: &str = include_shader!("debug_wireframe.frag"); + + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: false, + ..Default::default() + }, + line_width: Some(1.0), + ..Default::default() + }; + + Self { program, params } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + let debug_wireframes = ctx.debug_wireframes.borrow(); + + if !debug_wireframes.enabled || debug_wireframes.vertices.is_empty() { + return; + } + + let cam = match ctx.scene.world.query::<&Camera>().iter().next() { + Some((_, cam)) => *cam, + None => return, + }; + + let vertices: Vec = debug_wireframes.vertices.iter() + .map(|v| DebugVertex { position: v.position, color: v.color }) + .collect(); + + let vbuf = match VertexBuffer::new(ctx.display, &vertices) { + Ok(vb) => vb, + Err(_) => return, + }; + + let uniforms = uniform! { + view: cam.view().to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + }; + + ctx.target.draw( + &vbuf, + glium::index::NoIndices(PrimitiveType::LinesList), + &self.program, + &uniforms, + &self.params, + ).ok(); + } +} diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index fdfa1ea..22aa047 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,7 +1,9 @@ mod basic; mod skybox; mod egui; +mod debug_wireframe; pub use basic::BasicMeshRenderingSystem; pub use skybox::SkyboxRenderingSystem; pub use egui::EguiRenderer; +pub use debug_wireframe::DebugWireframeRenderingSystem; diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 92fd553..cf5f945 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -8,6 +8,7 @@ use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; +use raidillon_platform::DebugWireframesRef; use glam::Vec3; use winit::event_loop::EventLoop; use std::cell::Cell; @@ -19,6 +20,7 @@ pub struct RenderingContext<'a> { pub display: &'a Display, pub asset_manager: ModelManagerRef, pub egui_queue: Rc>, + pub debug_wireframes: DebugWireframesRef, pub env_light_dir: Vec3, pub should_egui_receive_input_events: Rc> } diff --git a/platform/src/context.rs b/platform/src/context.rs index 0990527..31bd6cb 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -6,6 +6,62 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::EguiQueue; use crate::settings::Settings; +/// a single debug wireframe vertex with position and color +#[derive(Clone, Copy)] +pub struct DebugWireframeVertex { + pub position: [f32; 3], + pub color: [f32; 4], +} + +/// shared buffer for debug wireframe rendering +#[derive(Clone, Default)] +pub struct DebugWireframes { + pub vertices: Vec, + pub enabled: bool, +} + +impl DebugWireframes { + pub fn new() -> Self { + Self { vertices: Vec::new(), enabled: true } + } + + pub fn clear(&mut self) { + self.vertices.clear(); + } + + /// add a single line segment + pub fn add_line(&mut self, start: [f32; 3], end: [f32; 3], color: [f32; 4]) { + self.vertices.push(DebugWireframeVertex { position: start, color }); + self.vertices.push(DebugWireframeVertex { position: end, color }); + } + + /// add a wireframe box from min/max corners + pub fn add_box(&mut self, min: [f32; 3], max: [f32; 3], color: [f32; 4]) { + let [x0, y0, z0] = min; + let [x1, y1, z1] = max; + + // bottom face edges + self.add_line([x0, y0, z0], [x1, y0, z0], color); + self.add_line([x1, y0, z0], [x1, y0, z1], color); + self.add_line([x1, y0, z1], [x0, y0, z1], color); + self.add_line([x0, y0, z1], [x0, y0, z0], color); + + // top face edges + self.add_line([x0, y1, z0], [x1, y1, z0], color); + self.add_line([x1, y1, z0], [x1, y1, z1], color); + self.add_line([x1, y1, z1], [x0, y1, z1], color); + self.add_line([x0, y1, z1], [x0, y1, z0], color); + + // vertical edges + self.add_line([x0, y0, z0], [x0, y1, z0], color); + self.add_line([x1, y0, z0], [x1, y1, z0], color); + self.add_line([x1, y0, z1], [x1, y1, z1], color); + self.add_line([x0, y0, z1], [x0, y1, z1], color); + } +} + +pub type DebugWireframesRef = Rc>; + #[derive(Clone)] pub struct PlatformContext { pub current_event: Event<()>, @@ -16,8 +72,9 @@ pub struct PlatformContext { pub window: Arc>, pub egui_queue: Rc>, pub settings: Arc>, - /// Sets whether or not egui will receive input events. - /// Added to prevent the mouse state conflict between the engine and egui. + /// shared debug wireframe buffer + pub debug_wireframes: DebugWireframesRef, + /// sets whether or not egui will receive input events pub should_egui_receive_input_events: Rc>, } diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 94c467b..ef88e08 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -6,4 +6,4 @@ pub mod settings; pub use platform::Platform; pub use camera::Camera; -pub use context::{PlatformContext, TimeContext}; +pub use context::{PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef, DebugWireframeVertex}; From 2a255affe4bcec7d814a44ba69dc2f5a81f2c5ba Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 17:08:51 +0300 Subject: [PATCH 38/45] Fix the skybox visual artifact bug #10 --- assets/shaders/skybox.frag | 6 +++++- glium_platform/src/render/skybox.rs | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag index 864be50..ce28e95 100644 --- a/assets/shaders/skybox.frag +++ b/assets/shaders/skybox.frag @@ -9,15 +9,19 @@ uniform sampler2D equirect; const vec2 inv_atan = vec2(0.15915494309, 0.31830988618); vec2 sample_spherical_map(vec3 v) { - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + vec2 uv = vec2(atan(v.z, v.x), asin(clamp(v.y, -1.0, 1.0))); uv *= inv_atan; uv += 0.5; + uv.x = fract(uv.x); return uv; } void main() { vec2 uv = sample_spherical_map(normalize(direction)); uv.y = 1.0 - uv.y; + vec2 size = vec2(textureSize(equirect, 0)); + float epsY = 0.5 / max(size.y, 1.0); + uv.y = clamp(uv.y, epsY, 1.0 - epsY); vec3 color = texture(equirect, uv).rgb; frag_color = vec4(color, 1.0); } diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 94832d0..2c9b9d9 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -7,6 +7,7 @@ use glium::glutin::surface::WindowSurface; use glium::index::PrimitiveType; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; use glium::uniform; +use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; use glam::{Mat4, Vec2, Vec3}; use winit::event_loop::EventLoop; use raidillon_assets::include_shader; @@ -132,10 +133,15 @@ impl RenderingSystem for SkyboxRenderingSystem { let mut view = cam.view(); // remove translation from view matrix (only orientation) view.col_mut(3).x = 0.0; view.col_mut(3).y = 0.0; view.col_mut(3).z = 0.0; + let mut sampler = self.equirect_srgb.sampled(); + sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); + sampler = sampler.minify_filter(MinifySamplerFilter::Linear); + sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); + let uniforms = uniform! { view: view.to_cols_array_2d(), projection: cam.projection().to_cols_array_2d(), - equirect: &self.equirect_srgb, + equirect: sampler, }; let params = glium::DrawParameters { depth: glium::Depth { test: glium::draw_parameters::DepthTest::IfLessOrEqual, write: false, ..Default::default() }, ..Default::default() }; ctx.target.draw(&self.quad_vb, &self.quad_ib, &self.program, &uniforms, ¶ms).ok(); From b50a60755ae9890d3438f34aa87396492556640d Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 22:16:41 +0300 Subject: [PATCH 39/45] Add exclusive fullscreen --- game/src/systems/display_settings.rs | 2 + platform/Cargo.toml | 2 +- platform/src/settings.rs | 67 +++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/game/src/systems/display_settings.rs b/game/src/systems/display_settings.rs index 6384d15..5d8455c 100644 --- a/game/src/systems/display_settings.rs +++ b/game/src/systems/display_settings.rs @@ -58,6 +58,7 @@ impl System for DisplaySettings { for mode in [ WindowMode::Windowed, WindowMode::BorderlessFullscreen, + WindowMode::ExclusiveFullscreen, ] { ui.selectable_value( &mut state.selected_fullscreen_mode, @@ -88,5 +89,6 @@ fn window_mode_label(mode: WindowMode) -> &'static str { match mode { WindowMode::Windowed => "Windowed", WindowMode::BorderlessFullscreen => "Borderless Fullscreen", + WindowMode::ExclusiveFullscreen => "Exclusive Fullscreen", } } diff --git a/platform/Cargo.toml b/platform/Cargo.toml index 1b7b5b2..16fec56 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -8,5 +8,5 @@ winit = "0.30.12" raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } glam = "0.30.5" -serde = "1.0.228" +serde = { version = "1.0.228", features = ["derive"] } toml = "0.9.8" diff --git a/platform/src/settings.rs b/platform/src/settings.rs index f6d092f..a901137 100644 --- a/platform/src/settings.rs +++ b/platform/src/settings.rs @@ -1,4 +1,4 @@ -use winit::dpi::LogicalSize; +use winit::monitor::{MonitorHandle, VideoModeHandle}; use winit::window::{Fullscreen, Window}; use serde::{Serialize, Deserialize}; use std::error::Error; @@ -19,6 +19,7 @@ pub fn default_config_path() -> PathBuf { #[serde(rename_all = "snake_case")] pub enum WindowMode { BorderlessFullscreen, + ExclusiveFullscreen, #[default] Windowed, } @@ -93,9 +94,73 @@ impl DisplaySettings { let monitor = window.current_monitor().or_else(|| window.primary_monitor()); window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } + WindowMode::ExclusiveFullscreen => { + let monitor = window.current_monitor().or_else(|| window.primary_monitor()); + match monitor { + Some(monitor) => { + if let Some(video_mode) = pick_best_video_mode(&monitor) { + window.set_fullscreen(Some(Fullscreen::Exclusive(video_mode))); + } else { + // fallback to borderless + window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor)))); + } + } + None => { + // no monitor info, fallback to windowed + window.set_fullscreen(None); + } + } + } WindowMode::Windowed => { window.set_fullscreen(None); }, } } } + +fn pick_best_video_mode(monitor: &MonitorHandle) -> Option { + let target_size = monitor.size(); + + let mut best_native: Option = None; + let mut best_any: Option = None; + + for mode in monitor.video_modes() { + if mode.size() == target_size { + let replace = match best_native.as_ref() { + None => true, + Some(best) => { + (mode.refresh_rate_millihertz(), mode.bit_depth()) + > (best.refresh_rate_millihertz(), best.bit_depth()) + } + }; + if replace { + best_native = Some(mode.clone()); + } + } + + let replace = match best_any.as_ref() { + None => true, + Some(best) => is_better_video_mode(&mode, best), + }; + if replace { + best_any = Some(mode); + } + } + + best_native.or(best_any) +} + +fn is_better_video_mode(a: &VideoModeHandle, b: &VideoModeHandle) -> bool { + let a_size = a.size(); + let b_size = b.size(); + let a_area = u64::from(a_size.width) * u64::from(a_size.height); + let b_area = u64::from(b_size.width) * u64::from(b_size.height); + + match a_area.cmp(&b_area) { + std::cmp::Ordering::Greater => true, + std::cmp::Ordering::Less => false, + std::cmp::Ordering::Equal => { + (a.refresh_rate_millihertz(), a.bit_depth()) > (b.refresh_rate_millihertz(), b.bit_depth()) + } + } +} From 6ac8e8f503ab6c825b14b303141cedc6cbb9e5da Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 22:44:44 +0300 Subject: [PATCH 40/45] Update kinematic character controller to apply forces to dynamic bodies the character collides with --- .../systems/kinematic_character_controller.rs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 41561a5..fd031c5 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,7 +1,7 @@ use glam::{Quat, Vec3}; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder, RigidBodyType}; use rapier3d::prelude::vector; -use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::control::{CharacterCollision, KinematicCharacterController as RapierKinematicCharacterController}; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::Event; @@ -53,7 +53,7 @@ impl System for KinematicCharacterController { )); self.speed = 5.0; - self.sensitivity = 0.1; + self.sensitivity = 0.05; self.gravity = -9.81; self.max_fall_speed = -50.0; self.vertical_velocity = 0.0; @@ -141,13 +141,14 @@ impl System for KinematicCharacterController { let mut total_displacement = self.desired_movement; total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + let mut collisions: Vec = Vec::new(); let corrected_movement = self.character_controller.move_shape( pctx.time_ctx.fixed_dt, &query_pipeline, &*self.character_collider.shape, &Isometry3::from(self.last_position), vector![total_displacement.x, total_displacement.y, total_displacement.z], - |_| {}, + |collision| collisions.push(collision), ); // update character rigid body with the new translation. @@ -165,6 +166,22 @@ impl System for KinematicCharacterController { } } + // apply impulses to dynamic bodies the character collided with + let character_push_force = 50.0; + for collision in collisions { + if let Some(collider) = p.collider_set.get(collision.handle) { + if let Some(rb_handle) = collider.parent() { + if let Some(rb) = p.rigid_body_set.get_mut(rb_handle) { + if rb.body_type() == RigidBodyType::Dynamic { + let push_direction = -collision.hit.normal1.into_inner(); + let impulse = push_direction * character_push_force * pctx.time_ctx.fixed_dt; + rb.apply_impulse(impulse, true); + } + } + } + } + } + self.desired_movement = Vec3::ZERO; } } From 98d2a9dacb0a813b03ed273d5ae83d2dcc543486 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 23:08:32 +0300 Subject: [PATCH 41/45] Add checkered sphere model --- assets/models/checkered-sphere.glb | 3 +++ game/src/main.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 assets/models/checkered-sphere.glb diff --git a/assets/models/checkered-sphere.glb b/assets/models/checkered-sphere.glb new file mode 100644 index 0000000..ad55283 --- /dev/null +++ b/assets/models/checkered-sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36498b406df94ee6990c72a932082101fe0427af52260893e299e4d1a44de084 +size 1796992 diff --git a/game/src/main.rs b/game/src/main.rs index 3e1f318..a619cb3 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -12,7 +12,7 @@ use crate::systems::{ PhysicsDebugSystem, }; -const TEST_GLTF: &str = "sphere.glb"; +const TEST_GLTF: &str = "checkered-sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; From a51aeb23bd71d00d8f9ab5207f6382fbe2e2f9d4 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 17 Dec 2025 20:56:02 +0300 Subject: [PATCH 42/45] Fix character collider shape --- game/src/systems/kinematic_character_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index fd031c5..b27450a 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -35,7 +35,7 @@ impl System for KinematicCharacterController { let p = scene.resources.get_mut::().expect("Physics missing"); let rb = RigidBodyBuilder::kinematic_position_based().build(); let rb_handle = p.rigid_body_set.insert(rb); - self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + self.character_collider = ColliderBuilder::capsule_y(1.0, 1.0); p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); let tr = Transform { translation: Vec3::new(0.0, 2.0, 3.0), From 71e991db779c4486d934f324c3e25b4bea7332e6 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 23:34:51 +0300 Subject: [PATCH 43/45] Improve lighting - No specular on surfaces facing away from the light source - Transform the light direction to view space to ensure that all lighting calculations happen in a consistent coordinate space. - Other lighting tweaks --- assets/shaders/gl_textured.frag | 33 ++++++++++++++++++----------- glium_platform/src/render/basic.rs | 8 +++++-- glium_platform/src/render/skybox.rs | 2 +- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/assets/shaders/gl_textured.frag b/assets/shaders/gl_textured.frag index b264f77..abfa702 100644 --- a/assets/shaders/gl_textured.frag +++ b/assets/shaders/gl_textured.frag @@ -6,28 +6,37 @@ in vec3 v_position; out vec4 frag_color; -uniform vec3 u_light; +uniform vec3 u_light; // direction TO the light (normalized) uniform sampler2D tex; -uniform vec3 color; // base colour factor (acts as solid colour when no texture) +uniform vec3 color; // base colour factor (acts as solid colour when no texture) void main() { // Combine base texture (or constant white) with colour factor supplied by CPU. vec3 base_col = texture(tex, v_tex).rgb * color; - vec3 ambient_color = base_col * 0.2; - vec3 diffuse_color = base_col * 0.6; - vec3 specular_color = vec3(1.0); + vec3 N = normalize(v_normal); + vec3 L = normalize(u_light); - // u_light is the direction **from the light towards the fragment**. - float diffuse = max(dot(normalize(v_normal), normalize(u_light)), 0.0); + // Classic Blinn-Phong lighting + // Ambient: always present + vec3 ambient = base_col * 0.15; - vec3 camera_dir = normalize(-v_position); - vec3 half_dir = normalize(normalize(u_light) + camera_dir); - float specular = pow(max(dot(half_dir, normalize(v_normal)), 0.0), 16.0); + // Diffuse: N dot L, clamped + float NdotL = max(dot(N, L), 0.0); + vec3 diffuse = base_col * NdotL * 0.7; - vec3 result = ambient_color + diffuse * diffuse_color + specular * specular_color; + // Specular: only on surfaces facing the light (NdotL > 0) + float specular = 0.0; + if (NdotL > 0.0) { + vec3 V = normalize(-v_position); // view direction (camera at origin in view space) + vec3 H = normalize(L + V); // half-vector + float NdotH = max(dot(N, H), 0.0); + specular = pow(NdotH, 32.0) * 0.5; // tighter highlight, moderated intensity + } - // Convert from linear to sRGB for display (approximate γ-correction) + vec3 result = ambient + diffuse + vec3(specular); + + // Convert from linear to sRGB for display (approximate gamma correction) result = pow(result, vec3(1.0 / 2.2)); frag_color = vec4(result, 1.0); diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index a55d865..3ed144b 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -59,7 +59,11 @@ impl RenderingSystem for BasicMeshRenderingSystem { }; // Use HDR-derived environment light direction if provided, otherwise default to downward - let light_dir: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; + let light_dir_world: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; + + // Transform light direction to view space (normals/positions are in view space) + let view_mat3 = glam::Mat3::from_mat4(cam.view()); + let light_dir_view = (view_mat3 * light_dir_world).normalize(); let asset_manager = ctx.asset_manager.borrow(); @@ -90,7 +94,7 @@ impl RenderingSystem for BasicMeshRenderingSystem { model: tr.matrix().to_cols_array_2d(), view: cam.view().to_cols_array_2d(), projection: cam.projection().to_cols_array_2d(), - u_light: [light_dir.x, light_dir.y, light_dir.z], + u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], tex: sampler, color: [c[0], c[1], c[2]], uv_offset: [mat.uv_offset.x, mat.uv_offset.y], diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 2c9b9d9..d11683b 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -119,7 +119,7 @@ impl RenderingSystem for SkyboxRenderingSystem { // Load EXR from assets/exr let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let path = std::path::Path::new(manifest_dir).join("../assets/exr/qwantani_sunset_puresky_2k.exr"); + let path = std::path::Path::new(manifest_dir).join("../assets/exr/citrus_orchard_road_puresky_4k.exr"); let (equirect_srgb, light_dir) = Self::load_hdr_equirect_and_analyze(display, &path); Self { program, quad_vb, quad_ib, equirect_srgb, light_dir } } From 0f2209a5d536994541166ae0d4111848d06d1831 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 31 Dec 2025 22:34:05 +0300 Subject: [PATCH 44/45] Update the gltf loader to support multiple meshes in one file --- glium_platform/src/assets.rs | 8 +- glium_platform/src/gltf_loader.rs | 151 ++++++++++++++++------------- glium_platform/src/render/basic.rs | 56 +++++------ 3 files changed, 115 insertions(+), 100 deletions(-) diff --git a/glium_platform/src/assets.rs b/glium_platform/src/assets.rs index 1b3b4e1..c8cb1d4 100644 --- a/glium_platform/src/assets.rs +++ b/glium_platform/src/assets.rs @@ -12,7 +12,7 @@ use raidillon_assets::model_manager::ModelID; /// Glium platform asset manager implementation. pub struct GliumAssetManager { - pub models: HashMap, + pub models: HashMap>, facade: Box, } @@ -28,8 +28,8 @@ impl GliumAssetManager { impl ModelManager for GliumAssetManager { fn load_gltf(&mut self, id: ModelID, path: &Path) { - let model = load_gltf(path, self.facade.as_ref()).unwrap(); - self.models.insert(id, model); + let models = load_gltf(path, self.facade.as_ref()).unwrap(); + self.models.insert(id, models); } fn unload_model(&mut self, id: ModelID) { @@ -48,6 +48,6 @@ impl ModelManager for GliumAssetManager { // } fn get_model(&self, id: &ModelID) -> Option<&dyn Any> { - self.models.get(id).map(|model| model as &dyn Any) + self.models.get(id).map(|models| models as &dyn Any) } } diff --git a/glium_platform/src/gltf_loader.rs b/glium_platform/src/gltf_loader.rs index e41bbfb..fe4ec37 100644 --- a/glium_platform/src/gltf_loader.rs +++ b/glium_platform/src/gltf_loader.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use glium::{backend::Facade, IndexBuffer, VertexBuffer}; use glium::index::PrimitiveType; use std::{fmt::Debug, path::Path}; @@ -8,31 +8,16 @@ use glium::uniforms::{SamplerWrapFunction, MinifySamplerFilter, MagnifySamplerFi use gltf::image::Format as GltfFormat; use glam::Vec2; -/// Load a glTF 2.0 file from disk and upload the first primitive to the GPU. -pub fn load_gltf

(path: P, facade: &dyn Facade) -> Result +/// Load a glTF 2.0 file from disk and upload all primitives to the GPU. +/// +/// Returns one [`Model`] per glTF primitive (across all meshes). +pub fn load_gltf

(path: P, facade: &dyn Facade) -> Result> where P: AsRef + Debug, { // -- parse the asset & bring buffer blobs into memory -- let (doc, buffers, images) = gltf::import(path.as_ref()).context("failed to import glTF file")?; - // -- grab the very first mesh / primitive -- - let mesh = doc.meshes().next().context("glTF has no meshes")?; - let primitive = mesh.primitives().next().context("mesh has no primitives")?; - - // ---------- MATERIAL ---------- - let mut mat = Material::default(); - - let mat_idx = primitive.material().index().context("primitive has no material")?; - let material = doc.materials().nth(mat_idx).unwrap(); - let pbr = material.pbr_metallic_roughness(); - - // Factors -------------------------------------------------- - mat.base_color_factor = pbr.base_color_factor(); - mat.metal_factor = pbr.metallic_factor(); - mat.roughness_factor = pbr.roughness_factor(); - mat.emissive_factor = material.emissive_factor(); - // Helper to update sampler settings from glTF sampler fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) { let sampler_info = t.sampler(); @@ -64,64 +49,92 @@ where } } - // Base-color texture (sRGB) - if let Some(info) = pbr.base_color_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.base_color = Some(glium_srgb_texture(facade, &images[view])?); - } + let mut out: Vec = Vec::new(); - // Metallic-Roughness (linear) - if let Some(info) = pbr.metallic_roughness_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?); - } + for mesh in doc.meshes() { + for primitive in mesh.primitives() { + // ---------- MATERIAL ---------- + let mut mat = Material::default(); + let material = primitive.material(); + let pbr = material.pbr_metallic_roughness(); - // Normal map (linear) - if let Some(info) = primitive.material().normal_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.normal = Some(glium_linear_texture(facade, &images[view])?); - } + // Factors -------------------------------------------------- + mat.base_color_factor = pbr.base_color_factor(); + mat.metal_factor = pbr.metallic_factor(); + mat.roughness_factor = pbr.roughness_factor(); + mat.emissive_factor = material.emissive_factor(); - // Occlusion (linear) - if let Some(info) = primitive.material().occlusion_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.occlusion = Some(glium_linear_texture(facade, &images[view])?); - } + // Base-color texture (sRGB) + if let Some(info) = pbr.base_color_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.base_color = Some(glium_srgb_texture(facade, &images[view])?); + } - // Emissive (sRGB) - if let Some(info) = primitive.material().emissive_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.emissive = Some(glium_srgb_texture(facade, &images[view])?); - } + // Metallic-Roughness (linear) + if let Some(info) = pbr.metallic_roughness_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?); + } - // KHR_texture_transform - if let Some(tex) = pbr.base_color_texture() { - if let Some(xform) = tex.texture_transform() { - mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]); - mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]); + // Normal map (linear) + if let Some(info) = material.normal_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.normal = Some(glium_linear_texture(facade, &images[view])?); + } + + // Occlusion (linear) + if let Some(info) = material.occlusion_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.occlusion = Some(glium_linear_texture(facade, &images[view])?); + } + + // Emissive (sRGB) + if let Some(info) = material.emissive_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.emissive = Some(glium_srgb_texture(facade, &images[view])?); + } + + // KHR_texture_transform (base color only, for now) + if let Some(tex) = pbr.base_color_texture() { + if let Some(xform) = tex.texture_transform() { + mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]); + mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]); + } + } + + // ---- Vertex/index data ---- + let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0)); + + let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect(); + let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect(); + let tex_coords: Vec<[f32; 2]> = reader + .read_tex_coords(0) + .map(|tc| tc.into_f32().collect()) + .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); + let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect(); + + // Interleave + let vertices: Vec = (0..positions.len()) + .map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }) + .collect(); + + let vbuf = VertexBuffer::immutable(facade, &vertices)?; + let ibuf = IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &indices)?; + + out.push(Model { mesh: Mesh { vbuf, ibuf }, material: mat }); } } - // ---- Vertex/index data ---- - let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0)); + if out.is_empty() { + bail!("glTF has no mesh primitives"); + } - let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect(); - let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect(); - let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0).map(|tc| tc.into_f32().collect()).unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); - let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect(); - - // Interleave - let vertices: Vec = (0..positions.len()).map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }).collect(); - - let vbuf = VertexBuffer::immutable(facade, &vertices)?; - let ibuf = IndexBuffer ::immutable(facade, PrimitiveType::TrianglesList, &indices)?; - - Ok(Model { mesh: Mesh { vbuf, ibuf }, material: mat }) + Ok(out) } /// Linear-space texture (RGBA8) from glTF image data. diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 3ed144b..6553cc0 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -73,41 +73,43 @@ impl RenderingSystem for BasicMeshRenderingSystem { _ => continue, }; - let model = match model.downcast_ref::() { - Some(model) => model, + let models = match model.downcast_ref::>() { + Some(models) => models, None => continue, }; - let mesh = &model.mesh; - let mat = &model.material; + for model in models { + let mesh = &model.mesh; + let mat = &model.material; - let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex); + let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex); - let mut sampler = tex_ref.sampled(); - sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); - sampler = sampler.minify_filter(MinifySamplerFilter::Linear); - sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); + let mut sampler = tex_ref.sampled(); + sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); + sampler = sampler.minify_filter(MinifySamplerFilter::Linear); + sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); - let c = mat.base_color_factor; + let c = mat.base_color_factor; - let uniforms = uniform! { - model: tr.matrix().to_cols_array_2d(), - view: cam.view().to_cols_array_2d(), - projection: cam.projection().to_cols_array_2d(), - u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], - tex: sampler, - color: [c[0], c[1], c[2]], - uv_offset: [mat.uv_offset.x, mat.uv_offset.y], - uv_scale: [mat.uv_scale.x, mat.uv_scale.y], - }; + let uniforms = uniform! { + model: tr.matrix().to_cols_array_2d(), + view: cam.view().to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], + tex: sampler, + color: [c[0], c[1], c[2]], + uv_offset: [mat.uv_offset.x, mat.uv_offset.y], + uv_scale: [mat.uv_scale.x, mat.uv_scale.y], + }; - ctx.target.draw( - &mesh.vbuf, - &mesh.ibuf, - &self.program, - &uniforms, - &self.params, - ).unwrap(); + ctx.target.draw( + &mesh.vbuf, + &mesh.ibuf, + &self.program, + &uniforms, + &self.params, + ).unwrap(); + } } } } From a3a235692126b78d7cfdf3bace5f3d1d60f9772a Mon Sep 17 00:00:00 2001 From: Emre Osmanoglu Date: Fri, 3 Apr 2026 01:03:25 +0300 Subject: [PATCH 45/45] Add license --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +.