diff --git a/Cargo.lock b/Cargo.lock index 24d4d43..97129d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[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" @@ -599,6 +608,12 @@ 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" @@ -891,6 +906,168 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556f6b2ea90b8d15a74e0e7bb41671c9bdf38cd9f78c284d750b9ce58a2b5be7" +dependencies = [ + "libm", +] + +[[package]] +name = "glam" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" +dependencies = [ + "libm", +] + [[package]] name = "glam" version = "0.33.0" @@ -1605,6 +1782,16 @@ dependencies = [ "regex-automata", ] +[[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.8.0" @@ -1677,12 +1864,57 @@ dependencies = [ "log", "num-traits", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "thiserror 2.0.18", "unicode-ident", ] +[[package]] +name = "nalgebra" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df76ea0ff5c7e6b88689085804d6132ded0ddb9de5ca5b8aeb9eeadc0508a70a" +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.10", + "glam 0.31.1", + "glam 0.32.1", + "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" @@ -1785,6 +2017,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[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" @@ -2176,6 +2419,38 @@ dependencies = [ "windows-link", ] +[[package]] +name = "parry2d" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8240961674a91f3eb4af1bb7451f27c18a07813d223af73789bd90b296f69940" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.11.1", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "simba", + "slab", + "smallvec", + "spade", + "thiserror 2.0.18", +] + +[[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.2" @@ -2331,6 +2606,19 @@ name = "profiling" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] [[package]] name = "pxfm" @@ -2374,6 +2662,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" +[[package]] +name = "rapier2d" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49586478ee35cd09ca37c7c2eaeb2b4e01ffee038163cd4c45005dbb8046261d" +dependencies = [ + "approx", + "arrayvec", + "bit-vec 0.8.0", + "bitflags 2.11.1", + "downcast-rs 2.0.2", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry2d", + "profiling", + "rustc-hash 2.1.2", + "simba", + "static_assertions", + "thiserror 2.0.18", + "wide", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2392,6 +2705,12 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.12.0" @@ -2542,12 +2861,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2595,6 +2926,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[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" @@ -2785,6 +3125,19 @@ dependencies = [ "libc", ] +[[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.9" @@ -2878,6 +3231,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spade" +version = "2.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9699399fd9349b00b184f5635b074f9ec93afffef30c853f8c875b32c0f8c7fa" +dependencies = [ + "hashbrown 0.16.1", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spirv" version = "0.4.0+sdk-1.4.341.0" @@ -3457,7 +3822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.1.4", "scoped-tls", "smallvec", @@ -3660,7 +4025,7 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.18", "wgpu-core-deps-apple", @@ -3804,6 +4169,16 @@ dependencies = [ "num-traits", ] +[[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.11" @@ -4183,6 +4558,7 @@ dependencies = [ "ze_ecs", "ze_input", "ze_log", + "ze_physics", "ze_renderer", ] @@ -4191,7 +4567,7 @@ name = "ze_core" version = "0.1.0" dependencies = [ "anyhow", - "glam", + "glam 0.33.0", "schemars", "serde", "thiserror 2.0.18", @@ -4226,12 +4602,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "ze_physics" +version = "0.1.0" +dependencies = [ + "rapier2d", + "ze_core", + "ze_ecs", +] + [[package]] name = "ze_renderer" version = "0.1.0" dependencies = [ "bytemuck", - "glam", + "glam 0.33.0", "image", "wgpu", "winit", diff --git a/Cargo.toml b/Cargo.toml index 91e07df..378c1a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "ZeroEngine/crates/ze_core", "ZeroEngine/crates/ze_input", "ZeroEngine/crates/ze_log", + "ZeroEngine/crates/ze_physics", "ZeroEngine/crates/ze_renderer", "ZeroEngine/crates/ze_ecs", "ZeroEngine/crates/zeroengine", diff --git a/ZeroEngine/crates/ze_app/Cargo.toml b/ZeroEngine/crates/ze_app/Cargo.toml index 7fbf801..f95b732 100644 --- a/ZeroEngine/crates/ze_app/Cargo.toml +++ b/ZeroEngine/crates/ze_app/Cargo.toml @@ -12,6 +12,7 @@ ze_core = { path = "../ze_core" } ze_input = { path = "../ze_input" } ze_log = { path = "../ze_log" } ze_ecs = { path = "../ze_ecs" } +ze_physics = { path = "../ze_physics" } winit = "0.30.13" ze_renderer = { version = "0.1.0", path = "../ze_renderer" } tokio = { version = "1.52.3", features = ["full"] } diff --git a/ZeroEngine/crates/ze_app/src/lib.rs b/ZeroEngine/crates/ze_app/src/lib.rs index 5f4198f..5050057 100644 --- a/ZeroEngine/crates/ze_app/src/lib.rs +++ b/ZeroEngine/crates/ze_app/src/lib.rs @@ -10,6 +10,7 @@ use winit::{ use ze_core::{ResourceManager, Result, bail}; use ze_ecs::{Scene, System, registry}; use ze_input::*; +use ze_physics::PhysicsSystem; use ze_renderer::{EditorCameraSystem, RenderSystem, register_renderer_components}; const DEFAULT_SCENE_NAME: &str = "main"; @@ -130,6 +131,7 @@ pub fn load_main_scene(resources: &ResourceManager) -> Result { ) })?; scene.add_system(EditorCameraSystem::new()); + scene.add_system(PhysicsSystem::new()); scene.add_system(RenderSystem::new()); Ok(scene) } diff --git a/ZeroEngine/crates/ze_ecs/src/components.rs b/ZeroEngine/crates/ze_ecs/src/components.rs index 62119cc..1514447 100644 --- a/ZeroEngine/crates/ze_ecs/src/components.rs +++ b/ZeroEngine/crates/ze_ecs/src/components.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use shipyard::{Component, EntityId}; -use ze_core::{Quat, Vec3}; +use ze_core::{Quat, Vec2, Vec3}; #[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct Name { @@ -47,3 +47,121 @@ pub struct Children { #[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct Inactive; + +#[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct RigidBody { + pub body_type: RigidBodyType, + #[serde(default = "default_true")] + pub use_gravity: bool, + pub gravity_scale: f32, + pub linear_damping: f32, + pub angular_damping: f32, + #[serde(default)] + pub mass: Option, + #[serde(default)] + pub freeze_position_x: bool, + #[serde(default)] + pub freeze_position_y: bool, + #[serde(default)] + pub freeze_rotation_x: bool, + #[serde(default)] + pub freeze_rotation_y: bool, + #[serde(default)] + pub freeze_rotation_z: bool, + #[serde(default)] + pub collision_detection: CollisionDetection, +} + +#[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PhysicsSettings { + #[schemars(with = "[f32; 2]")] + pub gravity: Vec2, + pub enable_debug_draw: bool, +} + +impl Default for PhysicsSettings { + fn default() -> Self { + Self { + gravity: Vec2::new(0.0, -9.81), + enable_debug_draw: false, + } + } +} + +impl Default for RigidBody { + fn default() -> Self { + Self { + body_type: RigidBodyType::Dynamic, + use_gravity: true, + gravity_scale: 1.0, + linear_damping: 0.05, + angular_damping: 0.0, + mass: None, + freeze_position_x: false, + freeze_position_y: false, + freeze_rotation_x: false, + freeze_rotation_y: false, + freeze_rotation_z: false, + collision_detection: CollisionDetection::Discrete, + } + } +} + +fn default_true() -> bool { true } + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +pub enum RigidBodyType { + Static, + Dynamic, + KinematicPositionBased, + KinematicVelocityBased, +} + +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub enum CollisionDetection { + #[default] + Discrete, + Continuous, +} + +#[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Collider { + pub shape: ColliderShape, + pub friction: f32, + pub restitution: f32, + pub density: f32, + pub is_sensor: bool, +} + +impl Default for Collider { + fn default() -> Self { + Self { + shape: ColliderShape::Box { + half_extents: Vec2::splat(0.5), + }, + friction: 0.5, + restitution: 0.0, + density: 1.0, + is_sensor: false, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub enum ColliderShape { + Box { + #[schemars(with = "[f32; 2]")] + half_extents: Vec2, + }, + Circle { + radius: f32, + }, + Capsule { + half_height: f32, + radius: f32, + }, + ConvexPolygon { + #[schemars(with = "Vec<[f32; 2]>")] + points: Vec, + }, +} diff --git a/ZeroEngine/crates/ze_ecs/src/scene.rs b/ZeroEngine/crates/ze_ecs/src/scene.rs index 768c3e9..bd19750 100644 --- a/ZeroEngine/crates/ze_ecs/src/scene.rs +++ b/ZeroEngine/crates/ze_ecs/src/scene.rs @@ -9,7 +9,7 @@ use shipyard::{Component, EntitiesView, EntityId, World}; use ze_core::{Result, anyhow}; use crate::{ - components::{Inactive, Name, Tag, Transform}, + components::{Collider, Inactive, Name, PhysicsSettings, RigidBody, Tag, Transform}, definitions::SaveFile, entity::Entity, registry::ComponentRegistry, @@ -119,6 +119,9 @@ impl Scene { registry.register::("ze.tag"); registry.register::("ze.inactive"); registry.register::("ze.transform"); + registry.register::("ze.physics_2d.rigidbody"); + registry.register::("ze.physics_2d.collider"); + registry.register::("ze.physics_2d.settings"); } } diff --git a/ZeroEngine/crates/ze_physics/Cargo.toml b/ZeroEngine/crates/ze_physics/Cargo.toml new file mode 100644 index 0000000..cab49a1 --- /dev/null +++ b/ZeroEngine/crates/ze_physics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ze_physics" +version = "0.1.0" +edition = "2024" + +[dependencies] +rapier2d = "0.29" +ze_core = { version = "0.1.0", path = "../ze_core" } +ze_ecs = { version = "0.1.0", path = "../ze_ecs" } diff --git a/ZeroEngine/crates/ze_physics/src/lib.rs b/ZeroEngine/crates/ze_physics/src/lib.rs new file mode 100644 index 0000000..c1b4daa --- /dev/null +++ b/ZeroEngine/crates/ze_physics/src/lib.rs @@ -0,0 +1,318 @@ +use std::collections::HashMap; + +use rapier2d::prelude::*; +use ze_core::{Quat, Result, Vec2}; +use ze_ecs::{ + Collider, ColliderShape, CollisionDetection, EntitiesView, EntityId, PhysicsSettings, RigidBody, RigidBodyType, + Scene, System, Transform, +}; + +pub const DEFAULT_GRAVITY: Vec2 = Vec2::new(0.0, -9.81); + +pub struct PhysicsWorld { + pipeline: PhysicsPipeline, + gravity: Vector, + integration_parameters: IntegrationParameters, + island_manager: IslandManager, + broad_phase: BroadPhaseBvh, + narrow_phase: NarrowPhase, + pub rigid_bodies: RigidBodySet, + pub colliders: ColliderSet, + impulse_joints: ImpulseJointSet, + multibody_joints: MultibodyJointSet, + ccd_solver: CCDSolver, + entity_bodies: HashMap, + entity_colliders: HashMap, +} + +#[derive(Debug, Clone, Copy)] +struct PhysicsBodyEntry { + handle: RigidBodyHandle, + body_type: RigidBodyType, +} + +impl PhysicsWorld { + pub fn new() -> Self { + Self { + pipeline: PhysicsPipeline::new(), + gravity: vector![DEFAULT_GRAVITY.x, DEFAULT_GRAVITY.y], + integration_parameters: IntegrationParameters::default(), + island_manager: IslandManager::new(), + broad_phase: BroadPhaseBvh::new(), + narrow_phase: NarrowPhase::new(), + rigid_bodies: RigidBodySet::new(), + colliders: ColliderSet::new(), + impulse_joints: ImpulseJointSet::new(), + multibody_joints: MultibodyJointSet::new(), + ccd_solver: CCDSolver::new(), + entity_bodies: HashMap::new(), + entity_colliders: HashMap::new(), + } + } + + pub fn with_gravity(gravity: Vec2) -> Self { + let mut world = Self::new(); + world.set_gravity(gravity); + world + } + + pub fn gravity(&self) -> Vec2 { Vec2::new(self.gravity.x, self.gravity.y) } + + pub fn set_gravity(&mut self, gravity: Vec2) { self.gravity = vector![gravity.x, gravity.y]; } + + pub fn step(&mut self, dt: f32) { + self.integration_parameters.dt = dt.max(0.0); + + self.pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigid_bodies, + &mut self.colliders, + &mut self.impulse_joints, + &mut self.multibody_joints, + &mut self.ccd_solver, + &(), + &(), + ); + } + + pub fn register_entity( + &mut self, + entity: EntityId, + rigid_body: &RigidBody, + collider: &Collider, + transform: &Transform, + ) { + if self.entity_bodies.contains_key(&entity) { + return; + } + + let body = RigidBodyBuilder::new(to_rapier_body_type(rigid_body.body_type)) + .translation(vector![transform.position.x, transform.position.y]) + .rotation(transform.rotation.to_euler(ze_core::glam::EulerRot::XYZ).2) + .gravity_scale(if rigid_body.use_gravity { + rigid_body.gravity_scale + } else { + 0.0 + }) + .linear_damping(rigid_body.linear_damping) + .angular_damping(rigid_body.angular_damping) + .locked_axes(to_locked_axes(rigid_body)) + .ccd_enabled(matches!(rigid_body.collision_detection, CollisionDetection::Continuous)) + .build(); + + let body_handle = self.rigid_bodies.insert(body); + let collider_handle = self.colliders.insert_with_parent( + build_collider(collider, rigid_body.mass, transform.scale.truncate()), + body_handle, + &mut self.rigid_bodies, + ); + + self.entity_bodies.insert( + entity, + PhysicsBodyEntry { + handle: body_handle, + body_type: rigid_body.body_type, + }, + ); + self.entity_colliders.insert(entity, collider_handle); + } + + pub fn sync_from_ecs(&mut self, scene: &Scene) { + let transforms = self + .entity_bodies + .iter() + .filter(|(_, entry)| is_kinematic(entry.body_type)) + .filter_map(|(entity, _)| { + scene + .world() + .get::<&Transform>(*entity) + .ok() + .map(|transform| (*entity, transform.position.x, transform.position.y, transform.rotation)) + }) + .collect::>(); + + for (entity, x, y, rotation) in transforms { + let Some(entry) = self.entity_bodies.get(&entity).copied() else { + continue; + }; + let Some(body) = self.rigid_bodies.get_mut(entry.handle) else { + continue; + }; + + let angle = rotation.to_euler(ze_core::glam::EulerRot::XYZ).2; + body.set_position(Isometry::new(vector![x, y], angle), true); + } + } + + pub fn sync_to_ecs(&self, scene: &mut Scene) -> Result<()> { + let updates = self + .entity_bodies + .iter() + .filter_map(|(entity, entry)| { + self.rigid_bodies.get(entry.handle).map(|body| { + let position = body.translation(); + (*entity, position.x, position.y, body.rotation().angle()) + }) + }) + .collect::>(); + + for (entity, x, y, angle) in updates { + let mut transform = scene.world_mut().get::<&mut Transform>(entity)?; + transform.position.x = x; + transform.position.y = y; + transform.rotation = Quat::from_rotation_z(angle); + } + + Ok(()) + } +} + +impl Default for PhysicsWorld { + fn default() -> Self { Self::new() } +} + +pub struct PhysicsSystem { + world: PhysicsWorld, + initialized: bool, +} + +impl PhysicsSystem { + pub fn new() -> Self { + Self { + world: PhysicsWorld::new(), + initialized: false, + } + } + + pub fn world(&self) -> &PhysicsWorld { &self.world } + + pub fn world_mut(&mut self) -> &mut PhysicsWorld { &mut self.world } + + fn register_scene_bodies(&mut self, scene: &Scene) { + let ecs_world = scene.world(); + let mut entities_to_register = Vec::new(); + + ecs_world.run(|entities: EntitiesView| { + for entity in entities.iter() { + let Ok((transform, rigid_body, collider)) = + ecs_world.get::<(&Transform, &RigidBody, &Collider)>(entity) + else { + continue; + }; + + entities_to_register.push((entity, rigid_body.clone(), collider.clone(), transform.clone())); + } + }); + + for (entity, rigid_body, collider, transform) in entities_to_register { + self.world.register_entity(entity, &rigid_body, &collider, &transform); + } + } +} + +impl Default for PhysicsSystem { + fn default() -> Self { Self::new() } +} + +impl System for PhysicsSystem { + fn name(&self) -> &'static str { "PhysicsSystem" } + + fn update(&mut self, scene: &mut Scene, dt: f32) -> Result<()> { + if !self.initialized { + self.register_scene_bodies(scene); + self.initialized = true; + } + + self.world.set_gravity(scene_gravity(scene)); + self.world.sync_from_ecs(scene); + self.world.step(dt); + self.world.sync_to_ecs(scene) + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } +} + +fn scene_gravity(scene: &Scene) -> Vec2 { + let world = scene.world(); + let mut gravity = DEFAULT_GRAVITY; + + world.run(|entities: EntitiesView| { + for entity in entities.iter() { + let Ok(settings) = world.get::<&PhysicsSettings>(entity) else { + continue; + }; + + gravity = settings.gravity; + break; + } + }); + + gravity +} + +fn to_rapier_body_type(body_type: RigidBodyType) -> rapier2d::dynamics::RigidBodyType { + match body_type { + RigidBodyType::Static => rapier2d::dynamics::RigidBodyType::Fixed, + RigidBodyType::Dynamic => rapier2d::dynamics::RigidBodyType::Dynamic, + RigidBodyType::KinematicPositionBased => rapier2d::dynamics::RigidBodyType::KinematicPositionBased, + RigidBodyType::KinematicVelocityBased => rapier2d::dynamics::RigidBodyType::KinematicVelocityBased, + } +} + +fn is_kinematic(body_type: RigidBodyType) -> bool { + matches!( + body_type, + RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased + ) +} + +fn to_locked_axes(rigid_body: &RigidBody) -> LockedAxes { + let mut axes = LockedAxes::empty(); + + axes.set(LockedAxes::TRANSLATION_LOCKED_X, rigid_body.freeze_position_x); + axes.set(LockedAxes::TRANSLATION_LOCKED_Y, rigid_body.freeze_position_y); + axes.set(LockedAxes::ROTATION_LOCKED_X, rigid_body.freeze_rotation_x); + axes.set(LockedAxes::ROTATION_LOCKED_Y, rigid_body.freeze_rotation_y); + axes.set(LockedAxes::ROTATION_LOCKED_Z, rigid_body.freeze_rotation_z); + + axes +} + +fn build_collider(collider: &Collider, mass: Option, scale: Vec2) -> rapier2d::geometry::Collider { + let scale = scale.abs().max(Vec2::splat(f32::EPSILON)); + let builder = match collider.shape { + ColliderShape::Box { half_extents } => { + let half_extents = half_extents * scale; + ColliderBuilder::cuboid(half_extents.x, half_extents.y) + } + ColliderShape::Circle { radius } => ColliderBuilder::ball(radius * scale.max_element()), + ColliderShape::Capsule { half_height, radius } => { + ColliderBuilder::capsule_y(half_height * scale.y, radius * scale.max_element()) + } + ColliderShape::ConvexPolygon { ref points } => { + let scaled_points = points + .iter() + .map(|point| point![point.x * scale.x, point.y * scale.y]) + .collect::>(); + + ColliderBuilder::convex_hull(&scaled_points).unwrap_or_else(|| ColliderBuilder::cuboid(0.5, 0.5)) + } + }; + + let builder = builder + .restitution(collider.restitution) + .friction(collider.friction) + .sensor(collider.is_sensor); + + let builder = if let Some(mass) = mass { + builder.mass(mass) + } else { + builder.density(collider.density) + }; + + builder.build() +} diff --git a/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs b/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs index 30ebb71..4dc3199 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs @@ -17,6 +17,11 @@ pub struct Builder<'a> { fragment_entry: String, pixel_format: wgpu::TextureFormat, vertex_buffer_layouts: Vec>, + topology: wgpu::PrimitiveTopology, + polygon_mode: wgpu::PolygonMode, + cull_mode: Option, + depth_write_enabled: bool, + depth_compare: wgpu::CompareFunction, name: String, bind_group_layouts: Vec>, device: &'a wgpu::Device, @@ -33,6 +38,11 @@ impl<'a> Builder<'a> { fragment_entry: "fs_main".to_string(), pixel_format: wgpu::TextureFormat::Bgra8UnormSrgb, vertex_buffer_layouts: vec![], + topology: wgpu::PrimitiveTopology::TriangleList, + polygon_mode: wgpu::PolygonMode::Fill, + cull_mode: Some(wgpu::Face::Back), + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, name: "Unnamed Pipeline".to_string(), bind_group_layouts: vec![], device, @@ -86,6 +96,31 @@ impl<'a> Builder<'a> { self } + pub fn with_topology(mut self, topology: wgpu::PrimitiveTopology) -> Self { + self.topology = topology; + self + } + + pub fn with_polygon_mode(mut self, polygon_mode: wgpu::PolygonMode) -> Self { + self.polygon_mode = polygon_mode; + self + } + + pub fn with_cull_mode(mut self, cull_mode: Option) -> Self { + self.cull_mode = cull_mode; + self + } + + pub fn with_depth_write_enabled(mut self, enabled: bool) -> Self { + self.depth_write_enabled = enabled; + self + } + + pub fn with_depth_compare(mut self, compare: wgpu::CompareFunction) -> Self { + self.depth_compare = compare; + self + } + pub fn with_bind_group_layout(mut self, layout: &'a wgpu::BindGroupLayout) -> Self { self.bind_group_layouts.push(Some(layout)); self @@ -131,8 +166,8 @@ impl<'a> Builder<'a> { let depth_stencil = wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: Some(true), - depth_compare: Some(wgpu::CompareFunction::Less), + depth_write_enabled: Some(self.depth_write_enabled), + depth_compare: Some(self.depth_compare), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }; @@ -155,11 +190,11 @@ impl<'a> Builder<'a> { compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, + topology: self.topology, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, + cull_mode: self.cull_mode, + polygon_mode: self.polygon_mode, unclipped_depth: false, conservative: false, }, diff --git a/ZeroEngine/crates/ze_renderer/src/lib.rs b/ZeroEngine/crates/ze_renderer/src/lib.rs index 5c34eb6..534cd5d 100644 --- a/ZeroEngine/crates/ze_renderer/src/lib.rs +++ b/ZeroEngine/crates/ze_renderer/src/lib.rs @@ -8,6 +8,7 @@ use std::sync::Arc; pub use components::*; pub use editor_camera_system::*; pub use render_system::*; +use wgpu::util::DeviceExt; use ze_core::{Mat4, ResourceManager, Vec3}; use crate::backend::{ @@ -24,6 +25,7 @@ pub struct Renderer { config: wgpu::SurfaceConfiguration, size: winit::dpi::PhysicalSize, pipeline: Pipeline, + debug_pipeline: Pipeline, quad_mesh: Mesh, texture_cache: TextureCache, materials: Vec, @@ -113,6 +115,8 @@ impl Renderer { .with_shader_source(resources.engine_string("shaders/engine/sprite.wgsl")?) .with_pixel_format(surface_format) .with_vertex_buffer_layout(Vertex::get_layout()) + .with_depth_write_enabled(false) + .with_depth_compare(wgpu::CompareFunction::Always) .with_bind_group_layout(&material_bind_group_layout) .with_bind_group_layout(&ubo_bind_group_layout) .with_bind_group_layout(&ubo_bind_group_layout); @@ -120,6 +124,19 @@ impl Renderer { render_pipeline = pipeline_builder.build()?; } + let debug_pipeline = pipeline::Builder::new(&device) + .with_name("Physics Debug Draw") + .with_shader_source(DEBUG_LINE_SHADER) + .with_pixel_format(surface_format) + .with_vertex_buffer_layout(Vertex::get_layout()) + .with_bind_group_layout(&ubo_bind_group_layout) + .with_topology(wgpu::PrimitiveTopology::LineList) + .with_polygon_mode(wgpu::PolygonMode::Line) + .with_cull_mode(None) + .with_depth_write_enabled(false) + .with_depth_compare(wgpu::CompareFunction::Always) + .build()?; + let projection_ubo = Some(Ubo::new(&device, &ubo_bind_group_layout)); let texture_cache = TextureCache::new(&device, &queue); let materials = Vec::new(); @@ -131,6 +148,7 @@ impl Renderer { config, size, pipeline: render_pipeline, + debug_pipeline, quad_mesh, texture_cache, materials, @@ -199,10 +217,11 @@ impl Renderer { pub fn request_sprite_redraw( &mut self, items: &[render_system::SpriteRenderItem], + debug_lines: &[render_system::DebugLine], camera: &render_system::CameraRenderData, resources: &ResourceManager, ) { - match self.render_sprite_items(items, camera, resources) { + match self.render_sprite_items(items, debug_lines, camera, resources) { Ok(_) => {} Err(wgpu::SurfaceStatus::Lost) => { let size = self.size; @@ -222,6 +241,7 @@ impl Renderer { fn render_sprite_items( &mut self, items: &[render_system::SpriteRenderItem], + debug_lines: &[render_system::DebugLine], camera: &render_system::CameraRenderData, resources: &ResourceManager, ) -> Result<(), wgpu::SurfaceStatus> { @@ -261,6 +281,29 @@ impl Renderer { )); } + let debug_vertices = debug_lines + .iter() + .flat_map(|line| { + [ + Vertex { + position: line.start.to_array(), + color: line.color, + }, + Vertex { + position: line.end.to_array(), + color: line.color, + }, + ] + }) + .collect::>(); + let debug_vertex_buffer = (!debug_vertices.is_empty()).then(|| { + self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Physics Debug Lines"), + contents: bytemuck::cast_slice(&debug_vertices), + usage: wgpu::BufferUsages::VERTEX, + }) + }); + let drawable = self.surface.get_current_texture(); let drawable = match drawable { wgpu::CurrentSurfaceTexture::Timeout => return Ok(()), @@ -324,6 +367,13 @@ impl Renderer { render_pass.draw_indexed(0..6, 0, 0..1); } + if let Some(debug_vertex_buffer) = &debug_vertex_buffer { + render_pass.set_pipeline(&self.debug_pipeline.render_pipeline); + render_pass.set_bind_group(0, &self.projection_ubo.as_ref().unwrap().bind_group, &[]); + render_pass.set_vertex_buffer(0, debug_vertex_buffer.slice(..)); + render_pass.draw(0..debug_vertices.len() as u32, 0..1); + } + drop(render_pass); self.queue.submit(std::iter::once(command_encoder.finish())); @@ -333,6 +383,33 @@ impl Renderer { } } +const DEBUG_LINE_SHADER: &str = r#" +@group(0) @binding(0) var view_projection: mat4x4; + +struct Vertex { + @location(0) position: vec3, + @location(1) color: vec4, +} + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +@vertex +fn vs_main(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + out.position = view_projection * vec4(vertex.position, 1.0); + out.color = vertex.color; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return in.color; +} +"#; + fn sprite_size_to_world_scale(size: &components::SpriteSize, dimensions: (u32, u32)) -> [f32; 2] { match size { components::SpriteSize::Auto => auto_sprite_size(dimensions.0, dimensions.1), diff --git a/ZeroEngine/crates/ze_renderer/src/render_system.rs b/ZeroEngine/crates/ze_renderer/src/render_system.rs index d9779bc..4c3d91e 100644 --- a/ZeroEngine/crates/ze_renderer/src/render_system.rs +++ b/ZeroEngine/crates/ze_renderer/src/render_system.rs @@ -1,5 +1,9 @@ -use ze_core::{AssetRef, Mat4, ResourceManager, Result, Vec3}; -use ze_ecs::{EntitiesView, Inactive, Scene, System, Transform}; +use ze_core::{AssetRef, Mat4, ResourceManager, Result, Vec2, Vec3}; +use ze_ecs::{ + Collider, ColliderShape, EntitiesView, EntityId, Inactive, PhysicsSettings, RigidBody, RigidBodyType, Scene, + System, Transform, +}; +use ze_input::{Input, ZKeyCode}; use crate::{ Renderer, @@ -21,9 +25,17 @@ pub struct SpriteRenderItem { pub layer: i32, } +#[derive(Debug, Clone)] +pub struct DebugLine { + pub start: Vec3, + pub end: Vec3, + pub color: [f32; 4], +} + #[derive(Default)] pub struct RenderSystem { items: Vec, + debug_lines: Vec, } impl RenderSystem { @@ -40,7 +52,8 @@ impl RenderSystem { }; self.items = Self::collect_items(scene); - renderer.request_sprite_redraw(&self.items, &camera, resources); + self.debug_lines = Self::collect_debug_lines(scene); + renderer.request_sprite_redraw(&self.items, &self.debug_lines, &camera, resources); Ok(()) } @@ -132,12 +145,201 @@ impl RenderSystem { items.sort_by_key(|item| item.layer); items } + + fn collect_debug_lines(scene: &Scene) -> Vec { + if !Self::debug_draw_enabled(scene) { + return Vec::new(); + } + + let mut lines = Vec::new(); + let world = scene.world(); + + world.run(|entities: EntitiesView| { + for entity in entities.iter() { + if world.get::<&Inactive>(entity).is_ok() { + continue; + } + + let Ok((transform, collider)) = world.get::<(&Transform, &Collider)>(entity) else { + continue; + }; + + let rigid_body = world.get::<&RigidBody>(entity).ok(); + let color = debug_color(&collider, rigid_body.as_deref().copied()); + append_collider_lines(&mut lines, &transform, &collider, color); + } + }); + + lines + } + + fn debug_draw_enabled(scene: &Scene) -> bool { + let world = scene.world(); + let mut enabled = false; + + world.run(|entities: EntitiesView| { + for entity in entities.iter() { + let Ok(settings) = world.get::<&PhysicsSettings>(entity) else { + continue; + }; + + enabled = settings.enable_debug_draw; + break; + } + }); + + enabled + } + + fn toggle_debug_draw(scene: &mut Scene) -> Result<()> { + if let Some(entity) = settings_entity(scene) { + let mut settings = scene.world_mut().get::<&mut PhysicsSettings>(entity)?; + settings.enable_debug_draw = !settings.enable_debug_draw; + return Ok(()); + } + + let entity = scene.create_entity("PhysicsSettings"); + scene.entity_mut(entity).add_component(PhysicsSettings { + enable_debug_draw: true, + ..PhysicsSettings::default() + }); + Ok(()) + } } impl System for RenderSystem { fn name(&self) -> &'static str { "RenderSystem" } - fn update(&mut self, _scene: &mut Scene, _dt: f32) -> Result<()> { Ok(()) } + fn update(&mut self, scene: &mut Scene, _dt: f32) -> Result<()> { + if Input::is_key_just_pressed(ZKeyCode::KF1) { + Self::toggle_debug_draw(scene)?; + } + + Ok(()) + } fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } } + +fn settings_entity(scene: &Scene) -> Option { + let world = scene.world(); + let mut matching_entity = None; + + world.run(|entities: EntitiesView| { + for entity in entities.iter() { + if world.get::<&PhysicsSettings>(entity).is_ok() { + matching_entity = Some(entity); + break; + } + } + }); + + matching_entity +} + +fn debug_color(collider: &Collider, rigid_body: Option<&RigidBody>) -> [f32; 4] { + if collider.is_sensor { + return [1.0, 0.0, 0.0, 1.0]; + } + + match rigid_body.map(|body| body.body_type) { + Some(RigidBodyType::Static) => [0.1, 0.35, 1.0, 1.0], + _ => [0.0, 1.0, 0.0, 1.0], + } +} + +fn append_collider_lines(lines: &mut Vec, transform: &Transform, collider: &Collider, color: [f32; 4]) { + match &collider.shape { + ColliderShape::Box { half_extents } => { + let points = [ + Vec2::new(-half_extents.x, -half_extents.y), + Vec2::new(half_extents.x, -half_extents.y), + Vec2::new(half_extents.x, half_extents.y), + Vec2::new(-half_extents.x, half_extents.y), + ]; + append_closed_polyline(lines, transform, &points, color); + } + ColliderShape::Circle { radius } => { + let radius = radius * transform.scale.truncate().abs().max_element(); + let points = circle_points(radius, 32); + append_world_space_closed_polyline(lines, transform, &points, color); + } + ColliderShape::Capsule { half_height, radius } => { + let scale = transform.scale.truncate().abs(); + let half_height = half_height * scale.y; + let radius = radius * scale.max_element(); + let points = capsule_points(half_height, radius, 12); + append_world_space_closed_polyline(lines, transform, &points, color); + } + ColliderShape::ConvexPolygon { points } => { + append_closed_polyline(lines, transform, points, color); + } + } +} + +fn append_closed_polyline(lines: &mut Vec, transform: &Transform, points: &[Vec2], color: [f32; 4]) { + if points.len() < 2 { + return; + } + + for i in 0..points.len() { + lines.push(DebugLine { + start: transform_local_point(transform, points[i]), + end: transform_local_point(transform, points[(i + 1) % points.len()]), + color, + }); + } +} + +fn append_world_space_closed_polyline( + lines: &mut Vec, + transform: &Transform, + points: &[Vec2], + color: [f32; 4], +) { + if points.len() < 2 { + return; + } + + for i in 0..points.len() { + lines.push(DebugLine { + start: transform_unscaled_point(transform, points[i]), + end: transform_unscaled_point(transform, points[(i + 1) % points.len()]), + color, + }); + } +} + +fn transform_local_point(transform: &Transform, point: Vec2) -> Vec3 { + let scaled = Vec3::new(point.x * transform.scale.x, point.y * transform.scale.y, 0.0); + transform.position + transform.rotation * scaled +} + +fn transform_unscaled_point(transform: &Transform, point: Vec2) -> Vec3 { + transform.position + transform.rotation * Vec3::new(point.x, point.y, 0.0) +} + +fn circle_points(radius: f32, segments: usize) -> Vec { + (0..segments) + .map(|i| { + let angle = i as f32 / segments as f32 * std::f32::consts::TAU; + Vec2::new(angle.cos() * radius, angle.sin() * radius) + }) + .collect() +} + +fn capsule_points(half_height: f32, radius: f32, arc_segments: usize) -> Vec { + let mut points = Vec::with_capacity((arc_segments + 1) * 2); + + for i in 0..=arc_segments { + let angle = i as f32 / arc_segments as f32 * std::f32::consts::PI; + points.push(Vec2::new(angle.cos() * radius, half_height + angle.sin() * radius)); + } + + for i in 0..=arc_segments { + let angle = std::f32::consts::PI + i as f32 / arc_segments as f32 * std::f32::consts::PI; + points.push(Vec2::new(angle.cos() * radius, -half_height + angle.sin() * radius)); + } + + points +} diff --git a/assets/scenes/main.zescene.json b/assets/scenes/main.zescene.json index 31e8456..c21ef32 100644 --- a/assets/scenes/main.zescene.json +++ b/assets/scenes/main.zescene.json @@ -28,7 +28,7 @@ "Orthographic": { "far": 100.0, "near": -100.0, - "size": 4.0 + "size": 8.0 } } } @@ -44,7 +44,7 @@ "value": { "position": [ 0.0, - 0.0, + 1.0, 5.0 ], "rotation": [ @@ -67,6 +67,36 @@ "index": 1, "gen": 0 }, + "components": [ + { + "component_type": "ze.name", + "value": { + "name": "PhysicsSettings" + } + }, + { + "component_type": "ze.physics_2d.settings", + "value": { + "enable_debug_draw": true, + "gravity": [ + 0.0, + -9.81 + ] + } + }, + { + "component_type": "ze.tag", + "value": { + "tag": "PhysicsSettings" + } + } + ] + }, + { + "id": { + "index": 2, + "gen": 0 + }, "components": [ { "component_type": "ze.name", @@ -74,6 +104,40 @@ "name": "Square" } }, + { + "component_type": "ze.physics_2d.rigidbody", + "value": { + "angular_damping": 0.0, + "body_type": "Dynamic", + "collision_detection": "Discrete", + "freeze_position_x": false, + "freeze_position_y": false, + "freeze_rotation_x": false, + "freeze_rotation_y": false, + "freeze_rotation_z": false, + "gravity_scale": 1.0, + "use_gravity": true, + "linear_damping": 0.05, + "mass": null + } + }, + { + "component_type": "ze.physics_2d.collider", + "value": { + "density": 1.0, + "friction": 0.5, + "is_sensor": false, + "restitution": 0.0, + "shape": { + "Box": { + "half_extents": [ + 0.5, + 0.5 + ] + } + } + } + }, { "component_type": "ze.renderer.sprite", "value": { @@ -86,7 +150,7 @@ "settings": { "flip_x": false, "flip_y": false, - "layer": 0, + "layer": 1, "visible": true }, "size": "Auto", @@ -106,15 +170,15 @@ "component_type": "ze.transform", "value": { "position": [ - 0.0, - -1.0, + 1.5, + 0.5, 0.0 ], "rotation": [ 0.0, 0.0, - 0.0, - 1.0 + 0.3827, + 0.9239 ], "scale": [ 1.0, @@ -127,7 +191,7 @@ }, { "id": { - "index": 2, + "index": 3, "gen": 0 }, "components": [ @@ -137,6 +201,50 @@ "name": "Triangle" } }, + { + "component_type": "ze.physics_2d.rigidbody", + "value": { + "angular_damping": 0.0, + "body_type": "Static", + "collision_detection": "Discrete", + "freeze_position_x": false, + "freeze_position_y": false, + "freeze_rotation_x": false, + "freeze_rotation_y": false, + "freeze_rotation_z": false, + "gravity_scale": 1.0, + "use_gravity": true, + "linear_damping": 0.05, + "mass": null + } + }, + { + "component_type": "ze.physics_2d.collider", + "value": { + "density": 1.0, + "friction": 0.5, + "is_sensor": false, + "restitution": 0.0, + "shape": { + "ConvexPolygon": { + "points": [ + [ + -0.5, + -0.5 + ], + [ + 0.5, + -0.5 + ], + [ + 0.0, + 0.5 + ] + ] + } + } + } + }, { "component_type": "ze.renderer.sprite", "value": { @@ -169,8 +277,102 @@ "component_type": "ze.transform", "value": { "position": [ - 1.0, + -1.0, + -0.85, + 0.0 + ], + "rotation": [ + 0.0, 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + } + ] + }, + { + "id": { + "index": 4, + "gen": 0 + }, + "components": [ + { + "component_type": "ze.name", + "value": { + "name": "Circle" + } + }, + { + "component_type": "ze.physics_2d.rigidbody", + "value": { + "angular_damping": 0.0, + "body_type": "Dynamic", + "collision_detection": "Discrete", + "freeze_position_x": false, + "freeze_position_y": false, + "freeze_rotation_x": false, + "freeze_rotation_y": false, + "freeze_rotation_z": false, + "gravity_scale": 1.0, + "use_gravity": true, + "linear_damping": 0.05, + "mass": null + } + }, + { + "component_type": "ze.physics_2d.collider", + "value": { + "density": 1.0, + "friction": 0.5, + "is_sensor": false, + "restitution": 0.0, + "shape": { + "Circle": { + "radius": 0.5 + } + } + } + }, + { + "component_type": "ze.renderer.sprite", + "value": { + "color": { + "mode": "None", + "saturation_threshold": 0.15000000596046448, + "strength": 1.0, + "tint": null + }, + "settings": { + "flip_x": false, + "flip_y": false, + "layer": 1, + "visible": true + }, + "size": "Auto", + "texture": { + "path": "textures/circle.png", + "source": "Game" + } + } + }, + { + "component_type": "ze.tag", + "value": { + "tag": "Circle" + } + }, + { + "component_type": "ze.transform", + "value": { + "position": [ + -0.5, + 3.0, 0.0 ], "rotation": [ @@ -187,6 +389,103 @@ } } ] + }, + { + "id": { + "index": 5, + "gen": 0 + }, + "components": [ + { + "component_type": "ze.name", + "value": { + "name": "Ground" + } + }, + { + "component_type": "ze.physics_2d.rigidbody", + "value": { + "angular_damping": 0.0, + "body_type": "Static", + "collision_detection": "Discrete", + "freeze_position_x": false, + "freeze_position_y": false, + "freeze_rotation_x": false, + "freeze_rotation_y": false, + "freeze_rotation_z": false, + "gravity_scale": 1.0, + "use_gravity": true, + "linear_damping": 0.05, + "mass": null + } + }, + { + "component_type": "ze.physics_2d.collider", + "value": { + "density": 1.0, + "friction": 0.5, + "is_sensor": false, + "restitution": 0.0, + "shape": { + "Box": { + "half_extents": [ + 0.5, + 0.5 + ] + } + } + } + }, + { + "component_type": "ze.renderer.sprite", + "value": { + "color": { + "mode": "None", + "saturation_threshold": 0.15000000596046448, + "strength": 1.0, + "tint": null + }, + "settings": { + "flip_x": false, + "flip_y": false, + "layer": 0, + "visible": true + }, + "size": "Auto", + "texture": { + "path": "textures/square.png", + "source": "Game" + } + } + }, + { + "component_type": "ze.tag", + "value": { + "tag": "Ground" + } + }, + { + "component_type": "ze.transform", + "value": { + "position": [ + 0.0, + -1.5, + 0.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 8.0, + 0.3, + 1.0 + ] + } + } + ] } ] } \ No newline at end of file diff --git a/assets/textures/circle.png b/assets/textures/circle.png new file mode 100644 index 0000000..e7a3b03 --- /dev/null +++ b/assets/textures/circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2903bb8efacba991387e2f358a81c13839d78902fa538b37128f489d472bbccb +size 5397