1use std::collections::BTreeMap;
7use std::collections::HashMap;
8
9use nalgebra::SVector;
10use symtropy_math::{Capsule, HalfSpace, HyperBox, Point, Sphere};
11
12use crate::body::{BodyHandle, NetId, RigidBody};
13use crate::broadphase;
14use crate::constraint::Constraint;
15use crate::contact::{CollisionEvent, ContactCache, ContactManifold};
16use crate::gjk;
17use crate::integrator;
18use crate::manifold_gen;
19
20#[cfg(feature = "deterministic-net")]
25#[inline]
26fn quantize_q16_16(v: f64) -> f64 {
27 (v * 65536.0).round() / 65536.0
28}
29
30pub trait PhysicsCallback<const D: usize> {
36 fn modulate_force(&self, body: BodyHandle, force: &SVector<f64, D>) -> SVector<f64, D>;
39
40 fn modulate_impulse(&self, impulse: f64, contact_point: &SVector<f64, D>) -> f64;
43
44 fn friction_multiplier(&self, contact_point: &SVector<f64, D>, body: BodyHandle) -> f64;
47
48 fn on_collision(&mut self, event: &CollisionEvent<D>);
52
53 fn record_dissipation(&mut self, energy: f64);
55
56 fn record_work(&mut self, body: BodyHandle, work_joules: f64);
60
61 fn apply_trauma(&mut self, event: &CollisionEvent<D>);
65}
66
67pub struct NoOpCallback;
69
70impl<const D: usize> PhysicsCallback<D> for NoOpCallback {
71 fn modulate_force(&self, _: BodyHandle, force: &SVector<f64, D>) -> SVector<f64, D> {
72 *force
73 }
74 fn modulate_impulse(&self, impulse: f64, _: &SVector<f64, D>) -> f64 {
75 impulse
76 }
77 fn friction_multiplier(&self, _: &SVector<f64, D>, _: BodyHandle) -> f64 {
78 1.0
79 }
80 fn on_collision(&mut self, _: &CollisionEvent<D>) {}
81 fn record_dissipation(&mut self, _: f64) {}
82 fn record_work(&mut self, _: BodyHandle, _: f64) {}
83 fn apply_trauma(&mut self, _: &CollisionEvent<D>) {}
84}
85
86pub struct PhysicsWorld<const D: usize> {
88 pub bodies: Vec<RigidBody<D>>,
89 pub constraints: Vec<Box<dyn Constraint<D>>>,
90 pub gravity: SVector<f64, D>,
91 pub contacts: Vec<ContactManifold<D>>,
93 pub collision_events: Vec<CollisionEvent<D>>,
95 pub sensor_events: Vec<crate::contact::SensorEvent>,
97 contact_cache: ContactCache<D>,
99 prev_cache: ContactCache<D>,
101 pub solver_iterations: usize,
103 pub sleep_threshold: f64,
105 pub sleep_ticks: u32,
107 pub slop: f64,
109 pub baumgarte: f64,
114 pub compliance: f64,
117 net_id_map: BTreeMap<NetId, BodyHandle>,
119 handle_to_index: HashMap<BodyHandle, usize>,
121 next_handle: usize,
122 static_broadphase: broadphase::StaticBroadphase<D>,
124 static_tree_dirty: bool,
126}
127
128impl<const D: usize> Default for PhysicsWorld<D> {
129 fn default() -> Self {
130 Self::new(SVector::zeros())
131 }
132}
133
134impl<const D: usize> PhysicsWorld<D> {
135 pub fn new(gravity: SVector<f64, D>) -> Self {
137 Self {
138 bodies: Vec::new(),
139 constraints: Vec::new(),
140 gravity,
141 contacts: Vec::new(),
142 collision_events: Vec::new(),
143 sensor_events: Vec::new(),
144 contact_cache: ContactCache::new(),
145 prev_cache: ContactCache::new(),
146 solver_iterations: 4,
147 slop: 0.01,
148 baumgarte: 0.2,
149 compliance: 0.0,
150 sleep_threshold: 0.5,
151 sleep_ticks: 60, net_id_map: BTreeMap::new(),
153 handle_to_index: HashMap::new(),
154 next_handle: 0,
155 static_broadphase: broadphase::StaticBroadphase::new(),
156 static_tree_dirty: false,
157 }
158 }
159
160 pub fn body_count(&self) -> usize {
162 self.bodies.len()
163 }
164
165 pub fn total_kinetic_energy(&self) -> f64 {
167 self.bodies
168 .iter()
169 .filter(|b| b.body_type == crate::body::BodyType::Dynamic)
170 .map(|b| b.kinetic_energy())
171 .sum()
172 }
173
174 pub fn sleeping_count(&self) -> usize {
176 self.bodies.iter().filter(|b| b.sleeping).count()
177 }
178
179 pub fn add_sphere(&mut self, position: Point<D>, radius: f64, mass: f64) -> BodyHandle {
181 let handle = self.allocate_handle();
182 let body = RigidBody::dynamic_sphere(handle, position, radius, mass);
183 let idx = self.bodies.len();
184 self.bodies.push(body);
185 self.handle_to_index.insert(handle, idx);
186 handle
187 }
188
189 pub fn add_body(&mut self, mut body: RigidBody<D>) -> BodyHandle {
191 use crate::body::BodyType;
192 let handle = self.allocate_handle();
193 body.handle = handle;
194 if body.body_type == BodyType::Static || body.body_type == BodyType::Kinematic {
195 self.static_tree_dirty = true;
196 }
197 let idx = self.bodies.len();
198 self.bodies.push(body);
199 self.handle_to_index.insert(handle, idx);
200 handle
201 }
202
203 pub fn add_bodies_deterministic(
209 &mut self,
210 mut bodies: Vec<(NetId, RigidBody<D>)>,
211 ) -> Result<Vec<BodyHandle>, String> {
212 bodies.sort_by_key(|(id, _)| *id);
213 let mut handles = Vec::with_capacity(bodies.len());
214 for (net_id, mut body) in bodies {
215 if self.net_id_map.contains_key(&net_id) {
216 return Err(format!("duplicate NetId({})", net_id.0));
217 }
218 let handle = self.allocate_handle();
219 body.handle = handle;
220 body.net_id = Some(net_id);
221 self.net_id_map.insert(net_id, handle);
222 let idx = self.bodies.len();
223 self.bodies.push(body);
224 self.handle_to_index.insert(handle, idx);
225 handles.push(handle);
226 }
227 Ok(handles)
228 }
229
230 pub fn handle_for_net_id(&self, net_id: NetId) -> Option<BodyHandle> {
232 self.net_id_map.get(&net_id).copied()
233 }
234
235 pub fn net_id_for_handle(&self, handle: BodyHandle) -> Option<NetId> {
237 self.body(handle).and_then(|b| b.net_id)
238 }
239
240 pub fn set_net_id(&mut self, handle: BodyHandle, net_id: NetId) {
242 let old_id = self.body(handle).and_then(|b| b.net_id);
243
244 if let Some(old) = old_id {
245 self.net_id_map.remove(&old);
246 }
247
248 if let Some(body) = self.body_mut(handle) {
249 body.net_id = Some(net_id);
250 self.net_id_map.insert(net_id, handle);
251 }
252 }
253
254 pub fn add_constraint(&mut self, constraint: Box<dyn Constraint<D>>) {
256 self.constraints.push(constraint);
257 }
258
259 pub fn body(&self, handle: BodyHandle) -> Option<&RigidBody<D>> {
261 self.handle_to_index
262 .get(&handle)
263 .and_then(|&idx| self.bodies.get(idx))
264 }
265
266 pub fn body_mut(&mut self, handle: BodyHandle) -> Option<&mut RigidBody<D>> {
268 self.handle_to_index
269 .get(&handle)
270 .copied()
271 .and_then(|idx| self.bodies.get_mut(idx))
272 }
273
274 pub fn step_with_callback(&mut self, dt: f64, callback: &mut dyn PhysicsCallback<D>) {
279 self.step_internal(dt, callback);
280 }
281
282 pub fn step(&mut self, dt: f64) {
284 let mut noop = NoOpCallback;
285 self.step_internal(dt, &mut noop);
286 }
287
288 fn step_internal(&mut self, dt: f64, callback: &mut dyn PhysicsCallback<D>) {
289 self.collision_events.clear();
291 self.sensor_events.clear();
292 std::mem::swap(&mut self.contact_cache, &mut self.prev_cache);
294 self.contact_cache.begin_frame();
295
296 if self.static_tree_dirty {
298 self.static_broadphase.rebuild(&self.bodies);
299 self.static_tree_dirty = false;
300 }
301
302 #[cfg(feature = "deterministic-net")]
304 for body in &mut self.bodies {
305 if !body.sleeping {
308 for i in 0..D {
309 body.force_accumulator[i] = quantize_q16_16(body.force_accumulator[i]);
310 }
311 }
312 }
313 for body in &mut self.bodies {
314 if !body.sleeping {
315 body.force_accumulator =
316 callback.modulate_force(body.handle, &body.force_accumulator);
317 integrator::integrate(body, &self.gravity, dt);
318 }
319 }
320 #[cfg(feature = "deterministic-net")]
321 for body in &mut self.bodies {
322 for i in 0..D {
324 body.transform.translation.0[i] = quantize_q16_16(body.transform.translation.0[i]);
325 body.linear_velocity[i] = quantize_q16_16(body.linear_velocity[i]);
326 }
327 }
328
329 let pairs = broadphase::find_pairs_incremental(&self.bodies, &self.static_broadphase);
331
332 self.contacts.clear();
334 for pair in &pairs {
335 let (idx_a, idx_b) = self.find_body_indices(pair.0, pair.1);
336 if let (Some(a), Some(b)) = (idx_a, idx_b) {
337 let pos_a = self.bodies[a].transform.translation.0;
338 let pos_b = self.bodies[b].transform.translation.0;
339
340 if let Some(manifold) = self.try_halfspace_contact(a, b, pair.0, pair.1) {
342 if self.bodies[a].is_sensor || self.bodies[b].is_sensor {
343 let (sensor, other) = if self.bodies[a].is_sensor {
344 (pair.0, pair.1)
345 } else {
346 (pair.1, pair.0)
347 };
348 self.sensor_events
349 .push(crate::contact::SensorEvent { sensor, other });
350 continue;
351 }
352 self.contacts.push(manifold);
353 continue;
354 }
355
356 if let Some(manifolds) = self.try_mesh_contact(a, b, pair.0, pair.1) {
358 for manifold in manifolds {
359 if self.bodies[a].is_sensor || self.bodies[b].is_sensor {
360 let (sensor, other) = if self.bodies[a].is_sensor {
361 (pair.0, pair.1)
362 } else {
363 (pair.1, pair.0)
364 };
365 self.sensor_events
366 .push(crate::contact::SensorEvent { sensor, other });
367 continue;
368 }
369 self.contacts.push(manifold);
370 }
371 continue;
372 }
373
374 let result = gjk::intersects(
375 self.bodies[a].collider.as_ref(),
376 &pos_a,
377 self.bodies[b].collider.as_ref(),
378 &pos_b,
379 );
380
381 if result.intersecting {
382 if self.bodies[a].is_sensor || self.bodies[b].is_sensor {
384 let (sensor, other) = if self.bodies[a].is_sensor {
385 (pair.0, pair.1)
386 } else {
387 (pair.1, pair.0)
388 };
389 self.sensor_events
390 .push(crate::contact::SensorEvent { sensor, other });
391 continue;
392 }
393
394 if let Some(epa_result) = crate::epa::penetration(
396 self.bodies[a].collider.as_ref(),
397 &pos_a,
398 self.bodies[b].collider.as_ref(),
399 &pos_b,
400 &result.simplex,
401 ) {
402 if epa_result.depth > 0.0 {
403 let manifold = manifold_gen::generate_contact_manifold(
405 self.bodies[a].collider.as_ref(),
406 &pos_a,
407 self.bodies[b].collider.as_ref(),
408 &pos_b,
409 epa_result.normal,
410 epa_result.depth,
411 pair.0,
412 pair.1,
413 );
414 self.contacts.push(manifold);
415 }
416 }
417 }
418 }
419 }
420
421 let islands = crate::island::build_islands(
423 &self.bodies,
424 &self.contacts,
425 &self.constraints,
426 &self.handle_to_index,
427 );
428 let active_contact_indices: Vec<usize> = islands
429 .iter()
430 .filter(|island| !island.sleeping)
431 .flat_map(|island| island.contact_indices.iter().copied())
432 .collect();
433
434 for &ci in &active_contact_indices {
437 if ci < self.contacts.len() {
438 self.warm_start_contact(ci);
439 }
440 }
441
442 for _ in 0..self.solver_iterations {
445 for &ci in &active_contact_indices {
446 if ci < self.contacts.len() {
447 let contact = self.contacts[ci].clone();
448 let updated = self.resolve_contact(contact, dt, callback);
449 self.contacts[ci] = updated;
450 }
451 }
452 }
453
454 for &ci in &active_contact_indices {
456 if ci < self.contacts.len() {
457 let c = &self.contacts[ci];
458 let total_lambda: f64 = c.points.iter().map(|p| p.lambda).sum();
459 self.contact_cache
460 .store(c.body_a, c.body_b, c.point(), total_lambda, 0.0);
461 }
462 }
463
464 let active_constraint_indices: Vec<usize> = islands
466 .iter()
467 .filter(|island| !island.sleeping)
468 .flat_map(|island| island.constraint_indices.iter().copied())
469 .collect();
470
471 for _ in 0..self.solver_iterations {
472 for &ci in &active_constraint_indices {
473 if ci >= self.constraints.len() {
474 continue;
475 }
476 let (ha, hb) = self.constraints[ci].bodies();
477 let (idx_a, idx_b) = self.find_body_indices(ha, hb);
478 if let (Some(a), Some(b)) = (idx_a, idx_b) {
479 if a < b {
480 let (left, right) = self.bodies.split_at_mut(b);
481 self.constraints[ci].solve(&mut left[a], &mut right[0], dt);
482 } else {
483 let (left, right) = self.bodies.split_at_mut(a);
484 self.constraints[ci].solve(&mut right[0], &mut left[b], dt);
485 }
486 }
487 }
488 }
489
490 for _ in 0..self.solver_iterations {
492 for &ci in &active_constraint_indices {
493 if ci >= self.constraints.len() {
494 continue;
495 }
496 let (ha, hb) = self.constraints[ci].bodies();
497 let (idx_a, idx_b) = self.find_body_indices(ha, hb);
498 if let (Some(a), Some(b)) = (idx_a, idx_b) {
499 if a < b {
500 let (left, right) = self.bodies.split_at_mut(b);
501 self.constraints[ci].solve_velocity(
502 &mut left[a],
503 &mut right[0],
504 dt,
505 Some(callback),
506 );
507 } else {
508 let (left, right) = self.bodies.split_at_mut(a);
509 self.constraints[ci].solve_velocity(
510 &mut right[0],
511 &mut left[b],
512 dt,
513 Some(callback),
514 );
515 }
516 }
517 }
518 }
519
520 let threshold = self.sleep_threshold;
522 let ticks = self.sleep_ticks;
523 for body in &mut self.bodies {
524 body.try_sleep(threshold, ticks);
525 }
526
527 self.decay_state(callback, dt);
529 }
530
531 fn decay_state(&mut self, _callback: &mut dyn PhysicsCallback<D>, _dt: f64) {
536 let _dummy_event: CollisionEvent<D> = CollisionEvent {
543 body_a: BodyHandle(0),
544 body_b: BodyHandle(0),
545 impulse: 0.0,
546 normal: SVector::zeros(),
547 depth: 0.0,
548 };
549
550 }
563
564 fn warm_start_contact(&mut self, ci: usize) {
569 let contact = self.contacts[ci].clone();
570 let (idx_a, idx_b) = self.find_body_indices(contact.body_a, contact.body_b);
571 let (Some(a), Some(b)) = (idx_a, idx_b) else {
572 return;
573 };
574
575 let primary_pt = contact.primary_point().position;
576 let cached_total = self
577 .prev_cache
578 .lookup(contact.body_a, contact.body_b, &primary_pt)
579 .map(|c| c.normal_impulse * 0.8) .unwrap_or(0.0);
581
582 if cached_total > 1e-15 {
583 let n_pts = contact.points.len().max(1) as f64;
584 let per_pt = cached_total / n_pts;
585
586 let impulse = contact.normal * cached_total;
588 integrator::apply_impulse(&mut self.bodies[a], &(-impulse));
589 integrator::apply_impulse(&mut self.bodies[b], &impulse);
590
591 for pt in &mut self.contacts[ci].points {
593 pt.lambda = per_pt;
594 }
595 }
596 }
597
598 fn resolve_contact(
607 &mut self,
608 mut contact: ContactManifold<D>,
609 dt: f64,
610 callback: &mut dyn PhysicsCallback<D>,
611 ) -> ContactManifold<D> {
612 let (idx_a, idx_b) = self.find_body_indices(contact.body_a, contact.body_b);
613 let (Some(a), Some(b)) = (idx_a, idx_b) else {
614 return contact;
615 };
616
617 let inv_mass_a = self.bodies[a].inv_mass;
618 let inv_mass_b = self.bodies[b].inv_mass;
619 let total_inv_mass = inv_mass_a + inv_mass_b;
620 if total_inv_mass < 1e-15 {
621 return contact;
622 }
623
624 let alpha = self.compliance / (dt * dt).max(1e-20);
626 let safe_dt = dt.max(1e-10);
627
628 let baumgarte = self.baumgarte;
629 let slop = self.slop;
630
631 let mut total_normal_impulse = 0.0_f64;
633
634 for pt in &mut contact.points {
635 let v_rel_n = {
636 let va = self.bodies[a].linear_velocity;
637 let vb = self.bodies[b].linear_velocity;
638 (vb - va).dot(&contact.normal)
639 };
640
641 let bias = (pt.depth - slop).max(0.0) * baumgarte / safe_dt;
643
644 let denom = total_inv_mass + alpha;
646 let delta_lambda = (-v_rel_n + bias) / denom;
647
648 let new_lambda = (pt.lambda + delta_lambda).max(0.0);
650 let actual_delta = new_lambda - pt.lambda;
651 pt.lambda = new_lambda;
652
653 if actual_delta.abs() > 1e-15 {
654 let modulated_delta = callback.modulate_impulse(actual_delta, &pt.position);
656
657 let impulse = contact.normal * modulated_delta;
658 integrator::apply_impulse(&mut self.bodies[a], &(-impulse));
659 integrator::apply_impulse(&mut self.bodies[b], &impulse);
660 total_normal_impulse += modulated_delta.abs();
661 }
662 }
663
664 if total_normal_impulse > 1e-15 {
666 let primary_pt = contact.point();
667 let v_rel = self.bodies[b].linear_velocity - self.bodies[a].linear_velocity;
668 let v_n = contact.normal * v_rel.dot(&contact.normal);
669 let v_t = v_rel - v_n;
670 let v_t_mag = v_t.norm();
671
672 if v_t_mag > 1e-10 {
673 let tangent = v_t / v_t_mag;
674 let mut mu = (self.bodies[a].friction * self.bodies[b].friction).sqrt();
675
676 mu *= callback.friction_multiplier(&primary_pt, contact.body_a);
678
679 let j_t_desired = -v_t_mag / total_inv_mass;
680 let j_t = j_t_desired.clamp(-mu * total_normal_impulse, mu * total_normal_impulse);
681 let friction_impulse = tangent * j_t;
682 integrator::apply_impulse(&mut self.bodies[a], &(-friction_impulse));
683 integrator::apply_impulse(&mut self.bodies[b], &friction_impulse);
684
685 callback.record_dissipation(j_t.abs() * 0.1);
687 }
688
689 let event = CollisionEvent {
691 body_a: contact.body_a,
692 body_b: contact.body_b,
693 impulse: total_normal_impulse,
694 normal: contact.normal,
695 depth: contact.depth(),
696 };
697
698 callback.on_collision(&event);
701 callback.apply_trauma(&event);
703 }
704
705 contact
706 }
707
708 #[inline]
709 fn allocate_handle(&mut self) -> BodyHandle {
710 let handle = BodyHandle(self.next_handle);
711 self.next_handle += 1;
712 handle
713 }
714
715 #[inline]
716 fn find_body_indices(
717 &self,
718 body_a: BodyHandle,
719 body_b: BodyHandle,
720 ) -> (Option<usize>, Option<usize>) {
721 (
722 self.handle_to_index.get(&body_a).copied(),
723 self.handle_to_index.get(&body_b).copied(),
724 )
725 }
726
727 fn try_halfspace_contact(
728 &self,
729 idx_a: usize,
730 idx_b: usize,
731 handle_a: BodyHandle,
732 handle_b: BodyHandle,
733 ) -> Option<ContactManifold<D>> {
734 let body_a = &self.bodies[idx_a];
735 let body_b = &self.bodies[idx_b];
736
737 if let Some(plane) = body_a.collider.as_any().downcast_ref::<HalfSpace<D>>() {
738 return self.contact_against_halfspace(plane, body_b, handle_a, handle_b, true);
739 }
740 if let Some(plane) = body_b.collider.as_any().downcast_ref::<HalfSpace<D>>() {
741 return self.contact_against_halfspace(plane, body_a, handle_a, handle_b, false);
742 }
743
744 None
745 }
746
747 fn try_mesh_contact(
748 &self,
749 idx_a: usize,
750 idx_b: usize,
751 handle_a: BodyHandle,
752 handle_b: BodyHandle,
753 ) -> Option<Vec<ContactManifold<D>>> {
754 use crate::manifold_gen::MeshColliderMetadata;
755 let body_a = &self.bodies[idx_a];
756 let body_b = &self.bodies[idx_b];
757
758 if let Some(mesh) = body_a
759 .collider
760 .as_any()
761 .downcast_ref::<&dyn MeshColliderMetadata<D>>()
762 {
763 return Some(mesh.generate_mesh_contacts(
764 handle_a,
765 &body_a.transform.translation.0,
766 body_b.collider.as_ref(),
767 handle_b,
768 &body_b.transform.translation.0,
769 ));
770 }
771 if let Some(mesh) = body_b
772 .collider
773 .as_any()
774 .downcast_ref::<&dyn MeshColliderMetadata<D>>()
775 {
776 return Some(mesh.generate_mesh_contacts(
777 handle_b,
778 &body_b.transform.translation.0,
779 body_a.collider.as_ref(),
780 handle_a,
781 &body_a.transform.translation.0,
782 ));
783 }
784
785 None
786 }
787
788 fn contact_against_halfspace(
789 &self,
790 plane: &HalfSpace<D>,
791 other: &RigidBody<D>,
792 handle_a: BodyHandle,
793 handle_b: BodyHandle,
794 plane_is_a: bool,
795 ) -> Option<ContactManifold<D>> {
796 let other_pos = other.transform.translation.0;
797 let normal = if plane_is_a {
798 plane.normal
799 } else {
800 -plane.normal
801 };
802
803 if let Some(sphere) = other.collider.as_any().downcast_ref::<Sphere<D>>() {
804 let (point, depth) = plane.contact_sphere(&other_pos, sphere.radius)?;
805 return Some(ContactManifold::single(
806 handle_a, handle_b, normal, point, depth,
807 ));
808 }
809
810 if let Some(capsule) = other.collider.as_any().downcast_ref::<Capsule<D>>() {
811 let contacts = plane.contact_capsule(
812 &other_pos,
813 capsule.half_height,
814 capsule.radius,
815 capsule.axis,
816 );
817 return Self::manifold_from_contacts(handle_a, handle_b, normal, contacts);
818 }
819
820 if let Some(hyperbox) = other.collider.as_any().downcast_ref::<HyperBox<D>>() {
821 let contacts = plane.contact_box(&other_pos, &hyperbox.half_extents);
822 return Self::manifold_from_contacts(handle_a, handle_b, normal, contacts);
823 }
824
825 None
826 }
827
828 fn manifold_from_contacts(
829 body_a: BodyHandle,
830 body_b: BodyHandle,
831 normal: SVector<f64, D>,
832 contacts: Vec<(SVector<f64, D>, f64)>,
833 ) -> Option<ContactManifold<D>> {
834 let mut contacts = contacts.into_iter();
835 let (point, depth) = contacts.next()?;
836 let mut manifold = ContactManifold::single(body_a, body_b, normal, point, depth);
837 for (position, depth) in contacts {
838 manifold.points.push(crate::contact::ContactPoint {
839 position,
840 depth,
841 lambda: 0.0,
842 });
843 }
844 Some(manifold)
845 }
846}