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 {
73 position: point,
74 depth,
75 lambda: 0.0,
76 });
77 Self {
78 body_a,
79 body_b,
80 normal,
81 points,
82 }
83 }
84
85 pub fn primary_point(&self) -> &ContactPoint<D> {
87 self.points
88 .iter()
89 .max_by(|a, b| a.depth.total_cmp(&b.depth))
90 .unwrap_or(&self.points[0])
91 }
92
93 pub fn depth(&self) -> f64 {
95 self.primary_point().depth
96 }
97
98 pub fn point(&self) -> SVector<f64, D> {
100 self.primary_point().position
101 }
102
103 pub fn impulse_magnitude(
107 &self,
108 relative_velocity: &SVector<f64, D>,
109 inv_mass_a: f64,
110 inv_mass_b: f64,
111 restitution: f64,
112 ) -> f64 {
113 let v_rel_n = relative_velocity.dot(&self.normal);
114
115 if v_rel_n > 0.0 {
117 return 0.0;
118 }
119
120 let denom = inv_mass_a + inv_mass_b;
121 if denom < 1e-15 {
122 return 0.0;
123 }
124
125 -(1.0 + restitution) * v_rel_n / denom
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct CachedImpulse<const D: usize> {
132 pub normal_impulse: f64,
134 pub tangent_impulse: f64,
136 pub point: SVector<f64, D>,
138}
139
140pub struct ContactCache<const D: usize> {
145 entries: std::collections::BTreeMap<(BodyHandle, BodyHandle), Vec<CachedImpulse<D>>>,
146 pub match_threshold: f64,
148}
149
150impl<const D: usize> ContactCache<D> {
151 pub fn new() -> Self {
152 Self {
153 entries: std::collections::BTreeMap::new(),
154 match_threshold: 2.0,
155 }
156 }
157
158 pub fn store(
160 &mut self,
161 body_a: BodyHandle,
162 body_b: BodyHandle,
163 point: SVector<f64, D>,
164 normal_impulse: f64,
165 tangent_impulse: f64,
166 ) {
167 let key = if body_a < body_b {
168 (body_a, body_b)
169 } else {
170 (body_b, body_a)
171 };
172 let entry = self.entries.entry(key).or_default();
173 entry.push(CachedImpulse {
174 normal_impulse,
175 tangent_impulse,
176 point,
177 });
178 }
179
180 pub fn lookup(
184 &self,
185 body_a: BodyHandle,
186 body_b: BodyHandle,
187 point: &SVector<f64, D>,
188 ) -> Option<&CachedImpulse<D>> {
189 let key = if body_a < body_b {
190 (body_a, body_b)
191 } else {
192 (body_b, body_a)
193 };
194 let entries = self.entries.get(&key)?;
195 entries
197 .iter()
198 .filter(|c| (c.point - point).norm() < self.match_threshold)
199 .min_by(|a, b| {
200 let da = (a.point - point).norm();
201 let db = (b.point - point).norm();
202 da.total_cmp(&db)
203 })
204 }
205
206 pub fn begin_frame(&mut self) {
208 self.entries.clear();
209 }
210
211 pub fn pair_count(&self) -> usize {
213 self.entries.len()
214 }
215}
216
217impl<const D: usize> Default for ContactCache<D> {
218 fn default() -> Self {
219 Self::new()
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn single_point_manifold() {
229 let m = ContactManifold::<3>::single(
230 BodyHandle(0),
231 BodyHandle(1),
232 SVector::from([0.0, 1.0, 0.0]),
233 SVector::from([1.0, 0.0, 0.0]),
234 0.5,
235 );
236 assert_eq!(m.points.len(), 1);
237 assert!((m.depth() - 0.5).abs() < 1e-12);
238 }
239
240 #[test]
241 fn multi_point_primary_is_deepest() {
242 let mut m = ContactManifold::<3>::single(
243 BodyHandle(0),
244 BodyHandle(1),
245 SVector::from([0.0, 1.0, 0.0]),
246 SVector::from([1.0, 0.0, 0.0]),
247 0.3,
248 );
249 m.points.push(ContactPoint {
250 position: SVector::from([2.0, 0.0, 0.0]),
251 depth: 0.7,
252 lambda: 0.0,
253 });
254 assert_eq!(m.points.len(), 2);
255 assert!((m.depth() - 0.7).abs() < 1e-12, "primary should be deepest");
256 }
257
258 #[test]
259 fn contact_cache_store_and_lookup() {
260 let mut cache = ContactCache::<3>::new();
261 let pt = SVector::from([1.0, 0.0, 0.0]);
262 cache.store(BodyHandle(0), BodyHandle(1), pt, 5.0, 1.0);
263
264 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
265 assert!(hit.is_some());
266 assert!((hit.unwrap().normal_impulse - 5.0).abs() < 1e-12);
267 }
268
269 #[test]
270 fn contact_cache_proximity_match() {
271 let mut cache = ContactCache::<3>::new();
272 cache.store(
273 BodyHandle(0),
274 BodyHandle(1),
275 SVector::from([1.0, 0.0, 0.0]),
276 5.0,
277 1.0,
278 );
279
280 let nearby = SVector::from([1.5, 0.0, 0.0]);
282 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &nearby);
283 assert!(hit.is_some(), "nearby point should match cached contact");
284
285 let far = SVector::from([100.0, 0.0, 0.0]);
287 let miss = cache.lookup(BodyHandle(0), BodyHandle(1), &far);
288 assert!(miss.is_none(), "far point should not match");
289 }
290
291 #[test]
292 fn contact_cache_symmetric_keys() {
293 let mut cache = ContactCache::<3>::new();
294 let pt = SVector::from([0.0, 0.0, 0.0]);
295 cache.store(BodyHandle(1), BodyHandle(0), pt, 3.0, 0.5);
296
297 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
299 assert!(hit.is_some(), "cache should be symmetric on body order");
300 }
301
302 #[test]
303 fn contact_cache_begin_frame_clears() {
304 let mut cache = ContactCache::<3>::new();
305 cache.store(BodyHandle(0), BodyHandle(1), SVector::zeros(), 1.0, 0.0);
306 assert_eq!(cache.pair_count(), 1);
307
308 cache.begin_frame();
309 assert_eq!(cache.pair_count(), 0);
310 }
311}