1#[inline]
18fn vadd(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
19 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
20}
21
22#[inline]
23fn vsub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
24 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
25}
26
27#[inline]
28fn vscale(a: [f64; 3], s: f64) -> [f64; 3] {
29 [a[0] * s, a[1] * s, a[2] * s]
30}
31
32#[inline]
33fn vdot(a: [f64; 3], b: [f64; 3]) -> f64 {
34 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
35}
36
37#[inline]
38fn vcross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
39 [
40 a[1] * b[2] - a[2] * b[1],
41 a[2] * b[0] - a[0] * b[2],
42 a[0] * b[1] - a[1] * b[0],
43 ]
44}
45
46#[inline]
47fn vlen_sq(a: [f64; 3]) -> f64 {
48 vdot(a, a)
49}
50
51#[inline]
52fn vlen(a: [f64; 3]) -> f64 {
53 vlen_sq(a).sqrt()
54}
55
56#[inline]
57fn vnormalize(a: [f64; 3]) -> [f64; 3] {
58 let l = vlen(a);
59 if l < 1e-300 {
60 [0.0, 0.0, 0.0]
61 } else {
62 vscale(a, 1.0 / l)
63 }
64}
65
66#[inline]
67fn vlerp(a: [f64; 3], b: [f64; 3], t: f64) -> [f64; 3] {
68 vadd(vscale(a, 1.0 - t), vscale(b, t))
69}
70
71#[inline]
72fn vdist(a: [f64; 3], b: [f64; 3]) -> f64 {
73 vlen(vsub(a, b))
74}
75
76#[inline]
77fn vclamp(x: f64, lo: f64, hi: f64) -> f64 {
78 x.max(lo).min(hi)
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum ClosestFeature {
88 Vertex,
90 Edge,
92 Face,
94}
95
96#[derive(Debug, Clone, Copy)]
101pub struct ClosestPointResult {
102 pub position: [f64; 3],
104 pub distance: f64,
106 pub feature: ClosestFeature,
108 pub u_param: f64,
110 pub v_param: f64,
112}
113
114impl ClosestPointResult {
115 fn new(
116 position: [f64; 3],
117 query_pt: [f64; 3],
118 feature: ClosestFeature,
119 u_param: f64,
120 v_param: f64,
121 ) -> Self {
122 let distance = vdist(position, query_pt);
123 Self {
124 position,
125 distance,
126 feature,
127 u_param,
128 v_param,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy)]
139pub struct SegSegResult {
140 pub point_a: [f64; 3],
142 pub point_b: [f64; 3],
144 pub s: f64,
146 pub t: f64,
148 pub distance: f64,
150}
151
152pub fn point_segment_closest(a: [f64; 3], b: [f64; 3], p: [f64; 3]) -> ClosestPointResult {
168 let ab = vsub(b, a);
169 let ap = vsub(p, a);
170 let len_sq = vlen_sq(ab);
171
172 let (t, feature) = if len_sq < 1e-300 {
173 (0.0, ClosestFeature::Vertex)
175 } else {
176 let raw_t = vdot(ap, ab) / len_sq;
177 if raw_t <= 0.0 {
178 (0.0, ClosestFeature::Vertex)
179 } else if raw_t >= 1.0 {
180 (1.0, ClosestFeature::Vertex)
181 } else {
182 (raw_t, ClosestFeature::Edge)
183 }
184 };
185
186 let pos = vadd(a, vscale(ab, t));
187 ClosestPointResult::new(pos, p, feature, t, 0.0)
188}
189
190pub fn point_triangle_closest(
210 v0: [f64; 3],
211 v1: [f64; 3],
212 v2: [f64; 3],
213 p: [f64; 3],
214) -> ClosestPointResult {
215 let ab = vsub(v1, v0);
216 let ac = vsub(v2, v0);
217 let ap = vsub(p, v0);
218
219 let d1 = vdot(ab, ap);
220 let d2 = vdot(ac, ap);
221 if d1 <= 0.0 && d2 <= 0.0 {
223 return ClosestPointResult::new(v0, p, ClosestFeature::Vertex, 0.0, 0.0);
224 }
225
226 let bp = vsub(p, v1);
227 let d3 = vdot(ab, bp);
228 let d4 = vdot(ac, bp);
229 if d3 >= 0.0 && d4 <= d3 {
231 return ClosestPointResult::new(v1, p, ClosestFeature::Vertex, 1.0, 0.0);
232 }
233
234 let vc = d1 * d4 - d3 * d2;
236 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
237 let v = d1 / (d1 - d3);
238 let pos = vadd(v0, vscale(ab, v));
239 return ClosestPointResult::new(pos, p, ClosestFeature::Edge, v, 0.0);
240 }
241
242 let cp = vsub(p, v2);
243 let d5 = vdot(ab, cp);
244 let d6 = vdot(ac, cp);
245 if d6 >= 0.0 && d5 <= d6 {
247 return ClosestPointResult::new(v2, p, ClosestFeature::Vertex, 0.0, 1.0);
248 }
249
250 let vb = d5 * d2 - d1 * d6;
252 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
253 let w = d2 / (d2 - d6);
254 let pos = vadd(v0, vscale(ac, w));
255 return ClosestPointResult::new(pos, p, ClosestFeature::Edge, 0.0, w);
256 }
257
258 let va = d3 * d6 - d5 * d4;
260 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
261 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
262 let pos = vadd(v1, vscale(vsub(v2, v1), w));
263 return ClosestPointResult::new(pos, p, ClosestFeature::Edge, 1.0 - w, w);
264 }
265
266 let denom = 1.0 / (va + vb + vc);
268 let v = vb * denom;
269 let w = vc * denom;
270 let pos = vadd(v0, vadd(vscale(ab, v), vscale(ac, w)));
271 ClosestPointResult::new(pos, p, ClosestFeature::Face, v, w)
272}
273
274pub fn segment_segment_closest(a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3]) -> SegSegResult {
291 let d1 = vsub(b, a);
292 let d2 = vsub(d, c);
293 let r = vsub(a, c);
294
295 let e = vdot(d1, d1);
296 let f = vdot(d2, d2);
297
298 let (mut s, t);
299
300 const EPS: f64 = 1e-10;
301
302 if e < EPS && f < EPS {
304 s = 0.0;
305 t = 0.0;
306 } else if e < EPS {
307 s = 0.0;
309 t = vclamp(vdot(r, d2) / f, 0.0, 1.0);
310 } else {
311 let c1 = vdot(d1, r);
312 if f < EPS {
313 t = 0.0;
315 s = vclamp(-c1 / e, 0.0, 1.0);
316 } else {
317 let b_coeff = vdot(d1, d2);
318 let denom = e * f - b_coeff * b_coeff;
319 if denom.abs() > EPS {
321 let c2 = vdot(d2, r);
322 s = vclamp((b_coeff * c2 - c1 * f) / denom, 0.0, 1.0);
323 } else {
324 s = 0.0;
326 }
327 let c2 = vdot(d2, r);
328 let tnom = b_coeff * s + c2;
329 if tnom < 0.0 {
330 t = 0.0;
331 s = vclamp(-c1 / e, 0.0, 1.0);
332 } else if tnom > f {
333 t = 1.0;
334 s = vclamp((b_coeff - c1) / e, 0.0, 1.0);
335 } else {
336 t = tnom / f;
337 }
338 }
339 }
340
341 let point_a = vadd(a, vscale(d1, s));
342 let point_b = vadd(c, vscale(d2, t));
343 let distance = vdist(point_a, point_b);
344 SegSegResult {
345 point_a,
346 point_b,
347 s,
348 t,
349 distance,
350 }
351}
352
353pub fn point_box_distance(min: [f64; 3], max: [f64; 3], p: [f64; 3]) -> f64 {
371 let mut dist_sq_outside = 0.0_f64;
373 for i in 0..3 {
374 if p[i] < min[i] {
375 let d = min[i] - p[i];
376 dist_sq_outside += d * d;
377 } else if p[i] > max[i] {
378 let d = p[i] - max[i];
379 dist_sq_outside += d * d;
380 }
381 }
382 if dist_sq_outside > 0.0 {
383 return dist_sq_outside.sqrt();
384 }
385
386 let mut min_pen = f64::MAX;
388 for i in 0..3 {
389 let d_lo = p[i] - min[i];
390 let d_hi = max[i] - p[i];
391 let pen = d_lo.min(d_hi);
392 if pen < min_pen {
393 min_pen = pen;
394 }
395 }
396 -min_pen
397}
398
399pub fn point_sphere_distance(center: [f64; 3], r: f64, p: [f64; 3]) -> f64 {
414 vdist(p, center) - r
415}
416
417pub fn point_capsule_distance(a: [f64; 3], b: [f64; 3], r: f64, p: [f64; 3]) -> f64 {
434 let res = point_segment_closest(a, b, p);
435 res.distance - r
436}
437
438#[derive(Debug, Clone)]
447pub enum SignedDistanceField {
448 Sphere {
450 center: [f64; 3],
452 r: f64,
454 },
455 Box {
457 half: [f64; 3],
459 },
460 Capsule {
462 a: [f64; 3],
464 b: [f64; 3],
466 r: f64,
468 },
469 Cylinder {
472 axis: [f64; 3],
474 r: f64,
476 h: f64,
478 },
479}
480
481impl SignedDistanceField {
482 pub fn eval(&self, p: [f64; 3]) -> f64 {
493 match self {
494 SignedDistanceField::Sphere { center, r } => point_sphere_distance(*center, *r, p),
495 SignedDistanceField::Box { half } => {
496 let q = [
498 p[0].abs() - half[0],
499 p[1].abs() - half[1],
500 p[2].abs() - half[2],
501 ];
502 let q_pos = [q[0].max(0.0), q[1].max(0.0), q[2].max(0.0)];
503 let outside = vlen(q_pos);
504 let inside = q[0].max(q[1]).max(q[2]).min(0.0);
505 outside + inside
506 }
507 SignedDistanceField::Capsule { a, b, r } => point_capsule_distance(*a, *b, *r, p),
508 SignedDistanceField::Cylinder { axis, r, h } => {
509 let ax = vnormalize(*axis);
511 let proj = vdot(p, ax);
512 let half_h = h / 2.0;
513 let radial_pt = vsub(p, vscale(ax, proj));
514 let rd = vlen(radial_pt) - r;
515 let yd = proj.abs() - half_h;
516 let outside_r = rd.max(0.0);
518 let outside_y = yd.max(0.0);
519 let outside = (outside_r * outside_r + outside_y * outside_y).sqrt();
520 let inside = rd.max(yd).min(0.0);
521 outside + inside
522 }
523 }
524 }
525}
526
527#[inline]
541pub fn sdf_union(a: f64, b: f64) -> f64 {
542 a.min(b)
543}
544
545#[inline]
555pub fn sdf_intersect(a: f64, b: f64) -> f64 {
556 a.max(b)
557}
558
559#[inline]
569pub fn sdf_subtract(a: f64, b: f64) -> f64 {
570 a.max(-b)
571}
572
573pub const PROXIMITY_CACHE_CAPACITY: usize = 64;
579
580#[derive(Debug, Clone, Copy)]
582pub struct ProximityCacheEntry {
583 pub id_a: u32,
585 pub id_b: u32,
587 pub point_a: [f64; 3],
589 pub point_b: [f64; 3],
591 pub distance: f64,
593 pub feature_a: ClosestFeature,
595 pub feature_b: ClosestFeature,
597 pub stamp: u64,
599}
600
601#[derive(Debug, Clone)]
617pub struct ProximityCache {
618 entries: Vec<ProximityCacheEntry>,
619}
620
621impl Default for ProximityCache {
622 fn default() -> Self {
623 Self::new()
624 }
625}
626
627impl ProximityCache {
628 pub fn new() -> Self {
630 Self {
631 entries: Vec::with_capacity(PROXIMITY_CACHE_CAPACITY),
632 }
633 }
634
635 pub fn len(&self) -> usize {
637 self.entries.len()
638 }
639
640 pub fn is_empty(&self) -> bool {
642 self.entries.is_empty()
643 }
644
645 pub fn get(&self, id_a: u32, id_b: u32) -> Option<&ProximityCacheEntry> {
649 let (lo, hi) = if id_a <= id_b {
650 (id_a, id_b)
651 } else {
652 (id_b, id_a)
653 };
654 self.entries.iter().find(|e| e.id_a == lo && e.id_b == hi)
655 }
656
657 pub fn insert(
661 &mut self,
662 id_a: u32,
663 id_b: u32,
664 point_a: [f64; 3],
665 point_b: [f64; 3],
666 distance: f64,
667 feature_a: ClosestFeature,
668 feature_b: ClosestFeature,
669 stamp: u64,
670 ) {
671 let (lo, hi, pa, pb, fa, fb) = if id_a <= id_b {
672 (id_a, id_b, point_a, point_b, feature_a, feature_b)
673 } else {
674 (id_b, id_a, point_b, point_a, feature_b, feature_a)
675 };
676
677 if let Some(e) = self
679 .entries
680 .iter_mut()
681 .find(|e| e.id_a == lo && e.id_b == hi)
682 {
683 e.point_a = pa;
684 e.point_b = pb;
685 e.distance = distance;
686 e.feature_a = fa;
687 e.feature_b = fb;
688 e.stamp = stamp;
689 return;
690 }
691
692 let entry = ProximityCacheEntry {
693 id_a: lo,
694 id_b: hi,
695 point_a: pa,
696 point_b: pb,
697 distance,
698 feature_a: fa,
699 feature_b: fb,
700 stamp,
701 };
702
703 if self.entries.len() < PROXIMITY_CACHE_CAPACITY {
704 self.entries.push(entry);
705 } else {
706 let oldest_idx = self
708 .entries
709 .iter()
710 .enumerate()
711 .min_by_key(|(_, e)| e.stamp)
712 .map(|(i, _)| i)
713 .unwrap_or(0);
714 self.entries[oldest_idx] = entry;
715 }
716 }
717
718 pub fn remove(&mut self, id_a: u32, id_b: u32) {
720 let (lo, hi) = if id_a <= id_b {
721 (id_a, id_b)
722 } else {
723 (id_b, id_a)
724 };
725 self.entries.retain(|e| !(e.id_a == lo && e.id_b == hi));
726 }
727
728 pub fn evict_stale(&mut self, min_stamp: u64) {
733 self.entries.retain(|e| e.stamp >= min_stamp);
734 }
735}
736
737#[derive(Debug, Clone, Copy)]
743pub struct TriTriResult {
744 pub point_a: [f64; 3],
746 pub point_b: [f64; 3],
748 pub distance: f64,
750}
751
752pub fn triangle_triangle_distance(
769 a0: [f64; 3],
770 a1: [f64; 3],
771 a2: [f64; 3],
772 b0: [f64; 3],
773 b1: [f64; 3],
774 b2: [f64; 3],
775) -> TriTriResult {
776 let mut best_dist = f64::MAX;
777 let mut best_pa = a0;
778 let mut best_pb = b0;
779
780 macro_rules! try_pt_tri {
782 ($pt:expr, $q0:expr, $q1:expr, $q2:expr, $is_a:expr) => {{
783 let r = point_triangle_closest($q0, $q1, $q2, $pt);
784 if r.distance < best_dist {
785 best_dist = r.distance;
786 if $is_a {
787 best_pa = $pt;
788 best_pb = r.position;
789 } else {
790 best_pa = r.position;
791 best_pb = $pt;
792 }
793 }
794 }};
795 }
796
797 try_pt_tri!(a0, b0, b1, b2, true);
799 try_pt_tri!(a1, b0, b1, b2, true);
800 try_pt_tri!(a2, b0, b1, b2, true);
801
802 try_pt_tri!(b0, a0, a1, a2, false);
804 try_pt_tri!(b1, a0, a1, a2, false);
805 try_pt_tri!(b2, a0, a1, a2, false);
806
807 let edges_a = [(a0, a1), (a1, a2), (a2, a0)];
809 let edges_b = [(b0, b1), (b1, b2), (b2, b0)];
810 for &(ea, eb) in &edges_a {
811 for &(ec, ed) in &edges_b {
812 let r = segment_segment_closest(ea, eb, ec, ed);
813 if r.distance < best_dist {
814 best_dist = r.distance;
815 best_pa = r.point_a;
816 best_pb = r.point_b;
817 }
818 }
819 }
820
821 TriTriResult {
822 point_a: best_pa,
823 point_b: best_pb,
824 distance: best_dist,
825 }
826}
827
828pub fn sdf_smooth_union(a: f64, b: f64, k: f64) -> f64 {
843 let h = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
844 vlerp([b, 0.0, 0.0], [a, 0.0, 0.0], h)[0] - k * h * (1.0 - h)
845}
846
847pub fn point_box_closest(min: [f64; 3], max: [f64; 3], p: [f64; 3]) -> [f64; 3] {
863 [
864 p[0].clamp(min[0], max[0]),
865 p[1].clamp(min[1], max[1]),
866 p[2].clamp(min[2], max[2]),
867 ]
868}
869
870pub fn point_in_triangle_barycentric(
887 v0: [f64; 3],
888 v1: [f64; 3],
889 v2: [f64; 3],
890 p: [f64; 3],
891) -> Option<(f64, f64)> {
892 let ab = vsub(v1, v0);
893 let ac = vsub(v2, v0);
894 let ap = vsub(p, v0);
895 let n = vcross(ab, ac);
896 let area2 = vlen(n);
897 if area2 < 1e-300 {
898 return None;
899 }
900 let inv_area2 = 1.0 / area2;
901 let u = vdot(vcross(ap, ac), n) * inv_area2;
902 let v = vdot(vcross(ab, ap), n) * inv_area2;
903 if u >= 0.0 && v >= 0.0 && u + v <= 1.0 {
904 Some((u, v))
905 } else {
906 None
907 }
908}
909
910pub fn point_plane_distance(normal: [f64; 3], offset_d: f64, p: [f64; 3]) -> f64 {
926 vdot(normal, p) - offset_d
927}
928
929pub fn point_segment_dist_sq(a: [f64; 3], b: [f64; 3], p: [f64; 3]) -> f64 {
943 let res = point_segment_closest(a, b, p);
944 res.distance * res.distance
945}
946
947#[cfg(test)]
952mod tests {
953 use super::*;
954
955 const EPS: f64 = 1e-9;
956
957 #[test]
960 fn test_point_segment_at_start() {
961 let r = point_segment_closest([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]);
962 assert!((r.u_param - 0.0).abs() < EPS, "t should be 0 at start");
963 assert!((r.distance - 1.0).abs() < EPS);
964 assert_eq!(r.feature, ClosestFeature::Vertex);
965 }
966
967 #[test]
968 fn test_point_segment_at_end() {
969 let r = point_segment_closest([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]);
970 assert!((r.u_param - 1.0).abs() < EPS, "t should be 1 at end");
971 assert!((r.distance - 1.0).abs() < EPS);
972 assert_eq!(r.feature, ClosestFeature::Vertex);
973 }
974
975 #[test]
976 fn test_point_segment_midpoint() {
977 let r = point_segment_closest([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 0.0]);
978 assert!((r.u_param - 0.5).abs() < EPS);
979 assert!((r.distance - 1.0).abs() < EPS);
980 assert_eq!(r.feature, ClosestFeature::Edge);
981 }
982
983 #[test]
984 fn test_point_segment_degenerate() {
985 let r = point_segment_closest([3.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
987 assert!((r.distance - 3.0).abs() < EPS);
988 }
989
990 #[test]
993 fn test_point_triangle_vertex_v0() {
994 let r = point_triangle_closest(
995 [0.0, 0.0, 0.0],
996 [1.0, 0.0, 0.0],
997 [0.0, 1.0, 0.0],
998 [-1.0, -1.0, 0.0],
999 );
1000 assert_eq!(r.feature, ClosestFeature::Vertex);
1001 assert!((r.position[0] - 0.0).abs() < EPS && (r.position[1] - 0.0).abs() < EPS);
1002 }
1003
1004 #[test]
1005 fn test_point_triangle_vertex_v1() {
1006 let r = point_triangle_closest(
1007 [0.0, 0.0, 0.0],
1008 [2.0, 0.0, 0.0],
1009 [0.0, 2.0, 0.0],
1010 [4.0, 0.0, 0.0],
1011 );
1012 assert_eq!(r.feature, ClosestFeature::Vertex);
1013 assert!((r.position[0] - 2.0).abs() < EPS);
1014 }
1015
1016 #[test]
1017 fn test_point_triangle_vertex_v2() {
1018 let r = point_triangle_closest(
1019 [0.0, 0.0, 0.0],
1020 [2.0, 0.0, 0.0],
1021 [0.0, 2.0, 0.0],
1022 [0.0, 4.0, 0.0],
1023 );
1024 assert_eq!(r.feature, ClosestFeature::Vertex);
1025 assert!((r.position[1] - 2.0).abs() < EPS);
1026 }
1027
1028 #[test]
1029 fn test_point_triangle_edge_v0v1() {
1030 let r = point_triangle_closest(
1031 [0.0, 0.0, 0.0],
1032 [2.0, 0.0, 0.0],
1033 [0.0, 2.0, 0.0],
1034 [1.0, -1.0, 0.0],
1035 );
1036 assert_eq!(r.feature, ClosestFeature::Edge);
1037 assert!((r.position[1] - 0.0).abs() < EPS);
1038 }
1039
1040 #[test]
1041 fn test_point_triangle_face_interior() {
1042 let r = point_triangle_closest(
1043 [0.0, 0.0, 0.0],
1044 [3.0, 0.0, 0.0],
1045 [0.0, 3.0, 0.0],
1046 [1.0, 1.0, 0.0],
1047 );
1048 assert_eq!(r.feature, ClosestFeature::Face);
1049 assert!((r.distance).abs() < EPS);
1050 }
1051
1052 #[test]
1053 fn test_point_triangle_above_face() {
1054 let r = point_triangle_closest(
1056 [0.0, 0.0, 0.0],
1057 [3.0, 0.0, 0.0],
1058 [0.0, 3.0, 0.0],
1059 [1.0, 1.0, 5.0],
1060 );
1061 assert!((r.distance - 5.0).abs() < EPS);
1062 }
1063
1064 #[test]
1067 fn test_segseg_crossing() {
1068 let r = segment_segment_closest(
1070 [0.0, 0.0, 0.0],
1071 [2.0, 0.0, 0.0],
1072 [1.0, -1.0, 0.0],
1073 [1.0, 1.0, 0.0],
1074 );
1075 assert!(r.distance < EPS, "crossing segments: dist={}", r.distance);
1076 }
1077
1078 #[test]
1079 fn test_segseg_parallel() {
1080 let r = segment_segment_closest(
1082 [0.0, 0.0, 0.0],
1083 [2.0, 0.0, 0.0],
1084 [0.0, 1.0, 0.0],
1085 [2.0, 1.0, 0.0],
1086 );
1087 assert!(
1088 (r.distance - 1.0).abs() < EPS,
1089 "parallel dist should be 1.0"
1090 );
1091 }
1092
1093 #[test]
1094 fn test_segseg_skew() {
1095 let r = segment_segment_closest(
1097 [0.0, 0.0, 0.0],
1098 [1.0, 0.0, 0.0],
1099 [0.5, 1.0, 1.0],
1100 [0.5, 1.0, -1.0],
1101 );
1102 assert!((r.distance - 1.0).abs() < EPS);
1104 }
1105
1106 #[test]
1107 fn test_segseg_endpoint_to_endpoint() {
1108 let r = segment_segment_closest(
1109 [0.0, 0.0, 0.0],
1110 [1.0, 0.0, 0.0],
1111 [2.0, 0.0, 0.0],
1112 [3.0, 0.0, 0.0],
1113 );
1114 assert!((r.distance - 1.0).abs() < EPS);
1115 assert!((r.s - 1.0).abs() < EPS);
1116 assert!((r.t - 0.0).abs() < EPS);
1117 }
1118
1119 #[test]
1122 fn test_point_box_inside() {
1123 let d = point_box_distance([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]);
1124 assert!(d < 0.0, "origin should be inside unit box");
1125 assert!((d - (-1.0)).abs() < EPS);
1126 }
1127
1128 #[test]
1129 fn test_point_box_outside_face() {
1130 let d = point_box_distance([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], [2.0, 0.0, 0.0]);
1131 assert!((d - 1.0).abs() < EPS);
1132 }
1133
1134 #[test]
1135 fn test_point_box_outside_corner() {
1136 let d = point_box_distance([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, 2.0, 2.0]);
1137 let expected = (3.0_f64).sqrt();
1138 assert!((d - expected).abs() < EPS);
1139 }
1140
1141 #[test]
1142 fn test_point_box_on_surface() {
1143 let d = point_box_distance([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], [1.0, 0.0, 0.0]);
1144 assert!(
1145 d.abs() < EPS,
1146 "point on surface should have zero signed dist"
1147 );
1148 }
1149
1150 #[test]
1153 fn test_point_sphere_inside() {
1154 let d = point_sphere_distance([0.0, 0.0, 0.0], 2.0, [1.0, 0.0, 0.0]);
1155 assert!((d - (-1.0)).abs() < EPS);
1156 }
1157
1158 #[test]
1159 fn test_point_sphere_outside() {
1160 let d = point_sphere_distance([0.0, 0.0, 0.0], 1.0, [3.0, 0.0, 0.0]);
1161 assert!((d - 2.0).abs() < EPS);
1162 }
1163
1164 #[test]
1165 fn test_point_sphere_on_surface() {
1166 let d = point_sphere_distance([1.0, 2.0, 3.0], 1.5, [2.5, 2.0, 3.0]);
1167 assert!(d.abs() < EPS);
1168 }
1169
1170 #[test]
1173 fn test_point_capsule_inside_cylinder_portion() {
1174 let d = point_capsule_distance([0.0, 0.0, 0.0], [0.0, 4.0, 0.0], 1.0, [0.0, 2.0, 0.0]);
1175 assert!((d - (-1.0)).abs() < EPS);
1176 }
1177
1178 #[test]
1179 fn test_point_capsule_outside_end_cap() {
1180 let d = point_capsule_distance([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.5, [0.0, 3.0, 0.0]);
1181 assert!((d - 1.5).abs() < EPS);
1183 }
1184
1185 #[test]
1188 fn test_sdf_sphere_inside() {
1189 let sdf = SignedDistanceField::Sphere {
1190 center: [0.0; 3],
1191 r: 2.0,
1192 };
1193 assert!(sdf.eval([0.0, 0.0, 0.0]) < 0.0);
1194 }
1195
1196 #[test]
1197 fn test_sdf_sphere_outside() {
1198 let sdf = SignedDistanceField::Sphere {
1199 center: [0.0; 3],
1200 r: 1.0,
1201 };
1202 let d = sdf.eval([2.0, 0.0, 0.0]);
1203 assert!((d - 1.0).abs() < EPS);
1204 }
1205
1206 #[test]
1207 fn test_sdf_box_center() {
1208 let sdf = SignedDistanceField::Box {
1209 half: [1.0, 1.0, 1.0],
1210 };
1211 let d = sdf.eval([0.0, 0.0, 0.0]);
1212 assert!(d < 0.0);
1213 assert!((d - (-1.0)).abs() < EPS);
1214 }
1215
1216 #[test]
1217 fn test_sdf_box_corner() {
1218 let sdf = SignedDistanceField::Box {
1219 half: [1.0, 1.0, 1.0],
1220 };
1221 let d = sdf.eval([2.0, 2.0, 2.0]);
1222 let expected = (3.0_f64).sqrt();
1223 assert!((d - expected).abs() < EPS);
1224 }
1225
1226 #[test]
1227 fn test_sdf_box_face() {
1228 let sdf = SignedDistanceField::Box {
1229 half: [1.0, 1.0, 1.0],
1230 };
1231 let d = sdf.eval([3.0, 0.0, 0.0]);
1232 assert!((d - 2.0).abs() < EPS);
1233 }
1234
1235 #[test]
1236 fn test_sdf_capsule_inside() {
1237 let sdf = SignedDistanceField::Capsule {
1238 a: [0.0, 0.0, 0.0],
1239 b: [0.0, 2.0, 0.0],
1240 r: 1.0,
1241 };
1242 assert!(sdf.eval([0.0, 1.0, 0.0]) < 0.0);
1243 }
1244
1245 #[test]
1246 fn test_sdf_capsule_outside() {
1247 let sdf = SignedDistanceField::Capsule {
1248 a: [0.0, 0.0, 0.0],
1249 b: [0.0, 2.0, 0.0],
1250 r: 0.5,
1251 };
1252 let d = sdf.eval([0.0, 5.0, 0.0]);
1253 assert!((d - 2.5).abs() < EPS);
1254 }
1255
1256 #[test]
1257 fn test_sdf_cylinder_inside() {
1258 let sdf = SignedDistanceField::Cylinder {
1259 axis: [0.0, 1.0, 0.0],
1260 r: 1.0,
1261 h: 4.0,
1262 };
1263 assert!(sdf.eval([0.0, 0.0, 0.0]) < 0.0);
1264 }
1265
1266 #[test]
1267 fn test_sdf_cylinder_outside_radially() {
1268 let sdf = SignedDistanceField::Cylinder {
1269 axis: [0.0, 1.0, 0.0],
1270 r: 1.0,
1271 h: 4.0,
1272 };
1273 let d = sdf.eval([3.0, 0.0, 0.0]);
1274 assert!((d - 2.0).abs() < EPS);
1275 }
1276
1277 #[test]
1280 fn test_csg_union_picks_smaller() {
1281 assert!((sdf_union(1.0, -0.5) - (-0.5)).abs() < EPS);
1282 assert!((sdf_union(-0.3, 0.7) - (-0.3)).abs() < EPS);
1283 }
1284
1285 #[test]
1286 fn test_csg_intersect_picks_larger() {
1287 assert!((sdf_intersect(-1.0, 0.5) - 0.5).abs() < EPS);
1288 assert!((sdf_intersect(-0.5, -0.3) - (-0.3)).abs() < EPS);
1289 }
1290
1291 #[test]
1292 fn test_csg_subtract() {
1293 assert!((sdf_subtract(-0.5, -0.3) - 0.3).abs() < EPS);
1295 assert!((sdf_subtract(-1.0, 2.0) - (-1.0)).abs() < EPS);
1296 }
1297
1298 #[test]
1299 fn test_csg_two_spheres_union() {
1300 let s1 = SignedDistanceField::Sphere {
1301 center: [-2.0, 0.0, 0.0],
1302 r: 1.5,
1303 };
1304 let s2 = SignedDistanceField::Sphere {
1305 center: [2.0, 0.0, 0.0],
1306 r: 1.5,
1307 };
1308 let p = [0.0, 0.0, 0.0];
1310 let combined = sdf_union(s1.eval(p), s2.eval(p));
1311 assert!((combined - 0.5).abs() < EPS);
1313 }
1314
1315 #[test]
1318 fn test_proximity_cache_insert_and_get() {
1319 let mut cache = ProximityCache::new();
1320 cache.insert(
1321 1,
1322 2,
1323 [0.0; 3],
1324 [1.0, 0.0, 0.0],
1325 1.0,
1326 ClosestFeature::Face,
1327 ClosestFeature::Face,
1328 0,
1329 );
1330 let e = cache.get(1, 2).expect("entry should exist");
1331 assert!((e.distance - 1.0).abs() < EPS);
1332 }
1333
1334 #[test]
1335 fn test_proximity_cache_order_independent() {
1336 let mut cache = ProximityCache::new();
1337 cache.insert(
1338 5,
1339 3,
1340 [0.0; 3],
1341 [1.0, 0.0, 0.0],
1342 2.0,
1343 ClosestFeature::Edge,
1344 ClosestFeature::Vertex,
1345 0,
1346 );
1347 assert!(cache.get(3, 5).is_some());
1348 assert!(cache.get(5, 3).is_some());
1349 }
1350
1351 #[test]
1352 fn test_proximity_cache_remove() {
1353 let mut cache = ProximityCache::new();
1354 cache.insert(
1355 1,
1356 2,
1357 [0.0; 3],
1358 [1.0, 0.0, 0.0],
1359 1.0,
1360 ClosestFeature::Face,
1361 ClosestFeature::Face,
1362 0,
1363 );
1364 cache.remove(1, 2);
1365 assert!(cache.get(1, 2).is_none());
1366 }
1367
1368 #[test]
1369 fn test_proximity_cache_evict_stale() {
1370 let mut cache = ProximityCache::new();
1371 cache.insert(
1372 1,
1373 2,
1374 [0.0; 3],
1375 [1.0, 0.0, 0.0],
1376 1.0,
1377 ClosestFeature::Face,
1378 ClosestFeature::Face,
1379 0,
1380 );
1381 cache.insert(
1382 3,
1383 4,
1384 [0.0; 3],
1385 [1.0, 0.0, 0.0],
1386 1.0,
1387 ClosestFeature::Face,
1388 ClosestFeature::Face,
1389 10,
1390 );
1391 cache.evict_stale(5);
1392 assert!(cache.get(1, 2).is_none());
1393 assert!(cache.get(3, 4).is_some());
1394 }
1395
1396 #[test]
1397 fn test_proximity_cache_capacity_evicts_oldest() {
1398 let mut cache = ProximityCache::new();
1399 for i in 0..PROXIMITY_CACHE_CAPACITY as u32 {
1400 cache.insert(
1401 i * 2,
1402 i * 2 + 1,
1403 [0.0; 3],
1404 [1.0, 0.0, 0.0],
1405 1.0,
1406 ClosestFeature::Face,
1407 ClosestFeature::Face,
1408 i as u64,
1409 );
1410 }
1411 assert_eq!(cache.len(), PROXIMITY_CACHE_CAPACITY);
1412 cache.insert(
1414 200,
1415 201,
1416 [0.0; 3],
1417 [1.0, 0.0, 0.0],
1418 1.0,
1419 ClosestFeature::Face,
1420 ClosestFeature::Face,
1421 1000,
1422 );
1423 assert_eq!(cache.len(), PROXIMITY_CACHE_CAPACITY);
1424 assert!(cache.get(0, 1).is_none());
1425 }
1426
1427 #[test]
1430 fn test_tri_tri_parallel_offset() {
1431 let r = triangle_triangle_distance(
1432 [0.0, 0.0, 0.0],
1433 [1.0, 0.0, 0.0],
1434 [0.0, 1.0, 0.0],
1435 [0.0, 0.0, 3.0],
1436 [1.0, 0.0, 3.0],
1437 [0.0, 1.0, 3.0],
1438 );
1439 assert!((r.distance - 3.0).abs() < EPS);
1440 }
1441
1442 #[test]
1443 fn test_tri_tri_touching() {
1444 let r = triangle_triangle_distance(
1445 [0.0, 0.0, 0.0],
1446 [1.0, 0.0, 0.0],
1447 [0.0, 1.0, 0.0],
1448 [0.0, 0.0, 0.0],
1449 [-1.0, 0.0, 0.0],
1450 [0.0, -1.0, 0.0],
1451 );
1452 assert!(r.distance < EPS, "touching at origin, dist={}", r.distance);
1453 }
1454
1455 #[test]
1458 fn test_point_box_closest_outside() {
1459 let cp = point_box_closest([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0], [3.0, 0.0, 0.0]);
1460 assert!((cp[0] - 1.0).abs() < EPS);
1461 assert!((cp[1] - 0.0).abs() < EPS);
1462 }
1463
1464 #[test]
1467 fn test_sdf_smooth_union_equals_min_when_far_apart() {
1468 let a = -10.0_f64;
1470 let b = 10.0_f64;
1471 let su = sdf_smooth_union(a, b, 0.1);
1472 let u = sdf_union(a, b);
1473 assert!((su - u).abs() < 0.01, "smooth_union far apart should ≈ min");
1474 }
1475}