1use crate::collider::Contact;
15use crate::math::{Fix128, Vec3Fix};
16
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20#[cfg(feature = "std")]
21use crate::det_hash::DetHashMap;
22
23pub(crate) const MAX_MANIFOLD_POINTS: usize = 4;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32#[non_exhaustive]
33pub struct CachedContactPoint {
34 pub local_point_a: Vec3Fix,
36 pub local_point_b: Vec3Fix,
38 pub normal: Vec3Fix,
41 pub depth: Fix128,
43 pub lambda_n: Fix128,
45 pub lambda_t1: Fix128,
47 pub lambda_t2: Fix128,
49 pub age: u32,
51}
52
53impl CachedContactPoint {
54 #[must_use]
56 pub const fn new(local_a: Vec3Fix, local_b: Vec3Fix, normal: Vec3Fix, depth: Fix128) -> Self {
57 Self {
58 local_point_a: local_a,
59 local_point_b: local_b,
60 normal,
61 depth,
62 lambda_n: Fix128::ZERO,
63 lambda_t1: Fix128::ZERO,
64 lambda_t2: Fix128::ZERO,
65 age: 0,
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72pub struct BodyPairKey {
73 pub body_a: u32,
75 pub body_b: u32,
77}
78
79impl BodyPairKey {
80 #[inline]
82 #[must_use]
83 pub const fn new(a: usize, b: usize) -> Self {
84 if a < b {
85 Self {
86 body_a: a as u32,
87 body_b: b as u32,
88 }
89 } else {
90 Self {
91 body_a: b as u32,
92 body_b: a as u32,
93 }
94 }
95 }
96}
97
98#[derive(Clone, Debug)]
103#[non_exhaustive]
104pub struct ContactManifold {
105 pub pair: BodyPairKey,
107 pub points: Vec<CachedContactPoint>,
109 pub normal: Vec3Fix,
111 pub friction: Fix128,
113 pub restitution: Fix128,
115 pub stale_frames: u32,
117}
118
119impl ContactManifold {
120 #[must_use]
122 pub fn new(pair: BodyPairKey, friction: Fix128, restitution: Fix128) -> Self {
123 Self {
124 pair,
125 points: Vec::with_capacity(MAX_MANIFOLD_POINTS),
126 normal: Vec3Fix::ZERO,
127 friction,
128 restitution,
129 stale_frames: 0,
130 }
131 }
132
133 pub fn add_or_update(&mut self, contact: &Contact, local_a: Vec3Fix, local_b: Vec3Fix) {
138 let threshold_sq = Fix128::from_ratio(1, 10000);
141
142 let mut best_match: Option<usize> = None;
144 let mut best_dist_sq = threshold_sq;
145
146 for (i, existing) in self.points.iter().enumerate() {
147 let dist_sq = (existing.local_point_a - local_a).length_squared();
148 if dist_sq < best_dist_sq {
149 best_dist_sq = dist_sq;
150 best_match = Some(i);
151 }
152 }
153
154 if let Some(idx) = best_match {
155 let lambda_n = self.points[idx].lambda_n;
157 let lambda_t1 = self.points[idx].lambda_t1;
158 let lambda_t2 = self.points[idx].lambda_t2;
159 let age = self.points[idx].age;
160
161 self.points[idx] =
162 CachedContactPoint::new(local_a, local_b, contact.normal, contact.depth);
163 self.points[idx].lambda_n = lambda_n;
164 self.points[idx].lambda_t1 = lambda_t1;
165 self.points[idx].lambda_t2 = lambda_t2;
166 self.points[idx].age = age + 1;
167 } else if self.points.len() < MAX_MANIFOLD_POINTS {
168 self.points.push(CachedContactPoint::new(
170 local_a,
171 local_b,
172 contact.normal,
173 contact.depth,
174 ));
175 } else {
176 let mut shallowest_idx = 0;
178 let mut shallowest_depth = self.points[0].depth;
179 for (i, p) in self.points.iter().enumerate().skip(1) {
180 if p.depth < shallowest_depth {
181 shallowest_depth = p.depth;
182 shallowest_idx = i;
183 }
184 }
185 if contact.depth > shallowest_depth {
186 self.points[shallowest_idx] =
187 CachedContactPoint::new(local_a, local_b, contact.normal, contact.depth);
188 }
189 }
190
191 self.update_normal();
193 self.stale_frames = 0;
194 }
195
196 fn update_normal(&mut self) {
198 if self.points.is_empty() {
199 self.normal = Vec3Fix::ZERO;
200 return;
201 }
202
203 let mut sum = Vec3Fix::ZERO;
204 for p in &self.points {
205 sum = sum + p.normal;
206 }
207 let len = sum.length();
208 if !len.is_zero() {
209 self.normal = sum / len;
210 }
211 }
212
213 #[inline]
215 #[must_use]
216 pub fn point_count(&self) -> usize {
217 self.points.len()
218 }
219
220 #[inline]
222 #[must_use]
223 pub fn is_empty(&self) -> bool {
224 self.points.is_empty()
225 }
226
227 pub fn clear(&mut self) {
229 self.points.clear();
230 self.normal = Vec3Fix::ZERO;
231 }
232
233 #[must_use]
235 pub fn warm_start_impulse(&self, point_idx: usize) -> (Fix128, Fix128, Fix128) {
236 if point_idx < self.points.len() {
237 let p = &self.points[point_idx];
238 (p.lambda_n, p.lambda_t1, p.lambda_t2)
239 } else {
240 (Fix128::ZERO, Fix128::ZERO, Fix128::ZERO)
241 }
242 }
243
244 pub fn store_impulses(
246 &mut self,
247 point_idx: usize,
248 lambda_n: Fix128,
249 lambda_t1: Fix128,
250 lambda_t2: Fix128,
251 ) {
252 if point_idx < self.points.len() {
253 self.points[point_idx].lambda_n = lambda_n;
254 self.points[point_idx].lambda_t1 = lambda_t1;
255 self.points[point_idx].lambda_t2 = lambda_t2;
256 }
257 }
258}
259
260pub struct ContactCache {
262 pub manifolds: Vec<ContactManifold>,
264 #[cfg(feature = "std")]
266 pub(crate) pair_index: DetHashMap<BodyPairKey, usize>,
267 pub max_stale_frames: u32,
269 pub warm_start_factor: Fix128,
271}
272
273impl ContactCache {
274 #[must_use]
276 pub fn new() -> Self {
277 Self {
278 manifolds: Vec::new(),
279 #[cfg(feature = "std")]
280 pair_index: DetHashMap::default(),
281 max_stale_frames: 3,
282 warm_start_factor: Fix128::from_ratio(8, 10), }
284 }
285
286 pub fn get_or_create(
292 &mut self,
293 pair: BodyPairKey,
294 friction: Fix128,
295 restitution: Fix128,
296 ) -> &mut ContactManifold {
297 let pos = self.locate(&pair);
304 #[cfg(feature = "std")]
305 if let Some(idx) = pos {
306 self.pair_index.insert(pair, idx);
307 }
308
309 if let Some(idx) = pos {
310 &mut self.manifolds[idx]
311 } else {
312 #[cfg(feature = "std")]
313 {
314 let idx = self.manifolds.len();
315 self.manifolds
316 .push(ContactManifold::new(pair, friction, restitution));
317 self.pair_index.insert(pair, idx);
318 self.manifolds
319 .last_mut()
320 .expect("just pushed, cannot be empty")
321 }
322 #[cfg(not(feature = "std"))]
323 {
324 self.manifolds
325 .push(ContactManifold::new(pair, friction, restitution));
326 self.manifolds
327 .last_mut()
328 .expect("just pushed, cannot be empty")
329 }
330 }
331 }
332
333 #[must_use]
340 pub fn find(&self, pair: &BodyPairKey) -> Option<&ContactManifold> {
341 self.locate(pair).map(|idx| &self.manifolds[idx])
342 }
343
344 fn locate(&self, pair: &BodyPairKey) -> Option<usize> {
351 #[cfg(feature = "std")]
352 {
353 let idx = *self.pair_index.get(pair)?;
354 if self.manifolds.get(idx).is_some_and(|m| m.pair == *pair) {
355 return Some(idx);
356 }
357 }
358 self.manifolds.iter().position(|m| m.pair == *pair)
359 }
360
361 pub fn begin_frame(&mut self) {
363 for manifold in &mut self.manifolds {
364 manifold.stale_frames += 1;
365 }
366 }
367
368 pub fn end_frame(&mut self) {
370 let max_stale = self.max_stale_frames;
371 self.manifolds.retain(|m| m.stale_frames <= max_stale);
372
373 #[cfg(feature = "std")]
375 {
376 self.pair_index.clear();
377 for (idx, manifold) in self.manifolds.iter().enumerate() {
378 self.pair_index.insert(manifold.pair, idx);
379 }
380 }
381 }
382
383 #[inline]
385 #[must_use]
386 pub fn manifold_count(&self) -> usize {
387 self.manifolds.len()
388 }
389
390 pub fn total_contact_points(&self) -> usize {
392 self.manifolds
393 .iter()
394 .map(ContactManifold::point_count)
395 .sum()
396 }
397
398 pub fn clear(&mut self) {
400 self.manifolds.clear();
401 #[cfg(feature = "std")]
402 self.pair_index.clear();
403 }
404
405 pub fn apply_warm_start(&self, bodies: &mut [crate::solver::RigidBody]) {
410 let factor = self.warm_start_factor;
411
412 for manifold in &self.manifolds {
413 let a_idx = manifold.pair.body_a as usize;
414 let b_idx = manifold.pair.body_b as usize;
415
416 if a_idx >= bodies.len() || b_idx >= bodies.len() {
417 continue;
418 }
419
420 for point in &manifold.points {
421 let impulse_n = manifold.normal * (point.lambda_n * factor);
422
423 let (t1, t2) = tangent_frame(manifold.normal);
425 let impulse_t = t1 * (point.lambda_t1 * factor) + t2 * (point.lambda_t2 * factor);
426
427 let total_impulse = impulse_n + impulse_t;
428
429 if !bodies[a_idx].inv_mass.is_zero() {
430 bodies[a_idx].velocity =
431 bodies[a_idx].velocity + total_impulse * bodies[a_idx].inv_mass;
432 }
433 if !bodies[b_idx].inv_mass.is_zero() {
434 bodies[b_idx].velocity =
435 bodies[b_idx].velocity - total_impulse * bodies[b_idx].inv_mass;
436 }
437 }
438 }
439 }
440}
441
442impl Default for ContactCache {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448impl core::fmt::Debug for ContactCache {
449 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
450 let mut s = f.debug_struct("ContactCache");
451 s.field(
452 "manifolds",
453 &format_args!("[{} items]", self.manifolds.len()),
454 );
455 #[cfg(feature = "std")]
456 s.field(
457 "pair_index",
458 &format_args!("[{} items]", self.pair_index.len()),
459 );
460 s.field("max_stale_frames", &self.max_stale_frames);
461 s.field("warm_start_factor", &self.warm_start_factor);
462 s.finish()
463 }
464}
465
466#[must_use]
468pub(crate) fn tangent_frame(normal: Vec3Fix) -> (Vec3Fix, Vec3Fix) {
469 let abs_x = normal.x.abs();
471 let abs_y = normal.y.abs();
472 let abs_z = normal.z.abs();
473
474 let reference = if abs_x <= abs_y && abs_x <= abs_z {
475 Vec3Fix::UNIT_X
476 } else if abs_y <= abs_z {
477 Vec3Fix::UNIT_Y
478 } else {
479 Vec3Fix::UNIT_Z
480 };
481
482 let t1 = normal.cross(reference).normalize();
483 let t2 = normal.cross(t1);
484 (t1, t2)
485}
486
487#[cfg(all(test, feature = "std"))]
488mod tests {
489 use super::*;
490 use crate::solver::RigidBody;
491
492 #[test]
493 fn test_body_pair_key_canonical() {
494 let k1 = BodyPairKey::new(3, 7);
495 let k2 = BodyPairKey::new(7, 3);
496 assert_eq!(k1, k2);
497 assert_eq!(k1.body_a, 3);
498 assert_eq!(k1.body_b, 7);
499 }
500
501 #[test]
502 fn test_manifold_add_point() {
503 let pair = BodyPairKey::new(0, 1);
504 let mut manifold =
505 ContactManifold::new(pair, Fix128::from_ratio(3, 10), Fix128::from_ratio(2, 10));
506
507 let contact = Contact {
508 depth: Fix128::from_ratio(1, 10),
509 normal: Vec3Fix::UNIT_Y,
510 point_a: Vec3Fix::ZERO,
511 point_b: Vec3Fix::ZERO,
512 };
513
514 manifold.add_or_update(&contact, Vec3Fix::ZERO, Vec3Fix::ZERO);
515 assert_eq!(manifold.point_count(), 1);
516 }
517
518 #[test]
519 fn test_manifold_max_points() {
520 let pair = BodyPairKey::new(0, 1);
521 let mut manifold =
522 ContactManifold::new(pair, Fix128::from_ratio(3, 10), Fix128::from_ratio(2, 10));
523
524 for i in 0..5 {
526 let contact = Contact {
527 depth: Fix128::from_int(i as i64 + 1),
528 normal: Vec3Fix::UNIT_Y,
529 point_a: Vec3Fix::from_int(i as i64 * 10, 0, 0),
530 point_b: Vec3Fix::from_int(i as i64 * 10, 0, 0),
531 };
532 manifold.add_or_update(
533 &contact,
534 Vec3Fix::from_int(i as i64 * 10, 0, 0),
535 Vec3Fix::from_int(i as i64 * 10, 0, 0),
536 );
537 }
538
539 assert!(manifold.point_count() <= MAX_MANIFOLD_POINTS);
540 }
541
542 #[test]
543 fn test_manifold_warm_start_preserved() {
544 let pair = BodyPairKey::new(0, 1);
545 let mut manifold =
546 ContactManifold::new(pair, Fix128::from_ratio(3, 10), Fix128::from_ratio(2, 10));
547
548 let contact = Contact {
549 depth: Fix128::from_ratio(1, 10),
550 normal: Vec3Fix::UNIT_Y,
551 point_a: Vec3Fix::ZERO,
552 point_b: Vec3Fix::ZERO,
553 };
554 manifold.add_or_update(&contact, Vec3Fix::ZERO, Vec3Fix::ZERO);
555
556 manifold.store_impulses(0, Fix128::from_int(5), Fix128::ONE, Fix128::ONE);
558
559 manifold.add_or_update(&contact, Vec3Fix::ZERO, Vec3Fix::ZERO);
561 let (ln, lt1, lt2) = manifold.warm_start_impulse(0);
562 assert_eq!(ln.hi, 5);
563 assert_eq!(lt1.hi, 1);
564 assert_eq!(lt2.hi, 1);
565 }
566
567 #[test]
568 fn test_contact_cache_lifecycle() {
569 let mut cache = ContactCache::new();
570
571 let pair = BodyPairKey::new(0, 1);
572 {
573 let manifold =
574 cache.get_or_create(pair, Fix128::from_ratio(3, 10), Fix128::from_ratio(2, 10));
575 let contact = Contact {
576 depth: Fix128::from_ratio(1, 10),
577 normal: Vec3Fix::UNIT_Y,
578 point_a: Vec3Fix::ZERO,
579 point_b: Vec3Fix::ZERO,
580 };
581 manifold.add_or_update(&contact, Vec3Fix::ZERO, Vec3Fix::ZERO);
582 }
583
584 assert_eq!(cache.manifold_count(), 1);
585 assert_eq!(cache.total_contact_points(), 1);
586
587 for _ in 0..5 {
589 cache.begin_frame();
590 cache.end_frame();
591 }
592
593 assert_eq!(cache.manifold_count(), 0);
594 }
595
596 #[test]
597 fn test_tangent_frame() {
598 let (t1, t2) = tangent_frame(Vec3Fix::UNIT_Y);
599
600 let dot_n_t1 = Vec3Fix::UNIT_Y.dot(t1);
602 let dot_n_t2 = Vec3Fix::UNIT_Y.dot(t2);
603 let dot_t1_t2 = t1.dot(t2);
604
605 assert!(dot_n_t1.abs() < Fix128::from_ratio(1, 100));
606 assert!(dot_n_t2.abs() < Fix128::from_ratio(1, 100));
607 assert!(dot_t1_t2.abs() < Fix128::from_ratio(1, 100));
608 }
609
610 #[test]
611 fn test_warm_start_application() {
612 let mut cache = ContactCache::new();
613 let pair = BodyPairKey::new(0, 1);
614
615 {
616 let manifold =
617 cache.get_or_create(pair, Fix128::from_ratio(3, 10), Fix128::from_ratio(2, 10));
618 let contact = Contact {
619 depth: Fix128::from_ratio(1, 10),
620 normal: Vec3Fix::UNIT_Y,
621 point_a: Vec3Fix::ZERO,
622 point_b: Vec3Fix::ZERO,
623 };
624 manifold.add_or_update(&contact, Vec3Fix::ZERO, Vec3Fix::ZERO);
625 manifold.store_impulses(0, Fix128::from_int(10), Fix128::ZERO, Fix128::ZERO);
626 }
627
628 let mut bodies = vec![
629 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
630 RigidBody::new_dynamic(Vec3Fix::from_int(0, 1, 0), Fix128::ONE),
631 ];
632
633 cache.apply_warm_start(&mut bodies);
634
635 assert!(bodies[0].velocity.y > Fix128::ZERO);
637 assert!(bodies[1].velocity.y < Fix128::ZERO);
638 }
639
640 fn fi(n: i64) -> Fix128 {
643 Fix128::from_int(n)
644 }
645
646 fn v3i(x: i64, y: i64, z: i64) -> Vec3Fix {
647 Vec3Fix::from_int(x, y, z)
648 }
649
650 fn contact(normal: Vec3Fix, depth: Fix128) -> Contact {
651 Contact {
652 depth,
653 normal,
654 point_a: Vec3Fix::ZERO,
655 point_b: Vec3Fix::ZERO,
656 }
657 }
658
659 fn manifold() -> ContactManifold {
660 ContactManifold::new(
661 BodyPairKey::new(0, 1),
662 Fix128::from_ratio(1, 2),
663 Fix128::ZERO,
664 )
665 }
666
667 #[test]
668 fn manifold_add_matches_within_1cm_and_preserves_impulses() {
669 let mut m = manifold();
670 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(1)), v3i(1, 0, 0), v3i(1, 1, 0));
671 assert_eq!(m.point_count(), 1);
672 assert!(!m.is_empty());
673 m.store_impulses(0, fi(5), fi(6), fi(7));
674 let near = Vec3Fix::new(
676 Fix128::ONE + Fix128::from_ratio(1, 200),
677 Fix128::ZERO,
678 Fix128::ZERO,
679 );
680 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(2)), near, v3i(1, 1, 0));
681 assert_eq!(m.point_count(), 1);
682 assert_eq!(m.warm_start_impulse(0), (fi(5), fi(6), fi(7)));
683 assert_eq!(m.points[0].age, 1);
684 assert_eq!(m.points[0].depth, fi(2));
685 assert_eq!(m.points[0].local_point_a, near);
686 let edge = Vec3Fix::new(
688 Fix128::ONE + Fix128::from_ratio(5, 200),
689 Fix128::ZERO,
690 Fix128::ZERO,
691 );
692 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(1)), edge, v3i(1, 1, 0));
693 assert_eq!(m.point_count(), 2);
694 assert_eq!(
695 m.warm_start_impulse(1),
696 (Fix128::ZERO, Fix128::ZERO, Fix128::ZERO)
697 );
698 assert_eq!(
700 m.warm_start_impulse(9),
701 (Fix128::ZERO, Fix128::ZERO, Fix128::ZERO)
702 );
703 m.store_impulses(9, fi(1), fi(1), fi(1));
704 assert_eq!(m.point_count(), 2);
705 m.clear();
706 assert!(m.is_empty());
707 assert_eq!(m.point_count(), 0);
708 assert_eq!(m.normal, Vec3Fix::ZERO);
709 }
710
711 #[test]
712 fn manifold_picks_nearest_of_several_candidates() {
713 let mut m = manifold();
714 m.add_or_update(
715 &contact(Vec3Fix::UNIT_Y, fi(1)),
716 v3i(0, 0, 0),
717 Vec3Fix::ZERO,
718 );
719 m.add_or_update(
720 &contact(Vec3Fix::UNIT_Y, fi(1)),
721 v3i(5, 0, 0),
722 Vec3Fix::ZERO,
723 );
724 m.store_impulses(0, fi(10), Fix128::ZERO, Fix128::ZERO);
725 m.store_impulses(1, fi(20), Fix128::ZERO, Fix128::ZERO);
726 let p = Vec3Fix::new(
728 fi(5) + Fix128::from_ratio(1, 250),
729 Fix128::ZERO,
730 Fix128::ZERO,
731 );
732 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(3)), p, Vec3Fix::ZERO);
733 assert_eq!(m.point_count(), 2);
734 assert_eq!(m.warm_start_impulse(1).0, fi(20));
735 assert_eq!(m.points[1].depth, fi(3));
736 assert_eq!(m.points[0].depth, fi(1));
737 }
738
739 #[test]
740 fn manifold_full_replaces_shallowest_only_if_deeper() {
741 let mut m = manifold();
742 for (i, d) in [3, 1, 4, 2].iter().enumerate() {
743 m.add_or_update(
744 &contact(Vec3Fix::UNIT_Y, fi(*d)),
745 v3i(i as i64 * 10, 0, 0),
746 Vec3Fix::ZERO,
747 );
748 }
749 assert_eq!(m.point_count(), MAX_MANIFOLD_POINTS);
750 m.add_or_update(
752 &contact(Vec3Fix::UNIT_Y, Fix128::from_ratio(1, 2)),
753 v3i(100, 0, 0),
754 Vec3Fix::ZERO,
755 );
756 assert_eq!(m.point_count(), 4);
757 assert!(m.points.iter().all(|p| p.local_point_a.x != fi(100)));
758 m.add_or_update(
760 &contact(Vec3Fix::UNIT_Y, fi(1)),
761 v3i(100, 0, 0),
762 Vec3Fix::ZERO,
763 );
764 assert!(m.points.iter().all(|p| p.local_point_a.x != fi(100)));
765 m.add_or_update(
767 &contact(Vec3Fix::UNIT_Y, Fix128::from_ratio(5, 2)),
768 v3i(100, 0, 0),
769 Vec3Fix::ZERO,
770 );
771 assert_eq!(m.point_count(), 4);
772 assert_eq!(m.points[1].local_point_a, v3i(100, 0, 0));
773 assert_eq!(m.points[1].depth, Fix128::from_ratio(5, 2));
774 let depths: Vec<Fix128> = m.points.iter().map(|p| p.depth).collect();
775 assert_eq!(depths, vec![fi(3), Fix128::from_ratio(5, 2), fi(4), fi(2)]);
776 }
777
778 #[test]
779 fn manifold_normal_is_normalized_average_of_point_normals() {
780 let mut m = manifold();
781 m.add_or_update(&contact(v3i(3, 0, 0), fi(1)), v3i(0, 0, 0), Vec3Fix::ZERO);
782 assert_eq!(m.normal, Vec3Fix::UNIT_X); m.add_or_update(&contact(v3i(0, 4, 0), fi(1)), v3i(10, 0, 0), Vec3Fix::ZERO);
784 assert_eq!(
786 m.normal,
787 Vec3Fix::new(fi(3) / fi(5), fi(4) / fi(5), Fix128::ZERO)
788 );
789 let before = m.normal;
791 m.add_or_update(
792 &contact(v3i(-3, -4, 0), fi(1)),
793 v3i(20, 0, 0),
794 Vec3Fix::ZERO,
795 );
796 assert_eq!(m.normal, before);
797 assert_eq!(m.stale_frames, 0);
798 }
799
800 #[test]
801 fn cache_get_or_create_find_frames_and_counts() {
802 let mut cache = ContactCache::new();
803 let k01 = BodyPairKey::new(0, 1);
804 let k23 = BodyPairKey::new(2, 3);
805 cache
806 .get_or_create(k01, Fix128::ONE, Fix128::ZERO)
807 .add_or_update(
808 &contact(Vec3Fix::UNIT_Y, fi(1)),
809 Vec3Fix::ZERO,
810 Vec3Fix::ZERO,
811 );
812 cache
813 .get_or_create(k01, Fix128::ONE, Fix128::ZERO)
814 .add_or_update(
815 &contact(Vec3Fix::UNIT_Y, fi(1)),
816 v3i(5, 0, 0),
817 Vec3Fix::ZERO,
818 );
819 cache
820 .get_or_create(k23, Fix128::ONE, Fix128::ZERO)
821 .add_or_update(
822 &contact(Vec3Fix::UNIT_Y, fi(1)),
823 Vec3Fix::ZERO,
824 Vec3Fix::ZERO,
825 );
826 assert_eq!(cache.manifold_count(), 2, "同じ pair は再利用");
827 assert_eq!(cache.total_contact_points(), 3);
828 assert_eq!(cache.find(&k01).map(ContactManifold::point_count), Some(2));
829 assert_eq!(cache.find(&k23).map(ContactManifold::point_count), Some(1));
830 assert!(cache.find(&BodyPairKey::new(4, 5)).is_none());
831 assert_eq!(BodyPairKey::new(1, 0), k01);
833 assert!(cache.find(&BodyPairKey::new(1, 0)).is_some());
834 for _ in 0..3 {
836 cache.begin_frame();
837 cache
838 .get_or_create(k23, Fix128::ONE, Fix128::ZERO)
839 .add_or_update(
840 &contact(Vec3Fix::UNIT_Y, fi(1)),
841 Vec3Fix::ZERO,
842 Vec3Fix::ZERO,
843 );
844 cache.end_frame();
845 }
846 assert_eq!(cache.manifold_count(), 2, "stale 3 == max 3 は残る");
847 cache.begin_frame();
848 cache.end_frame();
849 assert_eq!(cache.manifold_count(), 1, "stale 4 > 3 で落ちる");
850 assert!(cache.find(&k01).is_none());
851 assert!(cache.find(&k23).is_some(), "index は再構築される");
852 assert_eq!(cache.total_contact_points(), 1);
853 cache.clear();
854 assert_eq!(cache.manifold_count(), 0);
855 assert_eq!(cache.total_contact_points(), 0);
856 assert!(cache.find(&k23).is_none());
857 let dbg = format!("{cache:?}");
858 assert!(
859 dbg.contains("ContactCache") && dbg.contains("max_stale_frames: 3"),
860 "{dbg}"
861 );
862 }
863
864 #[test]
865 fn apply_warm_start_applies_scaled_cached_impulses() {
866 let mut cache = ContactCache::new();
867 cache.warm_start_factor = Fix128::from_ratio(1, 2);
868 let m = cache.get_or_create(BodyPairKey::new(0, 1), Fix128::ONE, Fix128::ZERO);
869 m.add_or_update(
870 &contact(Vec3Fix::UNIT_Z, fi(1)),
871 Vec3Fix::ZERO,
872 Vec3Fix::ZERO,
873 );
874 m.store_impulses(0, fi(4), fi(2), fi(6));
876 let mut bodies = vec![
877 crate::solver::RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
878 crate::solver::RigidBody::new_dynamic(v3i(1, 0, 0), Fix128::ONE),
879 ];
880 bodies[1].inv_mass = fi(3);
881 cache.apply_warm_start(&mut bodies);
882 assert_eq!(bodies[0].velocity, v3i(-3, 1, 2));
884 assert_eq!(bodies[1].velocity, v3i(9, -3, -6));
885 let mut st = vec![
887 crate::solver::RigidBody::new_static(Vec3Fix::ZERO),
888 crate::solver::RigidBody::new_dynamic(v3i(1, 0, 0), Fix128::ONE),
889 ];
890 cache.apply_warm_start(&mut st);
891 assert_eq!(st[0].velocity, Vec3Fix::ZERO);
892 assert_eq!(st[1].velocity, v3i(3, -1, -2));
893 let mut short = vec![crate::solver::RigidBody::new_dynamic(
894 Vec3Fix::ZERO,
895 Fix128::ONE,
896 )];
897 cache.apply_warm_start(&mut short);
898 assert_eq!(short[0].velocity, Vec3Fix::ZERO);
899 }
900
901 #[test]
902 fn tangent_frame_picks_least_parallel_axis_and_is_orthonormal() {
903 assert_eq!(
905 tangent_frame(Vec3Fix::UNIT_X),
906 (Vec3Fix::UNIT_Z, -Vec3Fix::UNIT_Y)
907 );
908 assert_eq!(
910 tangent_frame(Vec3Fix::UNIT_Y),
911 (-Vec3Fix::UNIT_Z, -Vec3Fix::UNIT_X)
912 );
913 assert_eq!(
915 tangent_frame(Vec3Fix::UNIT_Z),
916 (Vec3Fix::UNIT_Y, -Vec3Fix::UNIT_X)
917 );
918 let d = Vec3Fix::new(Fix128::ONE, Fix128::ONE, Fix128::ONE);
920 let (t1, t2) = tangent_frame(d);
921 let expect_t1 = d.cross(Vec3Fix::UNIT_X).normalize();
922 assert_eq!(t1, expect_t1);
923 assert_eq!(t2, d.cross(t1));
924 let n = Vec3Fix::new(
926 Fix128::from_ratio(1, 3),
927 Fix128::from_ratio(-2, 3),
928 Fix128::from_ratio(2, 3),
929 );
930 let (t1, t2) = tangent_frame(n);
931 let small = Fix128 { hi: 0, lo: 1 << 24 };
932 assert!(n.dot(t1).abs() < small && n.dot(t2).abs() < small && t1.dot(t2).abs() < small);
933 assert!((t1.length() - Fix128::ONE).abs() < small);
934 }
935
936 #[test]
937 fn apply_warm_start_factor_scales_normal_and_both_tangents() {
938 let mut cache = ContactCache::new();
939 cache.warm_start_factor = Fix128::from_ratio(1, 4);
940 let m = cache.get_or_create(BodyPairKey::new(0, 1), Fix128::ONE, Fix128::ZERO);
941 m.add_or_update(
942 &contact(Vec3Fix::UNIT_X, fi(1)),
943 Vec3Fix::ZERO,
944 Vec3Fix::ZERO,
945 );
946 m.store_impulses(0, fi(8), fi(12), fi(20));
948 let mut bodies = vec![
949 crate::solver::RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
950 crate::solver::RigidBody::new_static(v3i(1, 0, 0)),
951 ];
952 cache.apply_warm_start(&mut bodies);
953 assert_eq!(bodies[0].velocity, v3i(2, -5, 3));
955 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
956 }
957
958 #[test]
963 fn manifold_match_tie_prefers_first_candidate() {
964 let mut m = manifold();
965 m.add_or_update(
967 &contact(Vec3Fix::UNIT_Y, fi(1)),
968 Vec3Fix::ZERO,
969 Vec3Fix::ZERO,
970 );
971 let b = Vec3Fix::new(Fix128::from_ratio(1, 64), Fix128::ZERO, Fix128::ZERO);
972 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(2)), b, Vec3Fix::ZERO);
973 assert_eq!(m.point_count(), 2);
974 let mid = Vec3Fix::new(Fix128::from_ratio(1, 128), Fix128::ZERO, Fix128::ZERO);
976 let d_a = (m.points[0].local_point_a - mid).length_squared();
977 let d_b = (m.points[1].local_point_a - mid).length_squared();
978 assert_eq!(d_a, d_b);
979 assert_eq!(d_a, Fix128::from_ratio(1, 16384));
980 m.add_or_update(&contact(Vec3Fix::UNIT_Y, fi(3)), mid, Vec3Fix::ZERO);
981 assert_eq!(m.point_count(), 2);
982 assert_eq!(m.points[0].depth, fi(3), "先勝ち: index 0 が更新される");
983 assert_eq!(m.points[0].local_point_a, mid);
984 assert_eq!(m.points[0].age, 1);
985 assert_eq!(m.points[1].depth, fi(2), "index 1 は不変");
986 assert_eq!(m.points[1].local_point_a, b);
987 assert_eq!(m.points[1].age, 0);
988 }
989
990 #[test]
993 fn manifold_full_replaces_first_of_equal_shallowest() {
994 let mut m = manifold();
995 for (i, d) in [1, 1, 3, 4].iter().enumerate() {
996 m.add_or_update(
997 &contact(Vec3Fix::UNIT_Y, fi(*d)),
998 v3i(i as i64 * 10, 0, 0),
999 Vec3Fix::ZERO,
1000 );
1001 }
1002 assert_eq!(m.point_count(), MAX_MANIFOLD_POINTS);
1003 m.add_or_update(
1004 &contact(Vec3Fix::UNIT_Y, fi(2)),
1005 v3i(100, 0, 0),
1006 Vec3Fix::ZERO,
1007 );
1008 assert_eq!(m.point_count(), 4);
1009 assert_eq!(m.points[0].local_point_a, v3i(100, 0, 0));
1010 assert_eq!(m.points[0].depth, fi(2));
1011 assert_eq!(m.points[1].local_point_a, v3i(10, 0, 0), "同点の後方は不変");
1012 assert_eq!(m.points[1].depth, fi(1));
1013 let depths: Vec<Fix128> = m.points.iter().map(|p| p.depth).collect();
1014 assert_eq!(depths, vec![fi(2), fi(1), fi(3), fi(4)]);
1015 }
1016
1017 #[test]
1019 fn warm_start_and_store_at_exact_len_are_out_of_range() {
1020 let mut m = manifold();
1021 m.add_or_update(
1022 &contact(Vec3Fix::UNIT_Y, fi(1)),
1023 Vec3Fix::ZERO,
1024 Vec3Fix::ZERO,
1025 );
1026 m.store_impulses(0, fi(2), fi(3), fi(4));
1027 assert_eq!(m.point_count(), 1);
1028 assert_eq!(
1030 m.warm_start_impulse(1),
1031 (Fix128::ZERO, Fix128::ZERO, Fix128::ZERO)
1032 );
1033 m.store_impulses(1, fi(9), fi(9), fi(9));
1034 assert_eq!(m.point_count(), 1);
1035 assert_eq!(m.warm_start_impulse(0), (fi(2), fi(3), fi(4)));
1036 let e = manifold();
1038 assert_eq!(
1039 e.warm_start_impulse(0),
1040 (Fix128::ZERO, Fix128::ZERO, Fix128::ZERO)
1041 );
1042 let mut e2 = manifold();
1043 e2.store_impulses(0, fi(1), fi(1), fi(1));
1044 assert!(e2.is_empty());
1045 }
1046
1047 #[test]
1050 fn apply_warm_start_scales_by_body_a_inv_mass() {
1051 let mut cache = ContactCache::new();
1052 cache.warm_start_factor = Fix128::from_ratio(1, 2);
1053 let m = cache.get_or_create(BodyPairKey::new(0, 1), Fix128::ONE, Fix128::ZERO);
1054 m.add_or_update(
1055 &contact(Vec3Fix::UNIT_Z, fi(1)),
1056 Vec3Fix::ZERO,
1057 Vec3Fix::ZERO,
1058 );
1059 m.store_impulses(0, Fix128::ZERO, fi(6), Fix128::ZERO);
1061 let mut bodies = vec![
1062 crate::solver::RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
1063 crate::solver::RigidBody::new_static(v3i(1, 0, 0)),
1064 ];
1065 bodies[0].inv_mass = fi(4);
1066 cache.apply_warm_start(&mut bodies);
1067 assert_eq!(bodies[0].velocity, v3i(0, 12, 0));
1069 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
1070 }
1071
1072 #[test]
1075 fn tangent_frame_reference_requires_both_comparisons() {
1076 let n = v3i(2, 4, 1);
1080 let (t1, t2) = tangent_frame(n);
1081 assert_eq!(t1.z, Fix128::ZERO);
1082 assert!(t1.x > Fix128::ZERO && t1.y < Fix128::ZERO, "{t1:?}");
1083 assert_eq!(t2, n.cross(t1));
1084 let n2 = v3i(2, 1, 4);
1088 let (u1, u2) = tangent_frame(n2);
1089 assert_eq!(u1.y, Fix128::ZERO);
1090 assert!(u1.x < Fix128::ZERO && u1.z > Fix128::ZERO, "{u1:?}");
1091 assert_eq!(u2, n2.cross(u1));
1092 }
1093
1094 #[cfg(feature = "std")]
1095 #[test]
1096 fn a_stale_index_found_by_the_scan_is_re_indexed() {
1097 let mut cache = ContactCache::new();
1098 let (a, b) = (BodyPairKey::new(0, 1), BodyPairKey::new(2, 3));
1099 cache.get_or_create(a, Fix128::ONE, Fix128::ZERO);
1100 cache.get_or_create(b, Fix128::ONE, Fix128::ZERO);
1101 cache.manifolds.remove(0); assert_eq!(cache.pair_index.get(&b), Some(&1));
1103 assert_eq!(cache.get_or_create(b, Fix128::ONE, Fix128::ZERO).pair, b);
1104 assert_eq!(cache.pair_index.get(&b), Some(&0));
1105 assert_eq!(cache.manifolds.len(), 1, "no duplicate manifold for b");
1106 }
1107}