1#[inline]
11pub fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
12 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
13}
14
15#[inline]
16pub fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
17 [
18 a[1] * b[2] - a[2] * b[1],
19 a[2] * b[0] - a[0] * b[2],
20 a[0] * b[1] - a[1] * b[0],
21 ]
22}
23
24#[inline]
25pub fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
26 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
27}
28
29#[inline]
30pub fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
31 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
32}
33
34#[inline]
35pub fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
36 [a[0] * s, a[1] * s, a[2] * s]
37}
38
39#[inline]
40pub fn len3(a: [f64; 3]) -> f64 {
41 dot3(a, a).sqrt()
42}
43
44#[inline]
45pub fn normalize3(a: [f64; 3]) -> [f64; 3] {
46 let l = len3(a);
47 if l < 1e-12 {
48 [0.0, 0.0, 1.0]
49 } else {
50 scale3(a, 1.0 / l)
51 }
52}
53
54#[inline]
55fn neg3(a: [f64; 3]) -> [f64; 3] {
56 [-a[0], -a[1], -a[2]]
57}
58
59pub trait ConvexShape {
63 fn support(&self, direction: [f64; 3]) -> [f64; 3];
65}
66
67pub struct GjkSphere {
71 pub center: [f64; 3],
73 pub radius: f64,
75}
76
77impl ConvexShape for GjkSphere {
78 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
79 let n = normalize3(direction);
80 add3(self.center, scale3(n, self.radius))
81 }
82}
83
84pub struct GjkBox {
88 pub center: [f64; 3],
90 pub half_extents: [f64; 3],
92}
93
94impl ConvexShape for GjkBox {
95 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
96 [
97 self.center[0] + self.half_extents[0] * direction[0].signum(),
98 self.center[1] + self.half_extents[1] * direction[1].signum(),
99 self.center[2] + self.half_extents[2] * direction[2].signum(),
100 ]
101 }
102}
103
104pub struct GjkCapsule {
108 pub p1: [f64; 3],
110 pub p2: [f64; 3],
112 pub radius: f64,
114}
115
116impl ConvexShape for GjkCapsule {
117 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
118 let n = normalize3(direction);
119 let d1 = dot3(self.p1, n);
120 let d2 = dot3(self.p2, n);
121 let base = if d1 > d2 { self.p1 } else { self.p2 };
122 add3(base, scale3(n, self.radius))
123 }
124}
125
126pub fn minkowski_support(
130 shape_a: &dyn ConvexShape,
131 shape_b: &dyn ConvexShape,
132 dir: [f64; 3],
133) -> [f64; 3] {
134 sub3(shape_a.support(dir), shape_b.support(neg3(dir)))
135}
136
137#[derive(Clone)]
141pub struct Simplex {
142 pub points: Vec<[f64; 3]>,
144}
145
146impl Simplex {
147 fn new() -> Self {
148 Self {
149 points: Vec::with_capacity(4),
150 }
151 }
152
153 fn push(&mut self, p: [f64; 3]) {
154 self.points.push(p);
155 }
156
157 fn len(&self) -> usize {
158 self.points.len()
159 }
160}
161
162pub fn do_simplex(simplex: &mut Simplex, direction: &mut [f64; 3]) -> bool {
167 match simplex.len() {
168 2 => handle_line(simplex, direction),
169 3 => handle_triangle(simplex, direction),
170 4 => handle_tetrahedron(simplex, direction),
171 _ => false,
172 }
173}
174
175fn handle_line(simplex: &mut Simplex, direction: &mut [f64; 3]) -> bool {
176 let b = simplex.points[0];
177 let a = simplex.points[1];
178 let ab = sub3(b, a);
179 let ao = neg3(a);
180 if dot3(ab, ao) > 0.0 {
181 *direction = sub3(scale3(ab, dot3(ab, ao) / dot3(ab, ab)), ao);
182 let t = cross3(cross3(ab, ao), ab);
184 if len3(t) < 1e-12 {
185 *direction = [1.0, 0.0, 0.0];
187 } else {
188 *direction = t;
189 }
190 } else {
191 simplex.points = vec![a];
192 *direction = ao;
193 }
194 false
195}
196
197fn handle_triangle(simplex: &mut Simplex, direction: &mut [f64; 3]) -> bool {
198 let c = simplex.points[0];
199 let b = simplex.points[1];
200 let a = simplex.points[2];
201
202 let ab = sub3(b, a);
203 let ac = sub3(c, a);
204 let ao = neg3(a);
205 let abc = cross3(ab, ac);
206
207 if dot3(cross3(abc, ac), ao) > 0.0 {
209 if dot3(ac, ao) > 0.0 {
210 simplex.points = vec![c, a];
211 *direction = cross3(cross3(ac, ao), ac);
212 } else {
213 simplex.points = vec![b, a];
214 return handle_line(simplex, direction);
215 }
216 } else if dot3(cross3(ab, abc), ao) > 0.0 {
217 simplex.points = vec![b, a];
218 return handle_line(simplex, direction);
219 } else {
220 if dot3(abc, ao) > 0.0 {
222 *direction = abc;
223 } else {
224 simplex.points = vec![b, c, a];
225 *direction = neg3(abc);
226 }
227 }
228 false
229}
230
231fn handle_tetrahedron(simplex: &mut Simplex, direction: &mut [f64; 3]) -> bool {
232 let d = simplex.points[0];
233 let c = simplex.points[1];
234 let b = simplex.points[2];
235 let a = simplex.points[3];
236
237 let ab = sub3(b, a);
238 let ac = sub3(c, a);
239 let ad = sub3(d, a);
240 let ao = neg3(a);
241
242 let abc = cross3(ab, ac);
243 let acd = cross3(ac, ad);
244 let adb = cross3(ad, ab);
245
246 if dot3(abc, ao) > 0.0 {
247 simplex.points = vec![c, b, a];
248 return handle_triangle(simplex, direction);
249 }
250 if dot3(acd, ao) > 0.0 {
251 simplex.points = vec![d, c, a];
252 return handle_triangle(simplex, direction);
253 }
254 if dot3(adb, ao) > 0.0 {
255 simplex.points = vec![b, d, a];
256 return handle_triangle(simplex, direction);
257 }
258 true
259}
260
261pub fn gjk_intersect(shape_a: &dyn ConvexShape, shape_b: &dyn ConvexShape) -> bool {
265 let mut direction = [1.0_f64, 0.0, 0.0];
266 let mut simplex = Simplex::new();
267
268 let support = minkowski_support(shape_a, shape_b, direction);
269 simplex.push(support);
270 direction = neg3(support);
271
272 for _ in 0..64 {
273 if len3(direction) < 1e-12 {
274 return true;
275 }
276 let a = minkowski_support(shape_a, shape_b, direction);
277 if dot3(a, direction) < 0.0 {
278 return false;
279 }
280 simplex.push(a);
281 if do_simplex(&mut simplex, &mut direction) {
282 return true;
283 }
284 }
285 false
286}
287
288pub fn gjk_distance(shape_a: &dyn ConvexShape, shape_b: &dyn ConvexShape) -> Option<f64> {
290 if gjk_intersect(shape_a, shape_b) {
291 None
292 } else {
293 let mut direction = [1.0_f64, 0.0, 0.0];
295 let mut min_dist = f64::MAX;
296
297 for _ in 0..64 {
298 let s = minkowski_support(shape_a, shape_b, direction);
299 let d = len3(s);
300 if d < min_dist {
301 min_dist = d;
302 }
303 let new_dir = neg3(s);
304 if len3(new_dir) < 1e-12 {
305 break;
306 }
307 let next_s = minkowski_support(shape_a, shape_b, new_dir);
308 if dot3(next_s, new_dir) < dot3(s, direction) + 1e-10 {
309 break;
310 }
311 direction = new_dir;
312 }
313
314 let sa = shape_a.support(direction);
316 let sb = shape_b.support(neg3(direction));
317 let dist = len3(sub3(sa, sb));
318 Some(dist.max(0.0))
319 }
320}
321
322#[derive(Clone)]
326pub struct EpaFace {
327 pub a: [f64; 3],
329 pub b: [f64; 3],
331 pub c: [f64; 3],
333 pub normal: [f64; 3],
335 pub dist: f64,
337}
338
339impl EpaFace {
340 fn new(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> Self {
341 let ab = sub3(b, a);
342 let ac = sub3(c, a);
343 let n = cross3(ab, ac);
344 let mut normal = normalize3(n);
345 let mut dist = dot3(normal, a);
346 if dist < 0.0 {
348 normal = neg3(normal);
349 dist = -dist;
350 }
351 Self {
352 a,
353 b,
354 c,
355 normal,
356 dist,
357 }
358 }
359}
360
361pub struct EpaResult {
363 pub depth: f64,
365 pub normal: [f64; 3],
367 pub contact_a: [f64; 3],
369 pub contact_b: [f64; 3],
371}
372
373fn barycentric_contact(
374 shape_a: &dyn ConvexShape,
375 shape_b: &dyn ConvexShape,
376 face: &EpaFace,
377) -> ([f64; 3], [f64; 3]) {
378 let n = face.normal;
380 let proj = scale3(n, face.dist);
381
382 let v0 = sub3(face.b, face.a);
383 let v1 = sub3(face.c, face.a);
384 let v2 = sub3(proj, face.a);
385
386 let d00 = dot3(v0, v0);
387 let d01 = dot3(v0, v1);
388 let d11 = dot3(v1, v1);
389 let d20 = dot3(v2, v0);
390 let d21 = dot3(v2, v1);
391 let denom = d00 * d11 - d01 * d01;
392
393 let (u, v, w) = if denom.abs() < 1e-12 {
394 (1.0_f64 / 3.0, 1.0_f64 / 3.0, 1.0_f64 / 3.0)
395 } else {
396 let v_coord = (d11 * d20 - d01 * d21) / denom;
397 let w_coord = (d00 * d21 - d01 * d20) / denom;
398 let u_coord = 1.0 - v_coord - w_coord;
399 (u_coord, v_coord, w_coord)
400 };
401
402 let ca = add3(
408 add3(
409 scale3(shape_a.support(face.a), u),
410 scale3(shape_a.support(face.b), v),
411 ),
412 scale3(shape_a.support(face.c), w),
413 );
414 let cb = add3(
415 add3(
416 scale3(shape_b.support(neg3(face.a)), u),
417 scale3(shape_b.support(neg3(face.b)), v),
418 ),
419 scale3(shape_b.support(neg3(face.c)), w),
420 );
421 (ca, cb)
422}
423
424pub fn epa(
426 shape_a: &dyn ConvexShape,
427 shape_b: &dyn ConvexShape,
428 simplex: Simplex,
429) -> Option<EpaResult> {
430 if simplex.points.len() < 4 {
431 return None;
432 }
433
434 let mut faces: Vec<EpaFace> = vec![
435 EpaFace::new(simplex.points[0], simplex.points[1], simplex.points[2]),
436 EpaFace::new(simplex.points[0], simplex.points[1], simplex.points[3]),
437 EpaFace::new(simplex.points[0], simplex.points[2], simplex.points[3]),
438 EpaFace::new(simplex.points[1], simplex.points[2], simplex.points[3]),
439 ];
440
441 for _ in 0..64 {
442 let (min_idx, min_face) = {
444 let mut best_idx = 0;
445 let mut best_dist = f64::MAX;
446 for (i, face) in faces.iter().enumerate() {
447 if face.dist < best_dist {
448 best_dist = face.dist;
449 best_idx = i;
450 }
451 }
452 (best_idx, faces[best_idx].clone())
453 };
454
455 let support = minkowski_support(shape_a, shape_b, min_face.normal);
456 let d = dot3(support, min_face.normal);
457
458 if (d - min_face.dist).abs() < 1e-6 {
459 let (contact_a, contact_b) = barycentric_contact(shape_a, shape_b, &min_face);
461 return Some(EpaResult {
462 depth: min_face.dist,
463 normal: min_face.normal,
464 contact_a,
465 contact_b,
466 });
467 }
468
469 let mut silhouette: Vec<([f64; 3], [f64; 3])> = Vec::new();
471 let new_point = support;
472
473 let mut i = 0;
474 while i < faces.len() {
475 if dot3(faces[i].normal, sub3(new_point, faces[i].a)) > 0.0 {
476 let f = faces.remove(i);
477 for edge in [(f.a, f.b), (f.b, f.c), (f.c, f.a)] {
479 let shared = silhouette
481 .iter()
482 .position(|&(x, y)| x == edge.1 && y == edge.0);
483 if let Some(pos) = shared {
484 silhouette.remove(pos);
485 } else {
486 silhouette.push(edge);
487 }
488 }
489 } else {
490 i += 1;
491 }
492 }
493
494 for (ea, eb) in silhouette {
496 faces.push(EpaFace::new(ea, eb, new_point));
497 }
498
499 let _ = min_idx; }
502
503 let best = faces.iter().min_by(|a, b| {
505 a.dist
506 .partial_cmp(&b.dist)
507 .unwrap_or(std::cmp::Ordering::Equal)
508 })?;
509 let (contact_a, contact_b) = barycentric_contact(shape_a, shape_b, best);
510 Some(EpaResult {
511 depth: best.dist,
512 normal: best.normal,
513 contact_a,
514 contact_b,
515 })
516}
517
518pub fn gjk_epa_contact(shape_a: &dyn ConvexShape, shape_b: &dyn ConvexShape) -> Option<EpaResult> {
523 let mut direction = [1.0_f64, 0.0, 0.0];
525 let mut simplex = Simplex::new();
526
527 let first_support = minkowski_support(shape_a, shape_b, direction);
528 simplex.push(first_support);
529 direction = neg3(first_support);
530
531 let mut intersects = false;
532 for _ in 0..64 {
533 if len3(direction) < 1e-12 {
534 intersects = true;
535 break;
536 }
537 let a = minkowski_support(shape_a, shape_b, direction);
538 if dot3(a, direction) < 0.0 {
539 return None;
540 }
541 simplex.push(a);
542 if do_simplex(&mut simplex, &mut direction) {
543 intersects = true;
544 break;
545 }
546 }
547
548 if !intersects {
549 return None;
550 }
551
552 let simplex = ensure_tetrahedron(shape_a, shape_b, simplex);
554 epa(shape_a, shape_b, simplex)
555}
556
557fn ensure_tetrahedron(
558 shape_a: &dyn ConvexShape,
559 shape_b: &dyn ConvexShape,
560 mut simplex: Simplex,
561) -> Simplex {
562 let dirs = [
563 [1.0_f64, 0.0, 0.0],
564 [-1.0, 0.0, 0.0],
565 [0.0, 1.0, 0.0],
566 [0.0, -1.0, 0.0],
567 [0.0, 0.0, 1.0],
568 [0.0, 0.0, -1.0],
569 ];
570
571 for dir in dirs {
573 if simplex.len() >= 3 {
574 break;
575 }
576 let p = minkowski_support(shape_a, shape_b, dir);
577 let is_new = simplex.points.iter().all(|&q| len3(sub3(p, q)) > 1e-10);
578 if is_new {
579 simplex.push(p);
580 }
581 }
582
583 if simplex.len() < 3 {
584 return simplex;
585 }
586
587 if simplex.len() >= 4 {
589 let a = simplex.points[0];
590 let b = simplex.points[1];
591 let c = simplex.points[2];
592 let d = simplex.points[3];
593 let ab = sub3(b, a);
594 let ac = sub3(c, a);
595 let normal = cross3(ab, ac);
596 let vol = dot3(normal, sub3(d, a));
597 if vol.abs() > 1e-10 {
598 return simplex; }
600 simplex.points.truncate(3);
602 }
603
604 let a = simplex.points[0];
606 let b = simplex.points[1];
607 let c = simplex.points[2];
608 let ab = sub3(b, a);
609 let ac = sub3(c, a);
610 let normal = cross3(ab, ac);
611
612 for dir in dirs {
613 let p = minkowski_support(shape_a, shape_b, dir);
614 let is_new = simplex.points.iter().all(|&q| len3(sub3(p, q)) > 1e-10);
615 if !is_new {
616 continue;
617 }
618 let vol = dot3(normal, sub3(p, a));
620 if vol.abs() > 1e-10 {
621 simplex.push(p);
622 break;
623 }
624 }
625
626 simplex
627}
628
629pub fn closest_point_segment_to_origin(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
633 let ab = sub3(b, a);
634 let t = dot3(neg3(a), ab) / dot3(ab, ab).max(1e-30);
635 let t = t.clamp(0.0, 1.0);
636 add3(a, scale3(ab, t))
637}
638
639pub fn closest_point_triangle_to_origin(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [f64; 3] {
641 let ab = sub3(b, a);
642 let ac = sub3(c, a);
643 let ao = neg3(a);
644
645 let d1 = dot3(ab, ao);
646 let d2 = dot3(ac, ao);
647 if d1 <= 0.0 && d2 <= 0.0 {
648 return a;
649 }
650
651 let bo = neg3(b);
652 let d3 = dot3(ab, bo);
653 let d4 = dot3(ac, bo);
654 if d3 >= 0.0 && d4 <= d3 {
655 return b;
656 }
657
658 let co = neg3(c);
659 let d5 = dot3(ab, co);
660 let d6 = dot3(ac, co);
661 if d6 >= 0.0 && d5 <= d6 {
662 return c;
663 }
664
665 let vc = d1 * d4 - d3 * d2;
666 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
667 let v = d1 / (d1 - d3);
668 return add3(a, scale3(ab, v));
669 }
670
671 let vb = d5 * d2 - d1 * d6;
672 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
673 let w = d2 / (d2 - d6);
674 return add3(a, scale3(ac, w));
675 }
676
677 let va = d3 * d6 - d5 * d4;
678 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
679 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
680 return add3(b, scale3(sub3(c, b), w));
681 }
682
683 let denom = 1.0 / (va + vb + vc);
684 let v = vb * denom;
685 let w = vc * denom;
686 add3(a, add3(scale3(ab, v), scale3(ac, w)))
687}
688
689pub fn gjk_distance_with_witnesses(
695 shape_a: &dyn ConvexShape,
696 shape_b: &dyn ConvexShape,
697) -> Option<(f64, [f64; 3], [f64; 3])> {
698 if gjk_intersect(shape_a, shape_b) {
699 return None;
700 }
701 let mut dir = [1.0_f64, 0.0, 0.0];
703 let mut best_a = shape_a.support(dir);
704 let mut best_b = shape_b.support(neg3(dir));
705 let mut best_dist = f64::MAX;
706
707 for _ in 0..64 {
708 let sa = shape_a.support(dir);
709 let sb = shape_b.support(neg3(dir));
710 let diff = sub3(sa, sb); let d = len3(diff);
712 if d < best_dist {
713 best_dist = d;
714 best_a = sa;
715 best_b = sb;
716 }
717 let new_dir = neg3(diff);
718 if len3(new_dir) < 1e-12 {
719 break;
720 }
721 dir = normalize3(new_dir);
722 }
723 Some((best_dist.max(0.0), best_a, best_b))
724}
725
726pub fn rotate_vec(m: [[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
730 [
731 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
732 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
733 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
734 ]
735}
736
737pub struct TransformedShape<'a> {
740 pub shape: &'a dyn ConvexShape,
742 pub rotation: [[f64; 3]; 3],
744 pub translation: [f64; 3],
746}
747
748impl<'a> ConvexShape for TransformedShape<'a> {
749 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
750 let local_dir = [
752 self.rotation[0][0] * direction[0]
753 + self.rotation[1][0] * direction[1]
754 + self.rotation[2][0] * direction[2],
755 self.rotation[0][1] * direction[0]
756 + self.rotation[1][1] * direction[1]
757 + self.rotation[2][1] * direction[2],
758 self.rotation[0][2] * direction[0]
759 + self.rotation[1][2] * direction[1]
760 + self.rotation[2][2] * direction[2],
761 ];
762 let local_pt = self.shape.support(local_dir);
763 add3(rotate_vec(self.rotation, local_pt), self.translation)
765 }
766}
767
768pub fn gjk_intersect_transformed(
770 shape_a: &dyn ConvexShape,
771 rot_a: [[f64; 3]; 3],
772 pos_a: [f64; 3],
773 shape_b: &dyn ConvexShape,
774 rot_b: [[f64; 3]; 3],
775 pos_b: [f64; 3],
776) -> bool {
777 let ta = TransformedShape {
778 shape: shape_a,
779 rotation: rot_a,
780 translation: pos_a,
781 };
782 let tb = TransformedShape {
783 shape: shape_b,
784 rotation: rot_b,
785 translation: pos_b,
786 };
787 gjk_intersect(&ta, &tb)
788}
789
790pub fn epa_face_count_lower_bound(expansion_steps: usize) -> usize {
798 4 + 2 * expansion_steps
799}
800
801#[derive(Debug, Clone)]
803pub struct ClosestPoints {
804 pub point_a: [f64; 3],
806 pub point_b: [f64; 3],
808 pub distance: f64,
810}
811
812pub fn closest_points_between_shapes(
816 shape_a: &dyn ConvexShape,
817 shape_b: &dyn ConvexShape,
818) -> Option<ClosestPoints> {
819 gjk_distance_with_witnesses(shape_a, shape_b).map(|(dist, pa, pb)| ClosestPoints {
820 point_a: pa,
821 point_b: pb,
822 distance: dist,
823 })
824}
825
826pub struct GjkConvexHull {
830 pub vertices: Vec<[f64; 3]>,
832}
833
834impl GjkConvexHull {
835 pub fn new(vertices: Vec<[f64; 3]>) -> Self {
837 Self { vertices }
838 }
839}
840
841impl ConvexShape for GjkConvexHull {
842 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
843 let mut best = self.vertices[0];
844 let mut best_dot = dot3(best, direction);
845 for &v in &self.vertices[1..] {
846 let d = dot3(v, direction);
847 if d > best_dot {
848 best_dot = d;
849 best = v;
850 }
851 }
852 best
853 }
854}
855
856pub struct GjkCylinder {
858 pub base: [f64; 3],
860 pub axis: [f64; 3],
862 pub height: f64,
864 pub radius: f64,
866}
867
868impl ConvexShape for GjkCylinder {
869 fn support(&self, direction: [f64; 3]) -> [f64; 3] {
870 let axis = normalize3(self.axis);
872 let d_axial = dot3(direction, axis);
873 let radial = sub3(direction, scale3(axis, d_axial));
875 let radial_len = len3(radial);
876 let radial_pt = if radial_len > 1e-12 {
877 scale3(radial, self.radius / radial_len)
878 } else {
879 [self.radius, 0.0, 0.0]
880 };
881 let cap_offset = if d_axial >= 0.0 {
883 scale3(axis, self.height)
884 } else {
885 [0.0; 3]
886 };
887 add3(add3(self.base, radial_pt), cap_offset)
888 }
889}
890
891#[cfg(test)]
894mod tests {
895 use super::*;
896
897 fn sphere(cx: f64, cy: f64, cz: f64, r: f64) -> GjkSphere {
898 GjkSphere {
899 center: [cx, cy, cz],
900 radius: r,
901 }
902 }
903
904 fn aabb(cx: f64, cy: f64, cz: f64, hx: f64, hy: f64, hz: f64) -> GjkBox {
905 GjkBox {
906 center: [cx, cy, cz],
907 half_extents: [hx, hy, hz],
908 }
909 }
910
911 fn capsule(p1: [f64; 3], p2: [f64; 3], r: f64) -> GjkCapsule {
912 GjkCapsule { p1, p2, radius: r }
913 }
914
915 #[test]
918 fn test_dot3() {
919 assert!((dot3([1.0, 2.0, 3.0], [4.0, 5.0, 6.0]) - 32.0).abs() < 1e-12);
920 }
921
922 #[test]
923 fn test_cross3() {
924 let c = cross3([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
925 assert!((c[0]).abs() < 1e-12);
926 assert!((c[1]).abs() < 1e-12);
927 assert!((c[2] - 1.0).abs() < 1e-12);
928 }
929
930 #[test]
931 fn test_normalize3() {
932 let n = normalize3([3.0, 0.0, 0.0]);
933 assert!((n[0] - 1.0).abs() < 1e-12);
934 }
935
936 #[test]
937 fn test_normalize3_zero() {
938 let n = normalize3([0.0, 0.0, 0.0]);
939 assert!((n[2] - 1.0).abs() < 1e-12);
940 }
941
942 #[test]
943 fn test_len3() {
944 assert!((len3([3.0, 4.0, 0.0]) - 5.0).abs() < 1e-12);
945 }
946
947 #[test]
950 fn test_sphere_support() {
951 let s = sphere(0.0, 0.0, 0.0, 2.0);
952 let p = s.support([1.0, 0.0, 0.0]);
953 assert!((p[0] - 2.0).abs() < 1e-10);
954 }
955
956 #[test]
957 fn test_box_support() {
958 let b = aabb(0.0, 0.0, 0.0, 1.0, 2.0, 3.0);
959 let p = b.support([1.0, 1.0, 1.0]);
960 assert_eq!(p, [1.0, 2.0, 3.0]);
961 }
962
963 #[test]
964 fn test_capsule_support() {
965 let c = capsule([0.0, -1.0, 0.0], [0.0, 1.0, 0.0], 0.5);
966 let p = c.support([0.0, 1.0, 0.0]);
967 assert!((p[1] - 1.5).abs() < 1e-10);
968 }
969
970 #[test]
973 fn test_sphere_sphere_overlap() {
974 let a = sphere(0.0, 0.0, 0.0, 1.0);
975 let b = sphere(1.5, 0.0, 0.0, 1.0);
976 assert!(gjk_intersect(&a, &b));
977 }
978
979 #[test]
980 fn test_sphere_sphere_touching() {
981 let a = sphere(0.0, 0.0, 0.0, 1.0);
982 let b = sphere(2.0, 0.0, 0.0, 1.0);
983 let _ = gjk_intersect(&a, &b);
985 }
986
987 #[test]
988 fn test_sphere_sphere_separated() {
989 let a = sphere(0.0, 0.0, 0.0, 1.0);
990 let b = sphere(3.0, 0.0, 0.0, 1.0);
991 assert!(!gjk_intersect(&a, &b));
992 }
993
994 #[test]
995 fn test_sphere_sphere_distance_separated() {
996 let a = sphere(0.0, 0.0, 0.0, 1.0);
997 let b = sphere(4.0, 0.0, 0.0, 1.0);
998 let d = gjk_distance(&a, &b);
999 assert!(d.is_some());
1000 let d = d.unwrap();
1002 assert!(d > 0.0 && d < 5.0, "distance={}", d);
1003 }
1004
1005 #[test]
1006 fn test_sphere_sphere_distance_intersecting() {
1007 let a = sphere(0.0, 0.0, 0.0, 1.0);
1008 let b = sphere(1.0, 0.0, 0.0, 1.0);
1009 assert!(gjk_distance(&a, &b).is_none());
1010 }
1011
1012 #[test]
1015 fn test_box_box_overlap() {
1016 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1017 let b = aabb(1.5, 0.0, 0.0, 1.0, 1.0, 1.0);
1018 assert!(gjk_intersect(&a, &b));
1019 }
1020
1021 #[test]
1022 fn test_box_box_separated() {
1023 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1024 let b = aabb(3.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1025 assert!(!gjk_intersect(&a, &b));
1026 }
1027
1028 #[test]
1029 fn test_box_box_same_position() {
1030 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1031 let b = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1032 assert!(gjk_intersect(&a, &b));
1033 }
1034
1035 #[test]
1036 fn test_box_box_distance() {
1037 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1038 let b = aabb(4.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1039 let d = gjk_distance(&a, &b);
1040 assert!(d.is_some());
1041 assert!(d.unwrap() > 0.0);
1042 }
1043
1044 #[test]
1047 fn test_sphere_box_overlap() {
1048 let a = sphere(0.0, 0.0, 0.0, 1.0);
1049 let b = aabb(1.5, 0.0, 0.0, 1.0, 1.0, 1.0);
1050 assert!(gjk_intersect(&a, &b));
1051 }
1052
1053 #[test]
1054 fn test_sphere_box_separated() {
1055 let a = sphere(0.0, 0.0, 0.0, 0.5);
1056 let b = aabb(3.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1057 assert!(!gjk_intersect(&a, &b));
1058 }
1059
1060 #[test]
1061 fn test_sphere_box_distance() {
1062 let a = sphere(0.0, 0.0, 0.0, 0.5);
1063 let b = aabb(4.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1064 let d = gjk_distance(&a, &b);
1065 assert!(d.is_some());
1066 assert!(d.unwrap() > 0.0);
1067 }
1068
1069 #[test]
1072 fn test_capsule_sphere_overlap() {
1073 let a = capsule([0.0, -1.0, 0.0], [0.0, 1.0, 0.0], 1.0);
1074 let b = sphere(1.2, 0.0, 0.0, 0.5);
1076 assert!(gjk_intersect(&a, &b));
1077 }
1078
1079 #[test]
1080 fn test_capsule_sphere_separated() {
1081 let a = capsule([0.0, -1.0, 0.0], [0.0, 1.0, 0.0], 0.5);
1082 let b = sphere(5.0, 0.0, 0.0, 0.5);
1083 assert!(!gjk_intersect(&a, &b));
1084 }
1085
1086 #[test]
1087 fn test_capsule_box_overlap() {
1088 let a = capsule([0.0, -1.0, 0.0], [0.0, 1.0, 0.0], 1.0);
1089 let b = aabb(1.5, 0.0, 0.0, 1.0, 1.0, 1.0);
1090 assert!(gjk_intersect(&a, &b));
1091 }
1092
1093 #[test]
1096 fn test_epa_sphere_sphere_depth() {
1097 let a = sphere(0.0, 0.0, 0.0, 1.0);
1098 let b = sphere(1.0, 0.0, 0.0, 1.0);
1099 assert!(gjk_intersect(&a, &b), "spheres must intersect");
1100 if let Some(r) = gjk_epa_contact(&a, &b) {
1101 assert!(r.depth >= 0.0, "depth must be non-negative");
1102 }
1103 }
1104
1105 #[test]
1106 fn test_epa_sphere_sphere_no_contact_when_separated() {
1107 let a = sphere(0.0, 0.0, 0.0, 1.0);
1108 let b = sphere(3.0, 0.0, 0.0, 1.0);
1109 let result = gjk_epa_contact(&a, &b);
1110 assert!(result.is_none());
1111 }
1112
1113 #[test]
1114 fn test_epa_box_box_depth() {
1115 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1116 let b = aabb(1.5, 0.0, 0.0, 1.0, 1.0, 1.0);
1117 let result = gjk_epa_contact(&a, &b);
1118 assert!(result.is_some());
1119 let r = result.unwrap();
1120 assert!(r.depth > 0.0);
1121 }
1122
1123 #[test]
1124 fn test_epa_normal_unit_length() {
1125 let a = sphere(0.0, 0.0, 0.0, 1.0);
1126 let b = sphere(1.5, 0.0, 0.0, 1.0);
1127 if let Some(r) = gjk_epa_contact(&a, &b) {
1128 let nl = len3(r.normal);
1129 assert!((nl - 1.0).abs() < 1e-6, "normal length={}", nl);
1130 }
1131 }
1132
1133 #[test]
1136 fn test_minkowski_support_spheres() {
1137 let a = sphere(0.0, 0.0, 0.0, 1.0);
1138 let b = sphere(3.0, 0.0, 0.0, 1.0);
1139 let m = minkowski_support(&a, &b, [1.0, 0.0, 0.0]);
1142 assert!((m[0] - (-1.0)).abs() < 1e-10, "m[0]={}", m[0]);
1143 }
1144
1145 #[test]
1148 fn test_closest_point_segment_origin_inside_segment() {
1149 let p = closest_point_segment_to_origin([-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
1150 assert!(
1151 p[0].abs() < 1e-12,
1152 "closest point on segment to origin: {:?}",
1153 p
1154 );
1155 }
1156
1157 #[test]
1158 fn test_closest_point_segment_origin_past_end() {
1159 let p = closest_point_segment_to_origin([2.0, 0.0, 0.0], [3.0, 0.0, 0.0]);
1161 assert!((p[0] - 2.0).abs() < 1e-12, "expected [2,0,0], got {:?}", p);
1162 }
1163
1164 #[test]
1167 fn test_closest_point_triangle_origin_inside() {
1168 let a = [-1.0, 0.0, -1.0];
1170 let b = [1.0, 0.0, -1.0];
1171 let c = [0.0, 0.0, 1.0];
1172 let p = closest_point_triangle_to_origin(a, b, c);
1173 assert!(
1175 len3(p) < 0.5,
1176 "closest point should be near origin, got {:?}",
1177 p
1178 );
1179 }
1180
1181 #[test]
1182 fn test_closest_point_triangle_origin_outside_near_vertex() {
1183 let a = [10.0, 0.0, 0.0];
1185 let b = [11.0, 0.0, 0.0];
1186 let c = [10.0, 1.0, 0.0];
1187 let p = closest_point_triangle_to_origin(a, b, c);
1188 assert!((p[0] - 10.0).abs() < 1e-10, "expected x~10, got {:?}", p);
1189 }
1190
1191 #[test]
1194 fn test_gjk_distance_witnesses_separated_spheres() {
1195 let a = sphere(0.0, 0.0, 0.0, 1.0);
1196 let b = sphere(5.0, 0.0, 0.0, 1.0);
1197 let result = gjk_distance_with_witnesses(&a, &b);
1198 assert!(result.is_some(), "separated spheres should have a distance");
1199 let (dist, pa, pb) = result.unwrap();
1200 assert!(dist > 0.0, "distance should be positive");
1201 assert!(pa.iter().all(|x| x.is_finite()));
1202 assert!(pb.iter().all(|x| x.is_finite()));
1203 }
1204
1205 #[test]
1206 fn test_gjk_distance_witnesses_intersecting_returns_none() {
1207 let a = sphere(0.0, 0.0, 0.0, 1.0);
1208 let b = sphere(0.5, 0.0, 0.0, 1.0);
1209 assert!(gjk_distance_with_witnesses(&a, &b).is_none());
1210 }
1211
1212 fn identity_rot() -> [[f64; 3]; 3] {
1215 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
1216 }
1217
1218 #[test]
1219 fn test_transformed_shape_identity_matches_direct() {
1220 let s = sphere(0.0, 0.0, 0.0, 1.0);
1221 let t = TransformedShape {
1222 shape: &s,
1223 rotation: identity_rot(),
1224 translation: [0.0; 3],
1225 };
1226 let p_direct = s.support([1.0, 0.0, 0.0]);
1227 let p_trans = t.support([1.0, 0.0, 0.0]);
1228 assert!((p_direct[0] - p_trans[0]).abs() < 1e-10);
1229 }
1230
1231 #[test]
1232 fn test_transformed_shape_translation() {
1233 let s = sphere(0.0, 0.0, 0.0, 1.0);
1234 let t = TransformedShape {
1235 shape: &s,
1236 rotation: identity_rot(),
1237 translation: [3.0, 0.0, 0.0],
1238 };
1239 let p = t.support([1.0, 0.0, 0.0]);
1240 assert!((p[0] - 4.0).abs() < 1e-10, "p={:?}", p);
1242 }
1243
1244 #[test]
1245 fn test_gjk_intersect_transformed_separated() {
1246 let s = sphere(0.0, 0.0, 0.0, 1.0);
1247 let intersects = gjk_intersect_transformed(
1248 &s,
1249 identity_rot(),
1250 [0.0; 3],
1251 &s,
1252 identity_rot(),
1253 [5.0, 0.0, 0.0],
1254 );
1255 assert!(!intersects);
1256 }
1257
1258 #[test]
1259 fn test_gjk_intersect_transformed_overlapping() {
1260 let s = sphere(0.0, 0.0, 0.0, 1.0);
1261 let intersects = gjk_intersect_transformed(
1262 &s,
1263 identity_rot(),
1264 [0.0; 3],
1265 &s,
1266 identity_rot(),
1267 [1.5, 0.0, 0.0],
1268 );
1269 assert!(intersects);
1270 }
1271
1272 #[test]
1275 fn test_closest_points_separated_boxes() {
1276 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1277 let b = aabb(5.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1278 let cp = closest_points_between_shapes(&a, &b);
1279 assert!(cp.is_some(), "separated boxes should have closest points");
1280 let cp = cp.unwrap();
1281 assert!(cp.distance > 0.0);
1282 assert!(cp.point_a.iter().all(|x| x.is_finite()));
1283 assert!(cp.point_b.iter().all(|x| x.is_finite()));
1284 }
1285
1286 #[test]
1287 fn test_closest_points_intersecting_returns_none() {
1288 let a = aabb(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
1289 let b = aabb(0.5, 0.0, 0.0, 1.0, 1.0, 1.0);
1290 assert!(closest_points_between_shapes(&a, &b).is_none());
1291 }
1292
1293 #[test]
1296 fn test_convex_hull_support_in_x() {
1297 let hull = GjkConvexHull::new(vec![[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]);
1298 let p = hull.support([1.0, 0.0, 0.0]);
1299 assert!((p[0] - 2.0).abs() < 1e-12, "p={:?}", p);
1300 }
1301
1302 #[test]
1303 fn test_convex_hull_intersects_sphere() {
1304 let hull = GjkConvexHull::new(vec![
1306 [1.0, 0.0, 0.0],
1307 [-1.0, 0.0, 0.0],
1308 [0.0, 1.0, 0.0],
1309 [0.0, 0.0, 1.0],
1310 ]);
1311 let s = sphere(0.0, 0.0, 0.0, 0.5);
1312 assert!(gjk_intersect(&hull, &s));
1313 }
1314
1315 #[test]
1318 fn test_cylinder_support_top() {
1319 let cyl = GjkCylinder {
1320 base: [0.0, 0.0, 0.0],
1321 axis: [0.0, 1.0, 0.0],
1322 height: 2.0,
1323 radius: 0.5,
1324 };
1325 let p = cyl.support([0.0, 1.0, 0.0]);
1327 assert!(p[1] >= 1.9, "top support y should be near 2, got {}", p[1]);
1328 }
1329
1330 #[test]
1331 fn test_cylinder_intersects_sphere_at_side() {
1332 let cyl = GjkCylinder {
1333 base: [0.0, 0.0, 0.0],
1334 axis: [0.0, 1.0, 0.0],
1335 height: 2.0,
1336 radius: 1.0,
1337 };
1338 let s = sphere(1.5, 1.0, 0.0, 1.0);
1339 assert!(gjk_intersect(&cyl, &s));
1340 }
1341
1342 #[test]
1343 fn test_cylinder_separated_from_sphere() {
1344 let cyl = GjkCylinder {
1345 base: [0.0, 0.0, 0.0],
1346 axis: [0.0, 1.0, 0.0],
1347 height: 1.0,
1348 radius: 0.5,
1349 };
1350 let s = sphere(10.0, 0.5, 0.0, 0.5);
1351 assert!(!gjk_intersect(&cyl, &s));
1352 }
1353
1354 #[test]
1357 fn test_epa_face_count_lower_bound() {
1358 assert_eq!(epa_face_count_lower_bound(0), 4);
1359 assert_eq!(epa_face_count_lower_bound(3), 10);
1360 }
1361
1362 #[test]
1365 fn test_rotate_vec_identity() {
1366 let v = [1.0, 2.0, 3.0];
1367 let r = rotate_vec([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], v);
1368 assert!((r[0] - v[0]).abs() < 1e-12);
1369 assert!((r[1] - v[1]).abs() < 1e-12);
1370 assert!((r[2] - v[2]).abs() < 1e-12);
1371 }
1372
1373 #[test]
1374 fn test_rotate_vec_90_around_z() {
1375 let rot = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
1377 let r = rotate_vec(rot, [1.0, 0.0, 0.0]);
1378 assert!((r[0]).abs() < 1e-12, "r[0]={}", r[0]);
1379 assert!((r[1] - 1.0).abs() < 1e-12, "r[1]={}", r[1]);
1380 }
1381}