1#![allow(clippy::ptr_arg)]
2#![allow(dead_code)]
13#![allow(clippy::too_many_arguments)]
14
15use crate::math::Real;
16use crate::types::{Aabb, TimeStep, Transform};
17
18pub trait Steppable {
24 fn step(&mut self, time_step: &TimeStep);
26}
27
28pub trait PhysicsBody {
34 fn transform(&self) -> &Transform;
36
37 fn transform_mut(&mut self) -> &mut Transform;
39
40 fn mass(&self) -> Real;
42
43 fn inv_mass(&self) -> Real {
45 if self.mass() > 0.0 {
46 1.0 / self.mass()
47 } else {
48 0.0
49 }
50 }
51
52 fn is_dynamic(&self) -> bool {
54 self.mass() > 0.0
55 }
56
57 fn is_static(&self) -> bool {
59 self.mass() == 0.0
60 }
61}
62
63pub trait Collidable {
69 fn bounding_box(&self) -> Aabb;
71
72 fn can_collide_with(&self, _other: &dyn Collidable) -> bool {
74 true
75 }
76}
77
78pub trait ForceSource {
84 fn force_at(
89 &self,
90 transform: &Transform,
91 velocity: [Real; 3],
92 mass: Real,
93 ) -> ([Real; 3], [Real; 3]);
94}
95
96pub trait PhysicsConstraint {
102 fn dof(&self) -> usize;
104
105 fn positional_error(&self) -> Vec<Real>;
108
109 fn is_active(&self) -> bool;
111
112 fn compliance(&self) -> Real {
114 0.0
115 }
116}
117
118pub trait Integrator {
124 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real);
129}
130
131pub struct SemiImplicitEuler;
139
140impl Integrator for SemiImplicitEuler {
141 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
142 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
143 for i in 0..n {
144 v[i] += f[i] * inv_mass[i] * dt;
145 q[i] += v[i] * dt;
146 }
147 }
148}
149
150pub struct ExplicitEuler;
158
159impl Integrator for ExplicitEuler {
160 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
161 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
162 for i in 0..n {
163 q[i] += v[i] * dt;
164 v[i] += f[i] * inv_mass[i] * dt;
165 }
166 }
167}
168
169pub struct RungeKutta4;
173
174impl RungeKutta4 {
175 pub fn integrate_rk4(q: &mut [Real], v: &mut [Real], accel: &[Real], dt: Real) {
178 let n = q.len().min(v.len()).min(accel.len());
179 let h = dt;
180 let h2 = h / 2.0;
181 let h6 = h / 6.0;
182 for i in 0..n {
187 let a = accel[i];
188 let kv1 = a;
190 let kv2 = a; let kv3 = a;
192 let kv4 = a;
193 let kq1 = v[i];
195 let kq2 = v[i] + h2 * kv1;
196 let kq3 = v[i] + h2 * kv2;
197 let kq4 = v[i] + h * kv3;
198 v[i] += h6 * (kv1 + 2.0 * kv2 + 2.0 * kv3 + kv4);
199 q[i] += h6 * (kq1 + 2.0 * kq2 + 2.0 * kq3 + kq4);
200 }
201 }
202}
203
204impl Integrator for RungeKutta4 {
205 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
206 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
207 let accel: Vec<Real> = (0..n).map(|i| f[i] * inv_mass[i]).collect();
208 Self::integrate_rk4(q, v, &accel, dt);
209 }
210}
211
212pub trait Broadphase {
218 fn insert(&mut self, id: u64, aabb: Aabb);
220
221 fn update(&mut self, id: u64, aabb: Aabb);
223
224 fn remove(&mut self, id: u64);
226
227 fn overlapping_pairs(&self) -> Vec<(u64, u64)>;
229
230 fn query_aabb(&self, query: &Aabb) -> Vec<u64>;
232}
233
234#[derive(Debug, Clone)]
240pub struct ContactPoint {
241 pub point: [Real; 3],
243 pub normal: [Real; 3],
245 pub depth: Real,
247}
248
249pub trait Narrowphase {
251 fn test_overlap(&self, transform_a: &Transform, transform_b: &Transform) -> bool;
253
254 fn contacts(&self, transform_a: &Transform, transform_b: &Transform) -> Vec<ContactPoint>;
256}
257
258pub trait PhysicsStateful {
264 type State;
266
267 fn save_state(&self) -> Self::State;
269
270 fn restore_state(&mut self, state: Self::State);
272}
273
274pub trait Damped {
280 fn linear_damping(&self) -> Real;
282
283 fn angular_damping(&self) -> Real;
285
286 fn damp_velocity(&self, velocity: Real, dt: Real, is_angular: bool) -> Real {
288 let d = if is_angular {
289 self.angular_damping()
290 } else {
291 self.linear_damping()
292 };
293 velocity * (1.0 - d * dt).max(0.0)
294 }
295}
296
297pub trait Sleepable {
303 fn is_sleeping(&self) -> bool;
305
306 fn wake(&mut self);
308
309 fn sleep(&mut self);
311
312 fn should_sleep(
315 &self,
316 speed_sq: Real,
317 threshold_sq: Real,
318 accumulated_sleep_time: Real,
319 time_to_sleep: Real,
320 ) -> bool {
321 speed_sq < threshold_sq && accumulated_sleep_time >= time_to_sleep
322 }
323}
324
325pub trait Renderable {
331 fn render_transform(&self, alpha: Real) -> Transform;
334}
335
336#[derive(Debug, Clone)]
342pub struct PhysicsMaterial {
343 pub static_friction: Real,
345 pub dynamic_friction: Real,
347 pub restitution: Real,
349 pub rolling_friction: Real,
351}
352
353impl Default for PhysicsMaterial {
354 fn default() -> Self {
355 Self {
356 static_friction: 0.5,
357 dynamic_friction: 0.4,
358 restitution: 0.3,
359 rolling_friction: 0.01,
360 }
361 }
362}
363
364impl PhysicsMaterial {
365 pub fn rubber() -> Self {
367 Self {
368 static_friction: 1.0,
369 dynamic_friction: 0.8,
370 restitution: 0.7,
371 rolling_friction: 0.02,
372 }
373 }
374
375 pub fn steel() -> Self {
377 Self {
378 static_friction: 0.74,
379 dynamic_friction: 0.57,
380 restitution: 0.6,
381 rolling_friction: 0.001,
382 }
383 }
384
385 pub fn ice() -> Self {
387 Self {
388 static_friction: 0.03,
389 dynamic_friction: 0.02,
390 restitution: 0.05,
391 rolling_friction: 0.001,
392 }
393 }
394
395 pub fn combine(&self, other: &PhysicsMaterial) -> PhysicsMaterial {
398 PhysicsMaterial {
399 static_friction: (self.static_friction * other.static_friction).sqrt(),
400 dynamic_friction: (self.dynamic_friction * other.dynamic_friction).sqrt(),
401 restitution: self.restitution.min(other.restitution),
402 rolling_friction: (self.rolling_friction * other.rolling_friction).sqrt(),
403 }
404 }
405}
406
407pub trait HasMaterial {
409 fn material(&self) -> &PhysicsMaterial;
411}
412
413pub trait PhysicsDiagnostics {
419 fn diagnostics(&self) -> Vec<(&'static str, Real)>;
421}
422
423#[cfg(test)]
428mod tests {
429 use super::*;
430
431 use crate::VelocityVerlet;
432
433 use crate::fabrik_solve;
434
435 #[test]
438 fn test_semi_implicit_euler_constant_force() {
439 let integrator = SemiImplicitEuler;
440 let mut q = [0.0f64];
441 let mut v = [0.0f64];
442 let f = [1.0f64]; let inv_mass = [1.0f64]; integrator.integrate(&mut q, &mut v, &f, &inv_mass, 1.0);
445 assert!((v[0] - 1.0).abs() < 1e-12, "v={}", v[0]);
447 assert!((q[0] - 1.0).abs() < 1e-12, "q={}", q[0]);
448 }
449
450 #[test]
451 fn test_semi_implicit_euler_zero_force() {
452 let integrator = SemiImplicitEuler;
453 let mut q = [5.0f64];
454 let mut v = [2.0f64];
455 let f = [0.0f64];
456 let inv_mass = [1.0f64];
457 integrator.integrate(&mut q, &mut v, &f, &inv_mass, 0.5);
458 assert!((v[0] - 2.0).abs() < 1e-12);
460 assert!((q[0] - 6.0).abs() < 1e-12);
461 }
462
463 #[test]
466 fn test_explicit_euler_differs_from_semi_implicit() {
467 let f = [2.0f64];
469 let inv_mass = [1.0f64];
470 let dt = 1.0;
471
472 let mut q_si = [0.0f64];
473 let mut v_si = [0.0f64];
474 SemiImplicitEuler.integrate(&mut q_si, &mut v_si, &f, &inv_mass, dt);
475
476 let mut q_ex = [0.0f64];
477 let mut v_ex = [0.0f64];
478 ExplicitEuler.integrate(&mut q_ex, &mut v_ex, &f, &inv_mass, dt);
479
480 assert!((v_si[0] - 2.0).abs() < 1e-12);
482 assert!((q_si[0] - 2.0).abs() < 1e-12);
483 assert!((v_ex[0] - 2.0).abs() < 1e-12);
484 assert!((q_ex[0] - 0.0).abs() < 1e-12); }
486
487 #[test]
490 fn test_rk4_constant_acceleration() {
491 let mut q = [0.0f64];
494 let mut v = [0.0f64];
495 let a = [2.0f64];
496 let inv_mass = [1.0f64];
497 RungeKutta4.integrate(&mut q, &mut v, &a, &inv_mass, 1.0);
498 assert!((v[0] - 2.0).abs() < 1e-10, "v={}", v[0]);
499 assert!((q[0] - 1.0).abs() < 1e-10, "q={}", q[0]);
501 }
502
503 #[test]
504 fn test_rk4_with_initial_velocity() {
505 let mut q = [0.0f64];
506 let mut v = [3.0f64];
507 let a = [0.0f64]; let inv_mass = [1.0f64];
509 RungeKutta4.integrate(&mut q, &mut v, &a, &inv_mass, 2.0);
510 assert!((q[0] - 6.0).abs() < 1e-10, "q={}", q[0]);
512 assert!((v[0] - 3.0).abs() < 1e-10, "v={}", v[0]);
513 }
514
515 #[test]
518 fn test_material_combine_friction_geometric_mean() {
519 let a = PhysicsMaterial {
520 static_friction: 4.0,
521 dynamic_friction: 4.0,
522 restitution: 0.5,
523 rolling_friction: 0.01,
524 };
525 let b = PhysicsMaterial {
526 static_friction: 1.0,
527 dynamic_friction: 1.0,
528 restitution: 0.8,
529 rolling_friction: 0.01,
530 };
531 let c = a.combine(&b);
532 assert!((c.static_friction - 2.0).abs() < 1e-12);
534 assert!((c.dynamic_friction - 2.0).abs() < 1e-12);
535 assert!((c.restitution - 0.5).abs() < 1e-12);
537 }
538
539 #[test]
540 fn test_material_presets_valid() {
541 let rubber = PhysicsMaterial::rubber();
542 assert!(rubber.static_friction > 0.0);
543 assert!(rubber.restitution <= 1.0);
544
545 let steel = PhysicsMaterial::steel();
546 assert!(steel.dynamic_friction > 0.0);
547
548 let ice = PhysicsMaterial::ice();
549 assert!(ice.static_friction < 0.1);
550 }
551
552 struct SimpleDamped {
555 lin: Real,
556 ang: Real,
557 }
558 impl Damped for SimpleDamped {
559 fn linear_damping(&self) -> Real {
560 self.lin
561 }
562 fn angular_damping(&self) -> Real {
563 self.ang
564 }
565 }
566
567 #[test]
568 fn test_damped_velocity() {
569 let d = SimpleDamped { lin: 0.5, ang: 0.2 };
570 let v_after = d.damp_velocity(10.0, 1.0, false);
571 assert!((v_after - 5.0).abs() < 1e-12, "v={}", v_after);
573 }
574
575 #[test]
576 fn test_damped_velocity_clamp_zero() {
577 let d = SimpleDamped { lin: 2.0, ang: 0.0 };
578 let v_after = d.damp_velocity(10.0, 1.0, false);
580 assert_eq!(v_after, 0.0);
581 }
582
583 #[test]
586 fn test_sleepable_should_sleep() {
587 struct DummySleepable;
588 impl Sleepable for DummySleepable {
589 fn is_sleeping(&self) -> bool {
590 false
591 }
592 fn wake(&mut self) {}
593 fn sleep(&mut self) {}
594 }
595 let s = DummySleepable;
596 assert!(s.should_sleep(0.0001, 0.01, 1.0, 0.5));
597 assert!(!s.should_sleep(0.1, 0.01, 1.0, 0.5)); assert!(!s.should_sleep(0.0001, 0.01, 0.1, 0.5)); }
600
601 #[test]
604 fn test_contact_point_construction() {
605 let cp = ContactPoint {
606 point: [1.0, 2.0, 3.0],
607 normal: [0.0, 1.0, 0.0],
608 depth: 0.05,
609 };
610 assert!((cp.depth - 0.05).abs() < 1e-12);
611 assert_eq!(cp.normal[1], 1.0);
612 }
613
614 #[test]
617 fn test_integrator_length_mismatch_safe() {
618 let integrator = SemiImplicitEuler;
620 let mut q = vec![0.0, 0.0, 0.0];
621 let mut v = vec![1.0, 1.0];
622 let f = vec![0.0, 0.0, 0.0];
623 let inv_mass = vec![1.0, 1.0, 1.0];
624 integrator.integrate(&mut q, &mut v, &f, &inv_mass, 0.1);
626 assert_eq!(q[2], 0.0); }
628
629 #[test]
632 fn test_velocity_verlet_constant_accel() {
633 let mut q = [0.0f64];
634 let mut v = [0.0f64];
635 let a = [2.0f64];
636 let dt = 1.0;
637 VelocityVerlet.integrate(&mut q, &mut v, &a, &[1.0], dt);
638 assert!((q[0] - 1.0).abs() < 1e-12, "q={}", q[0]);
640 assert!((v[0] - 2.0).abs() < 1e-12, "v={}", v[0]);
641 }
642
643 #[test]
646 fn test_leapfrog_energy_conservation() {
647 let omega2 = 1.0f64; let dt = 0.01;
651 let n_steps = 1000;
652 let mut x = 1.0f64;
653 let mut v = 0.0f64;
654 let a0 = -omega2 * x;
656 let mut v_half = v + 0.5 * a0 * dt;
657 let e0 = 0.5 * v * v + 0.5 * x * x;
658 for _ in 0..n_steps {
659 x += v_half * dt;
660 let a = -omega2 * x;
661 v_half += a * dt;
662 v = v_half - 0.5 * a * dt;
663 }
664 let e_final = 0.5 * v * v + 0.5 * x * x;
665 assert!((e_final - e0).abs() < 0.01, "E0={e0}, Ef={e_final}");
667 }
668
669 #[test]
672 fn test_velocity_verlet_free_fall() {
673 let g = -9.81f64;
676 let dt = 0.001;
677 let n = 1000;
678 let mut y = 0.0f64;
679 let mut vy = 0.0f64;
680 let mut ay = g;
681 for _ in 0..n {
682 y += vy * dt + 0.5 * ay * dt * dt;
683 let ay_new = g;
684 vy += 0.5 * (ay + ay_new) * dt;
685 ay = ay_new;
686 }
687 let t = dt * n as f64;
688 let expected = 0.5 * g * t * t;
689 assert!((y - expected).abs() < 1e-8, "y={y}, expected={expected}");
690 }
691
692 #[test]
695 fn test_rkf45_step_count_positive() {
696 let mut y = [1.0f64];
698 let a = [0.0f64];
699 let inv_mass = [1.0f64];
700 RungeKutta4.integrate(&mut y, &mut a.clone(), &a, &inv_mass, 0.1);
701 assert!(y[0].is_finite());
702 }
703
704 #[test]
707 fn test_material_combine_zero_friction() {
708 let a = PhysicsMaterial {
709 static_friction: 0.0,
710 dynamic_friction: 0.0,
711 restitution: 0.5,
712 rolling_friction: 0.0,
713 };
714 let b = PhysicsMaterial::rubber();
715 let c = a.combine(&b);
716 assert_eq!(c.static_friction, 0.0);
718 assert_eq!(c.dynamic_friction, 0.0);
719 }
720
721 #[test]
722 fn test_material_combine_min_restitution() {
723 let a = PhysicsMaterial {
724 restitution: 0.9,
725 ..PhysicsMaterial::default()
726 };
727 let b = PhysicsMaterial {
728 restitution: 0.1,
729 ..PhysicsMaterial::default()
730 };
731 let c = a.combine(&b);
732 assert!((c.restitution - 0.1).abs() < 1e-12);
733 }
734
735 #[test]
738 fn test_fabrik_reaches_target_1dof() {
739 let bones = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
741 let lengths = vec![1.0f64, 1.0];
742 let target = [1.0, 0.5, 0.0]; let result = fabrik_solve(&bones, &lengths, &target, 50, 1e-6);
744 let ee = result.last().unwrap();
746 let d = ((ee[0] - target[0]).powi(2)
747 + (ee[1] - target[1]).powi(2)
748 + (ee[2] - target[2]).powi(2))
749 .sqrt();
750 assert!(d < 0.01, "FABRIK end effector distance to target: {d}");
751 }
752
753 #[test]
754 fn test_fabrik_unreachable_extends_toward_target() {
755 let bones = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0]];
757 let lengths = vec![1.0f64];
758 let target = [3.0, 0.0, 0.0];
759 let result = fabrik_solve(&bones, &lengths, &target, 20, 1e-6);
760 let ee = result.last().unwrap();
762 assert!(
763 ee[0] > 0.9,
764 "end effector should extend toward target: x={}",
765 ee[0]
766 );
767 }
768}
769
770pub struct VelocityVerlet;
785
786impl Integrator for VelocityVerlet {
787 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
788 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
789 let dt2 = dt * dt;
790 for i in 0..n {
791 let a = f[i] * inv_mass[i];
792 q[i] += v[i] * dt + 0.5 * a * dt2;
793 v[i] += a * dt;
794 }
795 }
796}
797
798pub struct Leapfrog;
812
813impl Integrator for Leapfrog {
814 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
815 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
816 for i in 0..n {
817 let a = f[i] * inv_mass[i];
818 let v_half = v[i] + a * dt * 0.5;
820 q[i] += v_half * dt;
822 v[i] = v_half + a * dt * 0.5;
824 }
825 }
826}
827
828pub struct AdamsBashforth2 {
837 pub f_prev: Option<Vec<Real>>,
839}
840
841impl AdamsBashforth2 {
842 pub fn new() -> Self {
844 Self { f_prev: None }
845 }
846
847 pub fn reset(&mut self) {
849 self.f_prev = None;
850 }
851
852 pub fn integrate_ab2(
854 &mut self,
855 q: &mut [Real],
856 v: &mut [Real],
857 f: &[Real],
858 inv_mass: &[Real],
859 dt: Real,
860 ) {
861 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
862 match &self.f_prev {
863 None => {
864 for i in 0..n {
866 let a = f[i] * inv_mass[i];
867 v[i] += a * dt;
868 q[i] += v[i] * dt;
869 }
870 }
871 Some(fp) => {
872 let m = fp.len().min(n);
873 for i in 0..m {
874 let a_curr = f[i] * inv_mass[i];
875 let a_prev = fp[i] * inv_mass[i];
876 let a_eff = 1.5 * a_curr - 0.5 * a_prev;
878 v[i] += a_eff * dt;
879 q[i] += v[i] * dt;
880 }
881 }
882 }
883 self.f_prev = Some(f[..n].to_vec());
884 }
885}
886
887impl Default for AdamsBashforth2 {
888 fn default() -> Self {
889 Self::new()
890 }
891}
892
893impl Integrator for AdamsBashforth2 {
894 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
895 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
897 for i in 0..n {
898 let a = f[i] * inv_mass[i];
899 v[i] += a * dt;
900 q[i] += v[i] * dt;
901 }
902 }
903}
904
905pub struct ImplicitEuler;
919
920impl Integrator for ImplicitEuler {
921 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
922 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
924 for i in 0..n {
925 v[i] += f[i] * inv_mass[i] * dt;
926 q[i] += v[i] * dt;
927 }
928 }
929}
930
931pub struct Midpoint;
946
947impl Integrator for Midpoint {
948 fn integrate(&self, q: &mut [Real], v: &mut [Real], f: &[Real], inv_mass: &[Real], dt: Real) {
949 let n = q.len().min(v.len()).min(f.len()).min(inv_mass.len());
950 let h2 = dt / 2.0;
951 for i in 0..n {
952 let a = f[i] * inv_mass[i];
953 let v_mid = v[i] + a * h2;
954 v[i] += a * dt;
955 q[i] += v_mid * dt;
956 }
957 }
958}
959
960pub fn xpbd_distance_correction(
978 p_a: [Real; 3],
979 p_b: [Real; 3],
980 inv_mass_a: Real,
981 inv_mass_b: Real,
982 rest_length: Real,
983 compliance: Real,
984 dt: Real,
985) -> ([Real; 3], [Real; 3]) {
986 let dx = [p_b[0] - p_a[0], p_b[1] - p_a[1], p_b[2] - p_a[2]];
987 let dist = (dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]).sqrt();
988 if dist < 1e-12 {
989 return ([0.0; 3], [0.0; 3]);
990 }
991 let n = [dx[0] / dist, dx[1] / dist, dx[2] / dist];
992 let c = dist - rest_length;
993 let alpha = compliance / (dt * dt);
994 let w = inv_mass_a + inv_mass_b;
995 if w < 1e-12 {
996 return ([0.0; 3], [0.0; 3]);
997 }
998 let lambda = -c / (w + alpha);
999 let da = [
1000 -lambda * inv_mass_a * n[0],
1001 -lambda * inv_mass_a * n[1],
1002 -lambda * inv_mass_a * n[2],
1003 ];
1004 let db = [
1005 lambda * inv_mass_b * n[0],
1006 lambda * inv_mass_b * n[1],
1007 lambda * inv_mass_b * n[2],
1008 ];
1009 (da, db)
1010}
1011
1012#[allow(clippy::too_many_arguments)]
1025pub fn contact_impulse_scalar(
1026 v_rel_n: Real,
1027 inv_mass_a: Real,
1028 inv_mass_b: Real,
1029 inv_inertia_a: Real,
1030 inv_inertia_b: Real,
1031 r_a: [Real; 3],
1032 r_b: [Real; 3],
1033 normal: [Real; 3],
1034) -> Real {
1035 fn cross_sq(r: [Real; 3], n: [Real; 3]) -> Real {
1037 let cx = r[1] * n[2] - r[2] * n[1];
1038 let cy = r[2] * n[0] - r[0] * n[2];
1039 let cz = r[0] * n[1] - r[1] * n[0];
1040 cx * cx + cy * cy + cz * cz
1041 }
1042 let k = inv_mass_a
1043 + inv_mass_b
1044 + inv_inertia_a * cross_sq(r_a, normal)
1045 + inv_inertia_b * cross_sq(r_b, normal);
1046 if k < 1e-12 { 0.0 } else { -v_rel_n / k }
1047}
1048
1049pub fn fabrik_solve(
1066 bones: &[[Real; 3]],
1067 lengths: &[Real],
1068 target: &[Real; 3],
1069 max_iter: usize,
1070 tolerance: Real,
1071) -> Vec<[Real; 3]> {
1072 let n = bones.len();
1073 if n < 2 || lengths.len() < n - 1 {
1074 return bones.to_vec();
1075 }
1076 let mut joints: Vec<[Real; 3]> = bones.to_vec();
1077 let root = joints[0];
1078 let total_length: Real = lengths.iter().sum();
1079
1080 let dist_to_target = dist3(&joints[0], target);
1082 if dist_to_target >= total_length {
1083 for i in 0..n - 1 {
1085 let d = dist3(&joints[i], target);
1086 let t = lengths[i] / d;
1087 joints[i + 1] = lerp3(&joints[i], target, t);
1088 }
1089 return joints;
1090 }
1091
1092 for _ in 0..max_iter {
1093 joints[n - 1] = *target;
1095 for i in (0..n - 1).rev() {
1096 let d = dist3(&joints[i + 1], &joints[i]);
1097 if d < 1e-12 {
1098 continue;
1099 }
1100 let t = lengths[i] / d;
1101 joints[i] = lerp3(&joints[i + 1], &joints[i], t);
1102 }
1103
1104 joints[0] = root;
1106 for i in 0..n - 1 {
1107 let d = dist3(&joints[i], &joints[i + 1]);
1108 if d < 1e-12 {
1109 continue;
1110 }
1111 let t = lengths[i] / d;
1112 joints[i + 1] = lerp3(&joints[i], &joints[i + 1], t);
1113 }
1114
1115 let ee_dist = dist3(&joints[n - 1], target);
1117 if ee_dist < tolerance {
1118 break;
1119 }
1120 }
1121 joints
1122}
1123
1124fn dist3(a: &[Real; 3], b: &[Real; 3]) -> Real {
1126 let dx = b[0] - a[0];
1127 let dy = b[1] - a[1];
1128 let dz = b[2] - a[2];
1129 (dx * dx + dy * dy + dz * dz).sqrt()
1130}
1131
1132fn lerp3(a: &[Real; 3], b: &[Real; 3], t: Real) -> [Real; 3] {
1134 [
1135 a[0] + t * (b[0] - a[0]),
1136 a[1] + t * (b[1] - a[1]),
1137 a[2] + t * (b[2] - a[2]),
1138 ]
1139}
1140
1141pub trait PbdConstraint {
1147 fn evaluate(&self, positions: &[[Real; 3]]) -> Real;
1149
1150 fn gradient(&self, positions: &[[Real; 3]]) -> Vec<[Real; 3]>;
1152
1153 fn particle_indices(&self) -> &[usize];
1155
1156 fn stiffness(&self) -> Real {
1158 1.0
1159 }
1160}
1161
1162pub struct PbdDistanceConstraint {
1164 pub indices: [usize; 2],
1166 pub rest_length: Real,
1168 pub k: Real,
1170}
1171
1172impl PbdDistanceConstraint {
1173 pub fn new(i: usize, j: usize, rest_length: Real, stiffness: Real) -> Self {
1175 Self {
1176 indices: [i, j],
1177 rest_length,
1178 k: stiffness,
1179 }
1180 }
1181}
1182
1183impl PbdConstraint for PbdDistanceConstraint {
1184 fn evaluate(&self, positions: &[[Real; 3]]) -> Real {
1185 let [i, j] = self.indices;
1186 if i >= positions.len() || j >= positions.len() {
1187 return 0.0;
1188 }
1189 let p = &positions[i];
1190 let q = &positions[j];
1191 dist3(&[p[0], p[1], p[2]], &[q[0], q[1], q[2]]) - self.rest_length
1192 }
1193
1194 fn gradient(&self, positions: &[[Real; 3]]) -> Vec<[Real; 3]> {
1195 let [i, j] = self.indices;
1196 if i >= positions.len() || j >= positions.len() {
1197 return vec![[0.0; 3]; 2];
1198 }
1199 let p = positions[i];
1200 let q = positions[j];
1201 let dx = [q[0] - p[0], q[1] - p[1], q[2] - p[2]];
1202 let d = (dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]).sqrt();
1203 if d < 1e-12 {
1204 return vec![[0.0; 3]; 2];
1205 }
1206 let n = [dx[0] / d, dx[1] / d, dx[2] / d];
1207 vec![[-n[0], -n[1], -n[2]], [n[0], n[1], n[2]]]
1209 }
1210
1211 fn particle_indices(&self) -> &[usize] {
1212 &self.indices
1213 }
1214
1215 fn stiffness(&self) -> Real {
1216 self.k
1217 }
1218}
1219
1220pub fn pbd_solve_iteration(
1225 positions: &mut Vec<[Real; 3]>,
1226 inv_masses: &[Real],
1227 constraints: &[&dyn PbdConstraint],
1228) {
1229 for c in constraints {
1230 let c_val = c.evaluate(positions);
1231 let grads = c.gradient(positions);
1232 let indices = c.particle_indices();
1233 if grads.len() < indices.len() {
1234 continue;
1235 }
1236 let mut denom = 0.0;
1238 for (k, &idx) in indices.iter().enumerate() {
1239 if idx < inv_masses.len() {
1240 let g = grads[k];
1241 denom += inv_masses[idx] * (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);
1242 }
1243 }
1244 if denom < 1e-12 {
1245 continue;
1246 }
1247 let lambda = -c_val / denom * c.stiffness();
1248 for (k, &idx) in indices.iter().enumerate() {
1249 if idx < positions.len() && idx < inv_masses.len() {
1250 let g = grads[k];
1251 let w = inv_masses[idx];
1252 positions[idx][0] += lambda * w * g[0];
1253 positions[idx][1] += lambda * w * g[1];
1254 positions[idx][2] += lambda * w * g[2];
1255 }
1256 }
1257 }
1258}
1259
1260pub trait CollisionFilter {
1266 fn should_collide(&self, id_a: u64, id_b: u64) -> bool;
1268}
1269
1270pub struct LayerFilter {
1275 pub layers: std::collections::HashMap<u64, u32>,
1277 pub masks: std::collections::HashMap<u64, u32>,
1279}
1280
1281impl LayerFilter {
1282 pub fn new() -> Self {
1284 Self {
1285 layers: std::collections::HashMap::new(),
1286 masks: std::collections::HashMap::new(),
1287 }
1288 }
1289
1290 pub fn register(&mut self, id: u64, layer: u32, mask: u32) {
1292 self.layers.insert(id, layer);
1293 self.masks.insert(id, mask);
1294 }
1295}
1296
1297impl Default for LayerFilter {
1298 fn default() -> Self {
1299 Self::new()
1300 }
1301}
1302
1303impl CollisionFilter for LayerFilter {
1304 fn should_collide(&self, id_a: u64, id_b: u64) -> bool {
1305 let layer_a = self.layers.get(&id_a).copied().unwrap_or(0xFFFF_FFFF);
1306 let layer_b = self.layers.get(&id_b).copied().unwrap_or(0xFFFF_FFFF);
1307 let mask_a = self.masks.get(&id_a).copied().unwrap_or(0xFFFF_FFFF);
1308 let mask_b = self.masks.get(&id_b).copied().unwrap_or(0xFFFF_FFFF);
1309 (mask_a & layer_b) != 0 && (mask_b & layer_a) != 0
1310 }
1311}
1312
1313pub trait PhysicsSensor {
1319 fn is_triggered(&self) -> bool;
1321
1322 fn overlapping_bodies(&self) -> Vec<u64>;
1324
1325 fn update(&mut self, body_positions: &[(u64, [Real; 3])]);
1327}
1328
1329pub struct SphereSensor {
1331 pub center: [Real; 3],
1333 pub radius: Real,
1335 pub current_overlaps: Vec<u64>,
1337}
1338
1339impl SphereSensor {
1340 pub fn new(center: [Real; 3], radius: Real) -> Self {
1342 Self {
1343 center,
1344 radius,
1345 current_overlaps: Vec::new(),
1346 }
1347 }
1348}
1349
1350impl PhysicsSensor for SphereSensor {
1351 fn is_triggered(&self) -> bool {
1352 !self.current_overlaps.is_empty()
1353 }
1354
1355 fn overlapping_bodies(&self) -> Vec<u64> {
1356 self.current_overlaps.clone()
1357 }
1358
1359 fn update(&mut self, body_positions: &[(u64, [Real; 3])]) {
1360 let r2 = self.radius * self.radius;
1361 self.current_overlaps = body_positions
1362 .iter()
1363 .filter_map(|(id, pos)| {
1364 let dx = pos[0] - self.center[0];
1365 let dy = pos[1] - self.center[1];
1366 let dz = pos[2] - self.center[2];
1367 if dx * dx + dy * dy + dz * dz <= r2 {
1368 Some(*id)
1369 } else {
1370 None
1371 }
1372 })
1373 .collect();
1374 }
1375}
1376
1377pub trait PhysicsInterpolatable: Sized {
1383 fn lerp_state(&self, other: &Self, t: Real) -> Self;
1385}
1386
1387#[cfg(test)]
1392mod expanded_tests {
1393 use super::*;
1394 use crate::AdamsBashforth2;
1395 use crate::LayerFilter;
1396 use crate::Leapfrog;
1397 use crate::Midpoint;
1398 use crate::PbdConstraint;
1399 use crate::PbdDistanceConstraint;
1400 use crate::SphereSensor;
1401 use crate::VelocityVerlet;
1402 use crate::dist3;
1403 use crate::fabrik_solve;
1404 use crate::pbd_solve_iteration;
1405 use crate::xpbd_distance_correction;
1406
1407 #[test]
1410 fn test_velocity_verlet_no_force() {
1411 let mut q = [3.0f64];
1412 let mut v = [2.0f64];
1413 VelocityVerlet.integrate(&mut q, &mut v, &[0.0], &[1.0], 0.5);
1414 assert!((q[0] - 4.0).abs() < 1e-12, "q={}", q[0]);
1416 assert!((v[0] - 2.0).abs() < 1e-12, "v={}", v[0]);
1417 }
1418
1419 #[test]
1422 fn test_leapfrog_rest() {
1423 let mut q = [1.0f64];
1424 let mut v = [0.0f64];
1425 Leapfrog.integrate(&mut q, &mut v, &[0.0], &[1.0], 1.0);
1426 assert!(
1427 (q[0] - 1.0).abs() < 1e-12,
1428 "q should not change: q={}",
1429 q[0]
1430 );
1431 }
1432
1433 #[test]
1436 fn test_midpoint_position_uses_midpoint_velocity() {
1437 let mut q = [0.0f64];
1438 let mut v = [0.0f64];
1439 Midpoint.integrate(&mut q, &mut v, &[4.0], &[1.0], 1.0);
1442 assert!((q[0] - 2.0).abs() < 1e-12, "q={}", q[0]);
1443 assert!((v[0] - 4.0).abs() < 1e-12, "v={}", v[0]);
1444 }
1445
1446 #[test]
1449 fn test_ab2_bootstrap_euler() {
1450 let mut ab2 = AdamsBashforth2::new();
1451 let mut q = [0.0f64];
1452 let mut v = [0.0f64];
1453 ab2.integrate_ab2(&mut q, &mut v, &[2.0], &[1.0], 1.0);
1454 assert!((v[0] - 2.0).abs() < 1e-12);
1456 assert!((q[0] - 2.0).abs() < 1e-12);
1457 }
1458
1459 #[test]
1460 fn test_ab2_second_step() {
1461 let mut ab2 = AdamsBashforth2::new();
1462 let mut q = [0.0f64];
1463 let mut v = [0.0f64];
1464 ab2.integrate_ab2(&mut q, &mut v, &[2.0], &[1.0], 1.0);
1465 ab2.integrate_ab2(&mut q, &mut v, &[2.0], &[1.0], 1.0);
1467 assert!(v[0].is_finite());
1468 assert!(q[0].is_finite());
1469 }
1470
1471 #[test]
1474 fn test_xpbd_distance_rigid() {
1475 let pa = [0.0, 0.0, 0.0];
1476 let pb = [2.0, 0.0, 0.0];
1477 let (da, db) = xpbd_distance_correction(pa, pb, 1.0, 1.0, 1.0, 0.0, 0.01);
1479 assert!(da[0] > 0.0, "da.x should be positive: {:?}", da);
1481 assert!(db[0] < 0.0, "db.x should be negative: {:?}", db);
1482 assert!((da[0].abs() - db[0].abs()).abs() < 1e-10);
1484 }
1485
1486 #[test]
1487 fn test_xpbd_distance_no_violation() {
1488 let pa = [0.0, 0.0, 0.0];
1489 let pb = [1.0, 0.0, 0.0];
1490 let (da, db) = xpbd_distance_correction(pa, pb, 1.0, 1.0, 1.0, 0.0, 0.01);
1491 assert!(da[0].abs() < 1e-12);
1492 assert!(db[0].abs() < 1e-12);
1493 }
1494
1495 #[test]
1496 fn test_xpbd_static_body() {
1497 let (da, db) =
1499 xpbd_distance_correction([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], 1.0, 0.0, 1.0, 0.0, 0.01);
1500 assert!(da[0].abs() > 0.0);
1501 assert_eq!(db[0], 0.0);
1502 }
1503
1504 #[test]
1507 fn test_pbd_distance_constraint_evaluate() {
1508 let c = PbdDistanceConstraint::new(0, 1, 1.0, 1.0);
1509 let positions = vec![[0.0, 0.0, 0.0f64], [2.0, 0.0, 0.0]];
1510 let violation = c.evaluate(&positions);
1511 assert!((violation - 1.0).abs() < 1e-12, "violation={}", violation);
1512 }
1513
1514 #[test]
1515 fn test_pbd_solve_iteration() {
1516 let c = PbdDistanceConstraint::new(0, 1, 1.0, 1.0);
1517 let constraints: Vec<&dyn PbdConstraint> = vec![&c];
1518 let mut positions = vec![[0.0, 0.0, 0.0f64], [3.0, 0.0, 0.0]];
1519 let inv_masses = [1.0f64, 1.0];
1520 pbd_solve_iteration(&mut positions, &inv_masses, &constraints);
1521 let d = (positions[1][0] - positions[0][0]).abs();
1523 assert!(d < 3.0, "distance should decrease: {d}");
1524 }
1525
1526 #[test]
1529 fn test_layer_filter_collision() {
1530 let mut filter = LayerFilter::new();
1531 filter.register(1, 0b01, 0b10); filter.register(2, 0b10, 0b01); assert!(filter.should_collide(1, 2));
1534 assert!(filter.should_collide(2, 1));
1535 }
1536
1537 #[test]
1538 fn test_layer_filter_no_collision() {
1539 let mut filter = LayerFilter::new();
1540 filter.register(1, 0b01, 0b01); filter.register(2, 0b10, 0b10); assert!(!filter.should_collide(1, 2));
1543 }
1544
1545 #[test]
1548 fn test_sphere_sensor_triggers() {
1549 let mut sensor = SphereSensor::new([0.0; 3], 5.0);
1550 let bodies = vec![(1u64, [1.0, 0.0, 0.0]), (2u64, [10.0, 0.0, 0.0])];
1551 sensor.update(&bodies);
1552 assert!(sensor.is_triggered());
1553 assert_eq!(sensor.overlapping_bodies(), vec![1]);
1554 }
1555
1556 #[test]
1557 fn test_sphere_sensor_no_trigger() {
1558 let mut sensor = SphereSensor::new([0.0; 3], 1.0);
1559 let bodies = vec![(1u64, [5.0, 0.0, 0.0])];
1560 sensor.update(&bodies);
1561 assert!(!sensor.is_triggered());
1562 }
1563
1564 #[test]
1567 fn test_fabrik_2joint_chain() {
1568 let bones = vec![[0.0, 0.0, 0.0f64], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1569 let lengths = vec![1.0f64, 1.0];
1570 let target = [1.0, 1.0, 0.0];
1571 let result = fabrik_solve(&bones, &lengths, &target, 50, 1e-6);
1572 assert_eq!(result.len(), 3);
1573 assert!((result[0][0]).abs() < 1e-10);
1575 let ee = result[2];
1577 let d = ((ee[0] - target[0]).powi(2) + (ee[1] - target[1]).powi(2)).sqrt();
1578 assert!(d < 0.01, "end effector dist to target: {d}");
1579 }
1580
1581 #[test]
1582 fn test_fabrik_preserves_bone_lengths() {
1583 let bones = vec![[0.0, 0.0, 0.0f64], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1584 let lengths = vec![1.0f64, 1.0];
1585 let target = [0.5, 0.5, 0.0];
1586 let result = fabrik_solve(&bones, &lengths, &target, 20, 1e-6);
1587 for i in 0..2 {
1589 let d = dist3(result[i], result[i + 1]);
1590 assert!((d - lengths[i]).abs() < 1e-6, "bone {i} length: {d}");
1591 }
1592 }
1593}