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}
20
21#[derive(Clone, Debug)]
23pub struct CollisionEvent<const D: usize> {
24 pub body_a: BodyHandle,
25 pub body_b: BodyHandle,
26 pub impulse: f64,
28 pub normal: SVector<f64, D>,
30 pub depth: f64,
32}
33
34#[derive(Clone, Debug)]
36pub struct SensorEvent {
37 pub sensor: BodyHandle,
39 pub other: BodyHandle,
41}
42
43#[derive(Clone, Debug)]
48pub struct ContactManifold<const D: usize> {
49 pub body_a: BodyHandle,
51 pub body_b: BodyHandle,
53 pub normal: SVector<f64, D>,
55 pub points: ArrayVec<ContactPoint<D>, MAX_CONTACTS>,
57}
58
59impl<const D: usize> ContactManifold<D> {
60 pub fn single(
62 body_a: BodyHandle,
63 body_b: BodyHandle,
64 normal: SVector<f64, D>,
65 point: SVector<f64, D>,
66 depth: f64,
67 ) -> Self {
68 let mut points = ArrayVec::new();
69 points.push(ContactPoint { position: point, depth });
70 Self {
71 body_a,
72 body_b,
73 normal,
74 points,
75 }
76 }
77
78 pub fn primary_point(&self) -> &ContactPoint<D> {
80 self.points
81 .iter()
82 .max_by(|a, b| a.depth.partial_cmp(&b.depth).unwrap())
83 .unwrap_or(&self.points[0])
84 }
85
86 pub fn depth(&self) -> f64 {
88 self.primary_point().depth
89 }
90
91 pub fn point(&self) -> SVector<f64, D> {
93 self.primary_point().position
94 }
95
96 pub fn impulse_magnitude(
100 &self,
101 relative_velocity: &SVector<f64, D>,
102 inv_mass_a: f64,
103 inv_mass_b: f64,
104 restitution: f64,
105 ) -> f64 {
106 let v_rel_n = relative_velocity.dot(&self.normal);
107
108 if v_rel_n > 0.0 {
110 return 0.0;
111 }
112
113 let denom = inv_mass_a + inv_mass_b;
114 if denom < 1e-15 {
115 return 0.0;
116 }
117
118 -(1.0 + restitution) * v_rel_n / denom
119 }
120}
121
122#[derive(Clone, Debug)]
124pub struct CachedImpulse<const D: usize> {
125 pub normal_impulse: f64,
127 pub tangent_impulse: f64,
129 pub point: SVector<f64, D>,
131}
132
133pub struct ContactCache<const D: usize> {
138 entries: std::collections::BTreeMap<(BodyHandle, BodyHandle), Vec<CachedImpulse<D>>>,
139 pub match_threshold: f64,
141}
142
143impl<const D: usize> ContactCache<D> {
144 pub fn new() -> Self {
145 Self {
146 entries: std::collections::BTreeMap::new(),
147 match_threshold: 2.0,
148 }
149 }
150
151 pub fn store(
153 &mut self,
154 body_a: BodyHandle,
155 body_b: BodyHandle,
156 point: SVector<f64, D>,
157 normal_impulse: f64,
158 tangent_impulse: f64,
159 ) {
160 let key = if body_a < body_b {
161 (body_a, body_b)
162 } else {
163 (body_b, body_a)
164 };
165 let entry = self.entries.entry(key).or_insert_with(Vec::new);
166 entry.push(CachedImpulse {
167 normal_impulse,
168 tangent_impulse,
169 point,
170 });
171 }
172
173 pub fn lookup(
177 &self,
178 body_a: BodyHandle,
179 body_b: BodyHandle,
180 point: &SVector<f64, D>,
181 ) -> Option<&CachedImpulse<D>> {
182 let key = if body_a < body_b {
183 (body_a, body_b)
184 } else {
185 (body_b, body_a)
186 };
187 let entries = self.entries.get(&key)?;
188 entries
190 .iter()
191 .filter(|c| (c.point - point).norm() < self.match_threshold)
192 .min_by(|a, b| {
193 let da = (a.point - point).norm();
194 let db = (b.point - point).norm();
195 da.partial_cmp(&db).unwrap()
196 })
197 }
198
199 pub fn begin_frame(&mut self) {
201 self.entries.clear();
202 }
203
204 pub fn pair_count(&self) -> usize {
206 self.entries.len()
207 }
208}
209
210impl<const D: usize> Default for ContactCache<D> {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn single_point_manifold() {
222 let m = ContactManifold::<3>::single(
223 BodyHandle(0), BodyHandle(1),
224 SVector::from([0.0, 1.0, 0.0]),
225 SVector::from([1.0, 0.0, 0.0]),
226 0.5,
227 );
228 assert_eq!(m.points.len(), 1);
229 assert!((m.depth() - 0.5).abs() < 1e-12);
230 }
231
232 #[test]
233 fn multi_point_primary_is_deepest() {
234 let mut m = ContactManifold::<3>::single(
235 BodyHandle(0), BodyHandle(1),
236 SVector::from([0.0, 1.0, 0.0]),
237 SVector::from([1.0, 0.0, 0.0]),
238 0.3,
239 );
240 m.points.push(ContactPoint {
241 position: SVector::from([2.0, 0.0, 0.0]),
242 depth: 0.7,
243 });
244 assert_eq!(m.points.len(), 2);
245 assert!((m.depth() - 0.7).abs() < 1e-12, "primary should be deepest");
246 }
247
248 #[test]
249 fn contact_cache_store_and_lookup() {
250 let mut cache = ContactCache::<3>::new();
251 let pt = SVector::from([1.0, 0.0, 0.0]);
252 cache.store(BodyHandle(0), BodyHandle(1), pt, 5.0, 1.0);
253
254 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
255 assert!(hit.is_some());
256 assert!((hit.unwrap().normal_impulse - 5.0).abs() < 1e-12);
257 }
258
259 #[test]
260 fn contact_cache_proximity_match() {
261 let mut cache = ContactCache::<3>::new();
262 cache.store(BodyHandle(0), BodyHandle(1), SVector::from([1.0, 0.0, 0.0]), 5.0, 1.0);
263
264 let nearby = SVector::from([1.5, 0.0, 0.0]);
266 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &nearby);
267 assert!(hit.is_some(), "nearby point should match cached contact");
268
269 let far = SVector::from([100.0, 0.0, 0.0]);
271 let miss = cache.lookup(BodyHandle(0), BodyHandle(1), &far);
272 assert!(miss.is_none(), "far point should not match");
273 }
274
275 #[test]
276 fn contact_cache_symmetric_keys() {
277 let mut cache = ContactCache::<3>::new();
278 let pt = SVector::from([0.0, 0.0, 0.0]);
279 cache.store(BodyHandle(1), BodyHandle(0), pt, 3.0, 0.5);
280
281 let hit = cache.lookup(BodyHandle(0), BodyHandle(1), &pt);
283 assert!(hit.is_some(), "cache should be symmetric on body order");
284 }
285
286 #[test]
287 fn contact_cache_begin_frame_clears() {
288 let mut cache = ContactCache::<3>::new();
289 cache.store(BodyHandle(0), BodyHandle(1), SVector::zeros(), 1.0, 0.0);
290 assert_eq!(cache.pair_count(), 1);
291
292 cache.begin_frame();
293 assert_eq!(cache.pair_count(), 0);
294 }
295}