diff --git a/Cargo.lock b/Cargo.lock index 0a091ac..3600aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,12 @@ dependencies = [ "equator", ] +[[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" @@ -73,7 +79,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -88,6 +94,15 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.1" @@ -179,6 +194,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.2" @@ -259,7 +280,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -402,6 +423,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -421,6 +464,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -470,6 +522,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "dpi" version = "0.1.2" @@ -482,6 +540,15 @@ 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 = "equator" version = "0.4.2" @@ -558,6 +625,12 @@ 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 = "foreign-types" version = "0.5.0" @@ -783,6 +856,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" @@ -797,6 +879,21 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[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 = "heck" @@ -957,7 +1054,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1038,6 +1135,12 @@ dependencies = [ "windows-targets 0.53.2", ] +[[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.4" @@ -1086,6 +1189,16 @@ dependencies = [ "imgref", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -1142,6 +1255,33 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1154,7 +1294,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1204,6 +1344,15 @@ dependencies = [ "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" @@ -1242,6 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1536,6 +1686,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.0" @@ -1568,6 +1727,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry3d" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f8d0a3b2f4c0e250d4599b69e490535521c3497e2b88b0b5d2ada251bc83a8" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 2.0.1", + "either", + "ena", + "hashbrown 0.15.4", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "spade", + "thiserror 2.0.12", +] + [[package]] name = "paste" version = "1.0.15" @@ -1744,11 +1928,14 @@ dependencies = [ "anyhow", "glam", "hecs", + "nalgebra", "raidillon_core", "raidillon_ecs", "raidillon_input", + "raidillon_physics", "raidillon_render", "raidillon_ui", + "rapier3d", "winit", ] @@ -1760,6 +1947,16 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_physics" +version = "0.1.0" +dependencies = [ + "glam", + "nalgebra", + "raidillon_ecs", + "rapier3d", +] + [[package]] name = "raidillon_render" version = "0.1.0" @@ -1768,10 +1965,10 @@ dependencies = [ "glam", "glium", "gltf", - "glutin", "hecs", "image", "raidillon_ecs", + "rapier3d", "winit", ] @@ -1785,6 +1982,7 @@ dependencies = [ "imgui-glium-renderer", "imgui-winit-support", "raidillon_render", + "rapier3d", "winit", ] @@ -1818,6 +2016,30 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rapier3d" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f1015500058823ba9c479c908d7069adcf3f0f51a3e49dca7efc5575df7e574" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "crossbeam", + "downcast-rs 2.0.1", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash", + "simba", + "thiserror 2.0.12", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -1848,7 +2070,7 @@ dependencies = [ "rand_chacha", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] @@ -1874,6 +2096,12 @@ 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" version = "1.10.0" @@ -1918,12 +2146,35 @@ version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +[[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.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[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" @@ -1962,6 +2213,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" @@ -2018,9 +2278,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -2043,6 +2303,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2084,7 +2357,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -2104,12 +2377,30 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strict-num" version = "0.1.1" @@ -2152,7 +2443,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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -2166,6 +2466,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiff" version = "0.9.1" @@ -2258,6 +2569,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2402,7 +2719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 0.38.44", "scoped-tls", "smallvec", @@ -2530,6 +2847,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[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 27dcb24..f8dae70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "raidillon_ui", "raidillon_game", "raidillon_input", + "raidillon_physics", ] diff --git a/raidillon_game/Cargo.toml b/raidillon_game/Cargo.toml index c127172..49635f0 100644 --- a/raidillon_game/Cargo.toml +++ b/raidillon_game/Cargo.toml @@ -13,3 +13,6 @@ raidillon_ui = { path = "../raidillon_ui" } raidillon_core = { path = "../raidillon_core" } hecs = "0.10.5" raidillon_input = { path = "../raidillon_input" } +raidillon_physics = { path = "../raidillon_physics" } +rapier3d = "0.26.1" +nalgebra = { version = "0.33", features = ["macros"] } diff --git a/raidillon_game/src/main.rs b/raidillon_game/src/main.rs index 825f1c2..de66293 100644 --- a/raidillon_game/src/main.rs +++ b/raidillon_game/src/main.rs @@ -5,9 +5,12 @@ use raidillon_ecs::Transform; use raidillon_render::{Camera, ECSRenderer, init_render_window, DisplayHandle}; use raidillon_ui::Gui; use raidillon_input::{Input, FPSCameraController}; +use raidillon_physics::{Physics, BodyKind, RigidBodyComponent}; +use rapier3d::prelude::RigidBodyHandle; use winit::keyboard::KeyCode; use winit::window::CursorGrabMode; use winit::event::MouseButton; +use nalgebra::vector; #[derive(Copy, Clone, Eq, PartialEq, Hash)] enum Action { @@ -22,7 +25,7 @@ fn main() -> Result<()> { .build() .expect("create event-loop"); - let (window, _display): (winit::window::Window, DisplayHandle) = init_render_window(&event_loop, "raidillon", (1280, 720))?; + let (window, _display): (winit::window::Window, DisplayHandle) = init_render_window(&event_loop, "raidillon", (1920, 1080))?; // Create ECS renderer which internally owns both the world and the renderer let mut ecsr = ECSRenderer::from_display_handle(&_display)?; @@ -42,23 +45,55 @@ fn main() -> Result<()> { let mut time = Time::new(); - let object_ent = ecsr.load_mesh_from_gltf("resources/models/tree.gltf", Transform { - translation: Vec3::new(0.0, -2.5, -5.0), + // Physics world + let mut physics = Physics::new(); + + // Load a sphere model instead of the tree + let sphere_ent = ecsr.load_mesh_from_gltf("resources/models/uvsphere-smooth.gltf", Transform { + translation: Vec3::new(0.0, 2.5, 0.0), rotation: Quat::IDENTITY, - scale: Vec3::new(0.01, 0.01, 0.01), + scale: Vec3::new(0.5, 0.5, 0.5), })?; + { + let tr = *ecsr.world.get::<&Transform>(sphere_ent)?; + let collider = rapier3d::prelude::ColliderBuilder::ball(0.5).build(); + let rb_handle = physics.add_rigid_body(BodyKind::Dynamic, tr, collider); + ecsr.world.insert_one(sphere_ent, RigidBodyComponent(rb_handle))?; + } + let ground_ent = ecsr.load_mesh_from_gltf("resources/models/plane.gltf", Transform { translation: Vec3::new(0.0, -1.5, 0.0), rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), + scale: Vec3::new(10.0, 1.0, 10.0), })?; + { + let tr = *ecsr.world.get::<&Transform>(ground_ent)?; + let collider = rapier3d::prelude::ColliderBuilder::cuboid(10.0, 0.1, 10.0).build(); + let rb_handle = physics.add_rigid_body(BodyKind::Static, tr, collider); + ecsr.world.insert_one(ground_ent, RigidBodyComponent(rb_handle))?; + } + + let player_initial_tr = Transform { + translation: Vec3::new(0.0, 1.0, 2.0), + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + }; + + let player_collider = rapier3d::prelude::ColliderBuilder::capsule_y(0.9, 0.4).build(); + let player_rb_handle: RigidBodyHandle = physics.add_rigid_body(BodyKind::Dynamic, player_initial_tr, player_collider); + if let Some(body) = physics.get_rigid_body_mut(player_rb_handle) { + body.set_locked_axes(rapier3d::prelude::LockedAxes::ROTATION_LOCKED, true); + } + let _player_ent = ecsr.world.spawn((player_initial_tr, RigidBodyComponent(player_rb_handle))); + + camera_controller.position = player_initial_tr.translation; let camera_ent = { let (w, h): (u32, u32) = window.inner_size().into(); ecsr.world.spawn((Camera { - eye: Vec3::new(0.0, 0.0, 2.0), + eye: player_initial_tr.translation, center: Vec3::ZERO, up: Vec3::Y, fovy: 60_f32.to_radians(), @@ -107,10 +142,17 @@ fn main() -> Result<()> { } WindowEvent::RedrawRequested => { gui.render_world(&mut ecsr, &window, |ui, ecsr| { - if let Ok(mut tr) = ecsr.world.query_one_mut::<&mut Transform>(object_ent) { + if let Ok(mut tr) = ecsr.world.query_one_mut::<&mut Transform>(sphere_ent) { ui.text("Hold right click to control the camera"); ui.text("WASD to move"); + static mut SHOW_COLLIDERS: bool = true; + unsafe { + if ui.checkbox("Show Colliders", &mut SHOW_COLLIDERS) { + } + ecsr.renderer.set_show_colliders(SHOW_COLLIDERS); + } + // Translation controls let mut translation = [tr.translation.x, tr.translation.y, tr.translation.z]; if ui.input_float3("Translation", &mut translation).build() { @@ -142,6 +184,7 @@ fn main() -> Result<()> { { let dt = time.delta_seconds(); + camera_controller.update( &input, dt, @@ -149,15 +192,57 @@ fn main() -> Result<()> { (Action::MoveForward, Action::MoveBackward, Action::MoveLeft, Action::MoveRight), ); - if let Ok(mut cam) = ecsr.world.query_one_mut::<&mut Camera>(camera_ent) { - cam.eye = camera_controller.position; - cam.center = camera_controller.position + camera_controller.front(); + let mut move_dir = Vec3::ZERO; + let front = camera_controller.front(); + let front_h = Vec3::new(front.x, 0.0, front.z).normalize_or_zero(); + let right_vec = front_h.cross(Vec3::Y).normalize_or_zero(); + + if input.action_held(Action::MoveForward) { move_dir += front_h; } + if input.action_held(Action::MoveBackward) { move_dir -= front_h; } + if input.action_held(Action::MoveLeft) { move_dir -= right_vec; } + if input.action_held(Action::MoveRight) { move_dir += right_vec; } + + if move_dir.length_squared() > 0.0 { + move_dir = move_dir.normalize(); + } + + if let Some(body) = physics.get_rigid_body_mut(player_rb_handle) { + let current_vel = body.linvel(); + let desired_vel = move_dir * camera_controller.speed; + body.set_linvel(vector![desired_vel.x, current_vel.y, desired_vel.z], true); + } + + physics.step(dt); + + { + let mut query = ecsr.world.query::<(&mut Transform, &RigidBodyComponent)>(); + for (_ent, (mut tr, rb_comp)) in query.iter() { + if let Some(body) = physics.get_rigid_body(rb_comp.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; + } + } + } + + if let Some(player_body) = physics.get_rigid_body(player_rb_handle) { + let eye = Physics::rapier_translation_to_glam(&player_body.position().translation.vector); + camera_controller.position = eye; + + if let Ok(mut cam) = ecsr.world.query_one_mut::<&mut Camera>(camera_ent) { + cam.eye = eye; + cam.center = eye + camera_controller.front(); + } } } input.end_frame(); gui.prepare_frame(&window); + + ecsr.renderer.set_colliders(Some(&physics.collider_set)); window.request_redraw(); } _ => {} diff --git a/raidillon_physics/Cargo.toml b/raidillon_physics/Cargo.toml new file mode 100644 index 0000000..2133496 --- /dev/null +++ b/raidillon_physics/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "raidillon_physics" +version = "0.1.0" +edition = "2021" + +[dependencies] +rapier3d = "0.26.1" +glam = "0.30.4" +nalgebra = { version = "0.33" } +raidillon_ecs = { path = "../raidillon_ecs" } \ No newline at end of file diff --git a/raidillon_physics/src/lib.rs b/raidillon_physics/src/lib.rs new file mode 100644 index 0000000..245a5bd --- /dev/null +++ b/raidillon_physics/src/lib.rs @@ -0,0 +1,108 @@ +use rapier3d::prelude::*; +use raidillon_ecs::Transform; +use glam::{Quat, Vec3}; +use ::nalgebra::{UnitQuaternion, Quaternion}; +use rapier3d::geometry::{DefaultBroadPhase, NarrowPhase}; +use rapier3d::prelude::QueryPipeline; + +#[derive(Copy, Clone)] +pub struct RigidBodyComponent(pub RigidBodyHandle); + +#[derive(Copy, Clone, Debug)] +pub enum BodyKind { + Static, + Dynamic, + Kinematic, +} + +pub struct Physics { + pub rigid_body_set: RigidBodySet, + pub collider_set: ColliderSet, + gravity: Vector, + integration_parameters: IntegrationParameters, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + query_pipeline: QueryPipeline, + ccd_solver: CCDSolver, + physics_pipeline: PhysicsPipeline, +} + +impl Physics { + pub fn new() -> Self { + Self { + rigid_body_set: RigidBodySet::new(), + collider_set: ColliderSet::new(), + gravity: vector![0.0, -9.81, 0.0], + integration_parameters: IntegrationParameters::default(), + island_manager: IslandManager::new(), + broad_phase: DefaultBroadPhase::new(), + narrow_phase: NarrowPhase::new(), + impulse_joint_set: ImpulseJointSet::new(), + multibody_joint_set: MultibodyJointSet::new(), + query_pipeline: QueryPipeline::new(), + ccd_solver: CCDSolver::new(), + physics_pipeline: PhysicsPipeline::new(), + } + } + + 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, + Some(&mut self.query_pipeline), + &(), // physics hooks + &(), // event handler + ); + } + + pub fn add_rigid_body(&mut self, kind: BodyKind, transform: Transform, collider: Collider) -> RigidBodyHandle { + let body_type = match kind { + BodyKind::Static => RigidBodyType::Fixed, + BodyKind::Dynamic => RigidBodyType::Dynamic, + BodyKind::Kinematic => RigidBodyType::KinematicPositionBased, + }; + + let rb = RigidBodyBuilder::new(body_type) + .translation(vector![transform.translation.x, transform.translation.y, transform.translation.z]) + .build(); + + let rb_handle = self.rigid_body_set.insert(rb); + + // Attach collider to rigid body + 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) + } + + fn quat_to_na(quat: Quat) -> UnitQuaternion { + UnitQuaternion::from_quaternion(Quaternion::new(quat.w, quat.x, quat.y, quat.z)) + } + + 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) + } +} \ No newline at end of file diff --git a/raidillon_render/Cargo.toml b/raidillon_render/Cargo.toml index ae6c94a..520ff7e 100644 --- a/raidillon_render/Cargo.toml +++ b/raidillon_render/Cargo.toml @@ -4,12 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -anyhow = "1.0.98" glam = "0.30.4" -glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] } -gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transform"] } -glutin = { version = "0.32.3", default-features = false } -hecs = "0.10.5" -image = "0.25.6" -raidillon_ecs = { path = "../raidillon_ecs" } +image = "0.25" +anyhow = "1" +hecs = "0.10.5" +glium = { version = "0.35", features = ["glutin"] } winit = "0.30" +raidillon_ecs = { path = "../raidillon_ecs" } +rapier3d = "0.26.1" +gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transform"] } diff --git a/raidillon_render/src/debug.rs b/raidillon_render/src/debug.rs new file mode 100644 index 0000000..909420b --- /dev/null +++ b/raidillon_render/src/debug.rs @@ -0,0 +1,79 @@ +use glium::{implement_vertex, VertexBuffer, Surface, Program, DrawParameters, index::NoIndices, index::PrimitiveType, uniform}; +use glium::glutin::surface::WindowSurface; +use rapier3d::prelude::{ColliderSet, Aabb}; +use glam::{Mat4}; + +#[derive(Copy, Clone)] +pub struct DebugVertex { + position: [f32; 3], +} + +implement_vertex!(DebugVertex, position); + +pub struct ColliderDebugRenderer { + program: Program, + display: glium::Display, +} + +impl ColliderDebugRenderer { + pub fn new(display: &glium::Display) -> anyhow::Result { + const VERT: &str = r#"#version 330 core +layout(location = 0) in vec3 position; +uniform mat4 vp; +void main() { + gl_Position = vp * vec4(position, 1.0); +} +"#; + const FRAG: &str = r#"#version 330 core +out vec4 color; +void main() { + color = vec4(1.0,1.0,0.0,1.0); +} +"#; + let program = Program::from_source(display, VERT, FRAG, None)?; + Ok(Self { program, display: display.clone() }) + } + + pub fn draw(&self, colliders: &ColliderSet, vp: Mat4, target: &mut S) { + let mut vertices: Vec = Vec::new(); + for (_, c) in colliders.iter() { + let aabb: Aabb = c.compute_aabb(); + let min = aabb.mins; + let max = aabb.maxs; + // 8 corners + let p0 = [min.x, min.y, min.z]; + let p1 = [max.x, min.y, min.z]; + let p2 = [max.x, max.y, min.z]; + let p3 = [min.x, max.y, min.z]; + let p4 = [min.x, min.y, max.z]; + let p5 = [max.x, min.y, max.z]; + let p6 = [max.x, max.y, max.z]; + let p7 = [min.x, max.y, max.z]; + // 12 edges (pairs) + let edges = [ + (p0, p1), (p1, p2), (p2, p3), (p3, p0), + (p4, p5), (p5, p6), (p6, p7), (p7, p4), + (p0, p4), (p1, p5), (p2, p6), (p3, p7), + ]; + for (a, b) in edges.iter() { + vertices.push(DebugVertex { position: *a }); + vertices.push(DebugVertex { position: *b }); + } + } + if vertices.is_empty() { return; } + let vb = VertexBuffer::new(&self.display, &vertices).unwrap(); + let no_indices = NoIndices(PrimitiveType::LinesList); + let uniforms = uniform! { vp: vp.to_cols_array_2d() }; + let params = DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLessOrEqual, + write: false, + .. Default::default() + }, + polygon_mode: glium::draw_parameters::PolygonMode::Line, + line_width: Some(1.0), + .. Default::default() + }; + target.draw(&vb, &no_indices, &self.program, &uniforms, ¶ms).ok(); + } +} diff --git a/raidillon_render/src/gltf_loader.rs b/raidillon_render/src/gltf_loader.rs index 4d006fb..1147844 100644 --- a/raidillon_render/src/gltf_loader.rs +++ b/raidillon_render/src/gltf_loader.rs @@ -29,87 +29,88 @@ where // ---------- 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(); + if let Some(mat_idx) = primitive.material().index() { + 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(); + // 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(); - mat.sampler.wrap_function.0 = match sampler_info.wrap_s() { - gltf::texture::WrappingMode::ClampToEdge => SamplerWrapFunction::Clamp, - gltf::texture::WrappingMode::MirroredRepeat => SamplerWrapFunction::Mirror, - gltf::texture::WrappingMode::Repeat => SamplerWrapFunction::Repeat, - }; - mat.sampler.wrap_function.1 = match sampler_info.wrap_t() { - gltf::texture::WrappingMode::ClampToEdge => SamplerWrapFunction::Clamp, - gltf::texture::WrappingMode::MirroredRepeat => SamplerWrapFunction::Mirror, - gltf::texture::WrappingMode::Repeat => SamplerWrapFunction::Repeat, - }; - if let Some(f) = sampler_info.mag_filter() { - mat.sampler.magnify_filter = match f { - gltf::texture::MagFilter::Nearest => MagnifySamplerFilter::Nearest, - gltf::texture::MagFilter::Linear => MagnifySamplerFilter::Linear, + // Helper to update sampler settings from glTF sampler + fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) { + let sampler_info = t.sampler(); + mat.sampler.wrap_function.0 = match sampler_info.wrap_s() { + gltf::texture::WrappingMode::ClampToEdge => SamplerWrapFunction::Clamp, + gltf::texture::WrappingMode::MirroredRepeat => SamplerWrapFunction::Mirror, + gltf::texture::WrappingMode::Repeat => SamplerWrapFunction::Repeat, }; - } - if let Some(f) = sampler_info.min_filter() { - mat.sampler.minify_filter = match f { - gltf::texture::MinFilter::Nearest => MinifySamplerFilter::Nearest, - gltf::texture::MinFilter::Linear => MinifySamplerFilter::Linear, - gltf::texture::MinFilter::NearestMipmapNearest => MinifySamplerFilter::NearestMipmapNearest, - gltf::texture::MinFilter::NearestMipmapLinear => MinifySamplerFilter::NearestMipmapLinear, - gltf::texture::MinFilter::LinearMipmapNearest => MinifySamplerFilter::LinearMipmapNearest, - gltf::texture::MinFilter::LinearMipmapLinear => MinifySamplerFilter::LinearMipmapLinear, + mat.sampler.wrap_function.1 = match sampler_info.wrap_t() { + gltf::texture::WrappingMode::ClampToEdge => SamplerWrapFunction::Clamp, + gltf::texture::WrappingMode::MirroredRepeat => SamplerWrapFunction::Mirror, + gltf::texture::WrappingMode::Repeat => SamplerWrapFunction::Repeat, }; + if let Some(f) = sampler_info.mag_filter() { + mat.sampler.magnify_filter = match f { + gltf::texture::MagFilter::Nearest => MagnifySamplerFilter::Nearest, + gltf::texture::MagFilter::Linear => MagnifySamplerFilter::Linear, + }; + } + if let Some(f) = sampler_info.min_filter() { + mat.sampler.minify_filter = match f { + gltf::texture::MinFilter::Nearest => MinifySamplerFilter::Nearest, + gltf::texture::MinFilter::Linear => MinifySamplerFilter::Linear, + gltf::texture::MinFilter::NearestMipmapNearest => MinifySamplerFilter::NearestMipmapNearest, + gltf::texture::MinFilter::NearestMipmapLinear => MinifySamplerFilter::NearestMipmapLinear, + gltf::texture::MinFilter::LinearMipmapNearest => MinifySamplerFilter::LinearMipmapNearest, + gltf::texture::MinFilter::LinearMipmapLinear => MinifySamplerFilter::LinearMipmapLinear, + }; + } } - } - // 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])?); - } + // 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])?); + } - // 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])?); - } + // 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])?); + } - // 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])?); - } + // 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) = 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])?); - } + // 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) = 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])?); - } + // 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 - 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]); + // 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]); + } } } diff --git a/raidillon_render/src/lib.rs b/raidillon_render/src/lib.rs index f883c91..2856588 100644 --- a/raidillon_render/src/lib.rs +++ b/raidillon_render/src/lib.rs @@ -4,6 +4,7 @@ pub mod gltf_loader; pub mod render; pub mod ecs_renderer; pub mod window; +pub mod debug; pub use camera::Camera; pub use render::GliumRenderer; diff --git a/raidillon_render/src/render.rs b/raidillon_render/src/render.rs index 1d7554d..35fd158 100644 --- a/raidillon_render/src/render.rs +++ b/raidillon_render/src/render.rs @@ -1,10 +1,13 @@ use crate::camera::Camera; use raidillon_ecs::{ModelHandle, Transform}; + use crate::model::{Model, Mesh}; +use crate::debug::ColliderDebugRenderer; +use rapier3d::prelude::ColliderSet; use glium::texture::{RawImage2d, SrgbTexture2d}; use glium::{uniform, Program, Surface}; use glium::uniforms::{MinifySamplerFilter, MagnifySamplerFilter, SamplerWrapFunction}; -use glam::{Vec3, Vec4}; +use glam::{Vec3, Vec4, Mat4}; use hecs::World; use glium::glutin::surface::WindowSurface; use image::io::Reader as ImageReader; @@ -22,6 +25,11 @@ pub struct GliumRenderer { skybox_program: Program, skybox_texture: SrgbTexture2d, skybox_mesh: Mesh, + + debug_renderer: ColliderDebugRenderer, + show_colliders: bool, + + collider_set: Option<*const ColliderSet>, } impl GliumRenderer { @@ -58,6 +66,8 @@ impl GliumRenderer { let cube_model = crate::gltf_loader::load_gltf("resources/models/cube.gltf", &display)?; let skybox_mesh = cube_model.mesh; + let debug_renderer = crate::debug::ColliderDebugRenderer::new(&display)?; + Ok(Self { display, program, @@ -67,9 +77,23 @@ impl GliumRenderer { skybox_program, skybox_texture, skybox_mesh, + debug_renderer, + show_colliders: false, + collider_set: None, }) } + /// Provide the collider set for the upcoming frame. Pass `None` when no + /// collider debug rendering is desired or right after rendering to release + /// the reference. + pub fn set_colliders(&mut self, colliders: Option<&ColliderSet>) { + self.collider_set = colliders.map(|c| c as *const ColliderSet); + } + + pub fn set_show_colliders(&mut self, v: bool) { + self.show_colliders = v; + } + fn draw_scene(&self, world: &World, target: &mut S) { let cam = match world.query::<&Camera>().iter().next() { Some((_, cam)) => *cam, @@ -147,6 +171,18 @@ impl GliumRenderer { &uniforms, &sky_params, ).unwrap(); + + if self.show_colliders { + if let Some(ptr) = self.collider_set { + // SAFETY: `set_colliders` guarantees the pointer is valid for + // the duration of this render call and we only read from it. + unsafe { + let colset: &ColliderSet = &*ptr; + let vp_mat = cam.projection() * cam.view(); + self.debug_renderer.draw(colset, vp_mat, target); + } + } + } } pub fn render_into(&mut self, world: &World, target: &mut S) { diff --git a/raidillon_ui/Cargo.toml b/raidillon_ui/Cargo.toml index 2f4b606..892110a 100644 --- a/raidillon_ui/Cargo.toml +++ b/raidillon_ui/Cargo.toml @@ -4,10 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] -anyhow = "1.0.98" -glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] } imgui = "0.12" imgui-winit-support = "0.13" imgui-glium-renderer = "0.13" -winit = "0.30" raidillon_render = { path = "../raidillon_render" } +anyhow = "1" +winit = "0.30" +rapier3d = "0.26.1" +glium = { version = "0.35", features=["glutin"] } diff --git a/raidillon_ui/src/ui.rs b/raidillon_ui/src/ui.rs index 224ee1a..98747f9 100644 --- a/raidillon_ui/src/ui.rs +++ b/raidillon_ui/src/ui.rs @@ -87,6 +87,8 @@ impl Gui { }); target.finish().expect("Failed to swap buffers"); + + ecsr.renderer.set_colliders(None); } pub fn ui(&mut self, build: F)