1use std::ops::Mul;
2
3use loam_math::{Bivector, IsometryGroup, Space};
4
5use crate::body::RigidBody;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum BroadphaseBound {
9 Certified,
11 Unknown,
13}
14
15pub trait PhysicsSpace: Space + IsometryGroup + Send + Sync {
16 type AngVel: Bivector + Send + Sync;
17
18 type Inertia: Copy + Send + Sync;
19
20 fn broadphase_bound(&self) -> BroadphaseBound;
21
22 fn supports_collider(&self, kind: crate::ColliderKind) -> bool;
23
24 fn valid_vector(&self, vector: Self::Vector) -> bool;
25
26 fn valid_inertia(&self, inertia: Self::Inertia) -> bool;
27
28 fn valid_orientation(&self, orientation: Self::Iso) -> bool;
29
30 fn valid_angular_velocity(&self, angular_velocity: Self::AngVel) -> bool;
31
32 fn valid_initial_state(
33 &self,
34 position: Self::Point,
35 velocity: Self::Vector,
36 inertia: Self::Inertia,
37 ) -> bool {
38 self.valid_point(position) && self.valid_vector(velocity) && self.valid_inertia(inertia)
39 }
40
41 fn integrate_orientation(&self, iso: Self::Iso, omega: Self::AngVel, dt: f32) -> Self::Iso;
42
43 fn apply_inv_inertia(&self, inertia: Self::Inertia, torque: Self::AngVel) -> Self::AngVel;
44
45 fn wedge(&self, a: Self::Vector, b: Self::Vector) -> Self::AngVel;
46
47 fn velocity_at_point(&self, body: &RigidBody<Self>, p: Self::Point) -> Self::Vector
48 where
49 Self: Sized;
50
51 fn effective_mass_inv(
53 &self,
54 a: &RigidBody<Self>,
55 b: &RigidBody<Self>,
56 contact_point: Self::Point,
57 direction: Self::Vector,
58 ) -> f32
59 where
60 Self: Sized;
61
62 fn apply_contact_impulse(
64 &self,
65 a: &mut RigidBody<Self>,
66 b: &mut RigidBody<Self>,
67 contact_point: Self::Point,
68 direction: Self::Vector,
69 magnitude: f32,
70 ) where
71 Self: Sized;
72}
73
74pub fn integrate_body<S>(space: &S, body: &mut RigidBody<S>, dt: f32)
76where
77 S: PhysicsSpace,
78 S::Vector: Mul<f32, Output = S::Vector>,
79{
80 if body.inv_mass() == 0.0 {
81 return;
82 }
83
84 let p_old = body.position;
85 let v_dt = body.velocity * dt;
86 let p_new = space.exp(p_old, v_dt);
87 body.velocity = space.parallel_transport(p_old, p_new, body.velocity);
88 body.position = p_new;
89 body.orientation = space.integrate_orientation(body.orientation, body.angular_velocity, dt);
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::body::BodyDef;
96 use crate::collider::Collider;
97 use crate::geometry::GeometryStore;
98 use glam::Vec3;
99 use loam_math::EuclideanR3;
100
101 fn row(def: BodyDef<EuclideanR3>) -> RigidBody<EuclideanR3> {
102 def.into_row(&mut GeometryStore::default(), &EuclideanR3)
103 }
104
105 #[test]
106 fn static_body_skips_integration() {
107 let mut body = row(BodyDef::<EuclideanR3>::fixed(
108 Vec3::ZERO,
109 Collider::sphere_at_origin(0.5),
110 1.0,
111 &EuclideanR3,
112 )
113 .unwrap());
114 body.velocity = Vec3::new(10.0, 0.0, 0.0);
115 integrate_body(&EuclideanR3, &mut body, 1.0);
116 assert_eq!(body.position, Vec3::ZERO);
117 }
118
119 #[test]
120 fn dynamic_body_in_e3_moves_linearly() {
121 let mut body = row(BodyDef::<EuclideanR3>::new(
122 Vec3::ZERO,
123 Vec3::new(1.0, 2.0, -3.0),
124 Collider::sphere_at_origin(0.1),
125 1.0,
126 0.1,
127 &EuclideanR3,
128 )
129 .unwrap());
130 integrate_body(&EuclideanR3, &mut body, 0.5);
131 assert_eq!(body.position, Vec3::new(0.5, 1.0, -1.5));
132 assert_eq!(body.velocity, Vec3::new(1.0, 2.0, -3.0));
133 }
134
135 #[test]
136 fn zero_dt_does_not_advance_state() {
137 let mut body = row(BodyDef::<EuclideanR3>::new(
138 Vec3::new(2.0, 3.0, 5.0),
139 Vec3::new(7.0, 11.0, 13.0),
140 Collider::sphere_at_origin(0.1),
141 1.0,
142 0.1,
143 &EuclideanR3,
144 )
145 .unwrap());
146 let before = (body.position, body.velocity);
147 integrate_body(&EuclideanR3, &mut body, 0.0);
148 assert_eq!((body.position, body.velocity), before);
149 }
150}