Skip to main content

maple_physics/
resource.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Mutex},
4};
5
6use glam::{Quat, Vec3};
7use maple_engine::{
8    GameContext, Node, Scene,
9    prelude::{EventLabel, Resource},
10    scene::NodeId,
11};
12use rapier3d::prelude::{
13    CCDSolver, ColliderBuilder, ColliderHandle, ColliderSet, CollisionEvent, DefaultBroadPhase,
14    EventHandler, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
15    NarrowPhase, PhysicsPipeline, RigidBodyBuilder, RigidBodyHandle, RigidBodySet,
16    nalgebra::UnitQuaternion,
17};
18
19use crate::nodes::{Collider3D, RigidBody3D};
20
21pub struct ColliderEnter {
22    pub other: NodeId,
23}
24impl EventLabel for ColliderEnter {}
25
26pub struct ColliderExit {
27    pub other: NodeId,
28}
29impl EventLabel for ColliderExit {}
30
31pub struct Physics {
32    gravity: Vec3,
33    integration_parameters: IntegrationParameters,
34    physics_pipeline: PhysicsPipeline,
35    island_manager: IslandManager,
36    broad_phase: DefaultBroadPhase,
37    narrow_phase: NarrowPhase,
38    impulsive_joint_set: ImpulseJointSet,
39    multibody_joint_set: MultibodyJointSet,
40    ccd_solver: CCDSolver,
41    physics_hooks: (),
42    event_handler: PhysicsEventHandler,
43
44    rigid_body_set: RigidBodySet,
45    collider_set: ColliderSet,
46
47    // shared between event handler and this
48    pending_collision_events: Arc<Mutex<Vec<CollisionEvent>>>,
49}
50
51impl Resource for Physics {}
52
53impl Physics {
54    /// create the physics resource
55    pub fn new(gravity: Vec3) -> Self {
56        let events = Arc::new(Mutex::new(Vec::new()));
57
58        Self {
59            gravity,
60            integration_parameters: IntegrationParameters::default(),
61            physics_pipeline: PhysicsPipeline::new(),
62            island_manager: IslandManager::new(),
63            broad_phase: DefaultBroadPhase::new(),
64            narrow_phase: NarrowPhase::new(),
65            impulsive_joint_set: ImpulseJointSet::new(),
66            multibody_joint_set: MultibodyJointSet::new(),
67            ccd_solver: CCDSolver::new(),
68            physics_hooks: (),
69            event_handler: PhysicsEventHandler {
70                events: events.clone(),
71            },
72
73            rigid_body_set: RigidBodySet::new(),
74            collider_set: ColliderSet::new(),
75            pending_collision_events: events.clone(),
76        }
77    }
78
79    pub fn set_gravity(&mut self, gravity: Vec3) {
80        self.gravity = gravity
81    }
82
83    pub fn add_collidor(
84        &mut self,
85        parent: &RigidBodyHandle,
86        collider: ColliderBuilder,
87    ) -> ColliderHandle {
88        self.collider_set
89            .insert_with_parent(collider, *parent, &mut self.rigid_body_set)
90    }
91
92    pub fn add_rigid_body(&mut self, body: RigidBodyBuilder) -> RigidBodyHandle {
93        self.rigid_body_set.insert(body)
94    }
95
96    /// Initialize any RigidBody3D nodes that haven't been added to the physics world yet
97    pub fn initialize_bodies(&mut self, scene: &Scene) {
98        use rapier3d::prelude::{
99            RigidBodyBuilder, RigidBodyType,
100            nalgebra::{UnitQuaternion, Vector3},
101        };
102
103        scene.for_each_with_id(&mut |node_id, node: &mut RigidBody3D| {
104            // Skip if already initialized
105            if node.handle.is_some() {
106                return;
107            }
108
109            // Build rigid body from configuration
110            let mut builder = match node.body_type {
111                RigidBodyType::Dynamic => RigidBodyBuilder::dynamic(),
112                RigidBodyType::Fixed => RigidBodyBuilder::fixed(),
113                RigidBodyType::KinematicPositionBased => {
114                    RigidBodyBuilder::kinematic_position_based()
115                }
116                RigidBodyType::KinematicVelocityBased => {
117                    RigidBodyBuilder::kinematic_velocity_based()
118                }
119            };
120
121            // Apply transform
122            let position = Vector3::new(
123                node.transform.position.x,
124                node.transform.position.y,
125                node.transform.position.z,
126            );
127            let rotation =
128                UnitQuaternion::new_normalize(rapier3d::prelude::nalgebra::Quaternion::new(
129                    node.transform.rotation.w,
130                    node.transform.rotation.x,
131                    node.transform.rotation.y,
132                    node.transform.rotation.z,
133                ));
134
135            builder = builder
136                .translation(position)
137                .rotation(rotation.scaled_axis());
138
139            // Apply all configuration
140            builder = builder
141                .gravity_scale(node.gravity_scale)
142                .linear_damping(node.linear_damping)
143                .angular_damping(node.angular_damping)
144                .linvel(node.velocity.into())
145                .angvel(node.angular_velocity.into())
146                .locked_axes(node.locked_axes)
147                .ccd_enabled(node.ccd_enabled)
148                .can_sleep(node.can_sleep)
149                .sleeping(node.sleeping)
150                .dominance_group(node.dominance_group)
151                .enabled(node.enabled);
152
153            if node.additional_mass > 0.0 {
154                builder = builder.additional_mass(node.additional_mass);
155            }
156
157            let handle = self.add_rigid_body(builder);
158            node.handle = Some(handle);
159
160            // Find and attach all Collider3D children
161            let children = scene.children(node_id);
162            for child_id in children {
163                if let Some(child) = scene.get::<Collider3D>(child_id) {
164                    let mut child_node = child.write();
165                    let collider_handle = child_node.get_rapier_collidor();
166                    child_node.handle = Some(self.add_collidor(&handle, collider_handle));
167                }
168            }
169        });
170    }
171
172    pub fn sync_to_rapier(&mut self, scene: &Scene) {
173        scene.for_each_ref(&mut |node: &RigidBody3D| {
174            let Some(handle) = node.handle else {
175                eprint!("node not added");
176                return;
177            };
178
179            let body = &mut self.rigid_body_set[handle];
180
181            // Check if position changed (only update if different to avoid resetting velocity)
182            let rapier_pos: Vec3 = (*body.translation()).into();
183            if (node.transform.position - rapier_pos).length_squared() > 1e-6 {
184                body.set_translation(node.transform.position.into(), true);
185            }
186
187            // Check if rotation changed (only update if different to avoid resetting angular velocity)
188            let rapier_rot: Quat = (*body.rotation()).into();
189            // Compare quaternions using dot product (close to 1.0 or -1.0 means same rotation)
190            let dot = node.transform.rotation.dot(rapier_rot).abs();
191            if dot < 0.9999 {
192                // If not nearly identical
193                let rotation =
194                    UnitQuaternion::new_normalize(rapier3d::prelude::nalgebra::Quaternion::new(
195                        node.transform.rotation.w,
196                        node.transform.rotation.x,
197                        node.transform.rotation.y,
198                        node.transform.rotation.z,
199                    ));
200                body.set_rotation(rotation, true);
201            }
202
203            // Always update velocity (user can freely modify)
204            body.set_linvel(node.velocity.into(), true);
205
206            // Always update angular velocity (user can freely modify)
207            body.set_angvel(node.angular_velocity.into(), true);
208        });
209    }
210
211    /// step in the physics sim should be every 1/60 of a second
212    pub fn step(&mut self) {
213        self.physics_pipeline.step(
214            &self.gravity.into(),
215            &self.integration_parameters,
216            &mut self.island_manager,
217            &mut self.broad_phase,
218            &mut self.narrow_phase,
219            &mut self.rigid_body_set,
220            &mut self.collider_set,
221            &mut self.impulsive_joint_set,
222            &mut self.multibody_joint_set,
223            &mut self.ccd_solver,
224            &self.physics_hooks,
225            &self.event_handler,
226        );
227    }
228
229    pub fn sync_to_maple(&self, scene: &Scene) {
230        scene.for_each(&mut |node: &mut RigidBody3D| {
231            let Some(handle) = node.handle else {
232                log::error!("not all nodes added");
233                return;
234            };
235
236            let body = &self.rigid_body_set[handle];
237
238            // Convert nalgebra types to glam using the convert-glam-030 feature
239            node.get_transform().position = (*body.translation()).into();
240            node.get_transform().rotation = (*body.rotation()).into();
241            node.velocity = (*body.linvel()).into();
242            node.angular_velocity = (*body.angvel()).into();
243        });
244    }
245
246    pub fn dispatch_events(&mut self, ctx: &GameContext) {
247        // take events since they will be cleared anyway
248        let events: Vec<CollisionEvent> = {
249            let mut events = self.pending_collision_events.lock().unwrap();
250            std::mem::take(&mut *events)
251        };
252
253        if events.is_empty() {
254            return;
255        }
256
257        let scene = &ctx.scene;
258
259        // map collider handle to node id
260        let handle_map: HashMap<ColliderHandle, NodeId> = {
261            let mut map = HashMap::new();
262            scene.for_each_with_id(&mut |id, node: &mut Collider3D| {
263                if let Some(handle) = node.handle {
264                    map.insert(handle, id);
265                }
266            });
267            map
268        };
269
270        for event in events {
271            let (h1, h2, is_enter) = match event {
272                CollisionEvent::Started(h1, h2, _) => (h1, h2, true),
273                CollisionEvent::Stopped(h1, h2, _) => (h1, h2, false),
274            };
275
276            let node1 = handle_map.get(&h1).copied();
277            let node2 = handle_map.get(&h2).copied();
278
279            if let (Some(id1), Some(id2)) = (node1, node2) {
280                if is_enter {
281                    scene.emit_to(id1, &ColliderEnter { other: id2 }, ctx);
282                    scene.emit_to(id2, &ColliderEnter { other: id1 }, ctx);
283                } else {
284                    scene.emit_to(id1, &ColliderExit { other: id2 }, ctx);
285                    scene.emit_to(id2, &ColliderExit { other: id1 }, ctx);
286                }
287            }
288        }
289    }
290}
291
292pub struct PhysicsEventHandler {
293    events: Arc<Mutex<Vec<CollisionEvent>>>,
294}
295
296impl EventHandler for PhysicsEventHandler {
297    fn handle_collision_event(
298        &self,
299        _bodies: &RigidBodySet,
300        _colliders: &ColliderSet,
301        event: rapier3d::prelude::CollisionEvent,
302        _contact_pair: Option<&rapier3d::prelude::ContactPair>,
303    ) {
304        self.events.lock().unwrap().push(event);
305    }
306
307    fn handle_contact_force_event(
308        &self,
309        _dt: f32,
310        _bodies: &RigidBodySet,
311        _colliders: &ColliderSet,
312        _contact_pair: &rapier3d::prelude::ContactPair,
313        _total_force_magnitude: f32,
314    ) {
315    }
316}