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 pending_collision_events: Arc<Mutex<Vec<CollisionEvent>>>,
49}
50
51impl Resource for Physics {}
52
53impl Physics {
54 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 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 if node.handle.is_some() {
106 return;
107 }
108
109 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 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 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 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 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 let rapier_rot: Quat = (*body.rotation()).into();
189 let dot = node.transform.rotation.dot(rapier_rot).abs();
191 if dot < 0.9999 {
192 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 body.set_linvel(node.velocity.into(), true);
205
206 body.set_angvel(node.angular_velocity.into(), true);
208 });
209 }
210
211 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 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 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 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}