1use arrayvec::ArrayVec;
5use nalgebra::SVector;
6
7use crate::body::BodyHandle;
8
9pub const MAX_CONTACTS: usize = 8;
11
12#[derive(Clone, Debug)]
14pub struct ContactPoint<const D: usize> {
15 pub position: SVector<f64, D>,
17 pub depth: f64,
19 pub lambda: f64,
22}
23
24#[derive(Clone, Debug)]
26pub struct CollisionEvent<const D: usize> {
27 pub body_a: BodyHandle,
28 pub body_b: BodyHandle,
29 pub impulse: f64,
31 pub normal: SVector<f64, D>,
33 pub depth: f64,
35}
36
37#[derive(Clone, Debug)]
39pub struct SensorEvent {
40 pub sensor: BodyHandle,
42 pub other: BodyHandle,
44}
45
46#[derive(Clone, Debug)]
51pub struct ContactManifold<const D: usize> {
52 pub body_a: BodyHandle,
54 pub body_b: BodyHandle,
56 pub normal: SVector<f64, D>,
58 pub points: ArrayVec<ContactPoint<D>, MAX_CONTACTS>,
60}
61
62impl<const D: usize> ContactManifold<D> {
63 pub fn single(
65 body_a: BodyHandle,
66 body_b: BodyHandle,
67 normal: SVector<f64, D>,
68 point: SVector<f64, D>,
69 depth: f64,
70 ) -> Self {
71 let mut points = ArrayVec::new();
72 points.push(ContactPoint { position: point, depth, lambda: 0.0 });
73 Self {
74 body_a,
75 body_b,
76 normal,
77 points,
78 }
79 }
80
81 pub fn primary_point(&self) -> &ContactPoint<D> {
83 self.points
84 .iter()
85 .max_by(|a, b| a.depth.total_cmp(&b.depth))
86 .unwrap_or(&self.points[0])
87 }
88
89 pub fn depth(&self) -> f64 {
91 self.primary_point().depth
92 }
93
94 pub fn point(&self) -> SVector<f64, D> {
96 self.primary_point().position
97 }
98
99 pub fn impulse_magnitude(
103 &self,
104 relative_velocity: &SVector<f64, D>,
105 inv_mass_a: f64,
106 inv_mass_b: f64,
107 restitution: f64,
108 ) -> f64 {
109 let v_rel_n = relative_velocity.dot(&self.normal);
110
111 if v_rel_n > 0.0 {
113 return 0.0;
114 }
115
116 let denom = inv_mass_a + inv_mass_b;
117 if denom < 1e-15 {
118 return 0.0;
119 }
120
121 -(1.0 + restitution) * v_rel_n / denom
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct CachedImpulse<const D: usize> {
128 pub normal_impulse: f64,
130 pub tangent_impulse: f64,
132 pub point: SVector<f64, D>,
134}
135
136pub struct ContactCache<const D: usize> {
141 entries: std::collections::BTreeMap<(BodyHandle, BodyHandle), Vec<CachedImpulse<D>>>,
142 pub match_threshold: f64,
144}
145
146impl<const D: usize> ContactCache<D> {
147 pub fn new() -> Self {
148 Self {
149 entries: std::collections::BTreeMap::new(),
150 match_threshold: 2.0,
151 }
152 }
153
154 pub fn store(
156 &mut self,
157 body_a: BodyHandle,
158 body_b: BodyHandle,
159 point: SVector<f64, D>,
160 normal_impulse: f64,
161 tangent_impulse: f64,
162 ) {
163 let key = if body_a < body_b {
164 (body_a, body_b)
165 } else {
166 (body_b, body_a)
167 };
168 let entry = self.entries.entry(key).or_insert_with(Vec::new);
169 entry.push(CachedImpulse {
170 normal_impulse,
171 tangent_impulse,
172 point,
173 });
174 }
175
176 pub fn lookup(
180 &self,
181 body_a: BodyHandle,
182 body_b: BodyHandle,
183 point: &SVector<f64, D>,
184 ) -> Option<&CachedImpulse<D>> {
185 let key = if body_a < body_b {
186 (body_a, body_b)
187 } else {
188 (body_b, body_a)
189 };
190 let entries = self.entries.get(&key)?;
191 entries
193 .iter()
194 .filter(|c| (c.point - point).norm() < self.match_threshold)
195 .min_by(|a, b| {
196 let da = (a.point - point).norm();
197 let db = (b.point - point).norm();
198 da.total_cmp(&db)
199 })
200 }
201
202 pub fn begin_frame(&mut self) {
204 self.entries.clear();
205 }
206
207 pub fn pair_count(&self) -> usize {
209 self.entries.len()
210 }
211}
212
213impl<const D: usize> Default for ContactCache<D> {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn single_point_manifold() {
225 let m = ContactManifold::<3>::single(
226 BodyHandle(0), BodyHandle(1),
227 SVector::from([0.0, 1.0, 0.0]),
228 SVector::from([1.0, 0.0, 0.0]),
229 0.5,
230 );
231 assert_eq!(m.points.len(), 1);
232 assert!((m.depth() - 0.5).abs() < 1e-12);
233 }
234
235 #[test]
236 fn multi_point_primary_is_deepest() {
237 let mut m = ContactManifold::<3>::single(
238 BodyHandle(0), BodyHandle(1),
239 SVector::from([0.0, 1.0, 0.0]),
240 SVector::from([1.0, 0.0, 0.0]),
241 0.3,
242 );
243 m.points.push(ContactPoint {
244 position: SVector::from([2.0, 0.0, 0.0]),
245 depth: 0.7,
246 lambda: 0.0,
247 });
248 assert_eq!(m.points.len(), 2);
249 assert!((m.depth() - 0.7).abs() < 1e-12, "primary should be deepest");
250 }
251
252 #[test]
253 fn contact_cache_store_and_lookup() {
254 let mut cache = ContactCache::<3>::new();
255 let pt = SVector::from([1.0, 0.0, 0.0]);
256 cache.store(BodyHandle(0), BodyHandle(1), pt, 5.0, 1.0);
257
258 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
259 assert!(hit.is_some());
260 assert!((hit.unwrap().normal_impulse - 5.0).abs() < 1e-12);
261 }
262
263 #[test]
264 fn contact_cache_proximity_match() {
265 let mut cache = ContactCache::<3>::new();
266 cache.store(BodyHandle(0), BodyHandle(1), SVector::from([1.0, 0.0, 0.0]), 5.0, 1.0);
267
268 let nearby = SVector::from([1.5, 0.0, 0.0]);
270 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &nearby);
271 assert!(hit.is_some(), "nearby point should match cached contact");
272
273 let far = SVector::from([100.0, 0.0, 0.0]);
275 let miss = cache.lookup(BodyHandle(0), BodyHandle(1), &far);
276 assert!(miss.is_none(), "far point should not match");
277 }
278
279 #[test]
280 fn contact_cache_symmetric_keys() {
281 let mut cache = ContactCache::<3>::new();
282 let pt = SVector::from([0.0, 0.0, 0.0]);
283 cache.store(BodyHandle(1), BodyHandle(0), pt, 3.0, 0.5);
284
285 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
287 assert!(hit.is_some(), "cache should be symmetric on body order");
288 }
289
290 #[test]
291 fn contact_cache_begin_frame_clears() {
292 let mut cache = ContactCache::<3>::new();
293 cache.store(BodyHandle(0), BodyHandle(1), SVector::zeros(), 1.0, 0.0);
294 assert_eq!(cache.pair_count(), 1);
295
296 cache.begin_frame();
297 assert_eq!(cache.pair_count(), 0);
298 }
299}