1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
12#![allow(non_snake_case)]
13
14use crate::math::Real;
15
16#[inline]
22fn v3_add(a: [Real; 3], b: [Real; 3]) -> [Real; 3] {
23 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
24}
25
26#[inline]
28fn v3_sub(a: [Real; 3], b: [Real; 3]) -> [Real; 3] {
29 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
30}
31
32#[inline]
34fn v3_scale(a: [Real; 3], s: Real) -> [Real; 3] {
35 [a[0] * s, a[1] * s, a[2] * s]
36}
37
38#[inline]
40fn v3_dot(a: [Real; 3], b: [Real; 3]) -> Real {
41 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
42}
43
44#[inline]
46fn v3_cross(a: [Real; 3], b: [Real; 3]) -> [Real; 3] {
47 [
48 a[1] * b[2] - a[2] * b[1],
49 a[2] * b[0] - a[0] * b[2],
50 a[0] * b[1] - a[1] * b[0],
51 ]
52}
53
54#[inline]
56fn v3_len_sq(a: [Real; 3]) -> Real {
57 v3_dot(a, a)
58}
59
60#[inline]
62fn v3_len(a: [Real; 3]) -> Real {
63 v3_len_sq(a).sqrt()
64}
65
66#[inline]
68fn v3_normalize(a: [Real; 3]) -> [Real; 3] {
69 let len = v3_len(a);
70 if len > 1e-15 {
71 v3_scale(a, 1.0 / len)
72 } else {
73 [0.0; 3]
74 }
75}
76
77#[inline]
79fn v3_neg(a: [Real; 3]) -> [Real; 3] {
80 [-a[0], -a[1], -a[2]]
81}
82
83pub trait ConvexShape {
89 fn support(&self, dir: [Real; 3]) -> [Real; 3];
91}
92
93#[derive(Debug, Clone)]
99pub struct Sphere {
100 pub center: [Real; 3],
102 pub radius: Real,
104}
105
106impl Sphere {
107 pub fn new(center: [Real; 3], radius: Real) -> Self {
109 Self { center, radius }
110 }
111}
112
113impl ConvexShape for Sphere {
114 fn support(&self, dir: [Real; 3]) -> [Real; 3] {
115 let n = v3_normalize(dir);
116 v3_add(self.center, v3_scale(n, self.radius))
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct Box3 {
123 pub center: [Real; 3],
125 pub half_extents: [Real; 3],
127}
128
129impl Box3 {
130 pub fn new(center: [Real; 3], half_extents: [Real; 3]) -> Self {
132 Self {
133 center,
134 half_extents,
135 }
136 }
137}
138
139impl ConvexShape for Box3 {
140 fn support(&self, dir: [Real; 3]) -> [Real; 3] {
141 [
142 self.center[0] + self.half_extents[0] * dir[0].signum(),
143 self.center[1] + self.half_extents[1] * dir[1].signum(),
144 self.center[2] + self.half_extents[2] * dir[2].signum(),
145 ]
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct Capsule {
152 pub a: [Real; 3],
154 pub b: [Real; 3],
156 pub radius: Real,
158}
159
160impl Capsule {
161 pub fn new(a: [Real; 3], b: [Real; 3], radius: Real) -> Self {
163 Self { a, b, radius }
164 }
165}
166
167impl ConvexShape for Capsule {
168 fn support(&self, dir: [Real; 3]) -> [Real; 3] {
169 let da = v3_dot(self.a, dir);
171 let db = v3_dot(self.b, dir);
172 let base = if da >= db { self.a } else { self.b };
173 v3_add(base, v3_scale(v3_normalize(dir), self.radius))
174 }
175}
176
177#[derive(Debug, Clone)]
183pub struct SphereSphereContact {
184 pub overlapping: bool,
186 pub normal: [Real; 3],
188 pub depth: Real,
190 pub point: [Real; 3],
192}
193
194pub fn sphere_sphere(a: &Sphere, b: &Sphere) -> SphereSphereContact {
196 let d = v3_sub(b.center, a.center);
197 let dist = v3_len(d);
198 let sum_r = a.radius + b.radius;
199 let depth = sum_r - dist;
200 let normal = if dist > 1e-12 {
201 v3_scale(d, 1.0 / dist)
202 } else {
203 [0.0, 1.0, 0.0] };
205 let point = v3_add(a.center, v3_scale(normal, a.radius - depth * 0.5));
206 SphereSphereContact {
207 overlapping: depth > 0.0,
208 normal,
209 depth,
210 point,
211 }
212}
213
214pub fn point_segment_closest(p: [Real; 3], a: [Real; 3], b: [Real; 3]) -> ([Real; 3], Real) {
221 let ab = v3_sub(b, a);
222 let ap = v3_sub(p, a);
223 let t = (v3_dot(ap, ab) / v3_len_sq(ab).max(1e-30)).clamp(0.0, 1.0);
224 let closest = v3_add(a, v3_scale(ab, t));
225 let dist = v3_len(v3_sub(p, closest));
226 (closest, dist)
227}
228
229pub fn sphere_capsule(sphere: &Sphere, cap: &Capsule) -> (bool, Real, [Real; 3], [Real; 3]) {
232 let (closest, dist) = point_segment_closest(sphere.center, cap.a, cap.b);
233 let depth = (sphere.radius + cap.radius) - dist;
234 let d = v3_sub(sphere.center, closest);
235 let normal = if dist > 1e-12 {
236 v3_scale(d, 1.0 / dist)
237 } else {
238 [0.0, 1.0, 0.0]
239 };
240 let point = v3_add(closest, v3_scale(normal, cap.radius - depth * 0.5));
241 (depth > 0.0, depth, normal, point)
242}
243
244#[derive(Debug, Clone, Copy)]
250pub struct AabbRaw {
251 pub min: [Real; 3],
253 pub max: [Real; 3],
255}
256
257impl AabbRaw {
258 pub fn new(min: [Real; 3], max: [Real; 3]) -> Self {
260 Self { min, max }
261 }
262
263 pub fn from_center_half(center: [Real; 3], half: [Real; 3]) -> Self {
265 Self {
266 min: [
267 center[0] - half[0],
268 center[1] - half[1],
269 center[2] - half[2],
270 ],
271 max: [
272 center[0] + half[0],
273 center[1] + half[1],
274 center[2] + half[2],
275 ],
276 }
277 }
278
279 pub fn overlaps(&self, other: &AabbRaw) -> bool {
281 self.min[0] <= other.max[0]
282 && self.max[0] >= other.min[0]
283 && self.min[1] <= other.max[1]
284 && self.max[1] >= other.min[1]
285 && self.min[2] <= other.max[2]
286 && self.max[2] >= other.min[2]
287 }
288
289 pub fn merge(&self, other: &AabbRaw) -> Self {
291 Self {
292 min: [
293 self.min[0].min(other.min[0]),
294 self.min[1].min(other.min[1]),
295 self.min[2].min(other.min[2]),
296 ],
297 max: [
298 self.max[0].max(other.max[0]),
299 self.max[1].max(other.max[1]),
300 self.max[2].max(other.max[2]),
301 ],
302 }
303 }
304
305 pub fn surface_area(&self) -> Real {
307 let dx = self.max[0] - self.min[0];
308 let dy = self.max[1] - self.min[1];
309 let dz = self.max[2] - self.min[2];
310 2.0 * (dx * dy + dy * dz + dz * dx)
311 }
312
313 pub fn center(&self) -> [Real; 3] {
315 [
316 (self.min[0] + self.max[0]) * 0.5,
317 (self.min[1] + self.max[1]) * 0.5,
318 (self.min[2] + self.max[2]) * 0.5,
319 ]
320 }
321}
322
323#[derive(Debug, Clone)]
329pub struct BvhLeaf {
330 pub id: u64,
332 pub aabb: AabbRaw,
334}
335
336#[derive(Debug, Clone)]
338enum BvhNode {
339 Leaf {
340 id: u64,
341 aabb: AabbRaw,
342 },
343 Internal {
344 aabb: AabbRaw,
345 left: Box<BvhNode>,
346 right: Box<BvhNode>,
347 },
348}
349
350impl BvhNode {
351 fn aabb(&self) -> &AabbRaw {
352 match self {
353 BvhNode::Leaf { aabb, .. } => aabb,
354 BvhNode::Internal { aabb, .. } => aabb,
355 }
356 }
357
358 fn query_overlap(&self, query: &AabbRaw, out: &mut Vec<u64>) {
359 if !self.aabb().overlaps(query) {
360 return;
361 }
362 match self {
363 BvhNode::Leaf { id, .. } => out.push(*id),
364 BvhNode::Internal { left, right, .. } => {
365 left.query_overlap(query, out);
366 right.query_overlap(query, out);
367 }
368 }
369 }
370
371 fn all_pairs<'a>(&'a self, other: &'a BvhNode, out: &mut Vec<(u64, u64)>) {
372 if !self.aabb().overlaps(other.aabb()) {
373 return;
374 }
375 match (self, other) {
376 (BvhNode::Leaf { id: id_a, .. }, BvhNode::Leaf { id: id_b, .. }) => {
377 if id_a != id_b {
378 out.push((*id_a.min(id_b), *id_a.max(id_b)));
379 }
380 }
381 (BvhNode::Internal { left, right, .. }, _) => {
382 left.all_pairs(other, out);
383 right.all_pairs(other, out);
384 }
385 (_, BvhNode::Internal { left, right, .. }) => {
386 self.all_pairs(left, out);
387 self.all_pairs(right, out);
388 }
389 }
390 }
391}
392
393pub struct Bvh {
395 root: Option<BvhNode>,
396}
397
398impl Bvh {
399 pub fn build(leaves: Vec<BvhLeaf>) -> Self {
401 if leaves.is_empty() {
402 return Self { root: None };
403 }
404 Self {
405 root: Some(Self::build_recursive(leaves)),
406 }
407 }
408
409 fn build_recursive(mut leaves: Vec<BvhLeaf>) -> BvhNode {
410 if leaves.len() == 1 {
411 let leaf = leaves.remove(0);
412 return BvhNode::Leaf {
413 id: leaf.id,
414 aabb: leaf.aabb,
415 };
416 }
417
418 let mut aabb = leaves[0].aabb;
420 for l in &leaves[1..] {
421 aabb = aabb.merge(&l.aabb);
422 }
423
424 let dx = aabb.max[0] - aabb.min[0];
426 let dy = aabb.max[1] - aabb.min[1];
427 let dz = aabb.max[2] - aabb.min[2];
428 let axis = if dx >= dy && dx >= dz {
429 0
430 } else if dy >= dz {
431 1
432 } else {
433 2
434 };
435
436 leaves.sort_by(|a, b| {
437 a.aabb.center()[axis]
438 .partial_cmp(&b.aabb.center()[axis])
439 .unwrap_or(std::cmp::Ordering::Equal)
440 });
441
442 let mid = leaves.len() / 2;
443 let right_leaves = leaves.split_off(mid);
444 let left_leaves = leaves;
445
446 let left = Box::new(Self::build_recursive(left_leaves));
447 let right = Box::new(Self::build_recursive(right_leaves));
448 BvhNode::Internal { aabb, left, right }
449 }
450
451 pub fn query(&self, aabb: &AabbRaw) -> Vec<u64> {
453 let mut result = Vec::new();
454 if let Some(root) = &self.root {
455 root.query_overlap(aabb, &mut result);
456 }
457 result
458 }
459
460 pub fn overlapping_pairs(&self) -> Vec<(u64, u64)> {
462 let mut pairs = Vec::new();
463 if let Some(root) = &self.root {
464 root.all_pairs(root, &mut pairs);
465 }
466 pairs.sort_unstable();
468 pairs.dedup();
469 pairs
470 }
471}
472
473fn gjk_support(a: &dyn ConvexShape, b: &dyn ConvexShape, dir: [Real; 3]) -> [Real; 3] {
479 v3_sub(a.support(dir), b.support(v3_neg(dir)))
480}
481
482#[derive(Debug, Clone)]
484pub struct GjkResult {
485 pub intersecting: bool,
487 pub distance: Real,
489 pub closest_a: [Real; 3],
491 pub closest_b: [Real; 3],
493}
494
495pub fn gjk(a: &dyn ConvexShape, b: &dyn ConvexShape) -> GjkResult {
500 const MAX_ITER: usize = 64;
501 const EPS: Real = 1e-10;
502
503 let mut simplex: Vec<[Real; 3]> = Vec::with_capacity(4);
504 let mut dir = [1.0, 0.0, 0.0_f64];
505
506 let first = gjk_support(a, b, dir);
507 simplex.push(first);
508 dir = v3_neg(first);
509
510 for _ in 0..MAX_ITER {
511 let len_sq = v3_len_sq(dir);
512 if len_sq < EPS {
513 return GjkResult {
515 intersecting: true,
516 distance: 0.0,
517 closest_a: [0.0; 3],
518 closest_b: [0.0; 3],
519 };
520 }
521
522 let support = gjk_support(a, b, dir);
523
524 if v3_dot(support, dir) < 0.0 {
526 let dist = v3_len_sq(dir).sqrt();
527 return GjkResult {
528 intersecting: false,
529 distance: dist,
530 closest_a: a.support(dir),
531 closest_b: b.support(v3_neg(dir)),
532 };
533 }
534
535 simplex.push(support);
536
537 if gjk_do_simplex(&mut simplex, &mut dir) {
538 return GjkResult {
539 intersecting: true,
540 distance: 0.0,
541 closest_a: [0.0; 3],
542 closest_b: [0.0; 3],
543 };
544 }
545 }
546
547 GjkResult {
549 intersecting: true,
550 distance: 0.0,
551 closest_a: [0.0; 3],
552 closest_b: [0.0; 3],
553 }
554}
555
556fn gjk_do_simplex(simplex: &mut Vec<[Real; 3]>, dir: &mut [Real; 3]) -> bool {
558 match simplex.len() {
559 2 => gjk_line_case(simplex, dir),
560 3 => gjk_triangle_case(simplex, dir),
561 4 => gjk_tetrahedron_case(simplex, dir),
562 _ => false,
563 }
564}
565
566fn gjk_line_case(simplex: &mut Vec<[Real; 3]>, dir: &mut [Real; 3]) -> bool {
567 let b = simplex[0];
568 let a = simplex[1];
569 let ab = v3_sub(b, a);
570 let ao = v3_neg(a);
571 if v3_dot(ab, ao) > 0.0 {
572 *dir = v3_sub(v3_scale(ab, v3_dot(ab, ao) / v3_dot(ab, ab)), ao);
574 } else {
575 simplex.clear();
576 simplex.push(a);
577 *dir = ao;
578 }
579 false
580}
581
582fn gjk_triangle_case(simplex: &mut Vec<[Real; 3]>, dir: &mut [Real; 3]) -> bool {
583 let c = simplex[0];
584 let b = simplex[1];
585 let a = simplex[2];
586 let ab = v3_sub(b, a);
587 let ac = v3_sub(c, a);
588 let ao = v3_neg(a);
589 let abc = v3_cross(ab, ac);
590
591 if v3_dot(v3_cross(abc, ac), ao) > 0.0 {
593 if v3_dot(ac, ao) > 0.0 {
594 simplex.clear();
595 simplex.push(c);
596 simplex.push(a);
597 *dir = v3_sub(v3_scale(ac, v3_dot(ac, ao) / v3_dot(ac, ac)), ao);
598 } else {
599 simplex.clear();
600 simplex.push(b);
601 simplex.push(a);
602 return gjk_line_case(simplex, dir);
603 }
604 } else if v3_dot(v3_cross(ab, abc), ao) > 0.0 {
605 simplex.clear();
606 simplex.push(b);
607 simplex.push(a);
608 return gjk_line_case(simplex, dir);
609 } else {
610 if v3_dot(abc, ao) > 0.0 {
612 *dir = abc;
613 } else {
614 simplex.swap(0, 1); *dir = v3_neg(abc);
616 }
617 }
618 false
619}
620
621fn gjk_tetrahedron_case(simplex: &mut Vec<[Real; 3]>, dir: &mut [Real; 3]) -> bool {
622 let d = simplex[0];
623 let c = simplex[1];
624 let b = simplex[2];
625 let a = simplex[3];
626 let ab = v3_sub(b, a);
627 let ac = v3_sub(c, a);
628 let ad = v3_sub(d, a);
629 let ao = v3_neg(a);
630
631 let abc = v3_cross(ab, ac);
632 let acd = v3_cross(ac, ad);
633 let adb = v3_cross(ad, ab);
634
635 if v3_dot(abc, ao) > 0.0 {
636 simplex.clear();
637 simplex.push(c);
638 simplex.push(b);
639 simplex.push(a);
640 return gjk_triangle_case(simplex, dir);
641 }
642 if v3_dot(acd, ao) > 0.0 {
643 simplex.clear();
644 simplex.push(d);
645 simplex.push(c);
646 simplex.push(a);
647 return gjk_triangle_case(simplex, dir);
648 }
649 if v3_dot(adb, ao) > 0.0 {
650 simplex.clear();
651 simplex.push(b);
652 simplex.push(d);
653 simplex.push(a);
654 return gjk_triangle_case(simplex, dir);
655 }
656 true
658}
659
660#[derive(Debug, Clone)]
666pub struct Obb {
667 pub center: [Real; 3],
669 pub axes: [[Real; 3]; 3],
671 pub half_extents: [Real; 3],
673}
674
675impl Obb {
676 pub fn new(center: [Real; 3], axes: [[Real; 3]; 3], half_extents: [Real; 3]) -> Self {
678 Self {
679 center,
680 axes,
681 half_extents,
682 }
683 }
684
685 pub fn axis_aligned(center: [Real; 3], half_extents: [Real; 3]) -> Self {
687 Self {
688 center,
689 axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
690 half_extents,
691 }
692 }
693
694 fn project(&self, ax: [Real; 3]) -> (Real, Real) {
696 let c = v3_dot(self.center, ax);
697 let r = self
698 .axes
699 .iter()
700 .zip(self.half_extents.iter())
701 .map(|(&a, &h)| v3_dot(a, ax).abs() * h)
702 .sum::<Real>();
703 (c - r, c + r)
704 }
705
706 pub fn vertices(&self) -> [[Real; 3]; 8] {
708 let mut verts = [[0.0; 3]; 8];
709 let signs = [
710 [-1.0, -1.0, -1.0],
711 [1.0, -1.0, -1.0],
712 [-1.0, 1.0, -1.0],
713 [1.0, 1.0, -1.0],
714 [-1.0, -1.0, 1.0],
715 [1.0, -1.0, 1.0],
716 [-1.0, 1.0, 1.0],
717 [1.0, 1.0, 1.0],
718 ];
719 for (i, s) in signs.iter().enumerate() {
720 let mut v = self.center;
721 for k in 0..3 {
722 v = v3_add(v, v3_scale(self.axes[k], s[k] * self.half_extents[k]));
723 }
724 verts[i] = v;
725 }
726 verts
727 }
728}
729
730pub fn obb_obb_sat(a: &Obb, b: &Obb) -> Option<(Real, [Real; 3])> {
733 let mut min_depth = Real::MAX;
734 let mut min_axis = [0.0; 3];
735
736 let mut test_axis = |ax: [Real; 3]| -> bool {
738 let len_sq = v3_len_sq(ax);
739 if len_sq < 1e-10 {
740 return true; }
742 let ax = v3_scale(ax, 1.0 / len_sq.sqrt());
743 let (a_min, a_max) = a.project(ax);
744 let (b_min, b_max) = b.project(ax);
745 if a_max < b_min || b_max < a_min {
746 return false; }
748 let depth = (a_max.min(b_max) - a_min.max(b_min))
749 .min(a_max - a_min)
750 .min(b_max - b_min);
751 if depth < min_depth {
752 min_depth = depth;
753 min_axis = ax;
754 }
755 true
756 };
757
758 for i in 0..3 {
760 if !test_axis(a.axes[i]) {
761 return None;
762 }
763 }
764 for i in 0..3 {
766 if !test_axis(b.axes[i]) {
767 return None;
768 }
769 }
770 for i in 0..3 {
772 for j in 0..3 {
773 let ax = v3_cross(a.axes[i], b.axes[j]);
774 if !test_axis(ax) {
775 return None;
776 }
777 }
778 }
779
780 Some((min_depth, min_axis))
781}
782
783#[derive(Debug, Clone)]
789pub struct RayAabbHit {
790 pub t_min: Real,
792 pub t_max: Real,
794}
795
796pub fn ray_aabb(origin: [Real; 3], dir: [Real; 3], aabb: &AabbRaw) -> Option<RayAabbHit> {
800 let mut t_min = Real::NEG_INFINITY;
801 let mut t_max = Real::INFINITY;
802
803 for i in 0..3 {
804 if dir[i].abs() < 1e-12 {
805 if origin[i] < aabb.min[i] || origin[i] > aabb.max[i] {
807 return None;
808 }
809 } else {
810 let inv_d = 1.0 / dir[i];
811 let t1 = (aabb.min[i] - origin[i]) * inv_d;
812 let t2 = (aabb.max[i] - origin[i]) * inv_d;
813 let (ta, tb) = if t1 < t2 { (t1, t2) } else { (t2, t1) };
814 t_min = t_min.max(ta);
815 t_max = t_max.min(tb);
816 if t_min > t_max {
817 return None;
818 }
819 }
820 }
821 Some(RayAabbHit { t_min, t_max })
822}
823
824pub fn ray_sphere(origin: [Real; 3], dir: [Real; 3], sphere: &Sphere) -> Option<Real> {
826 let oc = v3_sub(origin, sphere.center);
827 let a = v3_dot(dir, dir);
828 let b = 2.0 * v3_dot(oc, dir);
829 let c = v3_dot(oc, oc) - sphere.radius * sphere.radius;
830 let discriminant = b * b - 4.0 * a * c;
831 if discriminant < 0.0 {
832 return None;
833 }
834 let sqrt_d = discriminant.sqrt();
835 let t0 = (-b - sqrt_d) / (2.0 * a);
836 let t1 = (-b + sqrt_d) / (2.0 * a);
837 if t0 > 0.0 {
839 Some(t0)
840 } else if t1 > 0.0 {
841 Some(t1)
842 } else {
843 None
844 }
845}
846
847#[derive(Debug, Clone)]
853pub struct Contact {
854 pub point: [Real; 3],
856 pub normal: [Real; 3],
858 pub depth: Real,
860 pub id_a: u64,
862 pub id_b: u64,
864}
865
866#[derive(Debug, Clone, Default)]
868pub struct ContactManifold {
869 pub contacts: Vec<Contact>,
871}
872
873impl ContactManifold {
874 pub fn new() -> Self {
876 Self::default()
877 }
878
879 pub fn add_contact(&mut self, contact: Contact) {
881 if self.contacts.len() < 4 {
882 self.contacts.push(contact);
883 } else {
884 if let Some(min_idx) = self
886 .contacts
887 .iter()
888 .enumerate()
889 .min_by(|(_, a), (_, b)| {
890 a.depth
891 .partial_cmp(&b.depth)
892 .unwrap_or(std::cmp::Ordering::Equal)
893 })
894 .map(|(i, _)| i)
895 {
896 let new_depth = contact.depth;
897 if new_depth > self.contacts[min_idx].depth {
898 self.contacts[min_idx] = contact;
899 }
900 }
901 }
902 }
903
904 pub fn average_normal(&self) -> [Real; 3] {
906 if self.contacts.is_empty() {
907 return [0.0, 1.0, 0.0];
908 }
909 let mut n = [0.0; 3];
910 for c in &self.contacts {
911 n[0] += c.normal[0];
912 n[1] += c.normal[1];
913 n[2] += c.normal[2];
914 }
915 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
916 if len > 1e-12 {
917 [n[0] / len, n[1] / len, n[2] / len]
918 } else {
919 [0.0, 1.0, 0.0]
920 }
921 }
922
923 pub fn max_depth(&self) -> Real {
925 self.contacts
926 .iter()
927 .map(|c| c.depth)
928 .fold(Real::NEG_INFINITY, Real::max)
929 }
930
931 pub fn is_empty(&self) -> bool {
933 self.contacts.is_empty()
934 }
935}
936
937#[derive(Debug, Clone)]
943pub struct EpaResult {
944 pub depth: Real,
946 pub normal: [Real; 3],
948 pub point: [Real; 3],
950}
951
952pub fn epa(a: &dyn ConvexShape, b: &dyn ConvexShape) -> Option<EpaResult> {
957 let gjk_r = gjk(a, b);
959 if !gjk_r.intersecting {
960 return None;
961 }
962
963 const MAX_ITER: usize = 64;
964 const EPS: Real = 1e-8;
965
966 let dirs = [
968 [1.0, 0.0, 0.0_f64],
969 [-1.0, 0.0, 0.0],
970 [0.0, 1.0, 0.0],
971 [0.0, -1.0, 0.0],
972 [0.0, 0.0, 1.0],
973 [0.0, 0.0, -1.0],
974 ];
975
976 let mut polytope: Vec<[Real; 3]> = dirs.iter().map(|&d| gjk_support(a, b, d)).collect();
977
978 let mut faces: Vec<[usize; 3]> = vec![
983 [0, 1, 2],
984 [0, 2, 3],
985 [0, 3, 4],
986 [0, 4, 1],
987 [5, 2, 1],
988 [5, 3, 2],
989 [5, 4, 3],
990 [5, 1, 4],
991 ];
992
993 for _ in 0..MAX_ITER {
994 let mut min_dist = Real::MAX;
996 let mut min_idx = 0;
997 let mut min_normal = [0.0_f64; 3];
998
999 for (fi, face) in faces.iter().enumerate() {
1000 let a_pt = polytope[face[0]];
1001 let b_pt = polytope[face[1]];
1002 let c_pt = polytope[face[2]];
1003 let ab = v3_sub(b_pt, a_pt);
1004 let ac = v3_sub(c_pt, a_pt);
1005 let n = v3_normalize(v3_cross(ab, ac));
1006 let d = v3_dot(n, a_pt).abs();
1007 if d < min_dist {
1008 min_dist = d;
1009 min_idx = fi;
1010 min_normal = n;
1011 }
1012 }
1013
1014 let support = gjk_support(a, b, min_normal);
1016 let new_dist = v3_dot(min_normal, support);
1017
1018 if (new_dist - min_dist).abs() < EPS {
1019 let contact = v3_scale(min_normal, min_dist);
1021 return Some(EpaResult {
1022 depth: min_dist,
1023 normal: min_normal,
1024 point: contact,
1025 });
1026 }
1027
1028 let new_idx = polytope.len();
1030 polytope.push(support);
1031
1032 let mut new_faces: Vec<[usize; 3]> = Vec::new();
1033 let mut edges: Vec<(usize, usize)> = Vec::new();
1034
1035 for (fi, &face) in faces.iter().enumerate() {
1036 if fi == min_idx {
1037 edges.push((face[0], face[1]));
1039 edges.push((face[1], face[2]));
1040 edges.push((face[2], face[0]));
1041 } else {
1042 new_faces.push(face);
1043 }
1044 }
1045
1046 for (ea, eb) in edges {
1048 new_faces.push([ea, eb, new_idx]);
1049 }
1050 faces = new_faces;
1051 }
1052
1053 let normal = [0.0, 1.0, 0.0];
1055 Some(EpaResult {
1056 depth: 0.0,
1057 normal,
1058 point: [0.0; 3],
1059 })
1060}
1061
1062pub fn minkowski_sum_support(
1070 a: &dyn ConvexShape,
1071 b: &dyn ConvexShape,
1072 dir: [Real; 3],
1073) -> [Real; 3] {
1074 v3_add(a.support(dir), b.support(dir))
1075}
1076
1077pub fn shape_cast_sphere_vs_sphere(
1085 moving: &Sphere,
1086 velocity: [Real; 3],
1087 target: &Sphere,
1088 max_t: Real,
1089) -> Option<Real> {
1090 let oc = v3_sub(moving.center, target.center);
1093 let combined_r = moving.radius + target.radius;
1094
1095 let a = v3_dot(velocity, velocity);
1096 let b = 2.0 * v3_dot(oc, velocity);
1097 let c = v3_dot(oc, oc) - combined_r * combined_r;
1098
1099 if a < 1e-15 {
1100 return if c <= 0.0 { Some(0.0) } else { None };
1102 }
1103
1104 let disc = b * b - 4.0 * a * c;
1105 if disc < 0.0 {
1106 return None;
1107 }
1108
1109 let sqrt_d = disc.sqrt();
1110 let t0 = (-b - sqrt_d) / (2.0 * a);
1111 let t1 = (-b + sqrt_d) / (2.0 * a);
1112
1113 for t in [t0, t1] {
1115 if t >= 0.0 && t <= max_t {
1116 return Some(t);
1117 }
1118 }
1119 if c <= 0.0 { Some(0.0) } else { None }
1121}
1122
1123pub fn shape_cast_sphere_vs_aabb(
1127 sphere: &Sphere,
1128 velocity: [Real; 3],
1129 aabb: &AabbRaw,
1130 max_t: Real,
1131) -> Option<Real> {
1132 let r = sphere.radius;
1134 let expanded = AabbRaw::new(
1135 [aabb.min[0] - r, aabb.min[1] - r, aabb.min[2] - r],
1136 [aabb.max[0] + r, aabb.max[1] + r, aabb.max[2] + r],
1137 );
1138 let hit = ray_aabb(sphere.center, velocity, &expanded)?;
1139 if hit.t_min < 0.0 || hit.t_min > max_t {
1140 return None;
1141 }
1142 Some(hit.t_min.max(0.0))
1143}
1144
1145#[derive(Debug, Clone)]
1151pub struct Triangle {
1152 pub vertices: [[Real; 3]; 3],
1154}
1155
1156impl Triangle {
1157 pub fn new(v0: [Real; 3], v1: [Real; 3], v2: [Real; 3]) -> Self {
1159 Self {
1160 vertices: [v0, v1, v2],
1161 }
1162 }
1163
1164 pub fn normal(&self) -> [Real; 3] {
1166 let e1 = v3_sub(self.vertices[1], self.vertices[0]);
1167 let e2 = v3_sub(self.vertices[2], self.vertices[0]);
1168 v3_cross(e1, e2)
1169 }
1170}
1171
1172impl ConvexShape for Triangle {
1173 fn support(&self, dir: [Real; 3]) -> [Real; 3] {
1174 self.vertices
1175 .iter()
1176 .copied()
1177 .max_by(|&a, &b| {
1178 v3_dot(a, dir)
1179 .partial_cmp(&v3_dot(b, dir))
1180 .unwrap_or(std::cmp::Ordering::Equal)
1181 })
1182 .unwrap_or([0.0; 3])
1183 }
1184}
1185
1186#[derive(Debug, Clone)]
1194pub struct ConvexPointCloud {
1195 pub points: Vec<[Real; 3]>,
1197}
1198
1199impl ConvexPointCloud {
1200 pub fn new(points: Vec<[Real; 3]>) -> Self {
1202 Self { points }
1203 }
1204}
1205
1206impl ConvexShape for ConvexPointCloud {
1207 fn support(&self, dir: [Real; 3]) -> [Real; 3] {
1208 self.points
1209 .iter()
1210 .copied()
1211 .max_by(|&a, &b| {
1212 v3_dot(a, dir)
1213 .partial_cmp(&v3_dot(b, dir))
1214 .unwrap_or(std::cmp::Ordering::Equal)
1215 })
1216 .unwrap_or([0.0; 3])
1217 }
1218}
1219
1220#[cfg(test)]
1225mod tests {
1226 use super::*;
1227
1228 #[test]
1231 fn test_v3_dot_orthogonal() {
1232 assert_eq!(v3_dot([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]), 0.0);
1233 }
1234
1235 #[test]
1236 fn test_v3_cross_basis() {
1237 let c = v3_cross([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1238 assert!((c[2] - 1.0).abs() < 1e-12);
1239 assert!(c[0].abs() < 1e-12 && c[1].abs() < 1e-12);
1240 }
1241
1242 #[test]
1243 fn test_v3_normalize_unit() {
1244 let n = v3_normalize([3.0, 4.0, 0.0]);
1245 let len = v3_len(n);
1246 assert!((len - 1.0).abs() < 1e-12, "len={}", len);
1247 }
1248
1249 #[test]
1250 fn test_v3_normalize_zero_safe() {
1251 let n = v3_normalize([0.0; 3]);
1252 assert_eq!(n, [0.0; 3]);
1253 }
1254
1255 #[test]
1258 fn test_sphere_sphere_overlap() {
1259 let a = Sphere::new([0.0; 3], 1.0);
1260 let b = Sphere::new([1.5, 0.0, 0.0], 1.0);
1261 let r = sphere_sphere(&a, &b);
1262 assert!(r.overlapping, "spheres should overlap");
1263 assert!(r.depth > 0.0, "depth should be positive");
1264 }
1265
1266 #[test]
1267 fn test_sphere_sphere_no_overlap() {
1268 let a = Sphere::new([0.0; 3], 1.0);
1269 let b = Sphere::new([5.0, 0.0, 0.0], 1.0);
1270 let r = sphere_sphere(&a, &b);
1271 assert!(!r.overlapping);
1272 assert!(r.depth < 0.0);
1273 }
1274
1275 #[test]
1276 fn test_sphere_sphere_touching() {
1277 let a = Sphere::new([0.0; 3], 1.0);
1278 let b = Sphere::new([2.0, 0.0, 0.0], 1.0);
1279 let r = sphere_sphere(&a, &b);
1280 assert!((r.depth).abs() < 1e-10, "depth should be ~0 at touching");
1281 }
1282
1283 #[test]
1286 fn test_sphere_capsule_overlap() {
1287 let s = Sphere::new([0.0; 3], 0.5);
1288 let c = Capsule::new([0.0, 2.0, 0.0], [0.0, 4.0, 0.0], 0.5);
1289 let (overlap, depth, _, _) = sphere_capsule(&s, &c);
1290 assert!(!overlap, "should not overlap, depth={}", depth);
1291 }
1292
1293 #[test]
1294 fn test_sphere_capsule_nearby() {
1295 let s = Sphere::new([0.0, 1.0, 0.0], 0.6);
1296 let c = Capsule::new([0.0, 0.0, 0.0], [0.0, 3.0, 0.0], 0.6);
1297 let (overlap, depth, _, _) = sphere_capsule(&s, &c);
1298 assert!(overlap, "should overlap, depth={}", depth);
1299 }
1300
1301 #[test]
1304 fn test_aabb_overlap() {
1305 let a = AabbRaw::new([-1.0; 3], [1.0; 3]);
1306 let b = AabbRaw::new([0.5, 0.5, 0.5], [2.0, 2.0, 2.0]);
1307 assert!(a.overlaps(&b));
1308 }
1309
1310 #[test]
1311 fn test_aabb_no_overlap() {
1312 let a = AabbRaw::new([-1.0; 3], [1.0; 3]);
1313 let b = AabbRaw::new([2.0; 3], [3.0; 3]);
1314 assert!(!a.overlaps(&b));
1315 }
1316
1317 #[test]
1318 fn test_aabb_merge_contains_both() {
1319 let a = AabbRaw::new([-2.0; 3], [0.0; 3]);
1320 let b = AabbRaw::new([1.0; 3], [3.0; 3]);
1321 let m = a.merge(&b);
1322 assert!(m.min[0] <= -2.0 && m.max[0] >= 3.0);
1323 }
1324
1325 #[test]
1328 fn test_bvh_query_single_match() {
1329 let leaves = vec![
1330 BvhLeaf {
1331 id: 1,
1332 aabb: AabbRaw::new([0.0; 3], [1.0; 3]),
1333 },
1334 BvhLeaf {
1335 id: 2,
1336 aabb: AabbRaw::new([5.0; 3], [6.0; 3]),
1337 },
1338 ];
1339 let bvh = Bvh::build(leaves);
1340 let query = AabbRaw::new([0.0; 3], [0.5; 3]);
1341 let hits = bvh.query(&query);
1342 assert_eq!(hits, vec![1]);
1343 }
1344
1345 #[test]
1346 fn test_bvh_query_no_match() {
1347 let leaves = vec![BvhLeaf {
1348 id: 1,
1349 aabb: AabbRaw::new([10.0; 3], [11.0; 3]),
1350 }];
1351 let bvh = Bvh::build(leaves);
1352 let query = AabbRaw::new([0.0; 3], [1.0; 3]);
1353 assert!(bvh.query(&query).is_empty());
1354 }
1355
1356 #[test]
1357 fn test_bvh_empty() {
1358 let bvh = Bvh::build(vec![]);
1359 let query = AabbRaw::new([0.0; 3], [1.0; 3]);
1360 assert!(bvh.query(&query).is_empty());
1361 }
1362
1363 #[test]
1366 fn test_obb_sat_overlap() {
1367 let a = Obb::axis_aligned([0.0; 3], [1.0; 3]);
1368 let b = Obb::axis_aligned([1.5, 0.0, 0.0], [1.0; 3]);
1369 assert!(obb_obb_sat(&a, &b).is_some(), "overlapping OBBs");
1370 }
1371
1372 #[test]
1373 fn test_obb_sat_separated() {
1374 let a = Obb::axis_aligned([0.0; 3], [1.0; 3]);
1375 let b = Obb::axis_aligned([5.0, 0.0, 0.0], [1.0; 3]);
1376 assert!(obb_obb_sat(&a, &b).is_none(), "separated OBBs");
1377 }
1378
1379 #[test]
1382 fn test_ray_aabb_hit() {
1383 let origin = [-5.0, 0.0, 0.0];
1384 let dir = [1.0, 0.0, 0.0];
1385 let aabb = AabbRaw::new([-1.0; 3], [1.0; 3]);
1386 let hit = ray_aabb(origin, dir, &aabb);
1387 assert!(hit.is_some());
1388 let h = hit.unwrap();
1389 assert!(h.t_min < h.t_max);
1390 }
1391
1392 #[test]
1393 fn test_ray_aabb_miss() {
1394 let origin = [0.0, 5.0, 0.0];
1395 let dir = [1.0, 0.0, 0.0];
1396 let aabb = AabbRaw::new([-1.0; 3], [1.0; 3]);
1397 assert!(ray_aabb(origin, dir, &aabb).is_none());
1398 }
1399
1400 #[test]
1401 fn test_ray_sphere_hit() {
1402 let s = Sphere::new([0.0; 3], 1.0);
1403 let t = ray_sphere([-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], &s);
1404 assert!(t.is_some());
1405 assert!((t.unwrap() - 4.0).abs() < 1e-10);
1406 }
1407
1408 #[test]
1409 fn test_ray_sphere_miss() {
1410 let s = Sphere::new([0.0; 3], 1.0);
1411 let t = ray_sphere([0.0, 5.0, 0.0], [1.0, 0.0, 0.0], &s);
1412 assert!(t.is_none());
1413 }
1414
1415 #[test]
1418 fn test_gjk_spheres_intersecting() {
1419 let a = Sphere::new([0.0; 3], 1.5);
1420 let b = Sphere::new([1.0, 0.0, 0.0], 1.5);
1421 let r = gjk(&a, &b);
1422 assert!(r.intersecting);
1423 }
1424
1425 #[test]
1426 fn test_gjk_spheres_separated() {
1427 let a = Sphere::new([0.0; 3], 0.5);
1428 let b = Sphere::new([10.0, 0.0, 0.0], 0.5);
1429 let r = gjk(&a, &b);
1430 assert!(!r.intersecting);
1431 assert!(r.distance > 0.0);
1432 }
1433
1434 #[test]
1437 fn test_manifold_add_and_max_depth() {
1438 let mut m = ContactManifold::new();
1439 m.add_contact(Contact {
1440 point: [0.0; 3],
1441 normal: [0.0, 1.0, 0.0],
1442 depth: 0.1,
1443 id_a: 1,
1444 id_b: 2,
1445 });
1446 m.add_contact(Contact {
1447 point: [1.0, 0.0, 0.0],
1448 normal: [0.0, 1.0, 0.0],
1449 depth: 0.3,
1450 id_a: 1,
1451 id_b: 2,
1452 });
1453 assert_eq!(m.contacts.len(), 2);
1454 assert!((m.max_depth() - 0.3).abs() < 1e-12);
1455 }
1456
1457 #[test]
1458 fn test_manifold_average_normal_unit() {
1459 let mut m = ContactManifold::new();
1460 for _ in 0..3 {
1461 m.add_contact(Contact {
1462 point: [0.0; 3],
1463 normal: [0.0, 1.0, 0.0],
1464 depth: 0.1,
1465 id_a: 1,
1466 id_b: 2,
1467 });
1468 }
1469 let n = m.average_normal();
1470 let len = v3_len(n);
1471 assert!((len - 1.0).abs() < 1e-12);
1472 }
1473
1474 #[test]
1477 fn test_epa_two_overlapping_spheres() {
1478 let a = Sphere::new([0.0; 3], 1.5);
1479 let b = Sphere::new([1.0, 0.0, 0.0], 1.5);
1480 let result = epa(&a, &b);
1481 assert!(
1482 result.is_some(),
1483 "EPA should return penetration for overlapping spheres"
1484 );
1485 let ep = result.unwrap();
1486 assert!(ep.depth >= 0.0, "penetration depth should be non-negative");
1487 }
1488
1489 #[test]
1490 fn test_epa_concentric_spheres() {
1491 let a = Sphere::new([0.0; 3], 2.0);
1492 let b = Sphere::new([0.0; 3], 1.0);
1493 let result = epa(&a, &b);
1494 assert!(result.is_some());
1496 }
1497
1498 #[test]
1499 fn test_epa_box_sphere_overlap() {
1500 let b = Box3::new([0.0; 3], [1.0; 3]);
1501 let s = Sphere::new([0.5, 0.0, 0.0], 0.8);
1502 let result = epa(&b, &s);
1503 assert!(
1504 result.is_some(),
1505 "overlapping box and sphere should produce EPA result"
1506 );
1507 }
1508
1509 #[test]
1512 fn test_support_sphere_axis_aligned() {
1513 let s = Sphere::new([1.0, 2.0, 3.0], 2.0);
1514 let sp_x = s.support([1.0, 0.0, 0.0]);
1515 assert!(
1516 (sp_x[0] - 3.0).abs() < 1e-10,
1517 "x support = center.x + radius"
1518 );
1519 let sp_y = s.support([0.0, 1.0, 0.0]);
1520 assert!(
1521 (sp_y[1] - 4.0).abs() < 1e-10,
1522 "y support = center.y + radius"
1523 );
1524 }
1525
1526 #[test]
1527 fn test_support_box_positive_axes() {
1528 let b = Box3::new([0.0; 3], [2.0, 3.0, 4.0]);
1529 let sp = b.support([1.0, 1.0, 1.0]);
1530 assert!((sp[0] - 2.0).abs() < 1e-10);
1531 assert!((sp[1] - 3.0).abs() < 1e-10);
1532 assert!((sp[2] - 4.0).abs() < 1e-10);
1533 }
1534
1535 #[test]
1536 fn test_support_box_negative_axes() {
1537 let b = Box3::new([0.0; 3], [1.0; 3]);
1538 let sp = b.support([-1.0, -1.0, -1.0]);
1539 assert!((sp[0] + 1.0).abs() < 1e-10);
1540 assert!((sp[1] + 1.0).abs() < 1e-10);
1541 assert!((sp[2] + 1.0).abs() < 1e-10);
1542 }
1543
1544 #[test]
1545 fn test_support_capsule_chooses_correct_endpoint() {
1546 let cap = Capsule::new([0.0, -1.0, 0.0], [0.0, 1.0, 0.0], 0.5);
1547 let sp_up = cap.support([0.0, 1.0, 0.0]);
1548 assert!(sp_up[1] > 1.0, "y support above top endpoint");
1550 let sp_down = cap.support([0.0, -1.0, 0.0]);
1551 assert!(sp_down[1] < -1.0, "y support below bottom endpoint");
1552 }
1553
1554 #[test]
1557 fn test_minkowski_sum_sphere_sphere_support() {
1558 let a = Sphere::new([0.0; 3], 1.0);
1559 let b = Sphere::new([0.0; 3], 2.0);
1560 let dir = [1.0, 0.0, 0.0];
1561 let sp = minkowski_sum_support(&a, &b, dir);
1563 let expected = a.support(dir)[0] + b.support(dir)[0];
1564 assert!((sp[0] - expected).abs() < 1e-10);
1565 }
1566
1567 #[test]
1568 fn test_minkowski_sum_sphere_box() {
1569 let s = Sphere::new([0.0; 3], 0.5);
1570 let b = Box3::new([0.0; 3], [1.0, 1.0, 1.0]);
1571 let dir = [1.0, 0.0, 0.0];
1572 let sp = minkowski_sum_support(&s, &b, dir);
1573 assert!(sp[0] >= b.support(dir)[0]);
1575 }
1576
1577 #[test]
1580 fn test_shape_cast_sphere_hits_sphere() {
1581 let moving = Sphere::new([0.0; 3], 0.5);
1582 let target = Sphere::new([5.0, 0.0, 0.0], 0.5);
1583 let velocity = [1.0, 0.0, 0.0];
1584 let t = shape_cast_sphere_vs_sphere(&moving, velocity, &target, 10.0);
1585 assert!(t.is_some(), "moving sphere should hit static sphere");
1586 let tv = t.unwrap();
1587 assert!((0.0..=10.0).contains(&tv));
1588 }
1589
1590 #[test]
1591 fn test_shape_cast_sphere_misses_sphere() {
1592 let moving = Sphere::new([0.0; 3], 0.5);
1593 let target = Sphere::new([0.0, 100.0, 0.0], 0.5);
1594 let velocity = [1.0, 0.0, 0.0]; let t = shape_cast_sphere_vs_sphere(&moving, velocity, &target, 10.0);
1596 assert!(
1597 t.is_none(),
1598 "sphere moving in X should miss sphere far in Y"
1599 );
1600 }
1601
1602 #[test]
1603 fn test_shape_cast_already_overlapping() {
1604 let moving = Sphere::new([0.0; 3], 1.0);
1605 let target = Sphere::new([0.5, 0.0, 0.0], 1.0);
1606 let velocity = [1.0, 0.0, 0.0];
1607 let t = shape_cast_sphere_vs_sphere(&moving, velocity, &target, 10.0);
1608 if let Some(tv) = t {
1610 assert!(tv >= 0.0);
1611 }
1612 }
1613
1614 #[test]
1617 fn test_gjk_box_sphere_intersecting() {
1618 let b = Box3::new([0.0; 3], [1.0; 3]);
1619 let s = Sphere::new([1.5, 0.0, 0.0], 1.0);
1620 let r = gjk(&b, &s);
1621 assert!(r.intersecting, "box and sphere should intersect");
1622 }
1623
1624 #[test]
1625 fn test_gjk_box_sphere_separated() {
1626 let b = Box3::new([0.0; 3], [1.0; 3]);
1627 let s = Sphere::new([10.0, 0.0, 0.0], 0.5);
1628 let r = gjk(&b, &s);
1629 assert!(!r.intersecting, "box and sphere should be separated");
1630 assert!(r.distance > 0.0);
1631 }
1632
1633 #[test]
1634 fn test_gjk_capsule_sphere_overlapping() {
1635 let cap = Capsule::new([0.0, -2.0, 0.0], [0.0, 2.0, 0.0], 0.5);
1636 let s = Sphere::new([0.0, 0.0, 0.0], 0.5);
1637 let r = gjk(&cap, &s);
1638 assert!(r.intersecting, "capsule and sphere should overlap");
1639 }
1640
1641 #[test]
1642 fn test_gjk_capsule_sphere_separated() {
1643 let cap = Capsule::new([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.3);
1644 let s = Sphere::new([5.0, 0.0, 0.0], 0.3);
1645 let r = gjk(&cap, &s);
1646 assert!(!r.intersecting);
1647 }
1648
1649 #[test]
1652 fn test_aabb_surface_area_unit_cube() {
1653 let a = AabbRaw::new([0.0; 3], [1.0; 3]);
1654 assert!((a.surface_area() - 6.0).abs() < 1e-12);
1656 }
1657
1658 #[test]
1659 fn test_aabb_center() {
1660 let a = AabbRaw::new([-2.0; 3], [4.0; 3]);
1661 let c = a.center();
1662 assert!((c[0] - 1.0).abs() < 1e-12);
1663 }
1664
1665 #[test]
1666 fn test_aabb_from_center_half() {
1667 let a = AabbRaw::from_center_half([1.0, 2.0, 3.0], [0.5; 3]);
1668 assert!((a.min[0] - 0.5).abs() < 1e-12);
1669 assert!((a.max[1] - 2.5).abs() < 1e-12);
1670 }
1671
1672 #[test]
1675 fn test_obb_vertices_count() {
1676 let obb = Obb::axis_aligned([0.0; 3], [1.0; 3]);
1677 let verts = obb.vertices();
1678 assert_eq!(verts.len(), 8);
1679 }
1680
1681 #[test]
1682 fn test_obb_axis_aligned_vertices_correct() {
1683 let obb = Obb::axis_aligned([0.0; 3], [1.0; 3]);
1684 let verts = obb.vertices();
1685 for v in &verts {
1687 assert!(v[0].abs() <= 1.0 + 1e-10);
1688 assert!(v[1].abs() <= 1.0 + 1e-10);
1689 assert!(v[2].abs() <= 1.0 + 1e-10);
1690 }
1691 }
1692
1693 #[test]
1694 fn test_obb_sat_touching() {
1695 let a = Obb::axis_aligned([0.0; 3], [1.0; 3]);
1697 let b = Obb::axis_aligned([2.0, 0.0, 0.0], [1.0; 3]);
1698 let result = obb_obb_sat(&a, &b);
1700 let _ = result;
1702 }
1703
1704 #[test]
1707 fn test_bvh_all_overlapping_pairs() {
1708 let leaves = vec![
1709 BvhLeaf {
1710 id: 1,
1711 aabb: AabbRaw::new([0.0; 3], [2.0; 3]),
1712 },
1713 BvhLeaf {
1714 id: 2,
1715 aabb: AabbRaw::new([1.0; 3], [3.0; 3]),
1716 },
1717 BvhLeaf {
1718 id: 3,
1719 aabb: AabbRaw::new([10.0; 3], [11.0; 3]),
1720 },
1721 ];
1722 let bvh = Bvh::build(leaves);
1723 let pairs = bvh.overlapping_pairs();
1724 assert!(
1726 pairs.contains(&(1, 2)) || pairs.contains(&(2, 1)),
1727 "pair (1,2) should be in overlapping pairs: {:?}",
1728 pairs
1729 );
1730 }
1731
1732 #[test]
1733 fn test_bvh_single_leaf() {
1734 let leaves = vec![BvhLeaf {
1735 id: 42,
1736 aabb: AabbRaw::new([0.0; 3], [1.0; 3]),
1737 }];
1738 let bvh = Bvh::build(leaves);
1739 let hits = bvh.query(&AabbRaw::new([0.0; 3], [0.5; 3]));
1740 assert_eq!(hits, vec![42]);
1741 }
1742
1743 #[test]
1746 fn test_manifold_overflow_replaces_shallowest() {
1747 let mut m = ContactManifold::new();
1748 for i in 0..5 {
1749 m.add_contact(Contact {
1750 point: [i as f64, 0.0, 0.0],
1751 normal: [0.0, 1.0, 0.0],
1752 depth: (i as f64 + 1.0) * 0.1,
1753 id_a: 1,
1754 id_b: 2,
1755 });
1756 }
1757 assert!(m.contacts.len() <= 4);
1759 }
1760
1761 #[test]
1762 fn test_manifold_is_empty_after_new() {
1763 let m = ContactManifold::new();
1764 assert!(m.is_empty());
1765 }
1766
1767 #[test]
1768 fn test_manifold_max_depth_empty() {
1769 let m = ContactManifold::new();
1770 assert_eq!(m.max_depth(), f64::NEG_INFINITY);
1772 }
1773
1774 #[test]
1777 fn test_point_segment_closest_midpoint() {
1778 let (cp, dist) = point_segment_closest([0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
1779 assert!(cp[0].abs() < 1e-12, "closest x should be 0");
1780 assert!((dist - 1.0).abs() < 1e-10);
1781 }
1782
1783 #[test]
1784 fn test_point_segment_closest_beyond_end() {
1785 let (cp, dist) = point_segment_closest([5.0, 0.0, 0.0], [0.0; 3], [1.0, 0.0, 0.0]);
1786 assert!((cp[0] - 1.0).abs() < 1e-12, "closest x should clamp to 1");
1787 assert!((dist - 4.0).abs() < 1e-10);
1788 }
1789
1790 #[test]
1793 fn test_ray_sphere_from_inside() {
1794 let s = Sphere::new([0.0; 3], 2.0);
1795 let t = ray_sphere([0.0; 3], [1.0, 0.0, 0.0], &s);
1797 assert!(t.is_some());
1798 assert!(t.unwrap() > 0.0);
1799 }
1800
1801 #[test]
1802 fn test_ray_aabb_along_each_axis() {
1803 let aabb = AabbRaw::new([-1.0; 3], [1.0; 3]);
1804 for axis in 0..3 {
1805 let mut origin = [0.0_f64; 3];
1807 let mut dir = [0.0_f64; 3];
1808 origin[axis] = -5.0;
1809 dir[axis] = 1.0;
1810 let hit = ray_aabb(origin, dir, &aabb);
1811 assert!(hit.is_some(), "ray along axis {axis} should hit unit AABB");
1812 }
1813 }
1814}