Skip to main content

goud_engine/ecs/systems/
physics_sync_3d.rs

1//! 3D physics synchronization system.
2//!
3//! Provides [`PhysicsStepSystem3D`] which steps a [`PhysicsProvider3D`] each
4//! frame and synchronizes body positions and rotations back to ECS
5//! [`Transform`] components.
6//!
7//! # Usage
8//!
9//! ```rust,ignore
10//! use goud_engine::ecs::app::App;
11//! use goud_engine::ecs::schedule::CoreStage;
12//! use goud_engine::ecs::systems::physics_sync_3d::{PhysicsStepSystem3D, PhysicsHandleMap3D};
13//! use goud_engine::core::providers::impls::NullPhysicsProvider3D;
14//!
15//! let mut app = App::new();
16//! app.insert_resource(PhysicsHandleMap3D::default());
17//! app.add_system(
18//!     CoreStage::Update,
19//!     PhysicsStepSystem3D::new(Box::new(NullPhysicsProvider3D::new())),
20//! );
21//! ```
22
23use std::collections::HashMap;
24
25use crate::core::providers::physics3d::PhysicsProvider3D;
26use crate::core::providers::types::BodyHandle;
27use crate::ecs::entity::Entity;
28use crate::ecs::query::Access;
29use crate::ecs::system::System;
30use crate::ecs::World;
31
32/// Resource that maps ECS entities to physics body handles for 3D.
33#[derive(Debug, Default)]
34pub struct PhysicsHandleMap3D {
35    /// Maps entities to their physics body handles.
36    pub entity_to_body: HashMap<Entity, BodyHandle>,
37}
38
39/// System that steps the 3D physics simulation and writes positions back.
40///
41/// Each frame this system:
42/// 1. Steps the physics provider by a fixed timestep (1/60 s).
43/// 2. For each entity tracked in [`PhysicsHandleMap3D`], reads the body
44///    position and rotation from the provider and updates the entity's
45///    `Transform`.
46pub struct PhysicsStepSystem3D {
47    provider: Box<dyn PhysicsProvider3D>,
48}
49
50impl PhysicsStepSystem3D {
51    /// Creates a new 3D physics step system with the given provider.
52    pub fn new(provider: Box<dyn PhysicsProvider3D>) -> Self {
53        Self { provider }
54    }
55
56    /// Returns a reference to the underlying physics provider.
57    pub fn provider(&self) -> &dyn PhysicsProvider3D {
58        &*self.provider
59    }
60
61    /// Returns a mutable reference to the underlying physics provider.
62    pub fn provider_mut(&mut self) -> &mut dyn PhysicsProvider3D {
63        &mut *self.provider
64    }
65}
66
67impl System for PhysicsStepSystem3D {
68    fn name(&self) -> &'static str {
69        "PhysicsStepSystem3D"
70    }
71
72    fn component_access(&self) -> Access {
73        Access::new()
74    }
75
76    fn run(&mut self, world: &mut World) {
77        const FIXED_DT: f32 = 1.0 / 60.0;
78        if let Err(e) = self.provider.step(FIXED_DT) {
79            log::error!("PhysicsStepSystem3D: step failed: {e}");
80            return;
81        }
82
83        let handle_map = match world.get_resource_mut::<PhysicsHandleMap3D>() {
84            Some(map) => map
85                .entity_to_body
86                .iter()
87                .map(|(&entity, &handle)| (entity, handle))
88                .collect::<Vec<_>>(),
89            None => return,
90        };
91
92        for (entity, body_handle) in handle_map {
93            if let Ok(pos) = self.provider.body_position(body_handle) {
94                use crate::core::math::Vec3;
95                use crate::ecs::components::transform::Transform;
96                if let Some(transform) = world.get_mut::<Transform>(entity) {
97                    transform.set_position(Vec3::new(pos[0], pos[1], pos[2]));
98                }
99            }
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::core::providers::impls::NullPhysicsProvider3D;
108
109    #[test]
110    fn test_handle_map_3d_default() {
111        let map = PhysicsHandleMap3D::default();
112        assert!(map.entity_to_body.is_empty());
113    }
114
115    #[test]
116    fn test_system_3d_construction() {
117        let provider = NullPhysicsProvider3D::new();
118        let system = PhysicsStepSystem3D::new(Box::new(provider));
119        assert_eq!(system.name(), "PhysicsStepSystem3D");
120    }
121
122    #[test]
123    fn test_system_3d_run_empty_world() {
124        let provider = NullPhysicsProvider3D::new();
125        let mut system = PhysicsStepSystem3D::new(Box::new(provider));
126        let mut world = World::new();
127        system.run(&mut world);
128    }
129
130    #[test]
131    fn test_system_3d_run_with_empty_handle_map() {
132        let provider = NullPhysicsProvider3D::new();
133        let mut system = PhysicsStepSystem3D::new(Box::new(provider));
134        let mut world = World::new();
135        world.insert_resource(PhysicsHandleMap3D::default());
136        system.run(&mut world);
137    }
138
139    #[test]
140    fn test_system_3d_provider_accessors() {
141        let provider = NullPhysicsProvider3D::new();
142        let mut system = PhysicsStepSystem3D::new(Box::new(provider));
143        assert_eq!(system.provider().name(), "null");
144        assert_eq!(system.provider_mut().gravity(), [0.0, 0.0, 0.0]);
145    }
146
147    #[test]
148    fn test_system_3d_should_run() {
149        let provider = NullPhysicsProvider3D::new();
150        let system = PhysicsStepSystem3D::new(Box::new(provider));
151        let world = World::new();
152        assert!(system.should_run(&world));
153    }
154}