1use crate::body_collider::BodyCollider;
120use crate::collider::{Capsule, Support, AABB};
121use crate::compound::{CompoundChild, ShapeRef};
122use crate::heightfield::HeightField;
123use crate::math::{Fix128, QuatFix, Vec3Fix};
124use crate::plane_collider::PlaneCollider;
125#[cfg(feature = "std")]
126use crate::sdf_ccd::SdfCcdConfig;
127use crate::shape::{PosedShape, Shape};
128use crate::shape_raycast::{
129 box_planes, nearer, ray_local_cylinder, ray_local_torus, ray_planes, ray_solid_capsule,
130 ray_solid_sphere, to_local, to_world, torus_distance, LocalHit, RayFilter, RayTarget,
131 PARALLEL_EPSILON,
132};
133use crate::solver::PhysicsWorld;
134use crate::static_collider::StaticCollider;
135use crate::trimesh::{ray_triangle, TriMesh, Triangle};
136
137#[cfg(not(feature = "std"))]
138use alloc::{vec, vec::Vec};
139
140const TRACE_TOLERANCE: Fix128 = Fix128 {
142 hi: 0,
143 lo: 0x0000_0001_0000_0000,
144};
145
146const NEAR_TANGENT: Fix128 = Fix128 {
150 hi: 0,
151 lo: 0x0400_0000_0000_0000,
152};
153
154const TRACE_MAX_STEPS: usize = 1024;
157
158#[cfg(feature = "std")]
160const SDF_MAX_STEPS: usize = 4096;
161
162const GJK_RELATIVE: Fix128 = Fix128 {
164 hi: 0,
165 lo: 0x0000_0000_0100_0000,
166};
167
168const GJK_INTERSECT: Fix128 = Fix128 { hi: 0, lo: 1 };
174
175const GJK_MAX_ITERATIONS: usize = 64;
176
177const CURVE_COARSE: Fix128 = Fix128 {
180 hi: 0,
181 lo: 0x0001_0000_0000_0000,
182};
183
184const GOLDEN_STEPS: usize = 64;
186
187const CURVE_MAX_NODES: usize = 256;
189
190const CELL_MAX_NODES: usize = 512;
192
193const TREE_MAX_NODES: usize = 1024;
195
196#[cfg(feature = "std")]
198const SDF_BOX_DEPTH: u32 = 10;
199
200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206#[non_exhaustive]
207pub struct WorldShapeHit {
208 pub t: Fix128,
211 pub point: Vec3Fix,
214 pub normal: Vec3Fix,
217 pub target: RayTarget,
219 pub body: Option<usize>,
221}
222
223#[derive(Clone, Copy, Debug)]
225pub(crate) struct Penetration {
226 pub(crate) target: RayTarget,
228 pub(crate) depth: Fix128,
230 pub(crate) normal: Vec3Fix,
233}
234
235#[derive(Clone, Copy, Debug)]
241enum Dist {
242 Inside,
244 Outside {
247 dist: Fix128,
248 point: Vec3Fix,
249 normal: Vec3Fix,
250 },
251 AtLeast(Fix128),
253}
254
255impl Dist {
256 fn value(&self) -> Fix128 {
258 match *self {
259 Self::Inside => Fix128::NEG_ONE,
260 Self::Outside { dist, .. } => dist,
261 Self::AtLeast(d) => d,
262 }
263 }
264
265 fn within(&self, r: Fix128) -> bool {
267 match *self {
268 Self::Inside => true,
269 Self::Outside { dist, .. } => dist < r,
270 Self::AtLeast(_) => false,
271 }
272 }
273
274 fn toward(core: Vec3Fix, point: Vec3Fix) -> Self {
276 let (n, dist) = (core - point).normalize_with_length();
277 if dist.is_zero() {
278 return Self::Inside;
279 }
280 Self::Outside {
281 dist,
282 point,
283 normal: n,
284 }
285 }
286
287 fn shrunk(self, amount: Fix128) -> Self {
289 match self {
290 Self::Outside {
291 dist,
292 point,
293 normal,
294 } => {
295 let d = dist - amount;
296 if d.is_negative() {
297 Self::Inside
298 } else {
299 Self::Outside {
300 dist: d,
301 point: point + normal * amount,
302 normal,
303 }
304 }
305 }
306 Self::AtLeast(d) => Self::AtLeast(d - amount),
307 Self::Inside => Self::Inside,
308 }
309 }
310}
311
312fn starts_overlapping(d: &Dist, r: Fix128) -> bool {
316 match *d {
317 Dist::Inside => true,
318 Dist::Outside { dist, .. } => dist + TRACE_TOLERANCE < r,
319 Dist::AtLeast(_) => false,
320 }
321}
322
323fn half_vec(v: Vec3Fix) -> Vec3Fix {
324 Vec3Fix::new(v.x.half(), v.y.half(), v.z.half())
325}
326
327fn min_fix(a: Fix128, b: Fix128) -> Fix128 {
328 if b < a {
329 b
330 } else {
331 a
332 }
333}
334
335fn max_fix(a: Fix128, b: Fix128) -> Fix128 {
336 if b > a {
337 b
338 } else {
339 a
340 }
341}
342
343fn clamp(x: Fix128, lo: Fix128, hi: Fix128) -> Fix128 {
344 max_fix(lo, min_fix(x, hi))
345}
346
347fn closest_on_segment(a: Vec3Fix, b: Vec3Fix, p: Vec3Fix) -> Vec3Fix {
349 let ab = b - a;
350 let len2 = ab.length_squared();
351 if len2.is_zero() {
352 return a;
353 }
354 a + ab * clamp((p - a).dot(ab) / len2, Fix128::ZERO, Fix128::ONE)
355}
356
357fn closest_on_aabb(aabb: &AABB, p: Vec3Fix) -> Vec3Fix {
359 Vec3Fix::new(
360 clamp(p.x, aabb.min.x, aabb.max.x),
361 clamp(p.y, aabb.min.y, aabb.max.y),
362 clamp(p.z, aabb.min.z, aabb.max.z),
363 )
364}
365
366struct PointSupport(Vec3Fix);
368
369impl Support for PointSupport {
370 fn support(&self, _direction: Vec3Fix) -> Vec3Fix {
371 self.0
372 }
373}
374
375struct SegmentSupport(Vec3Fix, Vec3Fix);
377
378impl Support for SegmentSupport {
379 fn support(&self, direction: Vec3Fix) -> Vec3Fix {
380 if self.1.dot(direction) > self.0.dot(direction) {
381 self.1
382 } else {
383 self.0
384 }
385 }
386}
387
388struct TriangleSupport<'a>(&'a Triangle);
390
391impl Support for TriangleSupport<'_> {
392 fn support(&self, direction: Vec3Fix) -> Vec3Fix {
393 let t = self.0;
394 let (d0, d1, d2) = (
395 t.v0.dot(direction),
396 t.v1.dot(direction),
397 t.v2.dot(direction),
398 );
399 if d1 > d0 && d1 >= d2 {
400 t.v1
401 } else if d2 > d0 && d2 > d1 {
402 t.v2
403 } else {
404 t.v0
405 }
406 }
407}
408
409struct ChildSupport<'a> {
411 child: &'a CompoundChild,
412 position: Vec3Fix,
413 rotation: QuatFix,
414}
415
416impl Support for ChildSupport<'_> {
417 fn support(&self, direction: Vec3Fix) -> Vec3Fix {
418 self.child
419 .support_world(direction, self.position, self.rotation)
420 }
421}
422
423#[derive(Clone, Copy)]
426struct Vertex {
427 w: Vec3Fix,
428 a: Vec3Fix,
429 b: Vec3Fix,
430}
431
432fn vertex<A: Support, B: Support>(a: &A, b: &B, direction: Vec3Fix) -> Vertex {
433 let pa = a.support(direction);
434 let pb = b.support(-direction);
435 Vertex {
436 w: pa - pb,
437 a: pa,
438 b: pb,
439 }
440}
441
442fn closest_on_simplex(s: &[Vertex]) -> Option<(Vec<Vertex>, Vec<Fix128>)> {
445 match s.len() {
446 1 => Some((s.to_vec(), vec![Fix128::ONE])),
447 2 => Some(closest_on_edge(s[0], s[1])),
448 3 => Some(closest_on_triangle(s[0], s[1], s[2])),
449 _ => closest_on_tetrahedron(s[0], s[1], s[2], s[3]),
450 }
451}
452
453fn closest_on_edge(a: Vertex, b: Vertex) -> (Vec<Vertex>, Vec<Fix128>) {
454 let ab = b.w - a.w;
455 let len2 = ab.length_squared();
456 let t = if len2.is_zero() {
457 Fix128::ZERO
458 } else {
459 -a.w.dot(ab) / len2
460 };
461 if t <= Fix128::ZERO {
462 (vec![a], vec![Fix128::ONE])
463 } else if t >= Fix128::ONE {
464 (vec![b], vec![Fix128::ONE])
465 } else {
466 (vec![a, b], vec![Fix128::ONE - t, t])
467 }
468}
469
470fn closest_on_triangle(a: Vertex, b: Vertex, c: Vertex) -> (Vec<Vertex>, Vec<Fix128>) {
472 let ab = b.w - a.w;
473 let ac = c.w - a.w;
474 let ap = -a.w;
475 let d1 = ab.dot(ap);
476 let d2 = ac.dot(ap);
477 if d1 <= Fix128::ZERO && d2 <= Fix128::ZERO {
478 return (vec![a], vec![Fix128::ONE]);
479 }
480 let bp = -b.w;
481 let d3 = ab.dot(bp);
482 let d4 = ac.dot(bp);
483 if d3 >= Fix128::ZERO && d4 <= d3 {
484 return (vec![b], vec![Fix128::ONE]);
485 }
486 let vc = d1 * d4 - d3 * d2;
487 if vc <= Fix128::ZERO && d1 >= Fix128::ZERO && d3 <= Fix128::ZERO {
488 let v = d1 / (d1 - d3);
489 return (vec![a, b], vec![Fix128::ONE - v, v]);
490 }
491 let cp = -c.w;
492 let d5 = ab.dot(cp);
493 let d6 = ac.dot(cp);
494 if d6 >= Fix128::ZERO && d5 <= d6 {
495 return (vec![c], vec![Fix128::ONE]);
496 }
497 let vb = d5 * d2 - d1 * d6;
498 if vb <= Fix128::ZERO && d2 >= Fix128::ZERO && d6 <= Fix128::ZERO {
499 let w = d2 / (d2 - d6);
500 return (vec![a, c], vec![Fix128::ONE - w, w]);
501 }
502 let va = d3 * d6 - d5 * d4;
503 if va <= Fix128::ZERO && d4 - d3 >= Fix128::ZERO && d5 - d6 >= Fix128::ZERO {
504 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
505 return (vec![b, c], vec![Fix128::ONE - w, w]);
506 }
507 let denom = va + vb + vc;
508 if denom <= Fix128::ZERO {
509 let mut best = closest_on_edge(a, b);
511 for cand in [closest_on_edge(b, c), closest_on_edge(a, c)] {
512 if weighted(&cand).length_squared() < weighted(&best).length_squared() {
513 best = cand;
514 }
515 }
516 return best;
517 }
518 let v = vb / denom;
519 let w = vc / denom;
520 (vec![a, b, c], vec![Fix128::ONE - v - w, v, w])
521}
522
523fn closest_on_tetrahedron(
524 a: Vertex,
525 b: Vertex,
526 c: Vertex,
527 d: Vertex,
528) -> Option<(Vec<Vertex>, Vec<Fix128>)> {
529 let faces = [(a, b, c, d), (a, c, d, b), (a, d, b, c), (b, d, c, a)];
530 let mut best: Option<(Vec<Vertex>, Vec<Fix128>)> = None;
531 let mut inside = true;
532 for (p, q, r, opposite) in faces {
533 let n = (q.w - p.w).cross(r.w - p.w);
534 let origin_side = n.dot(-p.w);
535 let other_side = n.dot(opposite.w - p.w);
536 let outside = other_side.is_zero()
539 || (!origin_side.is_zero() && origin_side.is_negative() != other_side.is_negative());
540 if outside {
541 inside = false;
542 let cand = closest_on_triangle(p, q, r);
543 if best
544 .as_ref()
545 .is_none_or(|b| weighted(&cand).length_squared() < weighted(b).length_squared())
546 {
547 best = Some(cand);
548 }
549 }
550 }
551 if inside {
552 None
553 } else {
554 best
555 }
556}
557
558fn weighted(s: &(Vec<Vertex>, Vec<Fix128>)) -> Vec3Fix {
559 s.0.iter()
560 .zip(&s.1)
561 .fold(Vec3Fix::ZERO, |acc, (v, &l)| acc + v.w * l)
562}
563
564fn gjk_distance<A: Support, B: Support>(a: &A, b: &B) -> Option<(Fix128, Vec3Fix, Vec3Fix)> {
567 let mut simplex = vec![vertex(a, b, Vec3Fix::UNIT_X)];
568 let mut weights = vec![Fix128::ONE];
569 let mut v = simplex[0].w;
570 for _ in 0..GJK_MAX_ITERATIONS {
571 let vv = v.length_squared();
572 if vv <= GJK_INTERSECT {
573 return None;
574 }
575 let w = vertex(a, b, -v);
576 if vv - v.dot(w.w) <= vv * GJK_RELATIVE {
577 break;
578 }
579 if simplex.iter().any(|s| s.w == w.w) {
580 break;
581 }
582 let mut grown = simplex.clone();
583 grown.push(w);
584 let (sub, lambda) = closest_on_simplex(&grown)?;
585 let next = weighted(&(sub.clone(), lambda.clone()));
586 if next.length_squared() >= vv {
587 break;
589 }
590 simplex = sub;
591 weights = lambda;
592 v = next;
593 }
594 let pa = simplex
595 .iter()
596 .zip(&weights)
597 .fold(Vec3Fix::ZERO, |acc, (s, &l)| acc + s.a * l);
598 let pb = simplex
599 .iter()
600 .zip(&weights)
601 .fold(Vec3Fix::ZERO, |acc, (s, &l)| acc + s.b * l);
602 let dist = v.length();
603 if dist.is_zero() {
604 return None;
605 }
606 Some((dist, pa, pb))
607}
608
609fn convex_dist<S: Support>(a: Vec3Fix, b: Vec3Fix, solid: &S) -> Dist {
611 let found = if a == b {
612 gjk_distance(&PointSupport(a), solid)
613 } else {
614 gjk_distance(&SegmentSupport(a, b), solid)
615 };
616 match found {
617 None => Dist::Inside,
618 Some((dist, pa, pb)) => Dist::Outside {
619 dist,
620 point: pb,
621 normal: (pa - pb) / dist,
622 },
623 }
624}
625
626fn segment_segment(
629 p0: Vec3Fix,
630 p1: Vec3Fix,
631 q0: Vec3Fix,
632 q1: Vec3Fix,
633) -> Option<(Fix128, Vec3Fix, Vec3Fix)> {
634 if p0 == p1 && q0 == q1 {
635 let (_, d) = (p0 - q0).normalize_with_length();
636 return (!d.is_zero()).then_some((d, p0, q0));
637 }
638 if p0 == p1 {
639 let q = closest_on_segment(q0, q1, p0);
640 let d = (p0 - q).length();
641 return (!d.is_zero()).then_some((d, p0, q));
642 }
643 if q0 == q1 {
644 let p = closest_on_segment(p0, p1, q0);
645 let d = (p - q0).length();
646 return (!d.is_zero()).then_some((d, p, q0));
647 }
648 gjk_distance(&SegmentSupport(p0, p1), &SegmentSupport(q0, q1))
649}
650
651fn sphere_dist(a: Vec3Fix, b: Vec3Fix, center: Vec3Fix, radius: Fix128) -> Dist {
653 let s = closest_on_segment(a, b, center);
654 let (n, d) = (s - center).normalize_with_length();
655 if d < radius || d.is_zero() {
656 return Dist::Inside;
657 }
658 Dist::Outside {
659 dist: d - radius,
660 point: center + n * radius,
661 normal: n,
662 }
663}
664
665fn capsule_dist(a: Vec3Fix, b: Vec3Fix, capsule: &Capsule) -> Dist {
667 match segment_segment(a, b, capsule.a, capsule.b) {
668 None => Dist::Inside,
669 Some((d, pa, pb)) => Dist::Outside {
670 dist: d,
671 point: pb,
672 normal: (pa - pb) / d,
673 }
674 .shrunk(capsule.radius),
675 }
676}
677
678fn point_box_local(p: Vec3Fix, h: Vec3Fix) -> Dist {
681 if p.x.abs() < h.x && p.y.abs() < h.y && p.z.abs() < h.z {
682 return Dist::Inside;
683 }
684 let q = Vec3Fix::new(
685 clamp(p.x, -h.x, h.x),
686 clamp(p.y, -h.y, h.y),
687 clamp(p.z, -h.z, h.z),
688 );
689 if q == p {
690 let axis = |v: Fix128, e: Fix128, unit: Vec3Fix| {
692 (v.abs() == e).then(|| if v.is_negative() { -unit } else { unit })
693 };
694 let z = Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE);
695 let normal = axis(p.x, h.x, Vec3Fix::UNIT_X)
696 .or_else(|| axis(p.y, h.y, Vec3Fix::UNIT_Y))
697 .or_else(|| axis(p.z, h.z, z))
698 .unwrap_or(Vec3Fix::UNIT_Y);
699 return Dist::Outside {
700 dist: Fix128::ZERO,
701 point: p,
702 normal,
703 };
704 }
705 Dist::toward(p, q)
706}
707
708fn point_cylinder_local(p: Vec3Fix, radius: Fix128, hh: Fix128) -> Dist {
711 let (dir, rho) = Vec3Fix::new(p.x, Fix128::ZERO, p.z).normalize_with_length();
712 let qy = clamp(p.y, -hh, hh);
713 if rho < radius && p.y.abs() < hh {
714 return Dist::Inside;
715 }
716 let qr = min_fix(rho, radius);
717 let q = if rho.is_zero() {
718 Vec3Fix::new(Fix128::ZERO, qy, Fix128::ZERO)
719 } else {
720 Vec3Fix::new(dir.x * qr, qy, dir.z * qr)
721 };
722 if (q - p).length_squared().is_zero() {
723 let normal = if p.y.abs() == hh {
725 if p.y.is_negative() {
726 -Vec3Fix::UNIT_Y
727 } else {
728 Vec3Fix::UNIT_Y
729 }
730 } else {
731 dir
732 };
733 return Dist::Outside {
734 dist: Fix128::ZERO,
735 point: p,
736 normal,
737 };
738 }
739 Dist::toward(p, q)
740}
741
742fn point_torus_local(p: Vec3Fix, major: Fix128, minor: Fix128) -> Dist {
745 if torus_distance(p, major, minor).is_negative() {
746 return Dist::Inside;
747 }
748 let (ring_dir, rho) = Vec3Fix::new(p.x, Fix128::ZERO, p.z).normalize_with_length();
749 let ring_dir = if rho.is_zero() {
750 Vec3Fix::UNIT_X
751 } else {
752 ring_dir
753 };
754 let ring = ring_dir * major;
755 match Dist::toward(p, ring) {
756 Dist::Outside { dist, normal, .. } => Dist::Outside {
757 dist,
758 point: ring,
759 normal,
760 }
761 .shrunk(minor),
762 other => other,
763 }
764}
765
766fn dist_to_world(d: Dist, center: Vec3Fix, rotation: QuatFix) -> Dist {
768 match d {
769 Dist::Outside {
770 dist,
771 point,
772 normal,
773 } => Dist::Outside {
774 dist,
775 point: center + rotation.rotate_vec(point),
776 normal: rotation.rotate_vec(normal),
777 },
778 other => other,
779 }
780}
781
782fn curve_min(lipschitz: Fix128, f: impl Fn(Fix128) -> Dist) -> Dist {
786 const SEEDS: i64 = 16;
787 let mut best: Option<(Fix128, Fix128, Dist)> = None;
789 let mut stack: Vec<(Fix128, Fix128, Fix128)> = Vec::new();
791 let seed_half = Fix128::from_ratio(1, 2 * SEEDS);
792 let mut nodes = 0usize;
793 let mut todo: Vec<(Fix128, Fix128)> = (0..SEEDS)
794 .map(|k| (Fix128::from_ratio(2 * k + 1, 2 * SEEDS), seed_half))
795 .collect();
796 loop {
797 for (m, hw) in todo.drain(..) {
798 let d = f(m);
799 if matches!(d, Dist::Inside) {
800 return Dist::Inside;
801 }
802 if best.as_ref().is_none_or(|(_, _, b)| d.value() < b.value()) {
803 best = Some((m, hw, d));
804 }
805 stack.push((m, hw, d.value() - lipschitz * hw));
807 }
808 let best_value = best.as_ref().map_or(Fix128::ZERO, |(_, _, b)| b.value());
809 stack.retain(|&(_, hw, bound)| {
812 bound < best_value - CURVE_COARSE && lipschitz * hw > CURVE_COARSE
813 });
814 let Some(k) = (0..stack.len()).min_by_key(|&k| stack[k].2) else {
816 break;
817 };
818 nodes += 1;
819 if nodes > CURVE_MAX_NODES {
820 break;
821 }
822 let (mid, hw, _) = stack.remove(k);
823 let quarter = hw.half();
824 todo.push((mid - quarter, quarter));
825 todo.push((mid + quarter, quarter));
826 }
827 let Some((s_best, hw_best, d_best)) = best else {
828 return Dist::AtLeast(Fix128::ZERO);
829 };
830 let mut lo = max_fix(Fix128::ZERO, s_best - hw_best.double());
832 let mut hi = min_fix(Fix128::ONE, s_best + hw_best.double());
833 let ratio = Fix128::from_ratio(618_033_988_749_895, 1_000_000_000_000_000);
834 let mut x1 = hi - (hi - lo) * ratio;
835 let mut x2 = lo + (hi - lo) * ratio;
836 let mut f1 = f(x1);
837 let mut f2 = f(x2);
838 let mut refined = d_best;
839 for _ in 0..GOLDEN_STEPS {
840 for d in [f1, f2] {
841 if matches!(d, Dist::Inside) {
842 return Dist::Inside;
843 }
844 if d.value() < refined.value() {
845 refined = d;
846 }
847 }
848 if f1.value() <= f2.value() {
849 hi = x2;
850 x2 = x1;
851 f2 = f1;
852 x1 = hi - (hi - lo) * ratio;
853 f1 = f(x1);
854 } else {
855 lo = x1;
856 x1 = x2;
857 f1 = f2;
858 x2 = lo + (hi - lo) * ratio;
859 f2 = f(x2);
860 }
861 }
862 refined
863}
864
865fn segment_min(a: Vec3Fix, b: Vec3Fix, f: impl Fn(Vec3Fix) -> Dist) -> Dist {
868 if a == b {
869 return f(a);
870 }
871 let ab = b - a;
872 curve_min(ab.length(), |s| f(a + ab * s))
873}
874
875fn field_has_surface(field: &HeightField) -> bool {
881 field.width >= 2 && field.depth >= 2 && field.spacing > Fix128::ZERO
882}
883
884fn cell_range(
887 x0: Fix128,
888 x1: Fix128,
889 origin: Fix128,
890 spacing: Fix128,
891 n: u32,
892) -> Option<(u32, u32)> {
893 let last = i64::from(n) - 2;
894 let lo = ((x0 - origin) / spacing).floor().hi;
895 let hi = ((x1 - origin) / spacing).floor().hi;
896 if hi < 0 || lo > last {
897 return None;
898 }
899 Some((lo.clamp(0, last) as u32, hi.clamp(0, last) as u32))
900}
901
902fn cell_origin(field: &HeightField, gx: u32, gz: u32) -> (Fix128, Fix128) {
904 (
905 field.origin.x + field.spacing * Fix128::from_int(i64::from(gx)),
906 field.origin.z + field.spacing * Fix128::from_int(i64::from(gz)),
907 )
908}
909
910fn cell_heights(field: &HeightField, gx: u32, gz: u32) -> [Fix128; 4] {
912 [
913 field.get_height(gx, gz),
914 field.get_height(gx + 1, gz),
915 field.get_height(gx, gz + 1),
916 field.get_height(gx + 1, gz + 1),
917 ]
918}
919
920struct QuadSupport([Vec3Fix; 4]);
923
924impl Support for QuadSupport {
925 fn support(&self, direction: Vec3Fix) -> Vec3Fix {
926 let mut best = self.0[0];
927 let mut best_dot = best.dot(direction);
928 for &p in &self.0[1..] {
929 let d = p.dot(direction);
930 if d > best_dot {
931 best = p;
932 best_dot = d;
933 }
934 }
935 best
936 }
937}
938
939#[derive(Clone, Copy)]
942struct Cell {
943 x0: Fix128,
944 z0: Fix128,
945 s: Fix128,
946 h: [Fix128; 4],
947}
948
949impl Cell {
950 fn new(field: &HeightField, gx: u32, gz: u32) -> Self {
951 let (x0, z0) = cell_origin(field, gx, gz);
952 Self {
953 x0,
954 z0,
955 s: field.spacing,
956 h: cell_heights(field, gx, gz),
957 }
958 }
959
960 fn height(&self, u: Fix128, v: Fix128) -> Fix128 {
962 let [h00, h10, h01, h11] = self.h;
963 let one = Fix128::ONE;
964 h00 * (one - u) * (one - v) + h10 * u * (one - v) + h01 * (one - u) * v + h11 * u * v
965 }
966
967 fn point(&self, u: Fix128, v: Fix128) -> Vec3Fix {
968 Vec3Fix::new(
969 self.x0 + self.s * u,
970 self.height(u, v),
971 self.z0 + self.s * v,
972 )
973 }
974
975 fn twist(&self) -> Fix128 {
977 let [h00, h10, h01, h11] = self.h;
978 h00 - h10 - h01 + h11
979 }
980
981 fn normal(&self, u: Fix128, v: Fix128) -> Vec3Fix {
983 let [h00, h10, h01, h11] = self.h;
984 let one = Fix128::ONE;
985 let hu = (h10 - h00) * (one - v) + (h11 - h01) * v;
986 let hv = (h01 - h00) * (one - u) + (h11 - h10) * u;
987 Vec3Fix::new(-hu, self.s, -hv).normalize()
988 }
989
990 fn uv(&self, p: Vec3Fix, part: &Patch) -> (Fix128, Fix128) {
992 (
993 clamp((p.x - self.x0) / self.s, part.u0, part.u1),
994 clamp((p.z - self.z0) / self.s, part.v0, part.v1),
995 )
996 }
997}
998
999#[derive(Clone, Copy)]
1003struct Patch {
1004 u0: Fix128,
1005 u1: Fix128,
1006 v0: Fix128,
1007 v1: Fix128,
1008}
1009
1010impl Patch {
1011 const WHOLE: Self = Self {
1012 u0: Fix128::ZERO,
1013 u1: Fix128::ONE,
1014 v0: Fix128::ZERO,
1015 v1: Fix128::ONE,
1016 };
1017
1018 fn hull(&self, cell: &Cell) -> QuadSupport {
1019 QuadSupport([
1020 cell.point(self.u0, self.v0),
1021 cell.point(self.u1, self.v0),
1022 cell.point(self.u0, self.v1),
1023 cell.point(self.u1, self.v1),
1024 ])
1025 }
1026
1027 fn thickness(&self, cell: &Cell) -> Fix128 {
1032 (cell.twist().abs() * (self.u1 - self.u0) * (self.v1 - self.v0))
1033 .half()
1034 .half()
1035 }
1036
1037 fn split(&self) -> [Self; 4] {
1038 let um = (self.u0 + self.u1).half();
1039 let vm = (self.v0 + self.v1).half();
1040 [
1041 Self {
1042 u1: um,
1043 v1: vm,
1044 ..*self
1045 },
1046 Self {
1047 u0: um,
1048 v1: vm,
1049 ..*self
1050 },
1051 Self {
1052 u1: um,
1053 v0: vm,
1054 ..*self
1055 },
1056 Self {
1057 u0: um,
1058 v0: vm,
1059 ..*self
1060 },
1061 ]
1062 }
1063
1064 fn surface_under(&self, cell: &Cell, p: Vec3Fix) -> Vec3Fix {
1066 let (u, v) = cell.uv(p, self);
1067 cell.point(u, v)
1068 }
1069}
1070
1071fn cell_dist(cell: &Cell, a: Vec3Fix, b: Vec3Fix) -> (Fix128, Vec3Fix) {
1079 let to_core = |q: Vec3Fix| (closest_on_segment(a, b, q) - q).length();
1080 let eval = |part: &Patch| {
1081 let hull = part.hull(cell);
1082 let found = if a == b {
1083 gjk_distance(&PointSupport(a), &hull)
1084 } else {
1085 gjk_distance(&SegmentSupport(a, b), &hull)
1086 };
1087 let (lower, near) = match found {
1088 Some((d, _, pb)) => (d, pb),
1089 None => (
1090 Fix128::ZERO,
1091 cell.point((part.u0 + part.u1).half(), (part.v0 + part.v1).half()),
1092 ),
1093 };
1094 let q = part.surface_under(cell, near);
1095 (lower, q, to_core(q))
1096 };
1097 let (lower, q, upper) = eval(&Patch::WHOLE);
1098 let mut best = (upper, q);
1099 let mut open = vec![(lower, Patch::WHOLE)];
1100 let mut nodes = 0usize;
1101 while let Some(k) = (0..open.len()).min_by_key(|&k| open[k].0) {
1102 let (lower, part) = open.remove(k);
1103 if lower + TRACE_TOLERANCE >= best.0 {
1106 break;
1107 }
1108 nodes += 1;
1109 if nodes > CELL_MAX_NODES {
1110 return (lower, best.1);
1111 }
1112 for child in part.split() {
1113 let (lower, q, upper) = eval(&child);
1114 if upper < best.0 {
1115 best = (upper, q);
1116 }
1117 if lower + TRACE_TOLERANCE < best.0 && child.thickness(cell) > TRACE_TOLERANCE {
1120 open.push((lower, child));
1121 }
1122 }
1123 }
1124 best
1125}
1126
1127fn heightfield_dist(field: &HeightField, a: Vec3Fix, b: Vec3Fix, cap: Fix128) -> Dist {
1133 if !field_has_surface(field) {
1134 return Dist::AtLeast(cap);
1135 }
1136 let core = core_box(a, b, Fix128::ZERO);
1137 let far = aabb_gap(&core, &field.aabb());
1138 if far >= cap {
1139 return Dist::AtLeast(far);
1140 }
1141 let reach = core_box(a, b, cap);
1142 let s = field.spacing;
1143 let (Some((gx0, gx1)), Some((gz0, gz1))) = (
1144 cell_range(reach.min.x, reach.max.x, field.origin.x, s, field.width),
1145 cell_range(reach.min.z, reach.max.z, field.origin.z, s, field.depth),
1146 ) else {
1147 return Dist::AtLeast(cap);
1148 };
1149 let mut cells: Vec<(Fix128, u32, u32)> = Vec::new();
1152 for gz in gz0..=gz1 {
1153 for gx in gx0..=gx1 {
1154 let lower = aabb_gap(&core, &cell_box(field, gx, gz));
1155 if lower < cap {
1156 cells.push((lower, gz, gx));
1157 }
1158 }
1159 }
1160 cells.sort_unstable();
1161 let mut best: Option<(Fix128, Vec3Fix, Cell)> = None;
1162 for (lower, gz, gx) in cells {
1163 if best.is_some_and(|(d, _, _)| lower >= d) {
1164 break;
1165 }
1166 let cell = Cell::new(field, gx, gz);
1167 let (d, q) = cell_dist(&cell, a, b);
1168 if best.is_none_or(|(bd, _, _)| d < bd) {
1169 best = Some((d, q, cell));
1170 }
1171 }
1172 let Some((d, q, cell)) = best.filter(|&(d, _, _)| d < cap) else {
1173 return Dist::AtLeast(cap);
1174 };
1175 let core_point = closest_on_segment(a, b, q);
1176 if d <= TRACE_TOLERANCE {
1177 if a != b {
1178 return Dist::Inside;
1179 }
1180 let (u, v) = cell.uv(q, &Patch::WHOLE);
1181 let n = cell.normal(u, v);
1182 let side = if (a - q).dot(n).is_negative() { -n } else { n };
1183 return Dist::Outside {
1184 dist: d,
1185 point: q,
1186 normal: side,
1187 };
1188 }
1189 let normal = match (core_point - q).try_normalize() {
1190 Some(n) => n,
1191 None => return Dist::Inside,
1192 };
1193 Dist::Outside {
1194 dist: d,
1195 point: q,
1196 normal,
1197 }
1198}
1199
1200fn cell_box(field: &HeightField, gx: u32, gz: u32) -> AABB {
1203 let h = cell_heights(field, gx, gz);
1204 let (x0, z0) = cell_origin(field, gx, gz);
1205 let s = field.spacing;
1206 let lo = min_fix(min_fix(h[0], h[1]), min_fix(h[2], h[3]));
1207 let hi = max_fix(max_fix(h[0], h[1]), max_fix(h[2], h[3]));
1208 AABB::new(Vec3Fix::new(x0, lo, z0), Vec3Fix::new(x0 + s, hi, z0 + s))
1209}
1210
1211fn cell_toi(
1216 cell: &Cell,
1217 a: Vec3Fix,
1218 b: Vec3Fix,
1219 r: Fix128,
1220 d: Vec3Fix,
1221 max_t: Fix128,
1222) -> Option<Contact> {
1223 let toi = |part: &Patch| toi_convex(a, b, r, Fix128::ZERO, d, max_t, &part.hull(cell));
1224 if cell.twist().is_zero() {
1225 return toi(&Patch::WHOLE);
1226 }
1227 let found = toi_tree(
1228 &[Patch::WHOLE],
1229 toi,
1230 |part| part.thickness(cell) <= TRACE_TOLERANCE,
1231 |part| part.split().to_vec(),
1232 )?;
1233 let off = d * found.t;
1234 Some(refine_on_cell(cell, a + off, b + off, found))
1235}
1236
1237fn refine_on_cell(cell: &Cell, a: Vec3Fix, b: Vec3Fix, found: Contact) -> Contact {
1243 let [h00, h10, h01, h11] = cell.h;
1244 let k = cell.twist();
1245 let s = cell.s;
1246 let one = Fix128::ONE;
1247 let (mut u, mut v) = cell.uv(found.point, &Patch::WHOLE);
1248 for _ in 0..8 {
1249 let q = cell.point(u, v);
1250 let e = q - closest_on_segment(a, b, q);
1251 let hu = (h10 - h00) * (one - v) + (h11 - h01) * v;
1252 let hv = (h01 - h00) * (one - u) + (h11 - h10) * u;
1253 let pu = Vec3Fix::new(s, hu, Fix128::ZERO);
1254 let pv = Vec3Fix::new(Fix128::ZERO, hv, s);
1255 let (gu, gv) = (e.dot(pu), e.dot(pv));
1256 let (huu, hvv) = (pu.dot(pu), pv.dot(pv));
1257 let huv = pu.dot(pv) + e.y * k;
1258 let det = huu * hvv - huv * huv;
1259 if det <= Fix128::ZERO {
1260 return found;
1261 }
1262 u = clamp(u - (hvv * gu - huv * gv) / det, Fix128::ZERO, one);
1263 v = clamp(v - (huu * gv - huv * gu) / det, Fix128::ZERO, one);
1264 }
1265 let q = cell.point(u, v);
1266 let core = closest_on_segment(a, b, q);
1267 let before = (closest_on_segment(a, b, found.point) - found.point).length();
1268 match (core - q).normalize_with_length() {
1269 (n, dist)
1270 if !dist.is_zero()
1271 && dist <= before + TRACE_TOLERANCE + TRACE_TOLERANCE
1272 && n.dot(found.normal) > Fix128::ZERO =>
1273 {
1274 Contact {
1275 t: found.t,
1276 point: q,
1277 normal: n,
1278 }
1279 }
1280 _ => found,
1281 }
1282}
1283
1284fn sweep_heightfield(
1289 field: &HeightField,
1290 a: Vec3Fix,
1291 b: Vec3Fix,
1292 r: Fix128,
1293 d: Vec3Fix,
1294 max_t: Fix128,
1295) -> Option<Contact> {
1296 if !field_has_surface(field) {
1297 return None;
1298 }
1299 let start = core_box(a, b, r);
1300 let swept = swept_box(a, b, r, d, max_t);
1301 let s = field.spacing;
1302 let (Some((gx0, gx1)), Some((gz0, gz1))) = (
1303 cell_range(swept.min.x, swept.max.x, field.origin.x, s, field.width),
1304 cell_range(swept.min.z, swept.max.z, field.origin.z, s, field.depth),
1305 ) else {
1306 return None;
1307 };
1308 let mut cells: Vec<(Fix128, u32, u32)> = Vec::new();
1309 for gz in gz0..=gz1 {
1310 for gx in gx0..=gx1 {
1311 if let Some(entry) = box_entry(&start, d, max_t, &cell_box(field, gx, gz)) {
1312 cells.push((entry, gz, gx));
1313 }
1314 }
1315 }
1316 cells.sort_unstable();
1317 let mut best: Option<Contact> = None;
1318 for (entry, gz, gx) in cells {
1319 if best.is_some_and(|c| c.t < entry) {
1320 break;
1321 }
1322 if let Some(c) = cell_toi(&Cell::new(field, gx, gz), a, b, r, d, max_t) {
1323 if best.is_none_or(|bc| c.t < bc.t) {
1324 best = Some(c);
1325 }
1326 }
1327 }
1328 best
1329}
1330
1331fn ratio_within(num: Fix128, den: Fix128, big: Fix128) -> Fix128 {
1333 if num.abs() >= den.abs() * big {
1334 if num.is_negative() == den.is_negative() {
1335 big
1336 } else {
1337 -big
1338 }
1339 } else {
1340 num / den
1341 }
1342}
1343
1344fn box_entry(moving: &AABB, d: Vec3Fix, max_t: Fix128, target: &AABB) -> Option<Fix128> {
1347 let big = max_t + Fix128::ONE;
1348 let mut lo = Fix128::ZERO;
1349 let mut hi = max_t;
1350 for (m0, m1, t0, t1, di) in [
1351 (moving.min.x, moving.max.x, target.min.x, target.max.x, d.x),
1352 (moving.min.y, moving.max.y, target.min.y, target.max.y, d.y),
1353 (moving.min.z, moving.max.z, target.min.z, target.max.z, d.z),
1354 ] {
1355 if di.abs() < PARALLEL_EPSILON {
1356 let slack = max_t * di.abs();
1358 if m1 + slack < t0 || m0 - slack > t1 {
1359 return None;
1360 }
1361 continue;
1362 }
1363 let (enter, leave) = if di.is_negative() {
1364 (
1365 ratio_within(t1 - m0, di, big),
1366 ratio_within(t0 - m1, di, big),
1367 )
1368 } else {
1369 (
1370 ratio_within(t0 - m1, di, big),
1371 ratio_within(t1 - m0, di, big),
1372 )
1373 };
1374 lo = max_fix(lo, enter);
1375 hi = min_fix(hi, leave);
1376 }
1377 (lo <= hi).then_some(lo)
1378}
1379
1380fn swept_box(a: Vec3Fix, b: Vec3Fix, r: Fix128, d: Vec3Fix, max_t: Fix128) -> AABB {
1382 let start = core_box(a, b, r);
1383 let end = core_box(a + d * max_t, b + d * max_t, r);
1384 AABB::new(
1385 Vec3Fix::new(
1386 min_fix(start.min.x, end.min.x),
1387 min_fix(start.min.y, end.min.y),
1388 min_fix(start.min.z, end.min.z),
1389 ),
1390 Vec3Fix::new(
1391 max_fix(start.max.x, end.max.x),
1392 max_fix(start.max.y, end.max.y),
1393 max_fix(start.max.z, end.max.z),
1394 ),
1395 )
1396}
1397
1398fn sweep_mesh(
1404 mesh: &TriMesh,
1405 a: Vec3Fix,
1406 b: Vec3Fix,
1407 r: Fix128,
1408 d: Vec3Fix,
1409 max_t: Fix128,
1410) -> Option<Contact> {
1411 let start = core_box(a, b, r);
1412 let mut order: Vec<(Fix128, u32)> = mesh_candidates(mesh, &swept_box(a, b, r, d, max_t))
1413 .into_iter()
1414 .filter_map(|i| {
1415 let tri = &mesh.triangles[i as usize];
1416 let tri_box = AABB::new(
1417 Vec3Fix::new(
1418 min_fix(min_fix(tri.v0.x, tri.v1.x), tri.v2.x),
1419 min_fix(min_fix(tri.v0.y, tri.v1.y), tri.v2.y),
1420 min_fix(min_fix(tri.v0.z, tri.v1.z), tri.v2.z),
1421 ),
1422 Vec3Fix::new(
1423 max_fix(max_fix(tri.v0.x, tri.v1.x), tri.v2.x),
1424 max_fix(max_fix(tri.v0.y, tri.v1.y), tri.v2.y),
1425 max_fix(max_fix(tri.v0.z, tri.v1.z), tri.v2.z),
1426 ),
1427 );
1428 box_entry(&start, d, max_t, &tri_box).map(|entry| (entry, i))
1429 })
1430 .collect();
1431 order.sort_unstable();
1432 let mut best: Option<Contact> = None;
1433 for (entry, i) in order {
1434 if best.is_some_and(|c| c.t < entry) {
1435 break;
1436 }
1437 let tri = &mesh.triangles[i as usize];
1438 if let Some(c) = toi_convex(a, b, r, Fix128::ZERO, d, max_t, &TriangleSupport(tri)) {
1439 if best.is_none_or(|bc| c.t < bc.t) {
1440 best = Some(c);
1441 }
1442 }
1443 }
1444 best
1445}
1446
1447#[derive(Clone, Copy)]
1450struct Ring {
1451 center: Vec3Fix,
1452 rotation: QuatFix,
1453 major: Fix128,
1454 minor: Fix128,
1455}
1456
1457#[derive(Clone, Copy)]
1460struct RingArc {
1461 e0: Vec3Fix,
1462 e1: Vec3Fix,
1463}
1464
1465fn turn(a: Vec3Fix, b: Vec3Fix) -> Fix128 {
1468 a.x * b.z - a.z * b.x
1469}
1470
1471impl RingArc {
1472 fn thickness(&self, major: Fix128) -> Fix128 {
1475 let half_chord = ((self.e1 - self.e0).length() * major).half();
1476 major - (major * major - half_chord * half_chord).sqrt()
1477 }
1478
1479 fn split(&self) -> [Self; 2] {
1480 let mid = (self.e0 + self.e1).normalize();
1481 [
1482 Self {
1483 e0: self.e0,
1484 e1: mid,
1485 },
1486 Self {
1487 e0: mid,
1488 e1: self.e1,
1489 },
1490 ]
1491 }
1492}
1493
1494struct ArcSupport {
1497 ring: Ring,
1498 arc: RingArc,
1499}
1500
1501impl Support for ArcSupport {
1502 fn support(&self, direction: Vec3Fix) -> Vec3Fix {
1503 let local = self.ring.rotation.conjugate().rotate_vec(direction);
1504 let flat = Vec3Fix::new(local.x, Fix128::ZERO, local.z);
1505 let (e0, e1) = (self.arc.e0, self.arc.e1);
1506 let e = match flat.try_normalize() {
1507 Some(u) if !turn(e0, u).is_negative() && !turn(u, e1).is_negative() => u,
1508 _ if e1.dot(flat) > e0.dot(flat) => e1,
1509 _ => e0,
1510 };
1511 self.ring.center + self.ring.rotation.rotate_vec(e * self.ring.major)
1512 }
1513}
1514
1515impl Ring {
1516 fn sweep(
1521 &self,
1522 a: Vec3Fix,
1523 b: Vec3Fix,
1524 r: Fix128,
1525 d: Vec3Fix,
1526 max_t: Fix128,
1527 ) -> Option<Contact> {
1528 let x = Vec3Fix::UNIT_X;
1529 let z = Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE);
1530 let quarters = [
1531 RingArc { e0: x, e1: z },
1532 RingArc { e0: z, e1: -x },
1533 RingArc { e0: -x, e1: -z },
1534 RingArc { e0: -z, e1: x },
1535 ];
1536 toi_tree(
1537 &quarters,
1538 |arc| {
1539 let hull = ArcSupport {
1540 ring: *self,
1541 arc: *arc,
1542 };
1543 toi_convex(a, b, r, self.minor, d, max_t, &hull)
1544 },
1545 |arc| arc.thickness(self.major) <= TRACE_TOLERANCE,
1546 |arc| arc.split().to_vec(),
1547 )
1548 .map(|c| self.refine(a + d * c.t, b + d * c.t, c))
1549 }
1550
1551 fn refine(&self, a: Vec3Fix, b: Vec3Fix, found: Contact) -> Contact {
1555 let core = closest_on_segment(a, b, found.point);
1556 let inv = self.rotation.conjugate();
1557 let local = inv.rotate_vec(core - self.center);
1558 let Some(dir) = Vec3Fix::new(local.x, Fix128::ZERO, local.z).try_normalize() else {
1559 return found;
1560 };
1561 let ring = self.center + self.rotation.rotate_vec(dir * self.major);
1562 match (core - ring).try_normalize() {
1563 Some(n) if n.dot(found.normal) > Fix128::ZERO => Contact {
1564 t: found.t,
1565 point: ring + n * self.minor,
1566 normal: n,
1567 },
1568 _ => found,
1569 }
1570 }
1571}
1572
1573fn toi_convex<S: Support>(
1598 a: Vec3Fix,
1599 b: Vec3Fix,
1600 r: Fix128,
1601 inflate: Fix128,
1602 d: Vec3Fix,
1603 max_t: Fix128,
1604 solid: &S,
1605) -> Option<Contact> {
1606 let reach = r + inflate;
1607 let mut t = Fix128::ZERO;
1608 let mut clear: Option<Contact> = None;
1610 let mut deep: Option<Fix128> = None;
1612 for _ in 0..TRACE_MAX_STEPS {
1613 let off = d * t;
1614 let state = match convex_dist(a + off, b + off, solid) {
1615 Dist::Outside {
1616 dist,
1617 point,
1618 normal,
1619 } if dist - reach >= -TRACE_TOLERANCE => {
1620 let gap = dist - reach;
1621 let slope = d.dot(normal);
1622 let contact = Contact {
1623 t,
1624 point: point + normal * inflate,
1625 normal,
1626 };
1627 if gap <= TRACE_TOLERANCE {
1628 let gap_at = |t: Fix128| {
1629 let off = d * t;
1630 match convex_dist(a + off, b + off, solid) {
1631 Dist::Outside { dist, .. } => Some(dist - reach),
1632 _ => None,
1633 }
1634 };
1635 return touch_is_hit(contact, d, max_t, gap_at).then_some(contact);
1636 }
1637 if !slope.is_negative() {
1638 return None;
1639 }
1640 Some((gap, -slope, contact))
1641 }
1642 _ => None,
1643 };
1644 match state {
1645 Some((gap, speed, contact)) => {
1646 clear = Some(contact);
1647 let limit = deep.unwrap_or(max_t);
1648 if gap >= (limit - t) * speed {
1650 match deep {
1651 None if gap > (limit - t) * speed => return None,
1652 None => t = limit,
1653 Some(hi) => t = (t + hi).half(),
1654 }
1655 } else {
1656 t = t + gap / speed;
1657 }
1658 }
1659 None => {
1660 if t.is_zero() {
1661 return Some(Contact {
1662 t,
1663 point: half_vec(a + b),
1664 normal: -d,
1665 });
1666 }
1667 deep = Some(t);
1668 let lo = clear.map_or(Fix128::ZERO, |c| c.t);
1669 if t - lo <= TRACE_TOLERANCE {
1670 return clear;
1671 }
1672 t = (lo + t).half();
1673 }
1674 }
1675 }
1676 clear
1677}
1678
1679fn touch_is_hit(
1689 contact: Contact,
1690 d: Vec3Fix,
1691 max_t: Fix128,
1692 gap_at: impl Fn(Fix128) -> Option<Fix128>,
1693) -> bool {
1694 let slope = d.dot(contact.normal);
1695 if slope < -NEAR_TANGENT {
1696 return true;
1697 }
1698 if !slope.is_negative() {
1699 return false;
1700 }
1701 match gap_at(contact.t) {
1702 None => true,
1703 Some(gap) => dips_below(gap_at, contact.t, gap, -slope, max_t),
1704 }
1705}
1706
1707fn dips_below(
1713 gap_at: impl Fn(Fix128) -> Option<Fix128>,
1714 t0: Fix128,
1715 f0: Fix128,
1716 speed: Fix128,
1717 max_t: Fix128,
1718) -> bool {
1719 let deep = |g: Option<Fix128>| g.is_none_or(|g| g < -TRACE_TOLERANCE);
1720 let big = max_t + Fix128::ONE;
1721 let mut step = max_fix(
1722 ratio_within(max_fix(f0, Fix128::ZERO), speed, big),
1723 TRACE_TOLERANCE,
1724 );
1725 let (mut before, mut last, mut last_gap) = (t0, t0, f0);
1726 for _ in 0..GOLDEN_STEPS {
1727 let t1 = min_fix(t0 + step, max_t);
1728 let g1 = gap_at(t1);
1729 if deep(g1) {
1730 return true;
1731 }
1732 let g1 = g1.unwrap_or(Fix128::ZERO);
1733 if g1 > last_gap || t1 >= max_t {
1734 let (mut lo, mut hi) = (before, t1);
1736 let ratio = Fix128::from_ratio(618_033_988_749_895, 1_000_000_000_000_000);
1737 for _ in 0..GOLDEN_STEPS {
1738 let x1 = hi - (hi - lo) * ratio;
1739 let x2 = lo + (hi - lo) * ratio;
1740 let (g1, g2) = (gap_at(x1), gap_at(x2));
1741 if deep(g1) || deep(g2) {
1742 return true;
1743 }
1744 if g1 <= g2 {
1745 hi = x2;
1746 } else {
1747 lo = x1;
1748 }
1749 }
1750 return false;
1751 }
1752 before = last;
1753 last = t1;
1754 last_gap = g1;
1755 step = step.double();
1756 }
1757 false
1758}
1759
1760fn toi_tree<N: Copy>(
1769 roots: &[N],
1770 toi: impl Fn(&N) -> Option<Contact>,
1771 leaf: impl Fn(&N) -> bool,
1772 split: impl Fn(&N) -> Vec<N>,
1773) -> Option<Contact> {
1774 let mut open: Vec<(Contact, N)> = roots
1775 .iter()
1776 .filter_map(|n| toi(n).map(|c| (c, *n)))
1777 .collect();
1778 let mut nodes = 0usize;
1779 loop {
1780 let k = (0..open.len()).min_by_key(|&k| open[k].0.t)?;
1781 let (contact, node) = open.remove(k);
1782 if leaf(&node) {
1783 return Some(contact);
1784 }
1785 nodes += 1;
1786 if nodes > TREE_MAX_NODES {
1787 return Some(contact);
1788 }
1789 for child in split(&node) {
1790 if let Some(c) = toi(&child) {
1791 open.push((c, child));
1792 }
1793 }
1794 }
1795}
1796
1797fn heightfield_meets_aabb(field: &HeightField, aabb: &AABB) -> bool {
1801 if !field_has_surface(field) {
1802 return false;
1803 }
1804 let s = field.spacing;
1805 let x_end = field.origin.x + s * Fix128::from_int(i64::from(field.width) - 1);
1806 let z_end = field.origin.z + s * Fix128::from_int(i64::from(field.depth) - 1);
1807 let fx0 = max_fix(aabb.min.x, field.origin.x);
1808 let fx1 = min_fix(aabb.max.x, x_end);
1809 let fz0 = max_fix(aabb.min.z, field.origin.z);
1810 let fz1 = min_fix(aabb.max.z, z_end);
1811 if fx0 > fx1 || fz0 > fz1 {
1812 return false;
1813 }
1814 let (Some((gx0, gx1)), Some((gz0, gz1))) = (
1815 cell_range(fx0, fx1, field.origin.x, s, field.width),
1816 cell_range(fz0, fz1, field.origin.z, s, field.depth),
1817 ) else {
1818 return false;
1819 };
1820 for gz in gz0..=gz1 {
1821 for gx in gx0..=gx1 {
1822 let x0 = field.origin.x + s * Fix128::from_int(i64::from(gx));
1823 let z0 = field.origin.z + s * Fix128::from_int(i64::from(gz));
1824 let ux0 = max_fix(fx0, x0);
1825 let ux1 = min_fix(fx1, x0 + s);
1826 let uz0 = max_fix(fz0, z0);
1827 let uz1 = min_fix(fz1, z0 + s);
1828 if ux0 > ux1 || uz0 > uz1 {
1829 continue;
1830 }
1831 let [h00, h10, h01, h11] = cell_heights(field, gx, gz);
1832 let mut lo: Option<Fix128> = None;
1833 let mut hi: Option<Fix128> = None;
1834 for x in [ux0, ux1] {
1835 for z in [uz0, uz1] {
1836 let u = (x - x0) / s;
1837 let v = (z - z0) / s;
1838 let one = Fix128::ONE;
1839 let h = h00 * (one - u) * (one - v)
1840 + h10 * u * (one - v)
1841 + h01 * (one - u) * v
1842 + h11 * u * v;
1843 lo = Some(lo.map_or(h, |l| min_fix(l, h)));
1844 hi = Some(hi.map_or(h, |m| max_fix(m, h)));
1845 }
1846 }
1847 if let (Some(lo), Some(hi)) = (lo, hi) {
1848 if lo < aabb.max.y && hi > aabb.min.y {
1849 return true;
1850 }
1851 }
1852 }
1853 }
1854 false
1855}
1856
1857fn mesh_candidates(mesh: &TriMesh, query: &AABB) -> Vec<u32> {
1863 mesh.bvh.query(query)
1864}
1865
1866fn mesh_dist(mesh: &TriMesh, a: Vec3Fix, b: Vec3Fix, cap: Fix128) -> Dist {
1869 if mesh.triangles.is_empty() {
1870 return Dist::AtLeast(cap);
1871 }
1872 let core = core_box(a, b, Fix128::ZERO);
1873 let far = aabb_gap(&core, &mesh.bounds);
1874 if far >= cap {
1875 return Dist::AtLeast(far);
1876 }
1877 let mut best: Option<Dist> = None;
1878 for i in mesh_candidates(mesh, &core_box(a, b, cap)) {
1879 let tri = &mesh.triangles[i as usize];
1880 let d = if a == b {
1881 match Dist::toward(a, tri.closest_point(a)) {
1882 Dist::Inside => Dist::Outside {
1884 dist: Fix128::ZERO,
1885 point: a,
1886 normal: tri.normal().try_normalize().unwrap_or(Vec3Fix::UNIT_Y),
1887 },
1888 other => other,
1889 }
1890 } else {
1891 convex_dist(a, b, &TriangleSupport(tri))
1892 };
1893 if matches!(d, Dist::Inside) {
1894 return Dist::Inside;
1895 }
1896 if best.as_ref().is_none_or(|x| d.value() < x.value()) {
1897 best = Some(d);
1898 }
1899 }
1900 match best {
1901 Some(d) if d.value() < cap => d,
1902 _ => Dist::AtLeast(cap),
1903 }
1904}
1905
1906fn sweep_triangle(
1909 o: Vec3Fix,
1910 d: Vec3Fix,
1911 tri: &Triangle,
1912 r: Fix128,
1913 max_t: Fix128,
1914) -> Option<LocalHit> {
1915 let ray = crate::raycast::Ray {
1916 origin: o,
1917 direction: d,
1918 };
1919 let mut best = None;
1920 if let Some(n) = tri.normal().try_normalize() {
1921 for off in [n * r, -(n * r)] {
1922 let moved = Triangle::new(tri.v0 + off, tri.v1 + off, tri.v2 + off);
1923 best = nearer(
1924 best,
1925 ray_triangle(&ray, &moved, max_t).map(|h| LocalHit {
1926 t: h.t,
1927 normal: h.normal,
1928 }),
1929 );
1930 }
1931 }
1932 for (e0, e1) in [(tri.v0, tri.v1), (tri.v1, tri.v2), (tri.v2, tri.v0)] {
1933 best = nearer(
1934 best,
1935 ray_solid_capsule(o, d, &Capsule::new(e0, e1, r), max_t),
1936 );
1937 }
1938 best
1939}
1940
1941enum Piece<'a> {
1947 Sphere {
1948 center: Vec3Fix,
1949 radius: Fix128,
1950 },
1951 Capsule(Capsule),
1952 Box {
1953 center: Vec3Fix,
1954 half: Vec3Fix,
1955 rotation: QuatFix,
1956 },
1957 Posed(PosedShape),
1958 Hull(ChildSupport<'a>),
1959 Plane(&'a PlaneCollider),
1960 Height(&'a HeightField),
1961 Mesh(&'a TriMesh),
1962 #[cfg(feature = "std")]
1963 Sdf(&'a crate::sdf_collider::SdfCollider),
1964}
1965
1966fn body_pieces(world: &PhysicsWorld, i: usize) -> Vec<Piece<'_>> {
1968 let Some((radius, collider)) = world.ray_geometry(i) else {
1969 return Vec::new();
1970 };
1971 let body = &world.bodies[i];
1972 match collider {
1973 None => vec![Piece::Sphere {
1974 center: body.position,
1975 radius,
1976 }],
1977 Some(BodyCollider::Shape(shape)) => {
1978 let posed = PosedShape {
1979 shape: *shape,
1980 position: body.position,
1981 rotation: body.rotation,
1982 };
1983 vec![match *shape {
1984 Shape::Box { half_extents } => {
1985 let rotation = body.rotation.unit_rotation();
1986 Piece::Box {
1987 center: body.position - rotation.rotate_vec(shape.center_of_mass_offset()),
1988 half: half_extents,
1989 rotation,
1990 }
1991 }
1992 _ => Piece::Posed(posed),
1993 }]
1994 }
1995 Some(BodyCollider::Compound(compound)) => compound
1996 .children
1997 .iter()
1998 .map(|child| child_piece(child, body.position, body.rotation))
1999 .collect(),
2000 }
2001}
2002
2003fn child_piece(child: &CompoundChild, body_pos: Vec3Fix, body_rot: QuatFix) -> Piece<'_> {
2005 let body_rot = body_rot.unit_rotation();
2006 let child_rot = body_rot.mul(child.local_rotation);
2007 let child_pos = body_pos + body_rot.rotate_vec(child.local_position);
2008 match &child.shape {
2009 ShapeRef::Sphere(s) => Piece::Sphere {
2010 center: child_pos + child_rot.rotate_vec(s.center),
2011 radius: s.radius,
2012 },
2013 ShapeRef::Capsule(c) => Piece::Capsule(Capsule::new(
2014 child_pos + child_rot.rotate_vec(c.a),
2015 child_pos + child_rot.rotate_vec(c.b),
2016 c.radius,
2017 )),
2018 ShapeRef::Box(b) => Piece::Box {
2019 center: child_pos + child_rot.rotate_vec(b.center),
2020 half: b.half_extents,
2021 rotation: child_rot.mul(b.rotation),
2022 },
2023 ShapeRef::ConvexHull(_) => Piece::Hull(ChildSupport {
2024 child,
2025 position: body_pos,
2026 rotation: body_rot,
2027 }),
2028 }
2029}
2030
2031fn posed_frame(posed: &PosedShape) -> (Vec3Fix, QuatFix) {
2033 let rotation = posed.rotation.unit_rotation();
2034 (
2035 posed.position - rotation.rotate_vec(posed.shape.center_of_mass_offset()),
2036 rotation,
2037 )
2038}
2039
2040struct Settings {
2042 #[cfg(feature = "std")]
2043 sdf: SdfCcdConfig,
2044}
2045
2046impl Piece<'_> {
2047 fn dist(&self, a: Vec3Fix, b: Vec3Fix, cap: Fix128) -> Dist {
2050 match self {
2051 Self::Sphere { center, radius } => sphere_dist(a, b, *center, *radius),
2052 Self::Capsule(c) => capsule_dist(a, b, c),
2053 Self::Box {
2054 center,
2055 half,
2056 rotation,
2057 } => {
2058 if a == b {
2059 let inv = rotation.conjugate();
2060 let p = inv.rotate_vec(a - *center);
2061 dist_to_world(point_box_local(p, *half), *center, *rotation)
2062 } else {
2063 let obb = crate::box_collider::OrientedBox::new(*center, *half, *rotation);
2064 convex_dist(a, b, &obb)
2065 }
2066 }
2067 Self::Posed(posed) => {
2068 let (center, rotation) = posed_frame(posed);
2069 let inv = rotation.conjugate();
2070 match posed.shape {
2071 Shape::Cylinder {
2072 radius,
2073 half_height,
2074 } if a == b => dist_to_world(
2075 point_cylinder_local(inv.rotate_vec(a - center), radius, half_height),
2076 center,
2077 rotation,
2078 ),
2079 Shape::Torus {
2080 major_radius,
2081 minor_radius,
2082 } => {
2083 let f = |p: Vec3Fix| {
2084 dist_to_world(
2085 point_torus_local(
2086 inv.rotate_vec(p - center),
2087 major_radius,
2088 minor_radius,
2089 ),
2090 center,
2091 rotation,
2092 )
2093 };
2094 segment_min(a, b, f)
2095 }
2096 _ => convex_dist(a, b, posed),
2097 }
2098 }
2099 Self::Hull(child) => convex_dist(a, b, child),
2100 Self::Plane(plane) => plane_dist(plane, a, b),
2101 Self::Height(field) => heightfield_dist(field, a, b, cap),
2102 Self::Mesh(mesh) => mesh_dist(mesh, a, b, cap),
2103 #[cfg(feature = "std")]
2104 Self::Sdf(sdf) => segment_min(a, b, |p| point_sdf(sdf, p)),
2105 }
2106 }
2107
2108 fn sweep(
2116 &self,
2117 a: Vec3Fix,
2118 b: Vec3Fix,
2119 r: Fix128,
2120 d: Vec3Fix,
2121 max_t: Fix128,
2122 settings: &Settings,
2123 ) -> Option<Contact> {
2124 #[cfg(not(feature = "std"))]
2125 let _ = settings;
2126 let point_core = a == b;
2127 match self {
2128 Self::Sphere { center, radius } => {
2129 let c = if point_core {
2130 let h = ray_solid_sphere(a, d, *center, *radius + r, max_t)?;
2131 Contact::from_ray(a, d, r, h)
2132 } else {
2133 let grown = Capsule::new(a, b, r + *radius);
2136 let h = ray_solid_capsule(*center, -d, &grown, max_t)?;
2137 let normal = -h.normal;
2138 Contact {
2139 t: h.t,
2140 point: *center + normal * *radius,
2141 normal,
2142 }
2143 };
2144 self.entering(c, a, b, r, d, max_t)
2145 }
2146 Self::Capsule(c) => {
2147 if point_core {
2148 let grown = Capsule::new(c.a, c.b, c.radius + r);
2149 let h = ray_solid_capsule(a, d, &grown, max_t)?;
2150 self.entering(Contact::from_ray(a, d, r, h), a, b, r, d, max_t)
2151 } else {
2152 toi_convex(a, b, r, c.radius, d, max_t, &SegmentSupport(c.a, c.b))
2153 }
2154 }
2155 Self::Box {
2156 center,
2157 half,
2158 rotation,
2159 } => {
2160 if point_core {
2161 let (o, dl) = to_local(a, d, *center, *rotation);
2162 let h = to_world(sweep_local_box(o, dl, *half, r, max_t), *rotation)?;
2163 self.entering(Contact::from_ray(a, d, r, h), a, b, r, d, max_t)
2164 } else {
2165 let obb = crate::box_collider::OrientedBox::new(*center, *half, *rotation);
2166 toi_convex(a, b, r, Fix128::ZERO, d, max_t, &obb)
2167 }
2168 }
2169 Self::Posed(posed) => {
2170 let (center, rotation) = posed_frame(posed);
2171 match posed.shape {
2172 Shape::Cylinder {
2173 radius,
2174 half_height,
2175 } if point_core => {
2176 let (o, dl) = to_local(a, d, center, rotation);
2177 let h = to_world(
2178 Some(sweep_local_cylinder(o, dl, radius, half_height, r, max_t)?),
2179 rotation,
2180 )?;
2181 self.entering(Contact::from_ray(a, d, r, h), a, b, r, d, max_t)
2182 }
2183 Shape::Torus {
2184 major_radius,
2185 minor_radius,
2186 } => {
2187 let ring = Ring {
2188 center,
2189 rotation,
2190 major: major_radius,
2191 minor: minor_radius,
2192 };
2193 if point_core {
2194 let (o, dl) = to_local(a, d, center, rotation);
2195 let h = ray_local_torus(o, dl, major_radius, minor_radius + r, max_t);
2196 let c = Contact::from_ray(a, d, r, to_world(h, rotation)?);
2197 self.entering(c, a, b, r, d, max_t)
2200 .or_else(|| ring.sweep(a, b, r, d, max_t))
2201 } else {
2202 ring.sweep(a, b, r, d, max_t)
2203 }
2204 }
2205 _ => toi_convex(a, b, r, Fix128::ZERO, d, max_t, posed),
2206 }
2207 }
2208 Self::Hull(child) => toi_convex(a, b, r, Fix128::ZERO, d, max_t, child),
2209 Self::Plane(plane) => sweep_plane(plane, a, b, r, d, max_t),
2210 Self::Height(field) => sweep_heightfield(field, a, b, r, d, max_t),
2211 Self::Mesh(mesh) => {
2212 if point_core {
2213 let swept = core_box(a, a + d * max_t, r);
2214 let mut best: Option<Contact> = None;
2215 for i in mesh_candidates(mesh, &swept) {
2216 let tri = &mesh.triangles[i as usize];
2217 let Some(h) = sweep_triangle(a, d, tri, r, max_t) else {
2218 continue;
2219 };
2220 let gap_at = |t: Fix128| {
2221 let p = a + d * t;
2222 match Dist::toward(p, tri.closest_point(p)) {
2223 Dist::Outside { dist, .. } => Some(dist - r),
2224 _ => None,
2225 }
2226 };
2227 let mut c = Contact::from_ray(a, d, r, h);
2228 if c.t <= TRACE_TOLERANCE {
2229 if let Dist::Outside { point, normal, .. } =
2232 Dist::toward(a, tri.closest_point(a))
2233 {
2234 c = Contact {
2235 t: Fix128::ZERO,
2236 point,
2237 normal,
2238 };
2239 }
2240 }
2241 if !touch_is_hit(c, d, max_t, gap_at) {
2242 continue;
2243 }
2244 if best.is_none_or(|bc| c.t < bc.t) {
2245 best = Some(c);
2246 }
2247 }
2248 best
2249 } else {
2250 sweep_mesh(mesh, a, b, r, d, max_t)
2251 }
2252 }
2253 #[cfg(feature = "std")]
2254 Self::Sdf(_) => {
2255 let tol = Fix128::from_f32(settings.sdf.tolerance);
2256 self.trace(a, b, r, d, max_t, tol)
2257 }
2258 }
2259 .filter(|c| c.t >= Fix128::ZERO && c.t <= max_t)
2260 }
2261
2262 fn entering(
2269 &self,
2270 c: Contact,
2271 a: Vec3Fix,
2272 b: Vec3Fix,
2273 r: Fix128,
2274 d: Vec3Fix,
2275 max_t: Fix128,
2276 ) -> Option<Contact> {
2277 let cap = r + r + Fix128::ONE;
2278 let gap_at = |t: Fix128| {
2279 let off = d * t;
2280 match self.dist(a + off, b + off, cap) {
2281 Dist::Outside { dist, .. } => Some(dist - r),
2282 Dist::AtLeast(bound) => Some(bound - r),
2283 Dist::Inside => None,
2284 }
2285 };
2286 let c = if c.t > TRACE_TOLERANCE {
2287 c
2288 } else {
2289 match self.dist(a, b, cap) {
2290 Dist::Outside { point, normal, .. } => Contact {
2291 t: Fix128::ZERO,
2292 point,
2293 normal,
2294 },
2295 _ => return Some(c),
2296 }
2297 };
2298 touch_is_hit(c, d, max_t, gap_at).then_some(c)
2299 }
2300
2301 #[cfg(feature = "std")]
2306 fn trace(
2307 &self,
2308 a: Vec3Fix,
2309 b: Vec3Fix,
2310 r: Fix128,
2311 d: Vec3Fix,
2312 max_t: Fix128,
2313 tol: Fix128,
2314 ) -> Option<Contact> {
2315 let mut t = Fix128::ZERO;
2316 let cap = r + r + Fix128::ONE;
2317 let mut last: Option<Contact> = None;
2318 for _ in 0..SDF_MAX_STEPS {
2319 let off = d * t;
2320 let gap = match self.dist(a + off, b + off, cap) {
2321 Dist::Inside => {
2322 return Some(Contact {
2323 t,
2324 point: half_vec(a + b) + off,
2325 normal: -d,
2326 })
2327 }
2328 Dist::AtLeast(bound) => bound - r,
2329 Dist::Outside {
2330 dist,
2331 point,
2332 normal,
2333 } => {
2334 let gap = dist - r;
2335 let contact = Contact { t, point, normal };
2336 last = Some(contact);
2337 if gap <= tol {
2338 if d.dot(normal).is_negative() {
2341 return Some(contact);
2342 }
2343 tol
2344 } else {
2345 gap
2346 }
2347 }
2348 };
2349 if gap <= Fix128::ZERO {
2350 return None;
2351 }
2352 t = t + gap;
2353 if t > max_t {
2354 return None;
2355 }
2356 }
2357 last
2358 }
2359
2360 fn meets_aabb(&self, aabb: &AABB) -> bool {
2362 match self {
2363 Self::Sphere { center, radius } => {
2364 (closest_on_aabb(aabb, *center) - *center).length_squared() < *radius * *radius
2365 }
2366 Self::Capsule(c) => match gjk_distance(&SegmentSupport(c.a, c.b), aabb) {
2367 None => true,
2368 Some((dist, _, _)) => dist < c.radius,
2369 },
2370 Self::Box {
2371 center,
2372 half,
2373 rotation,
2374 } => obb_meets_aabb(*center, *half, *rotation, aabb),
2375 Self::Posed(posed) => match posed.shape {
2376 Shape::Torus {
2377 major_radius,
2378 minor_radius,
2379 } => torus_meets_aabb(posed, major_radius, minor_radius, aabb),
2380 _ => gjk_distance(posed, aabb).is_none(),
2381 },
2382 Self::Hull(child) => gjk_distance(child, aabb).is_none(),
2383 Self::Plane(plane) => {
2384 let c = half_vec(aabb.min + aabb.max);
2385 let h = half_vec(aabb.max - aabb.min);
2386 let n = plane.normal;
2387 let reach = n.x.abs() * h.x + n.y.abs() * h.y + n.z.abs() * h.z;
2388 (n.dot(c) - plane.offset).abs() < reach
2389 }
2390 Self::Height(field) => heightfield_meets_aabb(field, aabb),
2391 Self::Mesh(mesh) => mesh_candidates(mesh, aabb).into_iter().any(|i| {
2392 gjk_distance(&TriangleSupport(&mesh.triangles[i as usize]), aabb).is_none()
2393 }),
2394 #[cfg(feature = "std")]
2395 Self::Sdf(sdf) => sdf_meets_aabb(sdf, aabb),
2396 }
2397 }
2398}
2399
2400#[derive(Clone, Copy, Debug)]
2402struct Contact {
2403 t: Fix128,
2404 point: Vec3Fix,
2405 normal: Vec3Fix,
2406}
2407
2408impl Contact {
2409 fn from_ray(o: Vec3Fix, d: Vec3Fix, r: Fix128, h: LocalHit) -> Self {
2412 Self {
2413 t: h.t,
2414 point: o + d * h.t - h.normal * r,
2415 normal: h.normal,
2416 }
2417 }
2418}
2419
2420fn sweep_local_box(
2423 o: Vec3Fix,
2424 d: Vec3Fix,
2425 h: Vec3Fix,
2426 r: Fix128,
2427 max_t: Fix128,
2428) -> Option<LocalHit> {
2429 let mut best = None;
2430 for grow in [
2431 Vec3Fix::new(r, Fix128::ZERO, Fix128::ZERO),
2432 Vec3Fix::new(Fix128::ZERO, r, Fix128::ZERO),
2433 Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, r),
2434 ] {
2435 best = nearer(best, ray_planes(o, d, &box_planes(h + grow), max_t));
2436 }
2437 let signs = [Fix128::ONE, Fix128::NEG_ONE];
2438 for &s1 in &signs {
2439 for &s2 in &signs {
2440 for (a, b) in [
2441 (
2442 Vec3Fix::new(-h.x, s1 * h.y, s2 * h.z),
2443 Vec3Fix::new(h.x, s1 * h.y, s2 * h.z),
2444 ),
2445 (
2446 Vec3Fix::new(s1 * h.x, -h.y, s2 * h.z),
2447 Vec3Fix::new(s1 * h.x, h.y, s2 * h.z),
2448 ),
2449 (
2450 Vec3Fix::new(s1 * h.x, s2 * h.y, -h.z),
2451 Vec3Fix::new(s1 * h.x, s2 * h.y, h.z),
2452 ),
2453 ] {
2454 best = nearer(best, ray_solid_capsule(o, d, &Capsule::new(a, b, r), max_t));
2455 }
2456 }
2457 }
2458 best
2459}
2460
2461fn sweep_local_cylinder(
2464 o: Vec3Fix,
2465 d: Vec3Fix,
2466 radius: Fix128,
2467 hh: Fix128,
2468 r: Fix128,
2469 max_t: Fix128,
2470) -> Option<LocalHit> {
2471 let mut best = ray_local_cylinder(o, d, radius + r, hh, max_t);
2472 best = nearer(best, ray_local_cylinder(o, d, radius, hh + r, max_t));
2473 for y in [hh, -hh] {
2474 let shift = Vec3Fix::new(Fix128::ZERO, y, Fix128::ZERO);
2475 best = nearer(best, ray_local_torus(o - shift, d, radius, r, max_t));
2476 }
2477 best
2478}
2479
2480fn obb_meets_aabb(center: Vec3Fix, half: Vec3Fix, rotation: QuatFix, aabb: &AABB) -> bool {
2486 let z = Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE);
2487 let a_axes = [
2488 rotation.rotate_vec(Vec3Fix::UNIT_X),
2489 rotation.rotate_vec(Vec3Fix::UNIT_Y),
2490 rotation.rotate_vec(z),
2491 ];
2492 let a_half = [half.x, half.y, half.z];
2493 let b_axes = [Vec3Fix::UNIT_X, Vec3Fix::UNIT_Y, z];
2494 let b_center = half_vec(aabb.min + aabb.max);
2495 let b_ext = half_vec(aabb.max - aabb.min);
2496 let b_half = [b_ext.x, b_ext.y, b_ext.z];
2497 let between = b_center - center;
2498 let separates = |l: Vec3Fix| {
2499 let reach_a: Fix128 = (0..3).fold(Fix128::ZERO, |acc, i| {
2500 acc + a_half[i] * a_axes[i].dot(l).abs()
2501 });
2502 let reach_b: Fix128 = (0..3).fold(Fix128::ZERO, |acc, i| {
2503 acc + b_half[i] * b_axes[i].dot(l).abs()
2504 });
2505 between.dot(l).abs() >= reach_a + reach_b
2506 };
2507 let mut axes: Vec<Vec3Fix> = Vec::with_capacity(15);
2508 axes.extend_from_slice(&a_axes);
2509 axes.extend_from_slice(&b_axes);
2510 for ea in a_axes {
2511 for eb in b_axes {
2512 let l = ea.cross(eb);
2513 if !l.length_squared().is_zero() {
2514 axes.push(l);
2515 }
2516 }
2517 }
2518 !axes.into_iter().any(separates)
2519}
2520
2521fn plane_dist(plane: &PlaneCollider, a: Vec3Fix, b: Vec3Fix) -> Dist {
2523 let n = plane.normal;
2524 let sa = n.dot(a) - plane.offset;
2525 let sb = n.dot(b) - plane.offset;
2526 if !sa.is_zero() && !sb.is_zero() && sa.is_negative() != sb.is_negative() {
2527 return Dist::Inside;
2529 }
2530 let (p, s) = if sb.abs() < sa.abs() {
2531 (b, sb)
2532 } else {
2533 (a, sa)
2534 };
2535 let other = if sb.abs() < sa.abs() { sa } else { sb };
2537 let side = if s.is_negative() || (s.is_zero() && other.is_negative()) {
2538 -n
2539 } else {
2540 n
2541 };
2542 Dist::Outside {
2543 dist: s.abs(),
2544 point: p - n * s,
2545 normal: side,
2546 }
2547}
2548
2549fn sweep_plane(
2552 plane: &PlaneCollider,
2553 a: Vec3Fix,
2554 b: Vec3Fix,
2555 r: Fix128,
2556 d: Vec3Fix,
2557 max_t: Fix128,
2558) -> Option<Contact> {
2559 let n = plane.normal;
2560 let sa = n.dot(a) - plane.offset;
2561 let sb = n.dot(b) - plane.offset;
2562 if sa.is_zero() || sb.is_zero() || sa.is_negative() != sb.is_negative() {
2563 return None;
2564 }
2565 let (p, s) = if sb.abs() < sa.abs() {
2567 (b, sb)
2568 } else {
2569 (a, sa)
2570 };
2571 let normal = if s.is_negative() { -n } else { n };
2572 let speed = -normal.dot(d);
2573 if speed < PARALLEL_EPSILON {
2574 return None;
2575 }
2576 let t = max_fix((s.abs() - r) / speed, Fix128::ZERO);
2579 if t > max_t {
2580 return None;
2581 }
2582 Some(Contact {
2583 t,
2584 point: p + d * t - normal * r,
2585 normal,
2586 })
2587}
2588
2589fn torus_meets_aabb(posed: &PosedShape, major: Fix128, minor: Fix128, aabb: &AABB) -> bool {
2591 let (center, rotation) = posed_frame(posed);
2592 if (closest_on_aabb(aabb, center) - center).length() >= major + minor {
2594 return false;
2595 }
2596 let ring = |s: Fix128| {
2597 let (sin, cos) = (Fix128::TWO_PI * s).sin_cos();
2598 center + rotation.rotate_vec(Vec3Fix::new(major * cos, Fix128::ZERO, major * sin))
2599 };
2600 let d = curve_min(Fix128::TWO_PI * major, |s| {
2601 let p = ring(s);
2602 let q = closest_on_aabb(aabb, p);
2603 if q == p {
2604 Dist::Inside
2605 } else {
2606 Dist::toward(p, q)
2607 }
2608 });
2609 d.within(minor)
2610}
2611
2612#[cfg(feature = "std")]
2613fn point_sdf(sdf: &crate::sdf_collider::SdfCollider, p: Vec3Fix) -> Dist {
2614 let (lx, ly, lz) = sdf.world_to_local(p);
2615 let (d, (nx, ny, nz)) = sdf.field.distance_and_normal(lx, ly, lz);
2616 let d = d * sdf.scale_f32;
2617 if !d.is_finite() || d < 0.0 {
2618 return Dist::Inside;
2619 }
2620 let dist = Fix128::from_f32(d);
2621 let normal = sdf.local_normal_to_world(nx, ny, nz);
2622 Dist::Outside {
2623 dist,
2624 point: p - normal * dist,
2625 normal,
2626 }
2627}
2628
2629#[cfg(feature = "std")]
2634fn sdf_meets_aabb(sdf: &crate::sdf_collider::SdfCollider, aabb: &AABB) -> bool {
2635 let mut stack = vec![(aabb.min, aabb.max, 0u32)];
2636 while let Some((lo, hi, depth)) = stack.pop() {
2637 let c = half_vec(lo + hi);
2638 let half_diag = half_vec(hi - lo).length();
2639 let d = match point_sdf(sdf, c) {
2640 Dist::Inside => return true,
2641 Dist::Outside { dist, .. } => dist,
2642 Dist::AtLeast(d) => d,
2643 };
2644 if d >= half_diag {
2645 continue;
2646 }
2647 if depth >= SDF_BOX_DEPTH {
2648 return true;
2649 }
2650 for k in 0..8u8 {
2651 let pick = |bit: u8, l: Fix128, m: Fix128, h: Fix128| {
2652 if k & bit == 0 {
2653 (l, m)
2654 } else {
2655 (m, h)
2656 }
2657 };
2658 let (x0, x1) = pick(1, lo.x, c.x, hi.x);
2659 let (y0, y1) = pick(2, lo.y, c.y, hi.y);
2660 let (z0, z1) = pick(4, lo.z, c.z, hi.z);
2661 stack.push((
2662 Vec3Fix::new(x0, y0, z0),
2663 Vec3Fix::new(x1, y1, z1),
2664 depth + 1,
2665 ));
2666 }
2667 }
2668 false
2669}
2670
2671fn core_box(a: Vec3Fix, b: Vec3Fix, r: Fix128) -> AABB {
2673 let lo = Vec3Fix::new(min_fix(a.x, b.x), min_fix(a.y, b.y), min_fix(a.z, b.z));
2674 let hi = Vec3Fix::new(max_fix(a.x, b.x), max_fix(a.y, b.y), max_fix(a.z, b.z));
2675 let g = Vec3Fix::new(r, r, r);
2676 AABB::new(lo - g, hi + g)
2677}
2678
2679fn aabb_gap(a: &AABB, b: &AABB) -> Fix128 {
2681 let gap = |lo1: Fix128, hi1: Fix128, lo2: Fix128, hi2: Fix128| {
2682 max_fix(Fix128::ZERO, max_fix(lo2 - hi1, lo1 - hi2))
2683 };
2684 Vec3Fix::new(
2685 gap(a.min.x, a.max.x, b.min.x, b.max.x),
2686 gap(a.min.y, a.max.y, b.min.y, b.max.y),
2687 gap(a.min.z, a.max.z, b.min.z, b.max.z),
2688 )
2689 .length()
2690}
2691
2692fn tame_direction(direction: Vec3Fix) -> Vec3Fix {
2696 let m = max_fix(
2697 max_fix(direction.x.abs(), direction.y.abs()),
2698 direction.z.abs(),
2699 );
2700 if m > Fix128::from_int(1 << 20) {
2701 direction / m
2702 } else {
2703 direction
2704 }
2705}
2706
2707fn box_is_valid(aabb: &AABB) -> bool {
2708 aabb.min.x <= aabb.max.x && aabb.min.y <= aabb.max.y && aabb.min.z <= aabb.max.z
2709}
2710
2711fn for_each_target<'w>(
2718 world: &'w PhysicsWorld,
2719 filter: &RayFilter,
2720 query: &AABB,
2721 mut visit: impl FnMut(RayTarget, Option<usize>, &[Piece<'w>]),
2722) {
2723 let (bvh, _) = world.query_body_bvh(filter);
2724 let mut bodies: Vec<u32> = bvh.query(query);
2725 bodies.sort_unstable();
2726 bodies.dedup();
2727 for i in bodies {
2728 let i = i as usize;
2729 let pieces = body_pieces(world, i);
2730 visit(RayTarget::Body(i), Some(i), &pieces);
2731 }
2732 if filter.include_static {
2733 for (j, collider) in world.static_colliders_slice().iter().enumerate() {
2734 let piece = match collider {
2735 StaticCollider::Plane(p) => Piece::Plane(p),
2736 StaticCollider::HeightField(f) => Piece::Height(f),
2737 StaticCollider::TriMesh(m) => Piece::Mesh(m),
2738 };
2739 visit(
2740 RayTarget::StaticCollider(j),
2741 None,
2742 core::slice::from_ref(&piece),
2743 );
2744 }
2745 }
2746 #[cfg(feature = "std")]
2747 if filter.include_sdf {
2748 for (k, sdf) in world.sdf_colliders.iter().enumerate() {
2749 let body = (sdf.body_index < world.bodies.len()).then_some(sdf.body_index);
2750 if body.is_some_and(|b| !filter.sees_body(world, b)) {
2751 continue;
2752 }
2753 let piece = Piece::Sdf(sdf);
2754 visit(RayTarget::Sdf(k), body, core::slice::from_ref(&piece));
2755 }
2756 }
2757}
2758
2759impl PhysicsWorld {
2760 #[must_use]
2766 pub fn cast_sphere(
2767 &self,
2768 center: Vec3Fix,
2769 radius: Fix128,
2770 direction: Vec3Fix,
2771 max_t: Fix128,
2772 filter: &RayFilter,
2773 ) -> Option<WorldShapeHit> {
2774 if radius.is_negative() {
2775 return None;
2776 }
2777 let direction = tame_direction(direction);
2778 if radius.is_zero() {
2779 return self
2780 .cast_ray(center, direction, max_t, filter)
2781 .map(|h| WorldShapeHit {
2782 t: h.t,
2783 point: h.point,
2784 normal: h.normal,
2785 target: h.target,
2786 body: h.body,
2787 });
2788 }
2789 self.cast_core(center, center, radius, direction, max_t, filter)
2790 }
2791
2792 #[must_use]
2797 pub fn cast_capsule(
2798 &self,
2799 a: Vec3Fix,
2800 b: Vec3Fix,
2801 radius: Fix128,
2802 direction: Vec3Fix,
2803 max_t: Fix128,
2804 filter: &RayFilter,
2805 ) -> Option<WorldShapeHit> {
2806 if a == b {
2807 return self.cast_sphere(a, radius, direction, max_t, filter);
2808 }
2809 if radius.is_negative() {
2810 return None;
2811 }
2812 self.cast_core(a, b, radius, tame_direction(direction), max_t, filter)
2813 }
2814
2815 #[must_use]
2819 pub fn overlap_sphere(
2820 &self,
2821 center: Vec3Fix,
2822 radius: Fix128,
2823 filter: &RayFilter,
2824 ) -> Vec<RayTarget> {
2825 let mut out = Vec::new();
2826 if radius.is_negative() {
2827 return out;
2828 }
2829 let query = core_box(center, center, radius);
2830 let cap = radius + radius + Fix128::ONE;
2831 for_each_target(self, filter, &query, |target, _, pieces| {
2832 if pieces
2833 .iter()
2834 .any(|p| p.dist(center, center, cap).within(radius))
2835 {
2836 out.push(target);
2837 }
2838 });
2839 out.sort_unstable();
2840 out
2841 }
2842
2843 #[must_use]
2847 pub fn overlap_aabb(&self, aabb: &AABB, filter: &RayFilter) -> Vec<RayTarget> {
2848 let mut out = Vec::new();
2849 if !box_is_valid(aabb) {
2850 return out;
2851 }
2852 for_each_target(self, filter, aabb, |target, _, pieces| {
2853 if pieces.iter().any(|p| p.meets_aabb(aabb)) {
2854 out.push(target);
2855 }
2856 });
2857 out.sort_unstable();
2858 out
2859 }
2860
2861 pub(crate) fn capsule_penetrations(
2867 &self,
2868 a: Vec3Fix,
2869 b: Vec3Fix,
2870 r: Fix128,
2871 filter: &RayFilter,
2872 ) -> Option<Vec<Penetration>> {
2873 let query = core_box(a, b, r);
2874 let cap = r + r + Fix128::ONE;
2875 let mut out = Vec::new();
2876 let mut core_inside = false;
2877 for_each_target(self, filter, &query, |target, _, pieces| {
2878 for piece in pieces {
2879 match piece.dist(a, b, cap) {
2880 Dist::Inside => core_inside = true,
2881 Dist::Outside { dist, normal, .. } if dist + TRACE_TOLERANCE < r => {
2882 out.push(Penetration {
2883 target,
2884 depth: r - dist,
2885 normal,
2886 })
2887 }
2888 _ => {}
2889 }
2890 }
2891 });
2892 if core_inside {
2893 return None;
2894 }
2895 out.sort_by_key(|x| x.target);
2896 Some(out)
2897 }
2898
2899 fn cast_core(
2906 &self,
2907 a: Vec3Fix,
2908 b: Vec3Fix,
2909 r: Fix128,
2910 direction: Vec3Fix,
2911 max_t: Fix128,
2912 filter: &RayFilter,
2913 ) -> Option<WorldShapeHit> {
2914 if max_t <= Fix128::ZERO {
2915 return None;
2916 }
2917 let d = direction.try_normalize()?;
2918 let settings = Settings {
2919 #[cfg(feature = "std")]
2920 sdf: filter.sdf,
2921 };
2922 let swept = {
2923 let start = core_box(a, b, r);
2924 let end = core_box(a + d * max_t, b + d * max_t, r);
2925 AABB::new(
2926 Vec3Fix::new(
2927 min_fix(start.min.x, end.min.x),
2928 min_fix(start.min.y, end.min.y),
2929 min_fix(start.min.z, end.min.z),
2930 ),
2931 Vec3Fix::new(
2932 max_fix(start.max.x, end.max.x),
2933 max_fix(start.max.y, end.max.y),
2934 max_fix(start.max.z, end.max.z),
2935 ),
2936 )
2937 };
2938 let start_cap = r + r + Fix128::ONE;
2939 let centre = half_vec(a + b);
2940 let mut best: Option<WorldShapeHit> = None;
2941 for_each_target(self, filter, &swept, |target, body, pieces| {
2942 let mut nearest: Option<Contact> = None;
2943 for piece in pieces {
2944 let c = if starts_overlapping(&piece.dist(a, b, start_cap), r) {
2945 Some(Contact {
2946 t: Fix128::ZERO,
2947 point: centre,
2948 normal: -d,
2949 })
2950 } else {
2951 piece.sweep(a, b, r, d, max_t, &settings)
2952 };
2953 if let Some(c) = c {
2954 if nearest.is_none_or(|n| c.t < n.t) {
2955 nearest = Some(c);
2956 }
2957 }
2958 }
2959 if let Some(c) = nearest {
2960 let hit = WorldShapeHit {
2961 t: c.t,
2962 point: c.point,
2963 normal: c.normal,
2964 target,
2965 body,
2966 };
2967 if best.is_none_or(|b| (hit.t, hit.target) < (b.t, b.target)) {
2968 best = Some(hit);
2969 }
2970 }
2971 });
2972 best
2973 }
2974}
2975
2976#[cfg(test)]
2977mod tests {
2978 use super::*;
2990 use crate::box_collider::OrientedBox;
2991 use crate::collider::{ConvexHull, Sphere};
2992 use crate::compound::CompoundShape;
2993 use crate::solver::{PhysicsConfig, RigidBody};
2994
2995 const EXACT: f64 = 1e-12;
2996 const ITER: f64 = 1e-8;
2997
2998 fn fx(v: f64) -> Fix128 {
2999 Fix128::from_f64(v)
3000 }
3001
3002 fn v3(x: f64, y: f64, z: f64) -> Vec3Fix {
3003 Vec3Fix::new(fx(x), fx(y), fx(z))
3004 }
3005
3006 fn p3(p: [f64; 3]) -> Vec3Fix {
3007 v3(p[0], p[1], p[2])
3008 }
3009
3010 fn world() -> PhysicsWorld {
3011 PhysicsWorld::new(PhysicsConfig::default())
3012 }
3013
3014 fn all() -> RayFilter {
3015 RayFilter::default()
3016 }
3017
3018 fn sphere_cast(
3019 w: &PhysicsWorld,
3020 c: [f64; 3],
3021 r: f64,
3022 d: [f64; 3],
3023 max: f64,
3024 ) -> Option<WorldShapeHit> {
3025 w.cast_sphere(p3(c), fx(r), p3(d), fx(max), &all())
3026 }
3027
3028 fn capsule_cast(
3029 w: &PhysicsWorld,
3030 a: [f64; 3],
3031 b: [f64; 3],
3032 r: f64,
3033 d: [f64; 3],
3034 max: f64,
3035 ) -> Option<WorldShapeHit> {
3036 w.cast_capsule(p3(a), p3(b), fx(r), p3(d), fx(max), &all())
3037 }
3038
3039 fn overlap_s(w: &PhysicsWorld, c: [f64; 3], r: f64) -> Vec<RayTarget> {
3040 w.overlap_sphere(p3(c), fx(r), &all())
3041 }
3042
3043 fn overlap_b(w: &PhysicsWorld, lo: [f64; 3], hi: [f64; 3]) -> Vec<RayTarget> {
3044 w.overlap_aabb(&AABB::new(p3(lo), p3(hi)), &all())
3045 }
3046
3047 fn unit(v: [f64; 3]) -> [f64; 3] {
3048 let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
3049 [v[0] / l, v[1] / l, v[2] / l]
3050 }
3051
3052 #[track_caller]
3053 fn assert_vec(got: Vec3Fix, want: [f64; 3], tol: f64, what: &str) {
3054 let g = [got.x.to_f64(), got.y.to_f64(), got.z.to_f64()];
3055 for k in 0..3 {
3056 assert!(
3057 (g[k] - want[k]).abs() < tol,
3058 "{what} {g:?} but the closed form is {want:?}"
3059 );
3060 }
3061 }
3062
3063 #[track_caller]
3065 fn assert_hit(
3066 hit: Option<WorldShapeHit>,
3067 target: RayTarget,
3068 t: f64,
3069 normal: [f64; 3],
3070 point: [f64; 3],
3071 tol: f64,
3072 ) {
3073 let h = hit.unwrap_or_else(|| panic!("expected a hit at t = {t}, got none"));
3074 assert_eq!(h.target, target, "target");
3075 assert!(
3076 (h.t.to_f64() - t).abs() < tol,
3077 "t = {} but the closed form is {t}",
3078 h.t.to_f64()
3079 );
3080 assert_vec(h.normal, unit(normal), tol, "normal");
3081 assert_vec(h.point, point, tol, "point");
3082 }
3083
3084 #[track_caller]
3088 fn assert_face_hit(
3089 hit: Option<WorldShapeHit>,
3090 target: RayTarget,
3091 t: f64,
3092 y: f64,
3093 x0: f64,
3094 x1: f64,
3095 ) {
3096 let h = hit.unwrap_or_else(|| panic!("expected a hit at t = {t}, got none"));
3097 assert_eq!(h.target, target, "target");
3098 assert!((h.t.to_f64() - t).abs() < ITER, "t = {}", h.t.to_f64());
3099 assert_vec(h.normal, [0.0, 1.0, 0.0], ITER, "normal");
3100 assert!(
3101 (h.point.y.to_f64() - y).abs() < ITER,
3102 "y = {}",
3103 h.point.y.to_f64()
3104 );
3105 let x = h.point.x.to_f64();
3106 assert!(x > x0 - ITER && x < x1 + ITER, "x = {x}");
3107 }
3108
3109 fn shaped(w: &mut PhysicsWorld, shape: Shape, pos: Vec3Fix) -> usize {
3110 w.add_shaped_body(&shape, Fix128::ONE, pos)
3111 .expect("valid shape")
3112 }
3113
3114 fn unit_box(w: &mut PhysicsWorld) -> usize {
3115 shaped(
3116 w,
3117 Shape::Box {
3118 half_extents: v3(1.0, 1.0, 1.0),
3119 },
3120 Vec3Fix::ZERO,
3121 )
3122 }
3123
3124 fn sphere_body(w: &mut PhysicsWorld, pos: Vec3Fix, r: f64) -> usize {
3125 w.add_body_with_radius(RigidBody::new_static(pos), fx(r))
3126 }
3127
3128 fn plane(w: &mut PhysicsWorld, normal: Vec3Fix, offset: f64) -> usize {
3129 w.add_static_collider(StaticCollider::Plane(PlaneCollider::new(
3130 normal,
3131 fx(offset),
3132 )))
3133 }
3134
3135 fn floor_mesh(w: &mut PhysicsWorld) -> usize {
3137 let verts = [
3138 v3(0.0, 0.0, 0.0),
3139 v3(4.0, 0.0, 0.0),
3140 v3(4.0, 0.0, 4.0),
3141 v3(0.0, 0.0, 4.0),
3142 ];
3143 w.add_static_collider(StaticCollider::TriMesh(TriMesh::from_indexed(
3144 &verts,
3145 &[0, 1, 2, 0, 2, 3],
3146 )))
3147 }
3148
3149 fn saddle_field(w: &mut PhysicsWorld) -> usize {
3152 let heights = vec![Fix128::ZERO, Fix128::ZERO, Fix128::ZERO, Fix128::ONE];
3153 w.add_static_collider(StaticCollider::HeightField(HeightField::new(
3154 heights,
3155 2,
3156 2,
3157 Fix128::ONE,
3158 Vec3Fix::ZERO,
3159 )))
3160 }
3161
3162 #[test]
3165 fn sphere_cast_against_a_sphere_body() {
3166 let mut w = world();
3167 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3168 assert_hit(
3171 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [2.0, 0.0, 0.0], 100.0),
3172 RayTarget::Body(b),
3173 8.5,
3174 [-1.0, 0.0, 0.0],
3175 [-1.0, 0.0, 0.0],
3176 EXACT,
3177 );
3178 let s = 1.89f64.sqrt();
3181 let n = [-s / 1.5, 0.6 / 1.5, 0.0];
3182 assert_hit(
3183 sphere_cast(&w, [-10.0, 0.6, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3184 RayTarget::Body(b),
3185 10.0 - s,
3186 n,
3187 n,
3188 EXACT,
3189 );
3190 assert_eq!(
3192 sphere_cast(&w, [-10.0, 1.6, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3193 None
3194 );
3195 assert_eq!(
3197 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 5.0),
3198 None
3199 );
3200 let h = sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0).expect("hit");
3201 assert_eq!(h.body, Some(b));
3202 }
3203
3204 #[test]
3205 fn degenerate_casts_give_no_hit() {
3206 let mut w = world();
3207 sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3208 let c = [-10.0, 0.0, 0.0];
3209 assert_eq!(sphere_cast(&w, c, -0.5, [1.0, 0.0, 0.0], 100.0), None);
3211 assert_eq!(sphere_cast(&w, c, 0.5, [0.0, 0.0, 0.0], 100.0), None);
3212 assert_eq!(sphere_cast(&w, c, 0.5, [1.0, 0.0, 0.0], 0.0), None);
3213 assert_eq!(sphere_cast(&w, c, 0.5, [1.0, 0.0, 0.0], -1.0), None);
3214 assert_eq!(
3215 capsule_cast(&w, c, [-10.0, 1.0, 0.0], -0.5, [1.0, 0.0, 0.0], 100.0),
3216 None
3217 );
3218 assert_eq!(
3219 capsule_cast(&w, c, [-10.0, 1.0, 0.0], 0.5, [1.0, 0.0, 0.0], 0.0),
3220 None
3221 );
3222 }
3223
3224 #[test]
3225 fn radius_zero_is_a_ray_and_a_huge_direction_is_normalized() {
3226 let mut w = world();
3227 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3228 assert_hit(
3230 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.0, [1.0, 0.0, 0.0], 100.0),
3231 RayTarget::Body(b),
3232 9.0,
3233 [-1.0, 0.0, 0.0],
3234 [-1.0, 0.0, 0.0],
3235 EXACT,
3236 );
3237 assert_hit(
3239 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1e7, 0.0, 0.0], 100.0),
3240 RayTarget::Body(b),
3241 8.5,
3242 [-1.0, 0.0, 0.0],
3243 [-1.0, 0.0, 0.0],
3244 EXACT,
3245 );
3246 }
3247
3248 #[test]
3249 fn a_cast_that_starts_overlapping_hits_at_zero() {
3250 let mut w = world();
3251 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3252 assert_hit(
3255 sphere_cast(&w, [0.5, 0.0, 0.0], 0.5, [0.0, 1.0, 0.0], 100.0),
3256 RayTarget::Body(b),
3257 0.0,
3258 [0.0, -1.0, 0.0],
3259 [0.5, 0.0, 0.0],
3260 EXACT,
3261 );
3262 assert_hit(
3264 capsule_cast(
3265 &w,
3266 [0.0, 0.0, 0.0],
3267 [0.0, 4.0, 0.0],
3268 0.5,
3269 [1.0, 0.0, 0.0],
3270 100.0,
3271 ),
3272 RayTarget::Body(b),
3273 0.0,
3274 [-1.0, 0.0, 0.0],
3275 [0.0, 2.0, 0.0],
3276 EXACT,
3277 );
3278 }
3279
3280 #[test]
3281 fn a_cast_that_starts_touching_hits_only_when_it_moves_in() {
3282 let mut w = world();
3283 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3284 let c = [-1.5, 0.0, 0.0];
3286 assert_hit(
3288 sphere_cast(&w, c, 0.5, [1.0, 0.0, 0.0], 100.0),
3289 RayTarget::Body(b),
3290 0.0,
3291 [-1.0, 0.0, 0.0],
3292 [-1.0, 0.0, 0.0],
3293 EXACT,
3294 );
3295 assert_eq!(sphere_cast(&w, c, 0.5, [-1.0, 0.0, 0.0], 100.0), None);
3297 assert_eq!(sphere_cast(&w, c, 0.5, [0.0, 1.0, 0.0], 100.0), None);
3298
3299 let mut w = world();
3301 let p = plane(&mut w, Vec3Fix::UNIT_Y, 0.0);
3302 let c = [1.0, 0.5, 2.0];
3303 assert_hit(
3304 sphere_cast(&w, c, 0.5, [0.0, -1.0, 0.0], 100.0),
3305 RayTarget::StaticCollider(p),
3306 0.0,
3307 [0.0, 1.0, 0.0],
3308 [1.0, 0.0, 2.0],
3309 EXACT,
3310 );
3311 assert_eq!(sphere_cast(&w, c, 0.5, [1.0, 0.0, 0.0], 100.0), None);
3312 assert_eq!(sphere_cast(&w, c, 0.5, [0.0, 1.0, 0.0], 100.0), None);
3313
3314 let mut w = world();
3316 let bx = unit_box(&mut w);
3317 let c = [0.2, 1.5, 0.3];
3318 assert_hit(
3319 sphere_cast(&w, c, 0.5, [0.0, -1.0, 0.0], 100.0),
3320 RayTarget::Body(bx),
3321 0.0,
3322 [0.0, 1.0, 0.0],
3323 [0.2, 1.0, 0.3],
3324 EXACT,
3325 );
3326 assert_eq!(sphere_cast(&w, c, 0.5, [1.0, 0.0, 0.0], 100.0), None);
3327 }
3328
3329 #[test]
3330 fn sphere_cast_against_box_face_edge_and_turned_box() {
3331 let mut w = world();
3332 let b = unit_box(&mut w);
3333 assert_hit(
3335 sphere_cast(&w, [-10.0, 0.2, 0.3], 0.5, [1.0, 0.0, 0.0], 100.0),
3336 RayTarget::Body(b),
3337 8.5,
3338 [-1.0, 0.0, 0.0],
3339 [-1.0, 0.2, 0.3],
3340 EXACT,
3341 );
3342 assert_hit(
3345 sphere_cast(&w, [-10.0, 1.3, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3346 RayTarget::Body(b),
3347 8.6,
3348 [-0.8, 0.6, 0.0],
3349 [-1.0, 1.0, 0.0],
3350 EXACT,
3351 );
3352 let mut w = world();
3355 let b = unit_box(&mut w);
3356 w.bodies[b].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Y, Fix128::HALF_PI.half());
3357 let s2 = 2f64.sqrt();
3358 assert_hit(
3359 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3360 RayTarget::Body(b),
3361 9.5 - s2,
3362 [-1.0, 0.0, 0.0],
3363 [-s2, 0.0, 0.0],
3364 ITER,
3365 );
3366 }
3367
3368 #[test]
3369 fn sphere_cast_against_cylinder_side_cap_and_rim() {
3370 let mut w = world();
3371 let b = shaped(
3372 &mut w,
3373 Shape::Cylinder {
3374 radius: Fix128::ONE,
3375 half_height: Fix128::ONE,
3376 },
3377 Vec3Fix::ZERO,
3378 );
3379 assert_hit(
3381 sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3382 RayTarget::Body(b),
3383 8.5,
3384 [-1.0, 0.0, 0.0],
3385 [-1.0, 0.0, 0.0],
3386 EXACT,
3387 );
3388 assert_hit(
3390 sphere_cast(&w, [0.2, 10.0, 0.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3391 RayTarget::Body(b),
3392 8.5,
3393 [0.0, 1.0, 0.0],
3394 [0.2, 1.0, 0.0],
3395 EXACT,
3396 );
3397 assert_hit(
3400 sphere_cast(&w, [1.3, 10.0, 0.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3401 RayTarget::Body(b),
3402 8.6,
3403 [0.6, 0.8, 0.0],
3404 [1.0, 1.0, 0.0],
3405 EXACT,
3406 );
3407 assert_face_hit(
3409 capsule_cast(
3410 &w,
3411 [-0.5, 5.0, 0.0],
3412 [0.5, 5.0, 0.0],
3413 0.5,
3414 [0.0, -1.0, 0.0],
3415 100.0,
3416 ),
3417 RayTarget::Body(b),
3418 3.5,
3419 1.0,
3420 -0.5,
3421 0.5,
3422 );
3423 assert_eq!(
3425 overlap_s(&w, [0.0, 1.2, 0.0], 0.3),
3426 vec![RayTarget::Body(b)]
3427 );
3428 assert!(overlap_s(&w, [0.0, 1.2, 0.0], 0.1).is_empty());
3429 assert_eq!(
3431 overlap_s(&w, [0.0, 0.0, 0.0], 0.0),
3432 vec![RayTarget::Body(b)]
3433 );
3434 assert!(overlap_s(&w, [0.0, 1.0, 0.0], 0.0).is_empty());
3435 }
3436
3437 #[test]
3438 fn sphere_and_capsule_casts_against_an_ellipsoid() {
3439 let mut w = world();
3440 let b = shaped(
3441 &mut w,
3442 Shape::Ellipsoid {
3443 radii: v3(1.0, 2.0, 1.0),
3444 },
3445 Vec3Fix::ZERO,
3446 );
3447 assert_hit(
3449 sphere_cast(&w, [0.0, 10.0, 0.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3450 RayTarget::Body(b),
3451 7.5,
3452 [0.0, 1.0, 0.0],
3453 [0.0, 2.0, 0.0],
3454 ITER,
3455 );
3456 assert_hit(
3458 capsule_cast(
3459 &w,
3460 [-10.0, -0.5, 0.0],
3461 [-10.0, 0.5, 0.0],
3462 0.5,
3463 [1.0, 0.0, 0.0],
3464 100.0,
3465 ),
3466 RayTarget::Body(b),
3467 8.5,
3468 [-1.0, 0.0, 0.0],
3469 [-1.0, 0.0, 0.0],
3470 ITER,
3471 );
3472 assert_eq!(
3474 sphere_cast(&w, [1.6, 10.0, 0.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3475 None
3476 );
3477 assert_eq!(
3479 overlap_s(&w, [1.2, 0.0, 0.0], 0.3),
3480 vec![RayTarget::Body(b)]
3481 );
3482 assert!(overlap_s(&w, [1.2, 0.0, 0.0], 0.1).is_empty());
3483 assert_eq!(
3484 overlap_b(&w, [0.9, -0.1, -0.1], [1.5, 0.1, 0.1]),
3485 vec![RayTarget::Body(b)]
3486 );
3487 assert!(overlap_b(&w, [1.1, -0.1, -0.1], [1.5, 0.1, 0.1]).is_empty());
3488 }
3489
3490 #[test]
3491 fn casts_and_overlaps_against_a_torus() {
3492 let mut w = world();
3493 let b = shaped(
3495 &mut w,
3496 Shape::Torus {
3497 major_radius: fx(2.0),
3498 minor_radius: fx(0.5),
3499 },
3500 Vec3Fix::ZERO,
3501 );
3502 assert_hit(
3504 sphere_cast(&w, [2.0, 10.0, 0.0], 0.25, [0.0, -1.0, 0.0], 100.0),
3505 RayTarget::Body(b),
3506 9.25,
3507 [0.0, 1.0, 0.0],
3508 [2.0, 0.5, 0.0],
3509 EXACT,
3510 );
3511 assert_eq!(
3513 sphere_cast(&w, [0.0, 10.0, 0.0], 0.25, [0.0, -1.0, 0.0], 100.0),
3514 None
3515 );
3516 assert_hit(
3519 capsule_cast(
3520 &w,
3521 [1.5, 5.0, 0.0],
3522 [2.5, 5.0, 0.0],
3523 0.25,
3524 [0.0, -1.0, 0.0],
3525 100.0,
3526 ),
3527 RayTarget::Body(b),
3528 4.25,
3529 [0.0, 1.0, 0.0],
3530 [2.0, 0.5, 0.0],
3531 ITER,
3532 );
3533 assert!(overlap_s(&w, [0.0, 0.0, 0.0], 1.4).is_empty());
3535 assert_eq!(
3536 overlap_s(&w, [0.0, 0.0, 0.0], 1.6),
3537 vec![RayTarget::Body(b)]
3538 );
3539 assert_eq!(
3540 overlap_s(&w, [2.0, 0.0, 0.0], 0.1),
3541 vec![RayTarget::Body(b)]
3542 );
3543 assert!(overlap_b(&w, [-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]).is_empty());
3547 assert_eq!(
3548 overlap_b(&w, [-1.2, -1.0, -1.2], [1.2, 1.0, 1.2]),
3549 vec![RayTarget::Body(b)]
3550 );
3551 assert!(overlap_b(&w, [5.0, 5.0, 5.0], [6.0, 6.0, 6.0]).is_empty());
3553 }
3554
3555 #[test]
3556 fn casts_and_overlaps_against_compound_children() {
3557 let mut w = world();
3558 let mut c = CompoundShape::new();
3562 for s in [3.0, -3.0] {
3563 c.add_box(
3564 OrientedBox::new(Vec3Fix::ZERO, v3(0.5, 0.5, 0.5), QuatFix::IDENTITY),
3565 v3(s, 0.0, 0.0),
3566 QuatFix::IDENTITY,
3567 );
3568 c.add_sphere(
3569 Sphere::new(Vec3Fix::ZERO, fx(0.5)),
3570 v3(0.0, 0.0, s),
3571 QuatFix::IDENTITY,
3572 );
3573 let mut cube = Vec::new();
3574 for x in [-0.5, 0.5] {
3575 for y in [-0.5, 0.5] {
3576 for z in [-0.5, 0.5] {
3577 cube.push(v3(x, y, z));
3578 }
3579 }
3580 }
3581 c.add_convex_hull(
3582 ConvexHull::new(cube),
3583 v3(0.0, s + s / 3.0, 0.0),
3584 QuatFix::IDENTITY,
3585 );
3586 }
3587 c.add_capsule(
3588 Capsule::new(v3(-1.5, 0.0, 0.0), v3(1.5, 0.0, 0.0), fx(0.5)),
3589 Vec3Fix::ZERO,
3590 QuatFix::IDENTITY,
3591 );
3592 let b = w
3593 .add_compound_body(&c, Fix128::ONE, Vec3Fix::ZERO)
3594 .expect("valid compound");
3595 let body = RayTarget::Body(b);
3596 assert_hit(
3598 sphere_cast(&w, [3.0, 10.0, 0.0], 0.25, [0.0, -1.0, 0.0], 100.0),
3599 body,
3600 9.25,
3601 [0.0, 1.0, 0.0],
3602 [3.0, 0.5, 0.0],
3603 EXACT,
3604 );
3605 assert_hit(
3607 sphere_cast(&w, [0.0, 10.0, 3.0], 0.25, [0.0, -1.0, 0.0], 100.0),
3608 body,
3609 9.25,
3610 [0.0, 1.0, 0.0],
3611 [0.0, 0.5, 3.0],
3612 EXACT,
3613 );
3614 assert_hit(
3617 sphere_cast(&w, [1.0, 0.0, 10.0], 0.25, [0.0, 0.0, -1.0], 100.0),
3618 body,
3619 9.25,
3620 [0.0, 0.0, 1.0],
3621 [1.0, 0.0, 0.5],
3622 EXACT,
3623 );
3624 assert_hit(
3627 sphere_cast(&w, [10.0, 4.0, 0.0], 0.25, [-1.0, 0.0, 0.0], 100.0),
3628 body,
3629 9.25,
3630 [1.0, 0.0, 0.0],
3631 [0.5, 4.0, 0.0],
3632 ITER,
3633 );
3634 assert_face_hit(
3636 capsule_cast(
3637 &w,
3638 [2.8, 5.0, 0.0],
3639 [3.2, 5.0, 0.0],
3640 0.25,
3641 [0.0, -1.0, 0.0],
3642 100.0,
3643 ),
3644 body,
3645 4.25,
3646 0.5,
3647 2.8,
3648 3.2,
3649 );
3650 assert_hit(
3653 capsule_cast(
3654 &w,
3655 [0.0, 2.0, -1.0],
3656 [0.0, 2.0, 1.0],
3657 0.25,
3658 [0.0, -1.0, 0.0],
3659 100.0,
3660 ),
3661 body,
3662 1.25,
3663 [0.0, 1.0, 0.0],
3664 [0.0, 0.5, 0.0],
3665 ITER,
3666 );
3667 assert_hit(
3669 capsule_cast(
3670 &w,
3671 [-1.0, 5.0, -3.0],
3672 [1.0, 5.0, -3.0],
3673 0.25,
3674 [0.0, -1.0, 0.0],
3675 100.0,
3676 ),
3677 body,
3678 4.25,
3679 [0.0, 1.0, 0.0],
3680 [0.0, 0.5, -3.0],
3681 EXACT,
3682 );
3683 assert!(overlap_s(&w, [2.1, 0.0, 0.0], 0.05).is_empty());
3687 assert_eq!(overlap_s(&w, [2.1, 0.0, 0.0], 0.45), vec![body]);
3688 assert_eq!(overlap_b(&w, [3.4, 0.4, -0.1], [3.6, 0.6, 0.1]), vec![body]);
3690 assert_eq!(overlap_b(&w, [-0.1, 0.4, 2.9], [0.1, 0.6, 3.1]), vec![body]);
3691 assert_eq!(overlap_b(&w, [1.0, 0.4, -0.1], [1.2, 0.6, 0.1]), vec![body]);
3692 assert_eq!(overlap_b(&w, [0.4, 4.4, -0.1], [0.6, 4.6, 0.1]), vec![body]);
3693 assert!(overlap_b(&w, [1.0, 2.0, 1.0], [2.0, 3.0, 2.0]).is_empty());
3694 }
3695
3696 #[test]
3697 fn casts_and_overlaps_against_a_plane() {
3698 let mut w = world();
3699 let p = plane(&mut w, Vec3Fix::UNIT_Y, 0.0);
3700 let sp = RayTarget::StaticCollider(p);
3701 assert_hit(
3703 sphere_cast(&w, [0.0, 5.0, 0.0], 0.5, [1.0, -1.0, 0.0], 100.0),
3704 sp,
3705 4.5 * 2f64.sqrt(),
3706 [0.0, 1.0, 0.0],
3707 [4.5, 0.0, 0.0],
3708 EXACT,
3709 );
3710 assert_hit(
3712 sphere_cast(&w, [0.0, -5.0, 0.0], 0.5, [0.0, 1.0, 0.0], 100.0),
3713 sp,
3714 4.5,
3715 [0.0, -1.0, 0.0],
3716 [0.0, 0.0, 0.0],
3717 EXACT,
3718 );
3719 assert_eq!(
3721 sphere_cast(&w, [0.0, 1.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3722 None
3723 );
3724 assert_hit(
3726 capsule_cast(
3727 &w,
3728 [-1.0, 5.0, 0.0],
3729 [1.0, 6.0, 0.0],
3730 0.5,
3731 [0.0, -1.0, 0.0],
3732 100.0,
3733 ),
3734 sp,
3735 4.5,
3736 [0.0, 1.0, 0.0],
3737 [-1.0, 0.0, 0.0],
3738 EXACT,
3739 );
3740 assert_hit(
3742 capsule_cast(
3743 &w,
3744 [0.0, -1.0, 0.0],
3745 [0.0, 1.0, 0.0],
3746 0.5,
3747 [1.0, 0.0, 0.0],
3748 100.0,
3749 ),
3750 sp,
3751 0.0,
3752 [-1.0, 0.0, 0.0],
3753 [0.0, 0.0, 0.0],
3754 EXACT,
3755 );
3756 assert_eq!(overlap_s(&w, [0.0, 0.4, 0.0], 0.5), vec![sp]);
3758 assert!(overlap_s(&w, [0.0, 0.6, 0.0], 0.5).is_empty());
3759 assert_eq!(overlap_b(&w, [0.0, -0.1, 0.0], [1.0, 0.1, 1.0]), vec![sp]);
3761 assert!(overlap_b(&w, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]).is_empty());
3762 }
3763
3764 #[test]
3765 fn casts_along_a_mesh_floor_cross_its_edges() {
3766 let mut w = world();
3767 let m = floor_mesh(&mut w);
3768 let sm = RayTarget::StaticCollider(m);
3769 assert_hit(
3771 sphere_cast(&w, [1.0, 3.0, 3.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3772 sm,
3773 2.5,
3774 [0.0, 1.0, 0.0],
3775 [1.0, 0.0, 3.0],
3776 EXACT,
3777 );
3778 assert_hit(
3781 sphere_cast(&w, [-10.0, 0.3, 1.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3782 sm,
3783 9.6,
3784 [-0.8, 0.6, 0.0],
3785 [0.0, 0.0, 1.0],
3786 EXACT,
3787 );
3788 assert_eq!(
3791 sphere_cast(&w, [1.0, 0.5, 3.0], 0.5, [1.0, 0.0, -1.0], 2.0),
3792 None
3793 );
3794 assert_eq!(
3795 capsule_cast(
3796 &w,
3797 [1.0, 0.5, 2.0],
3798 [1.0, 1.5, 2.0],
3799 0.5,
3800 [1.0, 0.0, 0.0],
3801 2.0
3802 ),
3803 None
3804 );
3805 assert_hit(
3807 sphere_cast(&w, [1.0, 0.5, 3.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3808 sm,
3809 0.0,
3810 [0.0, 1.0, 0.0],
3811 [1.0, 0.0, 3.0],
3812 EXACT,
3813 );
3814 assert_hit(
3816 capsule_cast(
3817 &w,
3818 [2.0, 3.0, 1.0],
3819 [3.0, 4.0, 1.0],
3820 0.5,
3821 [0.0, -1.0, 0.0],
3822 100.0,
3823 ),
3824 sm,
3825 2.5,
3826 [0.0, 1.0, 0.0],
3827 [2.0, 0.0, 1.0],
3828 ITER,
3829 );
3830 assert_eq!(overlap_s(&w, [2.0, 0.3, 2.0], 0.5), vec![sm]);
3832 assert!(overlap_s(&w, [-0.6, 0.0, 2.0], 0.5).is_empty());
3833 assert_eq!(overlap_b(&w, [1.0, -0.1, 1.0], [2.0, 0.1, 2.0]), vec![sm]);
3834 assert!(overlap_b(&w, [1.0, 0.1, 1.0], [2.0, 1.0, 2.0]).is_empty());
3835 }
3836
3837 #[test]
3838 fn casts_and_overlaps_against_height_fields() {
3839 let mut w = world();
3841 let h = w.add_static_collider(StaticCollider::HeightField(HeightField::flat(
3842 5,
3843 5,
3844 Fix128::ONE,
3845 Vec3Fix::ZERO,
3846 fx(0.25),
3847 )));
3848 let sh = RayTarget::StaticCollider(h);
3849 assert_hit(
3850 sphere_cast(&w, [2.0, 5.0, 2.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3851 sh,
3852 4.25,
3853 [0.0, 1.0, 0.0],
3854 [2.0, 0.25, 2.0],
3855 ITER,
3856 );
3857 assert_face_hit(
3859 capsule_cast(
3860 &w,
3861 [1.5, 5.0, 2.0],
3862 [2.5, 5.0, 2.0],
3863 0.5,
3864 [0.0, -1.0, 0.0],
3865 100.0,
3866 ),
3867 sh,
3868 4.25,
3869 0.25,
3870 1.5,
3871 2.5,
3872 );
3873 assert_eq!(
3875 sphere_cast(&w, [0.5, 0.75, 0.5], 0.5, [1.0, 0.0, 0.0], 3.0),
3876 None
3877 );
3878 assert_eq!(
3880 sphere_cast(&w, [10.0, 5.0, 10.0], 0.5, [0.0, -1.0, 0.0], 100.0),
3881 None
3882 );
3883 assert_eq!(overlap_s(&w, [2.0, 0.5, 2.0], 0.3), vec![sh]);
3884 assert!(overlap_s(&w, [2.0, 0.6, 2.0], 0.3).is_empty());
3885 assert_eq!(overlap_b(&w, [1.0, 0.2, 1.0], [3.0, 0.3, 3.0]), vec![sh]);
3886 assert!(overlap_b(&w, [1.0, 0.3, 1.0], [3.0, 1.0, 3.0]).is_empty());
3887 assert!(overlap_b(&w, [5.0, 0.0, 5.0], [6.0, 1.0, 6.0]).is_empty());
3888 }
3889
3890 #[test]
3891 fn casts_and_overlaps_against_a_twisted_height_field_cell() {
3892 let mut w = world();
3893 let h = saddle_field(&mut w);
3894 let sh = RayTarget::StaticCollider(h);
3895 let k = 1.5f64.sqrt();
3900 let n = [-0.5 / k, 1.0 / k, -0.5 / k];
3901 let r = 0.1;
3902 let cx = 0.5 + r * n[0];
3903 assert_hit(
3905 sphere_cast(&w, [cx, 5.0, cx], r, [0.0, -1.0, 0.0], 100.0),
3906 sh,
3907 5.0 - (0.25 + r * n[1]),
3908 n,
3909 [0.5, 0.25, 0.5],
3910 ITER,
3911 );
3912 let at = |s: f64| [0.5 + s * n[0], 0.25 + s * n[1], 0.5 + s * n[2]];
3915 assert_eq!(overlap_s(&w, at(0.09), 0.1), vec![sh]);
3916 assert!(overlap_s(&w, at(0.11), 0.1).is_empty());
3917 assert_eq!(overlap_b(&w, [0.9, 0.9, 0.9], [1.0, 2.0, 1.0]), vec![sh]);
3920 assert!(overlap_b(&w, [0.0, 0.9, 0.0], [0.1, 2.0, 0.1]).is_empty());
3921 }
3922
3923 #[cfg(feature = "std")]
3924 #[test]
3925 fn casts_and_overlaps_against_an_sdf() {
3926 use crate::sdf_collider::{ClosureSdf, SdfCollider};
3927 let mut w = world();
3928 w.sdf_colliders.push(SdfCollider::new_static(
3930 Box::new(ClosureSdf::new(
3931 |x, y, z| (x * x + y * y + z * z).sqrt() - 1.0,
3932 |x, y, z| {
3933 let l = (x * x + y * y + z * z).sqrt();
3934 (x / l, y / l, z / l)
3935 },
3936 )),
3937 Vec3Fix::ZERO,
3938 QuatFix::IDENTITY,
3939 ));
3940 let tol = f64::from(all().sdf.tolerance);
3941 let h = sphere_cast(&w, [-5.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0).expect("hit");
3943 assert_eq!(h.target, RayTarget::Sdf(0));
3944 assert!(h.t.to_f64() > 3.5 - tol - 1e-5 && h.t.to_f64() < 3.5 + 1e-5);
3945 assert_vec(h.normal, [-1.0, 0.0, 0.0], 1e-3, "normal");
3946 let h = capsule_cast(
3948 &w,
3949 [-5.0, -0.5, 0.0],
3950 [-5.0, 0.5, 0.0],
3951 0.5,
3952 [1.0, 0.0, 0.0],
3953 100.0,
3954 )
3955 .expect("hit");
3956 assert!(h.t.to_f64() > 3.5 - tol - 1e-4 && h.t.to_f64() < 3.5 + 1e-4);
3957 assert_eq!(
3959 sphere_cast(&w, [-5.0, 2.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0),
3960 None
3961 );
3962 assert_eq!(overlap_s(&w, [1.2, 0.0, 0.0], 0.3), vec![RayTarget::Sdf(0)]);
3964 assert!(overlap_s(&w, [1.2, 0.0, 0.0], 0.1).is_empty());
3965 assert_eq!(
3966 overlap_b(&w, [0.8, -0.1, -0.1], [1.2, 0.1, 0.1]),
3967 vec![RayTarget::Sdf(0)]
3968 );
3969 assert!(overlap_b(&w, [2.0, 2.0, 2.0], [3.0, 3.0, 3.0]).is_empty());
3970 assert!(w
3972 .overlap_sphere(v3(1.2, 0.0, 0.0), fx(0.3), &all().with_sdf(false))
3973 .is_empty());
3974 }
3975
3976 #[test]
3977 fn capsule_cast_against_a_sphere_body_and_a_box() {
3978 let mut w = world();
3979 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
3980 let s = 1.25f64.sqrt();
3983 let n = [1.0 / 1.5, s / 1.5, 0.0];
3984 assert_hit(
3985 capsule_cast(
3986 &w,
3987 [1.0, 5.0, 0.0],
3988 [3.0, 5.0, 0.0],
3989 0.5,
3990 [0.0, -1.0, 0.0],
3991 100.0,
3992 ),
3993 RayTarget::Body(b),
3994 5.0 - s,
3995 n,
3996 n,
3997 EXACT,
3998 );
3999 assert_hit(
4001 capsule_cast(
4002 &w,
4003 [-10.0, 0.0, 0.0],
4004 [-10.0, 0.0, 0.0],
4005 0.5,
4006 [1.0, 0.0, 0.0],
4007 100.0,
4008 ),
4009 RayTarget::Body(b),
4010 8.5,
4011 [-1.0, 0.0, 0.0],
4012 [-1.0, 0.0, 0.0],
4013 EXACT,
4014 );
4015 let mut w = world();
4016 let b = unit_box(&mut w);
4017 assert_face_hit(
4019 capsule_cast(
4020 &w,
4021 [-0.5, 5.0, 0.0],
4022 [0.5, 5.0, 0.0],
4023 0.5,
4024 [0.0, -1.0, 0.0],
4025 100.0,
4026 ),
4027 RayTarget::Body(b),
4028 3.5,
4029 1.0,
4030 -0.5,
4031 0.5,
4032 );
4033 assert_eq!(
4035 capsule_cast(
4036 &w,
4037 [-0.5, 5.0, 0.0],
4038 [0.5, 5.0, 0.0],
4039 0.5,
4040 [0.0, 1.0, 0.0],
4041 100.0
4042 ),
4043 None
4044 );
4045 }
4046
4047 #[test]
4048 fn overlaps_of_bodies_and_the_box_edge_gap() {
4049 let mut w = world();
4050 let s = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
4051 assert_eq!(
4053 overlap_s(&w, [1.4, 0.0, 0.0], 0.5),
4054 vec![RayTarget::Body(s)]
4055 );
4056 assert!(overlap_s(&w, [1.4, 0.0, 0.0], 0.3).is_empty());
4057 assert!(overlap_s(&w, [0.0, 0.0, 0.0], -1.0).is_empty());
4059 assert_eq!(
4062 overlap_b(&w, [0.5, 0.5, 0.5], [2.0, 2.0, 2.0]),
4063 vec![RayTarget::Body(s)]
4064 );
4065 assert!(overlap_b(&w, [0.6, 0.6, 0.6], [2.0, 2.0, 2.0]).is_empty());
4067 assert!(overlap_b(&w, [1.0, -1.0, -1.0], [-1.0, 1.0, 1.0]).is_empty());
4069
4070 let mut w = world();
4071 let b = unit_box(&mut w);
4072 assert_eq!(
4075 overlap_s(&w, [1.3, 1.3, 0.0], 0.5),
4076 vec![RayTarget::Body(b)]
4077 );
4078 assert!(overlap_s(&w, [1.3, 1.3, 0.0], 0.4).is_empty());
4079 w.bodies[b].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Y, Fix128::HALF_PI.half());
4082 assert_eq!(
4083 overlap_b(&w, [1.3, -0.5, -0.1], [2.0, 0.5, 0.1]),
4084 vec![RayTarget::Body(b)]
4085 );
4086 assert!(overlap_b(&w, [1.5, -0.5, -0.1], [2.0, 0.5, 0.1]).is_empty());
4087 }
4088
4089 #[test]
4090 fn filters_and_ties() {
4091 let mut w = world();
4092 let b = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
4093 let p = plane(&mut w, Vec3Fix::UNIT_Y, -5.0);
4094 let c = v3(0.0, 10.0, 0.0);
4098 let down = -Vec3Fix::UNIT_Y;
4099 let h = w
4100 .cast_sphere(c, fx(0.5), down, fx(100.0), &all())
4101 .expect("hit");
4102 assert_eq!(h.target, RayTarget::Body(b));
4103 let h = w
4104 .cast_sphere(c, fx(0.5), down, fx(100.0), &all().excluding_body(b))
4105 .expect("hit");
4106 assert_eq!(h.target, RayTarget::StaticCollider(p));
4107 assert!((h.t.to_f64() - 14.5).abs() < EXACT);
4108 assert_eq!(
4109 w.cast_sphere(
4110 c,
4111 fx(0.5),
4112 down,
4113 fx(100.0),
4114 &all().excluding_body(b).with_static(false)
4115 ),
4116 None
4117 );
4118 let mut w = world();
4121 let b0 = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
4122 let b1 = sphere_body(&mut w, Vec3Fix::ZERO, 1.0);
4123 let h = sphere_cast(&w, [-10.0, 0.0, 0.0], 0.5, [1.0, 0.0, 0.0], 100.0).expect("hit");
4124 assert_eq!(h.target, RayTarget::Body(b0));
4125 assert_eq!(
4126 overlap_s(&w, [0.0, 0.0, 0.0], 0.1),
4127 vec![RayTarget::Body(b0), RayTarget::Body(b1)]
4128 );
4129 }
4130
4131 #[test]
4132 fn capsule_penetrations_closed_forms() {
4133 let mut w = world();
4134 let p = plane(&mut w, Vec3Fix::UNIT_Y, 0.0);
4135 let pens = w
4138 .capsule_penetrations(v3(0.0, 0.2, 0.0), v3(0.0, 1.4, 0.0), fx(0.3), &all())
4139 .expect("the segment is outside");
4140 assert_eq!(pens.len(), 1);
4141 assert_eq!(pens[0].target, RayTarget::StaticCollider(p));
4142 assert!((pens[0].depth.to_f64() - 0.1).abs() < EXACT);
4143 assert_vec(pens[0].normal, [0.0, 1.0, 0.0], EXACT, "normal");
4144 assert!(w
4146 .capsule_penetrations(v3(0.0, 0.4, 0.0), v3(0.0, 1.4, 0.0), fx(0.3), &all())
4147 .expect("outside")
4148 .is_empty());
4149 assert!(w
4151 .capsule_penetrations(v3(0.0, -0.2, 0.0), v3(0.0, 1.4, 0.0), fx(0.3), &all())
4152 .is_none());
4153 }
4154
4155 #[test]
4156 fn helper_closed_forms() {
4157 let a = AABB::new(v3(0.0, 0.0, 0.0), v3(1.0, 1.0, 1.0));
4159 let b = AABB::new(v3(2.0, 0.5, 3.0), v3(3.0, 2.0, 4.0));
4160 assert!((aabb_gap(&a, &b).to_f64() - 5f64.sqrt()).abs() < EXACT);
4161 assert_eq!(aabb_gap(&a, &a), Fix128::ZERO);
4162 let target = AABB::new(v3(3.0, 0.0, 0.0), v3(4.0, 1.0, 1.0));
4165 let entry = box_entry(&a, Vec3Fix::UNIT_X, fx(10.0), &target).expect("meets");
4166 assert!((entry.to_f64() - 2.0).abs() < EXACT);
4167 assert_eq!(box_entry(&a, Vec3Fix::UNIT_X, Fix128::ONE, &target), None);
4168 let above = AABB::new(v3(3.0, 5.0, 0.0), v3(4.0, 6.0, 1.0));
4169 assert_eq!(box_entry(&a, Vec3Fix::UNIT_X, fx(10.0), &above), None);
4170 let behind = AABB::new(v3(-4.0, 0.0, 0.0), v3(-3.0, 1.0, 1.0));
4172 let entry = box_entry(&a, -Vec3Fix::UNIT_X, fx(10.0), &behind).expect("meets");
4173 assert!((entry.to_f64() - 3.0).abs() < EXACT);
4174 let tiny = Fix128 { hi: 0, lo: 1 };
4176 assert!((ratio_within(Fix128::ONE, fx(4.0), fx(5.0)).to_f64() - 0.25).abs() < EXACT);
4177 assert_eq!(ratio_within(Fix128::ONE, tiny, fx(5.0)), fx(5.0));
4178 assert_eq!(ratio_within(Fix128::NEG_ONE, tiny, fx(5.0)), fx(-5.0));
4179 assert_eq!(
4181 tame_direction(Vec3Fix::new(
4182 Fix128::from_int(1 << 21),
4183 Fix128::ZERO,
4184 Fix128::ZERO
4185 )),
4186 Vec3Fix::UNIT_X
4187 );
4188 assert_eq!(tame_direction(v3(3.0, 4.0, 0.0)), v3(3.0, 4.0, 0.0));
4189 assert!(box_is_valid(&a));
4190 assert!(!box_is_valid(&AABB::new(
4191 v3(0.0, 1.0, 0.0),
4192 v3(1.0, 0.0, 1.0)
4193 )));
4194 let (s0, s1) = (v3(0.0, 0.0, 0.0), v3(2.0, 0.0, 0.0));
4196 assert_eq!(
4197 closest_on_segment(s0, s1, v3(1.0, 5.0, 0.0)),
4198 v3(1.0, 0.0, 0.0)
4199 );
4200 assert_eq!(closest_on_segment(s0, s1, v3(-3.0, 1.0, 0.0)), s0);
4201 assert_eq!(closest_on_segment(s0, s0, v3(5.0, 5.0, 5.0)), s0);
4202 assert!(segment_segment(
4204 v3(-1.0, 0.0, 0.0),
4205 v3(1.0, 0.0, 0.0),
4206 v3(0.0, -1.0, 0.0),
4207 v3(0.0, 1.0, 0.0)
4208 )
4209 .is_none());
4210 let (d, _, _) = segment_segment(
4211 v3(-1.0, 0.0, 0.0),
4212 v3(1.0, 0.0, 0.0),
4213 v3(0.0, -1.0, 1.0),
4214 v3(0.0, 1.0, 1.0),
4215 )
4216 .expect("apart");
4217 assert!((d.to_f64() - 1.0).abs() < ITER);
4218 let (d, _, _) =
4219 segment_segment(s0, s0, v3(3.0, 4.0, 0.0), v3(3.0, 4.0, 0.0)).expect("apart");
4220 assert!((d.to_f64() - 5.0).abs() < EXACT);
4221 let (d, _, _) =
4222 segment_segment(v3(1.0, 3.0, 0.0), v3(1.0, 3.0, 0.0), s0, s1).expect("apart");
4223 assert!((d.to_f64() - 3.0).abs() < EXACT);
4224 let (d, _, _) =
4225 segment_segment(s0, s1, v3(1.0, 0.0, 2.0), v3(1.0, 0.0, 2.0)).expect("apart");
4226 assert!((d.to_f64() - 2.0).abs() < EXACT);
4227 let h = v3(1.0, 1.0, 1.0);
4230 match point_box_local(v3(1.0, 0.5, 0.5), h) {
4231 Dist::Outside { dist, normal, .. } => {
4232 assert_eq!(dist, Fix128::ZERO);
4233 assert_eq!(normal, Vec3Fix::UNIT_X);
4234 }
4235 other => panic!("expected touching, got {other:?}"),
4236 }
4237 assert!((point_box_local(v3(4.0, 5.0, 0.0), h).value().to_f64() - 5.0).abs() < EXACT);
4238 assert!(matches!(point_box_local(Vec3Fix::ZERO, h), Dist::Inside));
4239 match point_cylinder_local(v3(0.0, -1.0, 0.0), Fix128::ONE, Fix128::ONE) {
4241 Dist::Outside { dist, normal, .. } => {
4242 assert_eq!(dist, Fix128::ZERO);
4243 assert_eq!(normal, -Vec3Fix::UNIT_Y);
4244 }
4245 other => panic!("expected touching, got {other:?}"),
4246 }
4247 assert!(
4248 (point_cylinder_local(v3(3.0, 0.0, 0.0), Fix128::ONE, Fix128::ONE)
4249 .value()
4250 .to_f64()
4251 - 2.0)
4252 .abs()
4253 < EXACT
4254 );
4255 assert!(matches!(
4257 Dist::toward(v3(2.0, 0.0, 0.0), Vec3Fix::ZERO).shrunk(fx(3.0)),
4258 Dist::Inside
4259 ));
4260 assert_eq!(
4261 Dist::AtLeast(fx(2.0)).shrunk(Fix128::ONE).value(),
4262 Fix128::ONE
4263 );
4264 assert!(!Dist::AtLeast(Fix128::ZERO).within(fx(10.0)));
4265 assert!(Dist::Inside.within(Fix128::ZERO));
4266 assert_eq!(Dist::Inside.value(), Fix128::NEG_ONE);
4267 assert!(matches!(
4268 Dist::toward(Vec3Fix::ZERO, Vec3Fix::ZERO),
4269 Dist::Inside
4270 ));
4271 }
4272}