1fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
12 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
13}
14
15fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
16 [
17 a[1] * b[2] - a[2] * b[1],
18 a[2] * b[0] - a[0] * b[2],
19 a[0] * b[1] - a[1] * b[0],
20 ]
21}
22
23fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
24 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
25}
26
27fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
28 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
29}
30
31fn scale(a: [f64; 3], s: f64) -> [f64; 3] {
32 [a[0] * s, a[1] * s, a[2] * s]
33}
34
35fn len(a: [f64; 3]) -> f64 {
36 (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
37}
38
39fn len_sq(a: [f64; 3]) -> f64 {
40 a[0] * a[0] + a[1] * a[1] + a[2] * a[2]
41}
42
43fn normalize(a: [f64; 3]) -> [f64; 3] {
44 let l = len(a);
45 if l > 1e-10 {
46 scale(a, 1.0 / l)
47 } else {
48 [0.0, 1.0, 0.0]
49 }
50}
51
52fn neg(a: [f64; 3]) -> [f64; 3] {
53 [-a[0], -a[1], -a[2]]
54}
55
56#[derive(Debug, Clone, PartialEq)]
60pub struct Contact {
61 pub point_a: [f64; 3],
63 pub point_b: [f64; 3],
65 pub normal: [f64; 3],
67 pub depth: f64,
69}
70
71#[derive(Debug, Clone)]
73pub enum NarrowPhaseResult {
74 NoContact,
76 SingleContact(Contact),
78 Manifold(Vec<Contact>),
80}
81
82impl NarrowPhaseResult {
83 pub fn has_contact(&self) -> bool {
85 !matches!(self, NarrowPhaseResult::NoContact)
86 }
87
88 pub fn first_contact(&self) -> Option<&Contact> {
90 match self {
91 NarrowPhaseResult::NoContact => None,
92 NarrowPhaseResult::SingleContact(c) => Some(c),
93 NarrowPhaseResult::Manifold(v) => v.first(),
94 }
95 }
96}
97
98pub fn sphere_sphere(ca: [f64; 3], ra: f64, cb: [f64; 3], rb: f64) -> NarrowPhaseResult {
105 let d = sub(cb, ca);
106 let dist = len(d);
107 let depth = ra + rb - dist;
108 if depth < 0.0 {
109 return NarrowPhaseResult::NoContact;
110 }
111 let normal = if dist > 1e-10 {
112 scale(d, 1.0 / dist)
113 } else {
114 [0.0, 1.0, 0.0]
115 };
116 let point_a = add(ca, scale(normal, ra));
117 let point_b = sub(cb, scale(normal, rb));
118 NarrowPhaseResult::SingleContact(Contact {
119 point_a,
120 point_b,
121 normal,
122 depth,
123 })
124}
125
126pub fn sphere_box(
133 sphere_center: [f64; 3],
134 radius: f64,
135 box_center: [f64; 3],
136 half_extents: [f64; 3],
137) -> NarrowPhaseResult {
138 let local = sub(sphere_center, box_center);
140 let clamped = [
141 local[0].clamp(-half_extents[0], half_extents[0]),
142 local[1].clamp(-half_extents[1], half_extents[1]),
143 local[2].clamp(-half_extents[2], half_extents[2]),
144 ];
145 let diff = sub(local, clamped);
146 let dist_sq = len_sq(diff);
147
148 if dist_sq >= radius * radius {
149 return NarrowPhaseResult::NoContact;
150 }
151
152 let dist = dist_sq.sqrt();
153 let (normal, depth) = if dist > 1e-10 {
154 (normalize(diff), radius - dist)
156 } else {
157 let dx = half_extents[0] - local[0].abs();
159 let dy = half_extents[1] - local[1].abs();
160 let dz = half_extents[2] - local[2].abs();
161 let n = if dx <= dy && dx <= dz {
162 [local[0].signum(), 0.0, 0.0]
163 } else if dy <= dz {
164 [0.0, local[1].signum(), 0.0]
165 } else {
166 [0.0, 0.0, local[2].signum()]
167 };
168 let pen = dx.min(dy).min(dz) + radius;
169 (n, pen)
170 };
171
172 let closest_world = add(box_center, clamped);
174 let point_a = sub(sphere_center, scale(normal, radius));
175 let point_b = closest_world;
176
177 NarrowPhaseResult::SingleContact(Contact {
178 point_a,
179 point_b,
180 normal,
181 depth,
182 })
183}
184
185pub fn box_box_sat(
196 center_a: [f64; 3],
197 half_a: [f64; 3],
198 rot_a: [[f64; 3]; 3],
199 center_b: [f64; 3],
200 half_b: [f64; 3],
201 rot_b: [[f64; 3]; 3],
202) -> NarrowPhaseResult {
203 let t_world = sub(center_b, center_a);
205
206 let t = [
208 dot(rot_a[0], t_world),
209 dot(rot_a[1], t_world),
210 dot(rot_a[2], t_world),
211 ];
212
213 let mut r = [[0.0f64; 3]; 3];
215 let mut abs_r = [[0.0f64; 3]; 3];
216 for i in 0..3 {
217 for j in 0..3 {
218 r[i][j] = dot(rot_a[i], rot_b[j]);
219 abs_r[i][j] = r[i][j].abs() + 1e-6;
220 }
221 }
222
223 let mut min_depth = f64::INFINITY;
224 let mut best_axis = [0.0f64, 1.0, 0.0];
225
226 for i in 0..3 {
228 let ra = half_a[i];
229 let rb = half_b[0] * abs_r[i][0] + half_b[1] * abs_r[i][1] + half_b[2] * abs_r[i][2];
230 let depth = (ra + rb) - t[i].abs();
231 if depth < 0.0 {
232 return NarrowPhaseResult::NoContact;
233 }
234 if depth < min_depth {
235 min_depth = depth;
236 let sign = if t[i] >= 0.0 { 1.0 } else { -1.0 };
237 best_axis = rot_a[i];
238 best_axis[0] *= sign;
239 best_axis[1] *= sign;
240 best_axis[2] *= sign;
241 }
242 }
243
244 for j in 0..3 {
246 let ra = half_a[0] * abs_r[0][j] + half_a[1] * abs_r[1][j] + half_a[2] * abs_r[2][j];
247 let rb = half_b[j];
248 let sep = t[0] * r[0][j] + t[1] * r[1][j] + t[2] * r[2][j];
249 let depth = (ra + rb) - sep.abs();
250 if depth < 0.0 {
251 return NarrowPhaseResult::NoContact;
252 }
253 if depth < min_depth {
254 min_depth = depth;
255 let sign = if sep >= 0.0 { 1.0 } else { -1.0 };
256 best_axis = rot_b[j];
257 best_axis[0] *= sign;
258 best_axis[1] *= sign;
259 best_axis[2] *= sign;
260 }
261 }
262
263 let edge_pairs: [(usize, usize); 9] = [
265 (0, 0),
266 (0, 1),
267 (0, 2),
268 (1, 0),
269 (1, 1),
270 (1, 2),
271 (2, 0),
272 (2, 1),
273 (2, 2),
274 ];
275
276 for (i, j) in edge_pairs {
277 let i1 = (i + 1) % 3;
278 let i2 = (i + 2) % 3;
279 let j1 = (j + 1) % 3;
280 let j2 = (j + 2) % 3;
281
282 let ra = half_a[i1] * abs_r[i2][j] + half_a[i2] * abs_r[i1][j];
283 let rb = half_b[j1] * abs_r[i][j2] + half_b[j2] * abs_r[i][j1];
284 let sep = t[i2] * r[i1][j] - t[i1] * r[i2][j];
285 let depth = (ra + rb) - sep.abs();
286 if depth < 0.0 {
287 return NarrowPhaseResult::NoContact;
288 }
289
290 let axis = cross(rot_a[i], rot_b[j]);
292 let axis_len = len(axis);
293 if axis_len > 1e-6 && depth < min_depth {
294 min_depth = depth;
295 let axis_n = scale(axis, 1.0 / axis_len);
296 let sign = if dot(axis_n, t_world) >= 0.0 {
298 1.0
299 } else {
300 -1.0
301 };
302 best_axis = scale(axis_n, sign);
303 }
304 }
305
306 let point_a = add(center_a, scale(best_axis, min_depth * 0.5));
307 let point_b = sub(center_b, scale(best_axis, min_depth * 0.5));
308
309 NarrowPhaseResult::SingleContact(Contact {
310 point_a,
311 point_b,
312 normal: best_axis,
313 depth: min_depth,
314 })
315}
316
317pub fn capsule_capsule(
325 a0: [f64; 3],
326 a1: [f64; 3],
327 ra: f64,
328 b0: [f64; 3],
329 b1: [f64; 3],
330 rb: f64,
331) -> NarrowPhaseResult {
332 let (_, _, ca, cb) = segment_segment_closest(a0, a1, b0, b1);
333 sphere_sphere(ca, ra, cb, rb)
334}
335
336pub fn closest_point_on_segment(p: [f64; 3], a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
342 let ab = sub(b, a);
343 let len_sq_ab = len_sq(ab);
344 if len_sq_ab < 1e-20 {
345 return a;
346 }
347 let t = (dot(sub(p, a), ab) / len_sq_ab).clamp(0.0, 1.0);
348 add(a, scale(ab, t))
349}
350
351pub fn closest_point_on_triangle(p: [f64; 3], a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [f64; 3] {
355 let ab = sub(b, a);
356 let ac = sub(c, a);
357 let ap = sub(p, a);
358
359 let d1 = dot(ab, ap);
360 let d2 = dot(ac, ap);
361 if d1 <= 0.0 && d2 <= 0.0 {
362 return a;
363 }
364
365 let bp = sub(p, b);
366 let d3 = dot(ab, bp);
367 let d4 = dot(ac, bp);
368 if d3 >= 0.0 && d4 <= d3 {
369 return b;
370 }
371
372 let vc = d1 * d4 - d3 * d2;
373 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
374 let v = d1 / (d1 - d3);
375 return add(a, scale(ab, v));
376 }
377
378 let cp = sub(p, c);
379 let d5 = dot(ab, cp);
380 let d6 = dot(ac, cp);
381 if d6 >= 0.0 && d5 <= d6 {
382 return c;
383 }
384
385 let vb = d5 * d2 - d1 * d6;
386 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
387 let w = d2 / (d2 - d6);
388 return add(a, scale(ac, w));
389 }
390
391 let va = d3 * d6 - d5 * d4;
392 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
393 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
394 return add(b, scale(sub(c, b), w));
395 }
396
397 let denom = 1.0 / (va + vb + vc);
398 let v = vb * denom;
399 let w = vc * denom;
400 add(add(a, scale(ab, v)), scale(ac, w))
401}
402
403pub fn segment_segment_closest(
407 a0: [f64; 3],
408 a1: [f64; 3],
409 b0: [f64; 3],
410 b1: [f64; 3],
411) -> (f64, f64, [f64; 3], [f64; 3]) {
412 let d1 = sub(a1, a0);
413 let d2 = sub(b1, b0);
414 let r = sub(a0, b0);
415
416 let a = dot(d1, d1);
417 let e = dot(d2, d2);
418 let f = dot(d2, r);
419
420 let (s, t) = if a <= 1e-10 && e <= 1e-10 {
421 (0.0_f64, 0.0_f64)
422 } else if a <= 1e-10 {
423 (0.0, (f / e).clamp(0.0, 1.0))
424 } else {
425 let c = dot(d1, r);
426 if e <= 1e-10 {
427 ((-c / a).clamp(0.0, 1.0), 0.0)
428 } else {
429 let b = dot(d1, d2);
430 let denom = a * e - b * b;
431 let s0 = if denom.abs() > 1e-10 {
432 ((b * f - c * e) / denom).clamp(0.0, 1.0)
433 } else {
434 0.0
435 };
436 let t_raw = (b * s0 + f) / e;
437 if t_raw < 0.0 {
438 ((-c / a).clamp(0.0, 1.0), 0.0)
439 } else if t_raw > 1.0 {
440 (((b - c) / a).clamp(0.0, 1.0), 1.0)
441 } else {
442 (s0, t_raw)
443 }
444 }
445 };
446
447 let pa = add(a0, scale(d1, s));
448 let pb = add(b0, scale(d2, t));
449 (s, t, pa, pb)
450}
451
452#[derive(Debug, Clone)]
456pub struct GjkSimplex {
457 pts: [[f64; 3]; 4],
458 count: usize,
459}
460
461impl GjkSimplex {
462 pub fn new() -> Self {
464 Self {
465 pts: [[0.0; 3]; 4],
466 count: 0,
467 }
468 }
469
470 pub fn push(&mut self, p: [f64; 3]) {
472 if self.count < 4 {
473 self.pts[self.count] = p;
474 self.count += 1;
475 }
476 }
477
478 pub fn len(&self) -> usize {
480 self.count
481 }
482
483 pub fn is_empty(&self) -> bool {
485 self.count == 0
486 }
487}
488
489impl Default for GjkSimplex {
490 fn default() -> Self {
491 Self::new()
492 }
493}
494
495fn minkowski_diff_support<A, B>(dir: [f64; 3], support_a: &A, support_b: &B) -> [f64; 3]
501where
502 A: Fn([f64; 3]) -> [f64; 3],
503 B: Fn([f64; 3]) -> [f64; 3],
504{
505 let pa = support_a(dir);
506 let pb = support_b(neg(dir));
507 sub(pa, pb)
508}
509
510pub fn gjk_intersect<A, B>(support_a: A, support_b: B) -> bool
515where
516 A: Fn([f64; 3]) -> [f64; 3],
517 B: Fn([f64; 3]) -> [f64; 3],
518{
519 let mut simplex = GjkSimplex::new();
520 let mut dir = [1.0f64, 0.0, 0.0];
521
522 let first = minkowski_diff_support(dir, &support_a, &support_b);
523 simplex.push(first);
524 dir = neg(first);
525
526 if len_sq(dir) < 1e-20 {
527 return true;
529 }
530
531 for _ in 0..64 {
532 let a = minkowski_diff_support(dir, &support_a, &support_b);
533 if dot(a, dir) < 0.0 {
534 return false;
536 }
537 simplex.push(a);
538
539 match do_simplex_gjk(&mut simplex, &mut dir) {
540 GjkStep::ContainsOrigin => return true,
541 GjkStep::Continue => {}
542 }
543 }
544 true
546}
547
548enum GjkStep {
549 ContainsOrigin,
550 Continue,
551}
552
553fn do_simplex_gjk(simplex: &mut GjkSimplex, dir: &mut [f64; 3]) -> GjkStep {
555 match simplex.count {
556 2 => {
557 let b = simplex.pts[0];
559 let a = simplex.pts[1];
560 let ab = sub(b, a);
561 let ao = neg(a);
562 if dot(ab, ao) > 0.0 {
563 *dir = cross(cross(ab, ao), ab);
564 } else {
565 simplex.pts[0] = a;
566 simplex.count = 1;
567 *dir = ao;
568 }
569 GjkStep::Continue
570 }
571 3 => {
572 let c = simplex.pts[0];
574 let b = simplex.pts[1];
575 let a = simplex.pts[2];
576 let ab = sub(b, a);
577 let ac = sub(c, a);
578 let ao = neg(a);
579 let abc = cross(ab, ac);
580
581 if dot(cross(abc, ac), ao) > 0.0 {
582 if dot(ac, ao) > 0.0 {
583 simplex.pts[0] = c;
584 simplex.pts[1] = a;
585 simplex.count = 2;
586 *dir = cross(cross(ac, ao), ac);
587 } else {
588 simplex.pts[0] = b;
589 simplex.pts[1] = a;
590 simplex.count = 2;
591 return do_simplex_gjk(simplex, dir);
592 }
593 } else if dot(cross(ab, abc), ao) > 0.0 {
594 simplex.pts[0] = b;
595 simplex.pts[1] = a;
596 simplex.count = 2;
597 return do_simplex_gjk(simplex, dir);
598 } else {
599 if dot(abc, ao) > 0.0 {
601 *dir = abc;
602 } else {
603 simplex.pts[0] = b;
605 simplex.pts[1] = c;
606 *dir = neg(abc);
608 }
609 }
610 GjkStep::Continue
611 }
612 4 => {
613 let d = simplex.pts[0];
615 let c = simplex.pts[1];
616 let b = simplex.pts[2];
617 let a = simplex.pts[3];
618 let ab = sub(b, a);
619 let ac = sub(c, a);
620 let ad = sub(d, a);
621 let ao = neg(a);
622
623 let abc = cross(ab, ac);
624 let acd = cross(ac, ad);
625 let adb = cross(ad, ab);
626
627 if dot(abc, ao) > 0.0 {
628 simplex.pts[0] = c;
630 simplex.pts[1] = b;
631 simplex.pts[2] = a;
632 simplex.count = 3;
633 return do_simplex_gjk(simplex, dir);
634 }
635 if dot(acd, ao) > 0.0 {
636 simplex.pts[0] = d;
637 simplex.pts[1] = c;
638 simplex.pts[2] = a;
639 simplex.count = 3;
640 return do_simplex_gjk(simplex, dir);
641 }
642 if dot(adb, ao) > 0.0 {
643 simplex.pts[0] = b;
644 simplex.pts[1] = d;
645 simplex.pts[2] = a;
646 simplex.count = 3;
647 return do_simplex_gjk(simplex, dir);
648 }
649 GjkStep::ContainsOrigin
650 }
651 _ => GjkStep::Continue,
652 }
653}
654
655fn clip_polygon_by_plane(
663 poly: &[[f64; 3]],
664 plane_normal: [f64; 3],
665 plane_offset: f64,
666) -> Vec<[f64; 3]> {
667 let mut out = Vec::new();
668 if poly.is_empty() {
669 return out;
670 }
671 let n = poly.len();
672 for i in 0..n {
673 let curr = poly[i];
674 let next = poly[(i + 1) % n];
675 let dc = dot(plane_normal, curr) - plane_offset;
676 let dn = dot(plane_normal, next) - plane_offset;
677
678 if dc >= 0.0 {
679 out.push(curr);
680 }
681 if (dc < 0.0) != (dn < 0.0) {
683 let t = dc / (dc - dn);
684 let interp = add(curr, scale(sub(next, curr), t));
685 out.push(interp);
686 }
687 }
688 out
689}
690
691pub fn clip_incident_face(
701 ref_center: [f64; 3],
702 ref_u: [f64; 3],
703 ref_v: [f64; 3],
704 ref_half_u: f64,
705 ref_half_v: f64,
706 ref_normal: [f64; 3],
707 incident_verts: &[[f64; 3]; 4],
708) -> Vec<Contact> {
709 let mut poly: Vec<[f64; 3]> = incident_verts.to_vec();
711
712 poly = clip_polygon_by_plane(&poly, ref_u, dot(ref_u, ref_center) + ref_half_u);
714 poly = clip_polygon_by_plane(&poly, neg(ref_u), -dot(ref_u, ref_center) + ref_half_u);
715 poly = clip_polygon_by_plane(&poly, ref_v, dot(ref_v, ref_center) + ref_half_v);
716 poly = clip_polygon_by_plane(&poly, neg(ref_v), -dot(ref_v, ref_center) + ref_half_v);
717
718 let ref_plane_offset = dot(ref_normal, ref_center);
720 poly.retain(|&p| dot(ref_normal, p) <= ref_plane_offset + 1e-6);
721
722 poly.into_iter()
723 .map(|pt| {
724 let proj = add(
725 ref_center,
726 add(
727 scale(ref_u, dot(ref_u, sub(pt, ref_center))),
728 scale(ref_v, dot(ref_v, sub(pt, ref_center))),
729 ),
730 );
731 let depth = ref_plane_offset - dot(ref_normal, pt);
732 Contact {
733 point_a: proj,
734 point_b: pt,
735 normal: ref_normal,
736 depth: depth.max(0.0),
737 }
738 })
739 .collect()
740}
741
742pub fn triangle_aabb_intersect(
752 tri_verts: &[[f64; 3]; 3],
753 box_center: [f64; 3],
754 half_extents: [f64; 3],
755) -> bool {
756 let v: [[f64; 3]; 3] = [
758 sub(tri_verts[0], box_center),
759 sub(tri_verts[1], box_center),
760 sub(tri_verts[2], box_center),
761 ];
762
763 for a in 0..3 {
765 let mn = v[0][a].min(v[1][a]).min(v[2][a]);
766 let mx = v[0][a].max(v[1][a]).max(v[2][a]);
767 if mn > half_extents[a] || mx < -half_extents[a] {
768 return false;
769 }
770 }
771
772 let e0 = sub(v[1], v[0]);
774 let e1 = sub(v[2], v[1]);
775 let n_tri = cross(e0, e1);
776 let d = dot(n_tri, v[0]);
777 let r = half_extents[0] * n_tri[0].abs()
779 + half_extents[1] * n_tri[1].abs()
780 + half_extents[2] * n_tri[2].abs();
781 if d.abs() > r {
782 return false;
783 }
784
785 let edges = [e0, e1, sub(v[0], v[2])];
787 let box_axes: [[f64; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
788
789 for &edge in &edges {
790 for &axis in &box_axes {
791 let sep_axis = cross(edge, axis);
792 if len_sq(sep_axis) < 1e-20 {
793 continue;
794 }
795 let p: [f64; 3] = [
796 dot(sep_axis, v[0]),
797 dot(sep_axis, v[1]),
798 dot(sep_axis, v[2]),
799 ];
800 let p_min = p[0].min(p[1]).min(p[2]);
801 let p_max = p[0].max(p[1]).max(p[2]);
802 let r_box = half_extents[0] * sep_axis[0].abs()
803 + half_extents[1] * sep_axis[1].abs()
804 + half_extents[2] * sep_axis[2].abs();
805 if p_min > r_box || p_max < -r_box {
806 return false;
807 }
808 }
809 }
810
811 true
812}
813
814pub fn obb_capsule(
822 obb_center: [f64; 3],
823 obb_half: [f64; 3],
824 obb_rot: [[f64; 3]; 3],
825 cap_a: [f64; 3],
826 cap_b: [f64; 3],
827 cap_r: f64,
828) -> NarrowPhaseResult {
829 let local_a = world_to_obb_local(sub(cap_a, obb_center), obb_rot);
831 let local_b = world_to_obb_local(sub(cap_b, obb_center), obb_rot);
832
833 let cp = closest_point_segment_aabb(local_a, local_b, obb_half);
835
836 let diff = sub(local_a, cp);
838 let cp_seg = closest_point_on_segment(cp, local_a, local_b);
841 let d = sub(cp_seg, cp);
842 let dist = len(d);
843 let depth = cap_r - dist;
844 if depth < 0.0 {
845 return NarrowPhaseResult::NoContact;
846 }
847
848 let _ = diff; let normal_local = if dist > 1e-10 {
850 normalize(d)
851 } else {
852 [0.0, 1.0, 0.0]
853 };
854 let normal_world = obb_local_to_world(normal_local, obb_rot);
856
857 let pa = add(
858 obb_center,
859 obb_local_to_world(add(cp, scale(normal_local, depth * 0.5)), obb_rot),
860 );
861 let pb = add(
862 obb_center,
863 obb_local_to_world(
864 sub(cp_seg, scale(normal_local, cap_r - depth * 0.5)),
865 obb_rot,
866 ),
867 );
868
869 NarrowPhaseResult::SingleContact(Contact {
870 point_a: pa,
871 point_b: pb,
872 normal: normal_world,
873 depth,
874 })
875}
876
877fn closest_point_segment_aabb(a: [f64; 3], b: [f64; 3], half: [f64; 3]) -> [f64; 3] {
879 let candidates = [a, b, add(scale(a, 0.5), scale(b, 0.5))];
882 let mut best = [0.0f64; 3];
883 let mut best_dist = f64::INFINITY;
884 for &p in &candidates {
885 let clamped = [
886 p[0].clamp(-half[0], half[0]),
887 p[1].clamp(-half[1], half[1]),
888 p[2].clamp(-half[2], half[2]),
889 ];
890 let d = len_sq(sub(p, clamped));
891 if d < best_dist {
892 best_dist = d;
893 best = clamped;
894 }
895 }
896 best
897}
898
899fn world_to_obb_local(v: [f64; 3], rot: [[f64; 3]; 3]) -> [f64; 3] {
900 [dot(rot[0], v), dot(rot[1], v), dot(rot[2], v)]
901}
902
903fn obb_local_to_world(v: [f64; 3], rot: [[f64; 3]; 3]) -> [f64; 3] {
904 [
905 rot[0][0] * v[0] + rot[1][0] * v[1] + rot[2][0] * v[2],
906 rot[0][1] * v[0] + rot[1][1] * v[1] + rot[2][1] * v[2],
907 rot[0][2] * v[0] + rot[1][2] * v[1] + rot[2][2] * v[2],
908 ]
909}
910
911pub fn aabb_capsule(
918 box_center: [f64; 3],
919 half_extents: [f64; 3],
920 cap_a: [f64; 3],
921 cap_b: [f64; 3],
922 cap_r: f64,
923) -> NarrowPhaseResult {
924 let closest_on_seg = closest_point_on_segment(box_center, cap_a, cap_b);
926 sphere_box(closest_on_seg, cap_r, box_center, half_extents)
928}
929
930#[derive(Debug, Clone)]
937pub struct Heightfield {
938 pub heights: Vec<f64>,
940 pub n_x: usize,
942 pub n_z: usize,
944 pub cell_size: f64,
946 pub origin_x: f64,
948 pub origin_z: f64,
950}
951
952impl Heightfield {
953 pub fn flat(n_x: usize, n_z: usize, cell_size: f64) -> Self {
955 Self {
956 heights: vec![0.0; n_x * n_z],
957 n_x,
958 n_z,
959 cell_size,
960 origin_x: 0.0,
961 origin_z: 0.0,
962 }
963 }
964
965 pub fn height_at(&self, ix: usize, iz: usize) -> f64 {
967 self.heights[iz * self.n_x + ix]
968 }
969
970 pub fn height_world(&self, x: f64, z: f64) -> f64 {
972 let lx = (x - self.origin_x) / self.cell_size;
973 let lz = (z - self.origin_z) / self.cell_size;
974 let ix = (lx as usize).min(self.n_x - 2);
975 let iz = (lz as usize).min(self.n_z - 2);
976 let fx = (lx - ix as f64).clamp(0.0, 1.0);
977 let fz = (lz - iz as f64).clamp(0.0, 1.0);
978
979 let h00 = self.height_at(ix, iz);
980 let h10 = self.height_at(ix + 1, iz);
981 let h01 = self.height_at(ix, iz + 1);
982 let h11 = self.height_at(ix + 1, iz + 1);
983
984 h00 * (1.0 - fx) * (1.0 - fz)
985 + h10 * fx * (1.0 - fz)
986 + h01 * (1.0 - fx) * fz
987 + h11 * fx * fz
988 }
989
990 pub fn normal_world(&self, x: f64, z: f64) -> [f64; 3] {
992 let h = self.cell_size * 0.5;
993 let dydx = (self.height_world(x + h, z) - self.height_world(x - h, z)) / (2.0 * h);
994 let dydz = (self.height_world(x, z + h) - self.height_world(x, z - h)) / (2.0 * h);
995 normalize([-dydx, 1.0, -dydz])
996 }
997}
998
999pub fn heightfield_sphere(
1004 hf: &Heightfield,
1005 sphere_center: [f64; 3],
1006 radius: f64,
1007) -> NarrowPhaseResult {
1008 let x = sphere_center[0];
1009 let z = sphere_center[2];
1010 let y_surf = hf.height_world(x, z);
1011 let depth = radius - (sphere_center[1] - y_surf);
1012
1013 if depth < 0.0 {
1014 return NarrowPhaseResult::NoContact;
1015 }
1016
1017 let normal = hf.normal_world(x, z);
1018 let point_b = [x, y_surf, z];
1019 let point_a = sub(sphere_center, scale(normal, radius));
1020
1021 NarrowPhaseResult::SingleContact(Contact {
1022 point_a,
1023 point_b,
1024 normal,
1025 depth,
1026 })
1027}
1028
1029pub fn heightfield_box(
1036 hf: &Heightfield,
1037 box_center: [f64; 3],
1038 half_extents: [f64; 3],
1039) -> NarrowPhaseResult {
1040 let x_min = box_center[0] - half_extents[0];
1041 let x_max = box_center[0] + half_extents[0];
1042 let z_min = box_center[2] - half_extents[2];
1043 let z_max = box_center[2] + half_extents[2];
1044
1045 let mut deepest: Option<Contact> = None;
1046 let mut max_depth = 0.0f64;
1047
1048 let samples = [
1050 [x_min, z_min],
1051 [x_max, z_min],
1052 [x_min, z_max],
1053 [x_max, z_max],
1054 [box_center[0], box_center[2]],
1055 ];
1056
1057 for &[sx, sz] in &samples {
1058 let y_surf = hf.height_world(sx, sz);
1059 let box_bottom = box_center[1] - half_extents[1];
1060 let depth = y_surf - box_bottom;
1061 if depth > max_depth {
1062 max_depth = depth;
1063 let normal = hf.normal_world(sx, sz);
1064 deepest = Some(Contact {
1065 point_a: [sx, box_bottom, sz],
1066 point_b: [sx, y_surf, sz],
1067 normal,
1068 depth,
1069 });
1070 }
1071 }
1072
1073 match deepest {
1074 Some(c) => NarrowPhaseResult::SingleContact(c),
1075 None => NarrowPhaseResult::NoContact,
1076 }
1077}
1078
1079pub fn triangle_sphere(
1086 tri_a: [f64; 3],
1087 tri_b: [f64; 3],
1088 tri_c: [f64; 3],
1089 sphere_center: [f64; 3],
1090 radius: f64,
1091) -> NarrowPhaseResult {
1092 let cp = closest_point_on_triangle(sphere_center, tri_a, tri_b, tri_c);
1093 let d = sub(sphere_center, cp);
1094 let dist = len(d);
1095 let depth = radius - dist;
1096
1097 if depth < 0.0 {
1098 return NarrowPhaseResult::NoContact;
1099 }
1100
1101 let normal = if dist > 1e-10 {
1102 scale(d, 1.0 / dist)
1103 } else {
1104 let ab = sub(tri_b, tri_a);
1106 let ac = sub(tri_c, tri_a);
1107 normalize(cross(ab, ac))
1108 };
1109
1110 let point_a = sub(sphere_center, scale(normal, radius));
1111 NarrowPhaseResult::SingleContact(Contact {
1112 point_a,
1113 point_b: cp,
1114 normal,
1115 depth,
1116 })
1117}
1118
1119#[cfg(test)]
1122mod tests {
1123 use super::*;
1124
1125 fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
1126 (a - b).abs() < tol
1127 }
1128
1129 fn vec_approx_eq(a: [f64; 3], b: [f64; 3], tol: f64) -> bool {
1130 approx_eq(a[0], b[0], tol) && approx_eq(a[1], b[1], tol) && approx_eq(a[2], b[2], tol)
1131 }
1132
1133 #[test]
1136 fn test_sphere_sphere_touching() {
1137 let ca = [0.0f64, 0.0, 0.0];
1138 let cb = [2.0f64, 0.0, 0.0];
1139 match sphere_sphere(ca, 1.0, cb, 1.0) {
1140 NarrowPhaseResult::SingleContact(c) => {
1141 assert!(approx_eq(c.depth, 0.0, 1e-10), "depth={}", c.depth);
1142 }
1143 _ => panic!("expected SingleContact for touching spheres"),
1144 }
1145 }
1146
1147 #[test]
1148 fn test_sphere_sphere_overlapping() {
1149 let ca = [0.0f64, 0.0, 0.0];
1150 let cb = [1.5f64, 0.0, 0.0];
1151 match sphere_sphere(ca, 1.0, cb, 1.0) {
1152 NarrowPhaseResult::SingleContact(c) => {
1153 assert!(approx_eq(c.depth, 0.5, 1e-10), "depth={}", c.depth);
1154 let nlen = len(c.normal);
1155 assert!(approx_eq(nlen, 1.0, 1e-10), "normal length={}", nlen);
1156 }
1157 _ => panic!("expected SingleContact for overlapping spheres"),
1158 }
1159 }
1160
1161 #[test]
1162 fn test_sphere_sphere_separated() {
1163 let ca = [0.0f64, 0.0, 0.0];
1164 let cb = [5.0f64, 0.0, 0.0];
1165 assert!(
1166 matches!(
1167 sphere_sphere(ca, 1.0, cb, 1.0),
1168 NarrowPhaseResult::NoContact
1169 ),
1170 "separated spheres must return NoContact"
1171 );
1172 }
1173
1174 #[test]
1177 fn test_sphere_box_hit() {
1178 let sc = [1.3f64, 0.0, 0.0];
1181 let bc = [0.0f64, 0.0, 0.0];
1182 let he = [1.0f64, 1.0, 1.0];
1183 match sphere_box(sc, 0.5, bc, he) {
1184 NarrowPhaseResult::SingleContact(c) => {
1185 assert!(c.depth > 0.0, "depth={}", c.depth);
1186 assert!(approx_eq(len(c.normal), 1.0, 1e-10));
1187 }
1188 _ => panic!("expected contact"),
1189 }
1190 }
1191
1192 #[test]
1193 fn test_sphere_box_miss() {
1194 let sc = [5.0f64, 0.0, 0.0];
1195 let bc = [0.0f64, 0.0, 0.0];
1196 let he = [1.0f64, 1.0, 1.0];
1197 assert!(
1198 matches!(sphere_box(sc, 0.5, bc, he), NarrowPhaseResult::NoContact),
1199 "far sphere must return NoContact"
1200 );
1201 }
1202
1203 #[test]
1206 fn test_box_box_axis_aligned_overlapping() {
1207 let identity = [[1.0f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1208 let ca = [0.0f64, 0.0, 0.0];
1209 let cb = [1.5f64, 0.0, 0.0];
1210 let half = [1.0f64, 1.0, 1.0];
1211 match box_box_sat(ca, half, identity, cb, half, identity) {
1212 NarrowPhaseResult::SingleContact(c) => {
1213 assert!(c.depth > 0.0, "depth={}", c.depth);
1214 assert!(
1215 approx_eq(c.depth, 0.5, 1e-5),
1216 "expected depth≈0.5, got {}",
1217 c.depth
1218 );
1219 }
1220 _ => panic!("expected contact for overlapping boxes"),
1221 }
1222 }
1223
1224 #[test]
1225 fn test_box_box_axis_aligned_separated() {
1226 let identity = [[1.0f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1227 let ca = [0.0f64, 0.0, 0.0];
1228 let cb = [5.0f64, 0.0, 0.0];
1229 let half = [1.0f64, 1.0, 1.0];
1230 assert!(
1231 matches!(
1232 box_box_sat(ca, half, identity, cb, half, identity),
1233 NarrowPhaseResult::NoContact
1234 ),
1235 "separated boxes must return NoContact"
1236 );
1237 }
1238
1239 #[test]
1242 fn test_capsule_capsule_overlap() {
1243 let a0 = [0.0f64, -1.0, 0.0];
1244 let a1 = [0.0f64, 1.0, 0.0];
1245 let b0 = [0.8f64, -1.0, 0.0];
1246 let b1 = [0.8f64, 1.0, 0.0];
1247 match capsule_capsule(a0, a1, 0.5, b0, b1, 0.5) {
1248 NarrowPhaseResult::SingleContact(c) => {
1249 assert!(c.depth > 0.0, "depth={}", c.depth);
1250 assert!(approx_eq(len(c.normal), 1.0, 1e-10));
1251 }
1252 _ => panic!("expected contact for overlapping capsules"),
1253 }
1254 }
1255
1256 #[test]
1257 fn test_capsule_capsule_separated() {
1258 let a0 = [0.0f64, 0.0, 0.0];
1259 let a1 = [1.0f64, 0.0, 0.0];
1260 let b0 = [10.0f64, 0.0, 0.0];
1261 let b1 = [11.0f64, 0.0, 0.0];
1262 assert!(
1263 matches!(
1264 capsule_capsule(a0, a1, 0.3, b0, b1, 0.3),
1265 NarrowPhaseResult::NoContact
1266 ),
1267 "far capsules must return NoContact"
1268 );
1269 }
1270
1271 #[test]
1274 fn test_closest_point_on_segment_midpoint() {
1275 let p = [1.0f64, 1.0, 0.0];
1276 let a = [0.0f64, 0.0, 0.0];
1277 let b = [2.0f64, 0.0, 0.0];
1278 let cp = closest_point_on_segment(p, a, b);
1279 assert!(vec_approx_eq(cp, [1.0, 0.0, 0.0], 1e-10), "cp={:?}", cp);
1280 }
1281
1282 #[test]
1283 fn test_closest_point_on_segment_endpoint() {
1284 let p = [-1.0f64, 0.0, 0.0];
1285 let a = [0.0f64, 0.0, 0.0];
1286 let b = [2.0f64, 0.0, 0.0];
1287 let cp = closest_point_on_segment(p, a, b);
1288 assert!(vec_approx_eq(cp, [0.0, 0.0, 0.0], 1e-10), "cp={:?}", cp);
1289 }
1290
1291 #[test]
1292 fn test_closest_point_on_triangle_vertex() {
1293 let p = [-1.0f64, -1.0, 0.0];
1295 let a = [0.0f64, 0.0, 0.0];
1296 let b = [2.0f64, 0.0, 0.0];
1297 let c = [1.0f64, 2.0, 0.0];
1298 let cp = closest_point_on_triangle(p, a, b, c);
1299 assert!(vec_approx_eq(cp, a, 1e-10), "cp={:?}", cp);
1300 }
1301
1302 #[test]
1305 fn test_gjk_sphere_pair_overlap() {
1306 let support_a = |d: [f64; 3]| -> [f64; 3] {
1308 let n = normalize(d);
1309 scale(n, 1.0) };
1311 let support_b = |d: [f64; 3]| -> [f64; 3] {
1312 let n = normalize(d);
1313 add([1.0, 0.0, 0.0], scale(n, 1.0)) };
1315 assert!(
1316 gjk_intersect(support_a, support_b),
1317 "overlapping spheres must intersect"
1318 );
1319 }
1320
1321 #[test]
1322 fn test_gjk_sphere_pair_separated() {
1323 let support_a = |d: [f64; 3]| -> [f64; 3] { scale(normalize(d), 1.0) };
1325 let support_b =
1326 |d: [f64; 3]| -> [f64; 3] { add([5.0, 0.0, 0.0], scale(normalize(d), 1.0)) };
1327 assert!(
1328 !gjk_intersect(support_a, support_b),
1329 "separated spheres must not intersect"
1330 );
1331 }
1332
1333 #[test]
1336 fn test_narrow_phase_result_has_contact() {
1337 assert!(!NarrowPhaseResult::NoContact.has_contact());
1338 let c = Contact {
1339 point_a: [0.0; 3],
1340 point_b: [0.0; 3],
1341 normal: [0.0, 1.0, 0.0],
1342 depth: 0.1,
1343 };
1344 assert!(NarrowPhaseResult::SingleContact(c.clone()).has_contact());
1345 assert!(NarrowPhaseResult::Manifold(vec![c]).has_contact());
1346 }
1347
1348 #[test]
1351 fn test_triangle_aabb_intersect_overlap() {
1352 let tri: [[f64; 3]; 3] = [[-0.5, 0.0, -0.5], [0.5, 0.0, -0.5], [0.0, 0.0, 0.5]];
1354 let box_c = [0.0f64, 0.0, 0.0];
1355 let half = [1.0f64, 1.0, 1.0];
1356 assert!(
1357 triangle_aabb_intersect(&tri, box_c, half),
1358 "Triangle inside box must intersect"
1359 );
1360 }
1361
1362 #[test]
1363 fn test_triangle_aabb_intersect_separated() {
1364 let tri: [[f64; 3]; 3] = [[10.0, 0.0, 0.0], [11.0, 0.0, 0.0], [10.5, 1.0, 0.0]];
1366 let box_c = [0.0f64, 0.0, 0.0];
1367 let half = [1.0f64, 1.0, 1.0];
1368 assert!(
1369 !triangle_aabb_intersect(&tri, box_c, half),
1370 "Separated triangle must not intersect box"
1371 );
1372 }
1373
1374 #[test]
1375 fn test_triangle_aabb_intersect_edge_touch() {
1376 let tri: [[f64; 3]; 3] = [
1378 [1.0, 0.0, 0.0], [2.0, 0.5, 0.0],
1380 [2.0, -0.5, 0.0],
1381 ];
1382 let box_c = [0.0f64, 0.0, 0.0];
1383 let half = [1.0f64, 1.0, 1.0];
1384 assert!(
1386 triangle_aabb_intersect(&tri, box_c, half),
1387 "Triangle touching box face must intersect"
1388 );
1389 }
1390
1391 #[test]
1394 fn test_aabb_capsule_overlap() {
1395 let box_c = [0.0f64, 0.0, 0.0];
1398 let half = [1.0f64, 1.0, 1.0];
1399 let cap_a = [0.0f64, -2.0, 0.0];
1400 let cap_b = [0.0f64, 2.0, 0.0];
1401 match aabb_capsule(box_c, half, cap_a, cap_b, 0.5) {
1402 NarrowPhaseResult::SingleContact(c) => {
1403 assert!(c.depth >= 0.0, "depth={}", c.depth);
1404 }
1405 _ => panic!("expected contact for overlapping AABB-capsule"),
1406 }
1407 }
1408
1409 #[test]
1410 fn test_aabb_capsule_separated() {
1411 let box_c = [0.0f64, 0.0, 0.0];
1412 let half = [0.5f64, 0.5, 0.5];
1413 let cap_a = [5.0f64, 0.0, 0.0];
1414 let cap_b = [6.0f64, 0.0, 0.0];
1415 assert!(
1416 matches!(
1417 aabb_capsule(box_c, half, cap_a, cap_b, 0.3),
1418 NarrowPhaseResult::NoContact
1419 ),
1420 "Far capsule must return NoContact"
1421 );
1422 }
1423
1424 #[test]
1427 fn test_obb_capsule_axis_aligned_overlap() {
1428 let identity = [[1.0f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1429 let obb_c = [0.0f64, 0.0, 0.0];
1430 let obb_h = [1.0f64, 1.0, 1.0];
1431 let cap_a = [0.0f64, -2.0, 0.0];
1432 let cap_b = [0.0f64, 2.0, 0.0];
1433 let result = obb_capsule(obb_c, obb_h, identity, cap_a, cap_b, 0.5);
1434 assert!(result.has_contact(), "OBB-capsule should have contact");
1435 }
1436
1437 #[test]
1440 fn test_heightfield_sphere_above() {
1441 let hf = Heightfield::flat(5, 5, 1.0);
1442 let sphere_c = [2.0f64, 5.0, 2.0];
1444 assert!(
1445 matches!(
1446 heightfield_sphere(&hf, sphere_c, 0.5),
1447 NarrowPhaseResult::NoContact
1448 ),
1449 "Sphere high above heightfield must return NoContact"
1450 );
1451 }
1452
1453 #[test]
1454 fn test_heightfield_sphere_below() {
1455 let hf = Heightfield::flat(5, 5, 1.0);
1456 let sphere_c = [2.0f64, 0.3, 2.0];
1458 match heightfield_sphere(&hf, sphere_c, 0.5) {
1459 NarrowPhaseResult::SingleContact(c) => {
1460 assert!(
1461 approx_eq(c.depth, 0.2, 1e-6),
1462 "Expected depth 0.2, got {}",
1463 c.depth
1464 );
1465 }
1466 _ => panic!("expected contact for sphere below heightfield"),
1467 }
1468 }
1469
1470 #[test]
1471 fn test_heightfield_sphere_contact_normal() {
1472 let hf = Heightfield::flat(5, 5, 1.0);
1473 let sphere_c = [2.0f64, 0.3, 2.0];
1474 match heightfield_sphere(&hf, sphere_c, 0.5) {
1475 NarrowPhaseResult::SingleContact(c) => {
1476 assert!(
1478 approx_eq(c.normal[1], 1.0, 1e-6),
1479 "Normal should be (0,1,0) for flat surface: {:?}",
1480 c.normal
1481 );
1482 }
1483 _ => panic!("expected contact"),
1484 }
1485 }
1486
1487 #[test]
1490 fn test_heightfield_box_above() {
1491 let hf = Heightfield::flat(5, 5, 1.0);
1492 let box_c = [2.0f64, 5.0, 2.0];
1493 let half = [0.5f64, 0.5, 0.5];
1494 assert!(
1495 matches!(
1496 heightfield_box(&hf, box_c, half),
1497 NarrowPhaseResult::NoContact
1498 ),
1499 "Box far above heightfield must return NoContact"
1500 );
1501 }
1502
1503 #[test]
1504 fn test_heightfield_box_penetrating() {
1505 let hf = Heightfield::flat(5, 5, 1.0);
1506 let box_c = [2.0f64, 0.0, 2.0];
1508 let half = [0.5f64, 0.5, 0.5];
1509 match heightfield_box(&hf, box_c, half) {
1510 NarrowPhaseResult::SingleContact(c) => {
1511 assert!(
1512 c.depth > 0.0,
1513 "Penetrating box should have positive depth: {}",
1514 c.depth
1515 );
1516 }
1517 _ => panic!("expected contact for penetrating box"),
1518 }
1519 }
1520
1521 #[test]
1524 fn test_triangle_sphere_contact() {
1525 let tri_a = [-1.0f64, 0.0, -1.0];
1527 let tri_b = [1.0f64, 0.0, -1.0];
1528 let tri_c = [0.0f64, 0.0, 1.0];
1529 let sphere_c = [0.0f64, 0.3, 0.0];
1530 let radius = 0.5;
1531 match triangle_sphere(tri_a, tri_b, tri_c, sphere_c, radius) {
1532 NarrowPhaseResult::SingleContact(c) => {
1533 assert!(approx_eq(c.depth, 0.2, 1e-6), "depth={}", c.depth);
1534 assert!(
1535 approx_eq(len(c.normal), 1.0, 1e-10),
1536 "normal not unit: {:?}",
1537 c.normal
1538 );
1539 }
1540 _ => panic!("expected contact"),
1541 }
1542 }
1543
1544 #[test]
1545 fn test_triangle_sphere_no_contact() {
1546 let tri_a = [-1.0f64, 0.0, -1.0];
1547 let tri_b = [1.0f64, 0.0, -1.0];
1548 let tri_c = [0.0f64, 0.0, 1.0];
1549 let sphere_c = [0.0f64, 5.0, 0.0];
1550 assert!(
1551 matches!(
1552 triangle_sphere(tri_a, tri_b, tri_c, sphere_c, 0.5),
1553 NarrowPhaseResult::NoContact
1554 ),
1555 "Sphere far above triangle must return NoContact"
1556 );
1557 }
1558
1559 #[test]
1560 fn test_heightfield_bilinear_flat() {
1561 let hf = Heightfield::flat(4, 4, 1.0);
1562 assert!(approx_eq(hf.height_world(1.5, 1.5), 0.0, 1e-12));
1564 assert!(approx_eq(hf.height_world(0.1, 2.9), 0.0, 1e-12));
1565 }
1566
1567 #[test]
1568 fn test_heightfield_normal_flat_is_up() {
1569 let hf = Heightfield::flat(4, 4, 1.0);
1570 let n = hf.normal_world(1.5, 1.5);
1571 assert!(
1572 vec_approx_eq(n, [0.0, 1.0, 0.0], 1e-6),
1573 "Flat HF normal: {:?}",
1574 n
1575 );
1576 }
1577}