1#[inline]
24pub fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
25 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
26}
27
28#[inline]
30pub fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
31 [
32 a[1] * b[2] - a[2] * b[1],
33 a[2] * b[0] - a[0] * b[2],
34 a[0] * b[1] - a[1] * b[0],
35 ]
36}
37
38#[inline]
40pub fn norm_sq3(a: [f64; 3]) -> f64 {
41 dot3(a, a)
42}
43
44#[inline]
46pub fn norm3(a: [f64; 3]) -> f64 {
47 norm_sq3(a).sqrt()
48}
49
50#[inline]
52pub fn normalize3(a: [f64; 3]) -> [f64; 3] {
53 let n = norm3(a);
54 if n < 1e-15 {
55 return [0.0, 1.0, 0.0];
56 }
57 [a[0] / n, a[1] / n, a[2] / n]
58}
59
60#[inline]
62pub fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
63 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
64}
65
66#[inline]
68pub fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
69 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
70}
71
72#[inline]
74pub fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
75 [a[0] * s, a[1] * s, a[2] * s]
76}
77
78#[inline]
80fn neg3(a: [f64; 3]) -> [f64; 3] {
81 [-a[0], -a[1], -a[2]]
82}
83
84#[derive(Debug, Clone, Copy, PartialEq)]
90pub struct ContactPoint {
91 pub pos_a: [f64; 3],
93 pub pos_b: [f64; 3],
95 pub normal: [f64; 3],
97 pub depth: f64,
99 pub feature_a: u32,
101 pub feature_b: u32,
103 pub impulse: f64,
105}
106
107impl ContactPoint {
108 pub fn new(pos_a: [f64; 3], pos_b: [f64; 3], normal: [f64; 3], depth: f64) -> Self {
110 Self {
111 pos_a,
112 pos_b,
113 normal,
114 depth,
115 feature_a: 0,
116 feature_b: 0,
117 impulse: 0.0,
118 }
119 }
120
121 pub fn midpoint(&self) -> [f64; 3] {
123 scale3(add3(self.pos_a, self.pos_b), 0.5)
124 }
125}
126
127pub const MAX_MANIFOLD_POINTS: usize = 4;
133
134#[derive(Debug, Clone)]
136pub struct ContactManifoldEx {
137 pub points: Vec<ContactPoint>,
139 pub normal: [f64; 3],
141 pub body_a: u32,
143 pub body_b: u32,
145 pub last_frame: u64,
147}
148
149impl ContactManifoldEx {
150 pub fn new(body_a: u32, body_b: u32) -> Self {
152 Self {
153 points: Vec::new(),
154 normal: [0.0, 1.0, 0.0],
155 body_a,
156 body_b,
157 last_frame: 0,
158 }
159 }
160
161 pub fn num_points(&self) -> usize {
163 self.points.len()
164 }
165
166 pub fn add_point(&mut self, cp: ContactPoint) {
168 if self.points.len() < MAX_MANIFOLD_POINTS {
169 self.points.push(cp);
170 }
171 }
172
173 pub fn max_depth(&self) -> f64 {
175 self.points.iter().map(|p| p.depth).fold(0.0_f64, f64::max)
176 }
177
178 pub fn is_active(&self) -> bool {
180 self.points.iter().any(|p| p.depth > 0.0)
181 }
182
183 pub fn prune_inactive(&mut self) {
185 self.points.retain(|p| p.depth > -0.01);
186 }
187}
188
189pub fn clip_polygon_by_plane(
198 polygon: &[[f64; 3]],
199 plane_point: [f64; 3],
200 plane_normal: [f64; 3],
201) -> Vec<[f64; 3]> {
202 if polygon.is_empty() {
203 return Vec::new();
204 }
205 let mut result = Vec::new();
206 let n = polygon.len();
207
208 for i in 0..n {
209 let a = polygon[i];
210 let b = polygon[(i + 1) % n];
211 let da = dot3(sub3(a, plane_point), plane_normal);
212 let db = dot3(sub3(b, plane_point), plane_normal);
213
214 if da >= 0.0 {
215 result.push(a);
216 }
217 if (da >= 0.0) != (db >= 0.0) {
218 let t = da / (da - db);
220 let p = add3(a, scale3(sub3(b, a), t));
221 result.push(p);
222 }
223 }
224 result
225}
226
227pub fn sat_face_axis(axis: [f64; 3], ref_pts: &[[f64; 3]], inc_pts: &[[f64; 3]]) -> f64 {
236 let ref_max = ref_pts
237 .iter()
238 .map(|&p| dot3(p, axis))
239 .fold(f64::NEG_INFINITY, f64::max);
240 let inc_min = inc_pts
241 .iter()
242 .map(|&p| dot3(p, axis))
243 .fold(f64::INFINITY, f64::min);
244 inc_min - ref_max
245}
246
247pub fn sat_edge_axis(
251 edge_a: [f64; 3],
252 edge_b: [f64; 3],
253 ref_center: [f64; 3],
254 ref_pts: &[[f64; 3]],
255 inc_pts: &[[f64; 3]],
256) -> f64 {
257 let axis = cross3(edge_a, edge_b);
258 let len = norm3(axis);
259 if len < 1e-10 {
260 return f64::NEG_INFINITY;
261 } let n = scale3(axis, 1.0 / len);
263
264 let n = if dot3(sub3(ref_center, inc_pts[0]), n) < 0.0 {
266 neg3(n)
267 } else {
268 n
269 };
270
271 let ref_max = ref_pts
272 .iter()
273 .map(|&p| dot3(p, n))
274 .fold(f64::NEG_INFINITY, f64::max);
275 let inc_min = inc_pts
276 .iter()
277 .map(|&p| dot3(p, n))
278 .fold(f64::INFINITY, f64::min);
279 inc_min - ref_max
280}
281
282pub fn find_incident_face(normals: &[[f64; 3]], ref_normal: [f64; 3]) -> (usize, [f64; 3]) {
286 let mut best_idx = 0;
287 let mut best_dot = f64::INFINITY;
288 for (i, &n) in normals.iter().enumerate() {
289 let d = dot3(n, ref_normal);
290 if d < best_dot {
291 best_dot = d;
292 best_idx = i;
293 }
294 }
295 (best_idx, normals[best_idx])
296}
297
298#[derive(Debug, Clone)]
306pub struct PolyhedronContact {
307 pub max_contacts: usize,
309}
310
311impl PolyhedronContact {
312 pub fn new(max_contacts: usize) -> Self {
314 Self { max_contacts }
315 }
316
317 pub fn face_face_contact(
325 &self,
326 ref_face: &[[f64; 3]],
327 inc_face: &[[f64; 3]],
328 ref_normal: [f64; 3],
329 depth: f64,
330 ) -> Vec<ContactPoint> {
331 if ref_face.is_empty() || inc_face.is_empty() {
332 return Vec::new();
333 }
334
335 let mut clipped = inc_face.to_vec();
337 let n = ref_face.len();
338 for i in 0..n {
339 if clipped.is_empty() {
340 break;
341 }
342 let edge_start = ref_face[i];
343 let edge_end = ref_face[(i + 1) % n];
344 let edge_dir = sub3(edge_end, edge_start);
345 let side_normal = normalize3(cross3(ref_normal, edge_dir));
347 clipped = clip_polygon_by_plane(&clipped, edge_start, side_normal);
348 }
349
350 let mut contacts = Vec::new();
352 let ref_plane_d = dot3(ref_face[0], ref_normal);
353 for p in &clipped {
354 let pen = ref_plane_d - dot3(*p, ref_normal);
355 if pen > -depth {
356 let pos_b = *p;
357 let pos_a = add3(*p, scale3(ref_normal, pen));
358 contacts.push(ContactPoint::new(pos_a, pos_b, ref_normal, pen));
359 if contacts.len() >= self.max_contacts {
360 break;
361 }
362 }
363 }
364 contacts
365 }
366
367 pub fn edge_edge_closest(
371 &self,
372 pa: [f64; 3],
373 da: [f64; 3],
374 pb: [f64; 3],
375 db: [f64; 3],
376 ) -> ([f64; 3], [f64; 3], f64) {
377 let r = sub3(pa, pb);
378 let a = dot3(da, da);
379 let e = dot3(db, db);
380 let f = dot3(db, r);
381
382 if a < 1e-14 && e < 1e-14 {
383 return (pa, pb, norm3(r));
384 }
385
386 let s;
387 let t;
388 if a < 1e-14 {
389 s = 0.0;
390 t = (f / e).clamp(0.0, 1.0);
391 } else {
392 let c = dot3(da, r);
393 if e < 1e-14 {
394 s = (-c / a).clamp(0.0, 1.0);
395 t = 0.0;
396 } else {
397 let b = dot3(da, db);
398 let denom = a * e - b * b;
399 if denom.abs() > 1e-14 {
400 s = ((b * f - c * e) / denom).clamp(0.0, 1.0);
401 } else {
402 s = 0.5;
403 }
404 t = ((b * s + f) / e).clamp(0.0, 1.0);
405 let _ = s;
406 }
407 let _ = t;
408 }
409
410 let s_final = if a > 1e-14 {
412 let c = dot3(da, r);
413 let b = dot3(da, db);
414 ((b * t - c) / a).clamp(0.0, 1.0)
415 } else {
416 0.0
417 };
418
419 let t_final = if e > 1e-14 {
420 let b = dot3(da, db);
421 ((b * s_final + f) / e).clamp(0.0, 1.0)
422 } else {
423 0.0
424 };
425
426 let _ = (s, t);
428
429 let ca = add3(pa, scale3(da, s_final));
430 let cb = add3(pb, scale3(db, t_final));
431 (ca, cb, norm3(sub3(ca, cb)))
432 }
433}
434
435#[derive(Debug, Clone)]
443pub struct BoxBoxManifold {
444 pub tolerance: f64,
446}
447
448impl BoxBoxManifold {
449 pub fn new(tolerance: f64) -> Self {
451 Self { tolerance }
452 }
453
454 fn box_vertices(
456 center: [f64; 3],
457 half_extents: [f64; 3],
458 axes: [[f64; 3]; 3],
459 ) -> Vec<[f64; 3]> {
460 let mut verts = Vec::with_capacity(8);
461 for sx in &[-1.0, 1.0] {
462 for sy in &[-1.0, 1.0] {
463 for sz in &[-1.0, 1.0] {
464 let v = add3(
465 center,
466 add3(
467 add3(
468 scale3(axes[0], sx * half_extents[0]),
469 scale3(axes[1], sy * half_extents[1]),
470 ),
471 scale3(axes[2], sz * half_extents[2]),
472 ),
473 );
474 verts.push(v);
475 }
476 }
477 }
478 verts
479 }
480
481 pub fn compute(
485 &self,
486 center_a: [f64; 3],
487 half_a: [f64; 3],
488 axes_a: [[f64; 3]; 3],
489 center_b: [f64; 3],
490 half_b: [f64; 3],
491 axes_b: [[f64; 3]; 3],
492 ) -> Vec<ContactPoint> {
493 let verts_a = Self::box_vertices(center_a, half_a, axes_a);
494 let verts_b = Self::box_vertices(center_b, half_b, axes_b);
495
496 let mut min_sep = f64::INFINITY;
498 let mut best_normal = [0.0f64, 1.0, 0.0];
499
500 for &axis in axes_a.iter().chain(axes_b.iter()) {
501 let n = normalize3(axis);
502 let sep = sat_face_axis(n, &verts_a, &verts_b);
503 if sep > self.tolerance {
504 return Vec::new(); }
506 if sep < min_sep {
507 min_sep = sep;
508 best_normal = n;
509 }
510 }
511
512 if dot3(sub3(center_b, center_a), best_normal) < 0.0 {
514 best_normal = neg3(best_normal);
515 }
516
517 let ref_d = dot3(center_a, best_normal) + half_a[0] + half_a[1] + half_a[2]; let mut contacts = Vec::new();
520 for &v in &verts_b {
521 let pen = ref_d - dot3(v, best_normal);
522 if pen > -self.tolerance {
523 let pos_b = v;
524 let pos_a = add3(v, scale3(best_normal, pen));
525 contacts.push(ContactPoint::new(pos_a, pos_b, best_normal, pen.max(0.0)));
526 if contacts.len() >= MAX_MANIFOLD_POINTS {
527 break;
528 }
529 }
530 }
531 contacts
532 }
533}
534
535#[derive(Debug, Clone)]
543pub struct CapsuleCapsuleManifold;
544
545impl CapsuleCapsuleManifold {
546 pub fn compute(
556 pa_start: [f64; 3],
557 pa_end: [f64; 3],
558 ra: f64,
559 pb_start: [f64; 3],
560 pb_end: [f64; 3],
561 rb: f64,
562 ) -> Vec<ContactPoint> {
563 let da = sub3(pa_end, pa_start);
564 let db = sub3(pb_end, pb_start);
565 let r = sub3(pa_start, pb_start);
566
567 let a = dot3(da, da);
568 let e = dot3(db, db);
569 let f = dot3(db, r);
570
571 let s;
572 let t;
573 if a <= 1e-14 && e <= 1e-14 {
574 s = 0.0;
575 t = 0.0;
576 } else if a <= 1e-14 {
577 s = 0.0;
578 t = (f / e).clamp(0.0, 1.0);
579 } else {
580 let c = dot3(da, r);
581 if e <= 1e-14 {
582 t = 0.0;
583 s = (-c / a).clamp(0.0, 1.0);
584 } else {
585 let b = dot3(da, db);
586 let denom = a * e - b * b;
587 if denom.abs() > 1e-14 {
588 s = ((b * f - c * e) / denom).clamp(0.0, 1.0);
589 } else {
590 s = 0.5;
591 }
592 t = ((b * s + f) / e).clamp(0.0, 1.0);
593 }
594 }
595
596 let ca = add3(pa_start, scale3(da, s));
597 let cb = add3(pb_start, scale3(db, t));
598 let diff = sub3(ca, cb);
599 let dist = norm3(diff);
600 let total_radius = ra + rb;
601
602 if dist >= total_radius {
603 return Vec::new();
604 }
605
606 let normal = if dist > 1e-10 {
607 normalize3(diff)
608 } else {
609 [0.0, 1.0, 0.0]
610 };
611 let depth = total_radius - dist;
612 let pos_a = sub3(ca, scale3(normal, ra));
613 let pos_b = add3(cb, scale3(normal, rb));
614
615 let mut contacts = vec![ContactPoint::new(pos_a, pos_b, normal, depth)];
616
617 let da_len = norm3(da);
619 let db_len = norm3(db);
620 if da_len > 1e-5 && db_len > 1e-5 {
621 let da_n = scale3(da, 1.0 / da_len);
622 let db_n = scale3(db, 1.0 / db_len);
623 let parallel = dot3(da_n, db_n).abs();
624 if parallel > 0.9 && (s > 0.1 && s < 0.9 || t > 0.1 && t < 0.9) {
625 let s2 = if s < 0.5 { 1.0 } else { 0.0 };
627 let t2 = ((dot3(da_n, db_n) * (s2 - s) + t) * db_len / db_len).clamp(0.0, 1.0);
628 let ca2 = add3(pa_start, scale3(da, s2));
629 let cb2 = add3(pb_start, scale3(db, t2));
630 let diff2 = sub3(ca2, cb2);
631 let dist2 = norm3(diff2);
632 if dist2 < total_radius {
633 let depth2 = total_radius - dist2;
634 let n2 = if dist2 > 1e-10 {
635 normalize3(diff2)
636 } else {
637 normal
638 };
639 let pa2 = sub3(ca2, scale3(n2, ra));
640 let pb2 = add3(cb2, scale3(n2, rb));
641 contacts.push(ContactPoint::new(pa2, pb2, n2, depth2));
642 }
643 }
644 }
645
646 contacts
647 }
648}
649
650#[derive(Debug, Clone)]
658pub struct MeshConvexManifold {
659 pub max_contacts: usize,
661 pub tolerance: f64,
663}
664
665impl MeshConvexManifold {
666 pub fn new(max_contacts: usize, tolerance: f64) -> Self {
668 Self {
669 max_contacts,
670 tolerance,
671 }
672 }
673
674 pub fn triangle_sphere_contact(
678 &self,
679 tri: ([f64; 3], [f64; 3], [f64; 3]),
680 tri_normal: [f64; 3],
681 sphere_center: [f64; 3],
682 sphere_radius: f64,
683 ) -> Option<ContactPoint> {
684 let cp = Self::closest_on_triangle(sphere_center, tri.0, tri.1, tri.2);
686 let diff = sub3(sphere_center, cp);
687 let dist = norm3(diff);
688 if dist >= sphere_radius {
689 return None;
690 }
691 let normal = if dist > 1e-10 {
692 normalize3(diff)
693 } else {
694 tri_normal
695 };
696 let depth = sphere_radius - dist;
697 let pos_a = sub3(sphere_center, scale3(normal, sphere_radius));
698 Some(ContactPoint::new(pos_a, cp, normal, depth))
699 }
700
701 fn closest_on_triangle(p: [f64; 3], a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [f64; 3] {
702 let ab = sub3(b, a);
703 let ac = sub3(c, a);
704 let ap = sub3(p, a);
705 let d1 = dot3(ab, ap);
706 let d2 = dot3(ac, ap);
707 if d1 <= 0.0 && d2 <= 0.0 {
708 return a;
709 }
710
711 let bp = sub3(p, b);
712 let d3 = dot3(ab, bp);
713 let d4 = dot3(ac, bp);
714 if d3 >= 0.0 && d4 <= d3 {
715 return b;
716 }
717
718 let cp_pt = sub3(p, c);
719 let d5 = dot3(ab, cp_pt);
720 let d6 = dot3(ac, cp_pt);
721 if d6 >= 0.0 && d5 <= d6 {
722 return c;
723 }
724
725 let vc = d1 * d4 - d3 * d2;
726 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
727 let v = d1 / (d1 - d3);
728 return add3(a, scale3(ab, v));
729 }
730 let vb = d5 * d2 - d1 * d6;
731 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
732 let w = d2 / (d2 - d6);
733 return add3(a, scale3(ac, w));
734 }
735 let va = d3 * d6 - d5 * d4;
736 if va <= 0.0 && d4 - d3 >= 0.0 && d5 - d6 >= 0.0 {
737 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
738 return add3(b, scale3(sub3(c, b), w));
739 }
740 let denom = 1.0 / (va + vb + vc);
741 let v = vb * denom;
742 let w = vc * denom;
743 add3(a, add3(scale3(ab, v), scale3(ac, w)))
744 }
745
746 pub fn mesh_sphere_contacts(
748 &self,
749 triangles: &[([f64; 3], [f64; 3], [f64; 3])],
750 normals: &[[f64; 3]],
751 sphere_center: [f64; 3],
752 sphere_radius: f64,
753 ) -> Vec<ContactPoint> {
754 let mut contacts = Vec::new();
755 for (i, &tri) in triangles.iter().enumerate() {
756 let n = normals.get(i).copied().unwrap_or([0.0, 1.0, 0.0]);
757 if let Some(cp) = self.triangle_sphere_contact(tri, n, sphere_center, sphere_radius) {
758 contacts.push(cp);
759 if contacts.len() >= self.max_contacts {
760 break;
761 }
762 }
763 }
764 contacts
765 }
766}
767
768#[derive(Debug, Clone)]
776pub struct ManifoldReduction;
777
778impl ManifoldReduction {
779 pub fn reduce(points: &[ContactPoint]) -> Vec<ContactPoint> {
787 if points.len() <= MAX_MANIFOLD_POINTS {
788 return points.to_vec();
789 }
790
791 let mut result = Vec::with_capacity(MAX_MANIFOLD_POINTS);
792
793 let p0_idx = points
795 .iter()
796 .enumerate()
797 .max_by(|(_, a), (_, b)| {
798 a.depth
799 .partial_cmp(&b.depth)
800 .unwrap_or(std::cmp::Ordering::Equal)
801 })
802 .map(|(i, _)| i)
803 .unwrap_or(0);
804 result.push(points[p0_idx]);
805 let p0 = points[p0_idx].pos_a;
806
807 let p1_idx = points
809 .iter()
810 .enumerate()
811 .max_by(|(_, a), (_, b)| {
812 norm_sq3(sub3(a.pos_a, p0))
813 .partial_cmp(&norm_sq3(sub3(b.pos_a, p0)))
814 .unwrap_or(std::cmp::Ordering::Equal)
815 })
816 .map(|(i, _)| i)
817 .unwrap_or(0);
818 result.push(points[p1_idx]);
819 let p1 = points[p1_idx].pos_a;
820
821 let p2_idx = points
823 .iter()
824 .enumerate()
825 .max_by(|(_, a), (_, b)| {
826 let da = Self::point_to_line_dist_sq(a.pos_a, p0, p1);
827 let db = Self::point_to_line_dist_sq(b.pos_a, p0, p1);
828 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
829 })
830 .map(|(i, _)| i)
831 .unwrap_or(0);
832 result.push(points[p2_idx]);
833 let p2 = points[p2_idx].pos_a;
834
835 let p3_idx = points
837 .iter()
838 .enumerate()
839 .max_by(|(_, a), (_, b)| {
840 let da = Self::point_to_plane_dist_sq(a.pos_a, p0, p1, p2);
841 let db = Self::point_to_plane_dist_sq(b.pos_a, p0, p1, p2);
842 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
843 })
844 .map(|(i, _)| i)
845 .unwrap_or(0);
846 result.push(points[p3_idx]);
847
848 result
849 }
850
851 fn point_to_line_dist_sq(p: [f64; 3], a: [f64; 3], b: [f64; 3]) -> f64 {
852 let ab = sub3(b, a);
853 let ap = sub3(p, a);
854 let t = dot3(ap, ab) / dot3(ab, ab).max(1e-15);
855 let closest = add3(a, scale3(ab, t.clamp(0.0, 1.0)));
856 norm_sq3(sub3(p, closest))
857 }
858
859 fn point_to_plane_dist_sq(p: [f64; 3], a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
860 let n = normalize3(cross3(sub3(b, a), sub3(c, a)));
861 let d = dot3(sub3(p, a), n);
862 d * d
863 }
864}
865
866#[derive(Debug, Clone)]
874pub struct PersistentManifold {
875 pub manifold: ContactManifoldEx,
877 pub match_threshold: f64,
879 pub max_lifetime: usize,
881 pub point_ages: Vec<usize>,
883}
884
885impl PersistentManifold {
886 pub fn new(body_a: u32, body_b: u32, match_threshold: f64) -> Self {
888 Self {
889 manifold: ContactManifoldEx::new(body_a, body_b),
890 match_threshold,
891 max_lifetime: 5,
892 point_ages: Vec::new(),
893 }
894 }
895
896 pub fn update(&mut self, new_points: &[ContactPoint], frame: u64) {
900 let threshold_sq = self.match_threshold * self.match_threshold;
901 let mut matched = vec![false; new_points.len()];
902 let mut new_manifold_pts = Vec::new();
903 let mut new_ages = Vec::new();
904
905 for (old_pt, &old_age) in self.manifold.points.iter().zip(self.point_ages.iter()) {
907 let mut best_idx = None;
908 let mut best_dist = f64::MAX;
909 for (j, new_pt) in new_points.iter().enumerate() {
910 if matched[j] {
911 continue;
912 }
913 let d = norm_sq3(sub3(old_pt.pos_a, new_pt.pos_a));
914 if d < threshold_sq && d < best_dist {
915 best_dist = d;
916 best_idx = Some(j);
917 }
918 }
919
920 if let Some(j) = best_idx {
921 matched[j] = true;
922 let mut updated = new_points[j];
923 updated.impulse = old_pt.impulse;
925 new_manifold_pts.push(updated);
926 new_ages.push(0);
927 } else if old_age < self.max_lifetime {
928 new_manifold_pts.push(*old_pt);
930 new_ages.push(old_age + 1);
931 }
932 }
933
934 for (j, &pt) in new_points.iter().enumerate() {
936 if !matched[j] {
937 new_manifold_pts.push(pt);
938 new_ages.push(0);
939 }
940 }
941
942 let reduced = ManifoldReduction::reduce(&new_manifold_pts);
944 self.point_ages = vec![0; reduced.len()];
945 self.manifold.points = reduced;
946 self.manifold.last_frame = frame;
947 }
948
949 pub fn num_points(&self) -> usize {
951 self.manifold.num_points()
952 }
953
954 pub fn warm_start_impulse(&self, point_idx: usize) -> f64 {
956 self.manifold
957 .points
958 .get(point_idx)
959 .map(|p| p.impulse)
960 .unwrap_or(0.0)
961 }
962}
963
964#[derive(Debug, Clone, Copy, PartialEq)]
970pub enum FeatureType {
971 Vertex,
973 Edge,
975 Face,
977}
978
979#[derive(Debug, Clone)]
981pub struct FeatureContact {
982 pub contact: ContactPoint,
984 pub feature_type_a: FeatureType,
986 pub feature_type_b: FeatureType,
988 pub prev_feature_a: FeatureType,
990 pub prev_feature_b: FeatureType,
992}
993
994impl FeatureContact {
995 pub fn new(
997 contact: ContactPoint,
998 feature_type_a: FeatureType,
999 feature_type_b: FeatureType,
1000 ) -> Self {
1001 Self {
1002 contact,
1003 feature_type_a,
1004 feature_type_b,
1005 prev_feature_a: feature_type_a,
1006 prev_feature_b: feature_type_b,
1007 }
1008 }
1009
1010 pub fn has_transition(&self) -> bool {
1012 self.feature_type_a != self.prev_feature_a || self.feature_type_b != self.prev_feature_b
1013 }
1014
1015 pub fn update_prev(&mut self) {
1017 self.prev_feature_a = self.feature_type_a;
1018 self.prev_feature_b = self.feature_type_b;
1019 }
1020
1021 pub fn classify_feature(normal: [f64; 3], face_normals: &[[f64; 3]]) -> FeatureType {
1025 let mut best_dot = 0.0f64;
1026 for &fn_ in face_normals {
1027 let d = dot3(normal, fn_).abs();
1028 if d > best_dot {
1029 best_dot = d;
1030 }
1031 }
1032 if best_dot > 0.95 {
1033 FeatureType::Face
1034 } else if best_dot > 0.7 {
1035 FeatureType::Edge
1036 } else {
1037 FeatureType::Vertex
1038 }
1039 }
1040}
1041
1042#[derive(Debug, Clone)]
1051pub struct ContactNormalSmoothing {
1052 pub method: NormalSmoothingMethod,
1054 pub crease_angle: f64,
1056}
1057
1058#[derive(Debug, Clone, PartialEq)]
1060pub enum NormalSmoothingMethod {
1061 Phong,
1063 Garland,
1065 Flat,
1067}
1068
1069impl ContactNormalSmoothing {
1070 pub fn new(method: NormalSmoothingMethod, crease_angle: f64) -> Self {
1072 Self {
1073 method,
1074 crease_angle,
1075 }
1076 }
1077
1078 pub fn smooth_normal(
1085 &self,
1086 face_normal: [f64; 3],
1087 vertex_normals: ([f64; 3], [f64; 3], [f64; 3]),
1088 u: f64,
1089 v: f64,
1090 ) -> [f64; 3] {
1091 match self.method {
1092 NormalSmoothingMethod::Flat => face_normal,
1093 NormalSmoothingMethod::Phong => {
1094 let w = 1.0 - u - v;
1095 let (na, nb, nc) = vertex_normals;
1096 let interpolated = [
1097 w * na[0] + u * nb[0] + v * nc[0],
1098 w * na[1] + u * nb[1] + v * nc[1],
1099 w * na[2] + u * nb[2] + v * nc[2],
1100 ];
1101 normalize3(interpolated)
1102 }
1103 NormalSmoothingMethod::Garland => {
1104 let (na, nb, nc) = vertex_normals;
1106 let avg = [
1107 (na[0] + nb[0] + nc[0]) / 3.0,
1108 (na[1] + nb[1] + nc[1]) / 3.0,
1109 (na[2] + nb[2] + nc[2]) / 3.0,
1110 ];
1111 let dot = dot3(normalize3(avg), face_normal);
1113 if dot.acos() < self.crease_angle {
1114 normalize3(avg)
1115 } else {
1116 face_normal
1117 }
1118 }
1119 }
1120 }
1121
1122 pub fn compute_vertex_normals(vertices: &[[f64; 3]], indices: &[[usize; 3]]) -> Vec<[f64; 3]> {
1126 let n = vertices.len();
1127 let mut normals = vec![[0.0f64; 3]; n];
1128
1129 for tri in indices {
1130 let a = vertices[tri[0]];
1131 let b = vertices[tri[1]];
1132 let c = vertices[tri[2]];
1133 let ab = sub3(b, a);
1134 let ac = sub3(c, a);
1135 let area_normal = cross3(ab, ac); for &vi in tri {
1137 normals[vi][0] += area_normal[0];
1138 normals[vi][1] += area_normal[1];
1139 normals[vi][2] += area_normal[2];
1140 }
1141 }
1142
1143 normals.iter_mut().for_each(|n| *n = normalize3(*n));
1144 normals
1145 }
1146}
1147
1148#[derive(Debug, Clone)]
1156pub struct ContactCaching {
1157 pub cache: std::collections::HashMap<(u32, u32), PersistentManifold>,
1159 pub max_size: usize,
1161 pub frame: u64,
1163}
1164
1165impl ContactCaching {
1166 pub fn new(max_size: usize) -> Self {
1168 Self {
1169 cache: std::collections::HashMap::new(),
1170 max_size,
1171 frame: 0,
1172 }
1173 }
1174
1175 pub fn get_or_create(&mut self, body_a: u32, body_b: u32) -> &mut PersistentManifold {
1177 let key = (body_a.min(body_b), body_a.max(body_b));
1178 if !self.cache.contains_key(&key) {
1179 if self.cache.len() >= self.max_size {
1180 self.evict_oldest();
1181 }
1182 self.cache
1183 .insert(key, PersistentManifold::new(body_a, body_b, 0.01));
1184 }
1185 self.cache
1186 .entry(key)
1187 .or_insert_with(|| PersistentManifold::new(body_a, body_b, 0.01))
1188 }
1189
1190 fn evict_oldest(&mut self) {
1192 let oldest_key = self
1193 .cache
1194 .iter()
1195 .min_by_key(|(_, m)| m.manifold.last_frame)
1196 .map(|(k, _)| *k);
1197 if let Some(k) = oldest_key {
1198 self.cache.remove(&k);
1199 }
1200 }
1201
1202 pub fn advance_frame(&mut self) {
1204 self.frame += 1;
1205 }
1206
1207 pub fn purge_stale(&mut self, max_age: u64) {
1209 let frame = self.frame;
1210 self.cache
1211 .retain(|_, m| frame.saturating_sub(m.manifold.last_frame) <= max_age);
1212 }
1213
1214 pub fn num_cached(&self) -> usize {
1216 self.cache.len()
1217 }
1218}
1219
1220#[derive(Debug, Clone, PartialEq)]
1226pub enum ContactEvent {
1227 Begin(u32, u32),
1229 End(u32, u32),
1231 Persist(u32, u32),
1233}
1234
1235pub trait ContactListener {
1237 fn on_contact_event(&mut self, event: ContactEvent);
1239}
1240
1241#[derive(Debug, Clone)]
1243pub struct ContactEventSystem {
1244 pub active_contacts: std::collections::HashSet<(u32, u32)>,
1246 pub pending_events: Vec<ContactEvent>,
1248}
1249
1250impl ContactEventSystem {
1251 pub fn new() -> Self {
1253 Self {
1254 active_contacts: std::collections::HashSet::new(),
1255 pending_events: Vec::new(),
1256 }
1257 }
1258
1259 pub fn update(&mut self, new_contacts: &[(u32, u32)]) {
1264 let new_set: std::collections::HashSet<_> = new_contacts
1265 .iter()
1266 .map(|&(a, b)| (a.min(b), a.max(b)))
1267 .collect();
1268
1269 self.pending_events.clear();
1270
1271 for &pair in &self.active_contacts {
1273 if !new_set.contains(&pair) {
1274 self.pending_events.push(ContactEvent::End(pair.0, pair.1));
1275 }
1276 }
1277
1278 for &pair in &new_set {
1280 if !self.active_contacts.contains(&pair) {
1281 self.pending_events
1282 .push(ContactEvent::Begin(pair.0, pair.1));
1283 } else {
1284 self.pending_events
1285 .push(ContactEvent::Persist(pair.0, pair.1));
1286 }
1287 }
1288
1289 self.active_contacts = new_set;
1290 }
1291
1292 pub fn dispatch<L: ContactListener>(&self, listener: &mut L) {
1294 for event in &self.pending_events {
1295 listener.on_contact_event(event.clone());
1296 }
1297 }
1298
1299 pub fn num_active(&self) -> usize {
1301 self.active_contacts.len()
1302 }
1303
1304 pub fn num_events(&self) -> usize {
1306 self.pending_events.len()
1307 }
1308}
1309
1310impl Default for ContactEventSystem {
1311 fn default() -> Self {
1312 Self::new()
1313 }
1314}
1315
1316#[cfg(test)]
1321mod tests {
1322 use super::*;
1323
1324 #[test]
1325 fn test_contact_point_new() {
1326 let cp = ContactPoint::new([0.0; 3], [0.0; 3], [0.0, 1.0, 0.0], 0.1);
1327 assert!((cp.depth - 0.1).abs() < 1e-10);
1328 }
1329
1330 #[test]
1331 fn test_contact_point_midpoint() {
1332 let cp = ContactPoint::new([0.0; 3], [2.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.1);
1333 let mid = cp.midpoint();
1334 assert!((mid[0] - 1.0).abs() < 1e-10);
1335 }
1336
1337 #[test]
1338 fn test_clip_polygon_by_plane_keep_all() {
1339 let poly = vec![[0.0, 0.0, 0.0_f64], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]];
1340 let clipped = clip_polygon_by_plane(&poly, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1342 assert_eq!(clipped.len(), 3, "all points should be kept");
1343 }
1344
1345 #[test]
1346 fn test_clip_polygon_by_plane_half() {
1347 let poly = vec![
1348 [0.0, -1.0, 0.0_f64],
1349 [0.0, 1.0, 0.0],
1350 [1.0, 1.0, 0.0],
1351 [1.0, -1.0, 0.0],
1352 ];
1353 let clipped = clip_polygon_by_plane(&poly, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1355 assert!(
1356 !clipped.is_empty(),
1357 "some points should remain after clipping"
1358 );
1359 for p in &clipped {
1360 assert!(
1361 p[1] >= -1e-10,
1362 "all remaining points should satisfy constraint"
1363 );
1364 }
1365 }
1366
1367 #[test]
1368 fn test_sat_face_axis_separated() {
1369 let ref_pts = vec![[0.0_f64, 0.0, 0.0], [1.0, 0.0, 0.0]];
1370 let inc_pts = vec![[3.0_f64, 0.0, 0.0], [4.0, 0.0, 0.0]];
1371 let sep = sat_face_axis([1.0, 0.0, 0.0], &ref_pts, &inc_pts);
1372 assert!(sep > 0.0, "should detect separation: {}", sep);
1373 }
1374
1375 #[test]
1376 fn test_sat_face_axis_overlapping() {
1377 let ref_pts = vec![[0.0_f64, 0.0, 0.0], [2.0, 0.0, 0.0]];
1378 let inc_pts = vec![[1.0_f64, 0.0, 0.0], [3.0, 0.0, 0.0]];
1379 let sep = sat_face_axis([1.0, 0.0, 0.0], &ref_pts, &inc_pts);
1380 assert!(sep < 0.0, "should detect overlap: {}", sep);
1381 }
1382
1383 #[test]
1384 fn test_find_incident_face() {
1385 let normals = vec![[0.0, 1.0, 0.0_f64], [0.0, -1.0, 0.0], [1.0, 0.0, 0.0]];
1386 let (idx, _) = find_incident_face(&normals, [0.0, 1.0, 0.0]);
1387 assert_eq!(idx, 1, "most anti-parallel face should be found");
1389 }
1390
1391 #[test]
1392 fn test_polyhedron_face_face_contact() {
1393 let pg = PolyhedronContact::new(4);
1394 let ref_face = vec![
1396 [0.0_f64, 0.0, 0.0],
1397 [1.0, 0.0, 0.0],
1398 [1.0, 1.0, 0.0],
1399 [0.0, 1.0, 0.0],
1400 ];
1401 let inc_face = vec![
1402 [0.2_f64, 0.2, -0.1],
1403 [0.8, 0.2, -0.1],
1404 [0.8, 0.8, -0.1],
1405 [0.2, 0.8, -0.1],
1406 ];
1407 let contacts = pg.face_face_contact(&ref_face, &inc_face, [0.0, 0.0, 1.0], 0.2);
1408 let _ = contacts;
1410 }
1411
1412 #[test]
1413 fn test_polyhedron_edge_edge_closest() {
1414 let pg = PolyhedronContact::new(4);
1415 let pa = [0.0, 0.0, 0.0_f64];
1416 let da = [1.0, 0.0, 0.0];
1417 let pb = [0.5, 0.5, -1.0_f64];
1418 let db = [0.0, 0.0, 1.0];
1419 let (ca, cb, dist) = pg.edge_edge_closest(pa, da, pb, db);
1420 let _ = (ca, cb);
1421 assert!(
1422 dist < 1.0,
1423 "closest distance should be less than 1: {}",
1424 dist
1425 );
1426 }
1427
1428 #[test]
1429 fn test_box_box_manifold_separated() {
1430 let bbox = BoxBoxManifold::new(0.01);
1431 let id = [[1.0, 0.0, 0.0_f64], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1432 let contacts = bbox.compute([0.0; 3], [1.0; 3], id, [5.0, 0.0, 0.0], [1.0; 3], id);
1433 assert!(
1434 contacts.is_empty(),
1435 "separated boxes should have no contacts"
1436 );
1437 }
1438
1439 #[test]
1440 fn test_capsule_capsule_separated() {
1441 let contacts = CapsuleCapsuleManifold::compute(
1442 [0.0; 3],
1443 [0.0, 1.0, 0.0],
1444 0.5,
1445 [5.0, 0.0, 0.0],
1446 [5.0, 1.0, 0.0],
1447 0.5,
1448 );
1449 assert!(
1450 contacts.is_empty(),
1451 "separated capsules should have no contacts"
1452 );
1453 }
1454
1455 #[test]
1456 fn test_capsule_capsule_intersecting() {
1457 let contacts = CapsuleCapsuleManifold::compute(
1458 [0.0, 0.0, 0.0],
1459 [0.0, 2.0, 0.0],
1460 0.5,
1461 [0.3, 0.0, 0.0],
1462 [0.3, 2.0, 0.0],
1463 0.5,
1464 );
1465 assert!(
1466 !contacts.is_empty(),
1467 "close capsules should generate contact"
1468 );
1469 assert!(contacts[0].depth > 0.0, "contact depth should be positive");
1470 }
1471
1472 #[test]
1473 fn test_mesh_convex_sphere_contact() {
1474 let manifold_gen = MeshConvexManifold::new(8, 1e-3);
1475 let tri = ([0.0, 0.0, 0.0_f64], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1476 let n = [0.0, 0.0, 1.0_f64];
1477 let sphere_center = [0.25, 0.25, 0.1]; let contact = manifold_gen.triangle_sphere_contact(tri, n, sphere_center, 0.5);
1479 assert!(contact.is_some(), "sphere above triangle should contact");
1480 }
1481
1482 #[test]
1483 fn test_manifold_reduction_under_limit() {
1484 let pts: Vec<_> = (0..3)
1485 .map(|i| {
1486 ContactPoint::new(
1487 [i as f64, 0.0, 0.0],
1488 [i as f64, 0.0, 0.0],
1489 [0.0, 1.0, 0.0],
1490 0.1,
1491 )
1492 })
1493 .collect();
1494 let reduced = ManifoldReduction::reduce(&pts);
1495 assert_eq!(reduced.len(), 3, "under 4 points should not reduce");
1496 }
1497
1498 #[test]
1499 fn test_manifold_reduction_over_limit() {
1500 let pts: Vec<_> = (0..10)
1501 .map(|i| {
1502 let d = (i as f64) * 0.5;
1503 ContactPoint::new(
1504 [i as f64, 0.0, 0.0],
1505 [i as f64, 0.0, 0.0],
1506 [0.0, 1.0, 0.0],
1507 d,
1508 )
1509 })
1510 .collect();
1511 let reduced = ManifoldReduction::reduce(&pts);
1512 assert!(
1513 reduced.len() <= MAX_MANIFOLD_POINTS,
1514 "reduced should have at most 4 points"
1515 );
1516 }
1517
1518 #[test]
1519 fn test_persistent_manifold_update() {
1520 let mut pm = PersistentManifold::new(1, 2, 0.1);
1521 let pts = vec![ContactPoint::new([0.0; 3], [0.0; 3], [0.0, 1.0, 0.0], 0.1)];
1522 pm.update(&pts, 1);
1523 assert_eq!(pm.num_points(), 1);
1524 }
1525
1526 #[test]
1527 fn test_persistent_manifold_warm_start() {
1528 let mut pm = PersistentManifold::new(1, 2, 0.1);
1529 let mut cp = ContactPoint::new([0.0; 3], [0.0; 3], [0.0, 1.0, 0.0], 0.1);
1530 cp.impulse = 5.0;
1531 pm.manifold.add_point(cp);
1532 pm.point_ages.push(0);
1533 let impulse = pm.warm_start_impulse(0);
1534 assert!((impulse - 5.0).abs() < 1e-10);
1535 }
1536
1537 #[test]
1538 fn test_feature_contact_classification() {
1539 let face_normals = vec![[0.0, 1.0, 0.0_f64], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
1540 let ft = FeatureContact::classify_feature([0.0, 1.0, 0.0], &face_normals);
1541 assert_eq!(
1542 ft,
1543 FeatureType::Face,
1544 "aligned with face normal should be Face"
1545 );
1546 }
1547
1548 #[test]
1549 fn test_contact_normal_smoothing_phong() {
1550 let smoother = ContactNormalSmoothing::new(NormalSmoothingMethod::Phong, 0.3);
1551 let face_n = [0.0, 0.0, 1.0_f64];
1552 let vn = ([0.0, 0.0, 1.0_f64], [0.0, 0.1, 0.995], [0.0, -0.1, 0.995]);
1553 let smooth_n = smoother.smooth_normal(face_n, vn, 0.5, 0.0);
1554 let len = norm3(smooth_n);
1555 assert!(
1556 (len - 1.0).abs() < 1e-6,
1557 "smoothed normal should be unit length"
1558 );
1559 }
1560
1561 #[test]
1562 fn test_contact_normal_vertex_normals() {
1563 let verts = vec![[0.0, 0.0, 0.0_f64], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1564 let indices = vec![[0, 1, 2]];
1565 let normals = ContactNormalSmoothing::compute_vertex_normals(&verts, &indices);
1566 assert_eq!(normals.len(), 3);
1567 for n in &normals {
1568 let len = norm3(*n);
1569 assert!(
1570 (len - 1.0).abs() < 1e-6 || len < 1e-10,
1571 "vertex normals should be unit: {}",
1572 len
1573 );
1574 }
1575 }
1576
1577 #[test]
1578 fn test_contact_caching_get_or_create() {
1579 let mut cache = ContactCaching::new(10);
1580 let m = cache.get_or_create(1, 2);
1581 assert_eq!(m.manifold.body_a, 1);
1582 }
1583
1584 #[test]
1585 fn test_contact_caching_size_limit() {
1586 let mut cache = ContactCaching::new(2);
1587 cache.get_or_create(1, 2).manifold.last_frame = 1;
1588 cache.get_or_create(3, 4).manifold.last_frame = 2;
1589 cache.get_or_create(5, 6).manifold.last_frame = 3;
1590 assert!(cache.num_cached() <= 2, "cache should not exceed max_size");
1591 }
1592
1593 #[test]
1594 fn test_contact_event_system_begin() {
1595 let mut evs = ContactEventSystem::new();
1596 evs.update(&[(1, 2)]);
1597 let begin = evs
1598 .pending_events
1599 .iter()
1600 .any(|e| matches!(e, ContactEvent::Begin(1, 2) | ContactEvent::Begin(2, 1)));
1601 assert!(begin, "should emit Begin event for new contact");
1602 }
1603
1604 #[test]
1605 fn test_contact_event_system_end() {
1606 let mut evs = ContactEventSystem::new();
1607 evs.update(&[(1, 2)]);
1608 evs.update(&[]); let end = evs
1610 .pending_events
1611 .iter()
1612 .any(|e| matches!(e, ContactEvent::End(_, _)));
1613 assert!(end, "should emit End event when contact is removed");
1614 }
1615
1616 #[test]
1617 fn test_contact_event_system_persist() {
1618 let mut evs = ContactEventSystem::new();
1619 evs.update(&[(1, 2)]);
1620 evs.update(&[(1, 2)]); let persist = evs
1622 .pending_events
1623 .iter()
1624 .any(|e| matches!(e, ContactEvent::Persist(_, _)));
1625 assert!(persist, "should emit Persist event for ongoing contact");
1626 }
1627
1628 #[test]
1629 fn test_contact_manifold_add_points() {
1630 let mut mf = ContactManifoldEx::new(1, 2);
1631 for i in 0..5 {
1632 mf.add_point(ContactPoint::new(
1633 [i as f64, 0.0, 0.0],
1634 [0.0; 3],
1635 [0.0, 1.0, 0.0],
1636 0.1,
1637 ));
1638 }
1639 assert!(
1640 mf.num_points() <= MAX_MANIFOLD_POINTS,
1641 "manifold should not exceed max points"
1642 );
1643 }
1644
1645 #[test]
1646 fn test_contact_manifold_prune() {
1647 let mut mf = ContactManifoldEx::new(1, 2);
1648 mf.add_point(ContactPoint::new([0.0; 3], [0.0; 3], [0.0, 1.0, 0.0], 0.1));
1649 mf.add_point(ContactPoint::new(
1650 [1.0, 0.0, 0.0],
1651 [0.0; 3],
1652 [0.0, 1.0, 0.0],
1653 -0.5,
1654 ));
1655 mf.prune_inactive();
1656 assert_eq!(mf.num_points(), 1, "negative depth point should be pruned");
1657 }
1658}