1fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
15 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
16}
17
18fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
19 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
20}
21
22fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
23 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
24}
25
26fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
27 [a[0] * s, a[1] * s, a[2] * s]
28}
29
30fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
31 [
32 a[1] * b[2] - a[2] * b[1],
33 a[2] * b[0] - a[0] * b[2],
34 a[0] * b[1] - a[1] * b[0],
35 ]
36}
37
38fn len3(a: [f64; 3]) -> f64 {
39 dot3(a, a).sqrt()
40}
41
42fn normalize3(a: [f64; 3]) -> [f64; 3] {
43 let l = len3(a).max(1e-15);
44 scale3(a, 1.0 / l)
45}
46
47fn lerp3(a: [f64; 3], b: [f64; 3], t: f64) -> [f64; 3] {
48 add3(scale3(a, 1.0 - t), scale3(b, t))
49}
50
51fn dist3(a: [f64; 3], b: [f64; 3]) -> f64 {
52 len3(sub3(a, b))
53}
54
55#[derive(Debug, Clone)]
61pub struct SpatialQueryResult {
62 pub distance: f64,
64 pub witness_point_a: [f64; 3],
66 pub witness_point_b: [f64; 3],
68 pub normal: [f64; 3],
70 pub valid: bool,
72}
73
74impl SpatialQueryResult {
75 pub fn new(
77 distance: f64,
78 witness_point_a: [f64; 3],
79 witness_point_b: [f64; 3],
80 normal: [f64; 3],
81 ) -> Self {
82 Self {
83 distance,
84 witness_point_a,
85 witness_point_b,
86 normal,
87 valid: true,
88 }
89 }
90
91 pub fn invalid() -> Self {
93 Self {
94 distance: f64::INFINITY,
95 witness_point_a: [0.0; 3],
96 witness_point_b: [0.0; 3],
97 normal: [0.0, 0.0, 1.0],
98 valid: false,
99 }
100 }
101}
102
103pub struct ProximityQuery;
109
110impl ProximityQuery {
111 pub fn closest_point_on_segment(a: [f64; 3], b: [f64; 3], p: [f64; 3]) -> ([f64; 3], f64) {
115 let ab = sub3(b, a);
116 let ap = sub3(p, a);
117 let denom = dot3(ab, ab);
118 if denom < 1e-15 {
119 return (a, 0.0);
120 }
121 let t = (dot3(ap, ab) / denom).clamp(0.0, 1.0);
122 (lerp3(a, b, t), t)
123 }
124
125 pub fn closest_point_to_aabb(lo: [f64; 3], hi: [f64; 3], p: [f64; 3]) -> [f64; 3] {
127 [
128 p[0].clamp(lo[0], hi[0]),
129 p[1].clamp(lo[1], hi[1]),
130 p[2].clamp(lo[2], hi[2]),
131 ]
132 }
133
134 pub fn point_in_convex_hull(p: [f64; 3], planes: &[([f64; 3], f64)]) -> bool {
138 planes.iter().all(|(n, d)| dot3(*n, p) <= *d + 1e-9)
139 }
140
141 pub fn signed_dist_to_plane(p: [f64; 3], normal: [f64; 3], offset: f64) -> f64 {
145 dot3(normal, p) - offset
146 }
147
148 pub fn closest_point_on_sphere(center: [f64; 3], radius: f64, p: [f64; 3]) -> [f64; 3] {
150 let d = sub3(p, center);
151 let len = len3(d).max(1e-15);
152 add3(center, scale3(d, radius / len))
153 }
154}
155
156pub struct SegmentSegmentDist;
162
163impl SegmentSegmentDist {
164 pub fn query(
168 p1: [f64; 3],
169 p2: [f64; 3],
170 p3: [f64; 3],
171 p4: [f64; 3],
172 ) -> (f64, [f64; 3], [f64; 3]) {
173 let d1 = sub3(p2, p1);
174 let d2 = sub3(p4, p3);
175 let r = sub3(p1, p3);
176
177 let a = dot3(d1, d1); let e = dot3(d2, d2); let f = dot3(d2, r);
180
181 let (s, t);
182 if a < 1e-15 && e < 1e-15 {
183 return (dist3(p1, p3), p1, p3);
185 }
186 if a < 1e-15 {
187 s = 0.0;
188 t = (f / e).clamp(0.0, 1.0);
189 } else {
190 let c = dot3(d1, r);
191 if e < 1e-15 {
192 t = 0.0;
193 s = (-c / a).clamp(0.0, 1.0);
194 } else {
195 let b = dot3(d1, d2);
196 let denom = a * e - b * b;
197 s = if denom > 1e-15 {
198 ((b * f - c * e) / denom).clamp(0.0, 1.0)
199 } else {
200 0.0
201 };
202 t = (b * s + f) / e;
203 if t < 0.0 {
204 let t_clamp = 0.0_f64;
205 let s_clamp = (-c / a).clamp(0.0, 1.0);
206 let wp1 = lerp3(p1, p2, s_clamp);
207 let wp2 = lerp3(p3, p4, t_clamp);
208 return (dist3(wp1, wp2), wp1, wp2);
209 } else if t > 1.0 {
210 let t_clamp = 1.0_f64;
211 let s_clamp = ((b - c) / a).clamp(0.0, 1.0);
212 let wp1 = lerp3(p1, p2, s_clamp);
213 let wp2 = lerp3(p3, p4, t_clamp);
214 return (dist3(wp1, wp2), wp1, wp2);
215 }
216 }
217 }
218 let wp1 = lerp3(p1, p2, s);
219 let wp2 = lerp3(p3, p4, t);
220 (dist3(wp1, wp2), wp1, wp2)
221 }
222}
223
224pub struct PointTriangleDist;
230
231impl PointTriangleDist {
232 pub fn closest_point(
236 p: [f64; 3],
237 v0: [f64; 3],
238 v1: [f64; 3],
239 v2: [f64; 3],
240 ) -> ([f64; 3], [f64; 3]) {
241 let ab = sub3(v1, v0);
242 let ac = sub3(v2, v0);
243 let ap = sub3(p, v0);
244
245 let d1 = dot3(ab, ap);
246 let d2 = dot3(ac, ap);
247 if d1 <= 0.0 && d2 <= 0.0 {
248 return (v0, [1.0, 0.0, 0.0]);
249 }
250
251 let bp = sub3(p, v1);
252 let d3 = dot3(ab, bp);
253 let d4 = dot3(ac, bp);
254 if d3 >= 0.0 && d4 <= d3 {
255 return (v1, [0.0, 1.0, 0.0]);
256 }
257
258 let cp = sub3(p, v2);
259 let d5 = dot3(ab, cp);
260 let d6 = dot3(ac, cp);
261 if d6 >= 0.0 && d5 <= d6 {
262 return (v2, [0.0, 0.0, 1.0]);
263 }
264
265 let vc = d1 * d4 - d3 * d2;
266 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
267 let v = d1 / (d1 - d3);
268 let q = add3(v0, scale3(ab, v));
269 return (q, [1.0 - v, v, 0.0]);
270 }
271
272 let vb = d5 * d2 - d1 * d6;
273 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
274 let w = d2 / (d2 - d6);
275 let q = add3(v0, scale3(ac, w));
276 return (q, [1.0 - w, 0.0, w]);
277 }
278
279 let va = d3 * d6 - d5 * d4;
280 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
281 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
282 let q = lerp3(v1, v2, w);
283 return (q, [0.0, 1.0 - w, w]);
284 }
285
286 let denom = 1.0 / (va + vb + vc);
287 let v = vb * denom;
288 let w = vc * denom;
289 let u = 1.0 - v - w;
290 let q = add3(add3(scale3(v0, u), scale3(v1, v)), scale3(v2, w));
291 (q, [u, v, w])
292 }
293
294 pub fn dist_sq(p: [f64; 3], v0: [f64; 3], v1: [f64; 3], v2: [f64; 3]) -> f64 {
296 let (q, _) = Self::closest_point(p, v0, v1, v2);
297 let d = sub3(p, q);
298 dot3(d, d)
299 }
300
301 pub fn normal(v0: [f64; 3], v1: [f64; 3], v2: [f64; 3]) -> [f64; 3] {
303 cross3(sub3(v1, v0), sub3(v2, v0))
304 }
305}
306
307pub struct SignedDistance;
313
314impl SignedDistance {
315 pub fn sphere(p: [f64; 3], center: [f64; 3], radius: f64) -> f64 {
317 dist3(p, center) - radius
318 }
319
320 pub fn box_sdf(p: [f64; 3], h: [f64; 3]) -> f64 {
322 let q = [p[0].abs() - h[0], p[1].abs() - h[1], p[2].abs() - h[2]];
323 let outer = len3([q[0].max(0.0), q[1].max(0.0), q[2].max(0.0)]);
324 let inner = q[0].max(q[1]).max(q[2]).min(0.0);
325 outer + inner
326 }
327
328 pub fn cylinder(p: [f64; 3], radius: f64) -> f64 {
330 let r = (p[0] * p[0] + p[2] * p[2]).sqrt();
331 r - radius
332 }
333
334 pub fn union(d1: f64, d2: f64) -> f64 {
336 d1.min(d2)
337 }
338
339 pub fn intersection(d1: f64, d2: f64) -> f64 {
341 d1.max(d2)
342 }
343
344 pub fn subtraction(d1: f64, d2: f64) -> f64 {
346 d1.max(-d2)
347 }
348
349 pub fn smooth_union(d1: f64, d2: f64, k: f64) -> f64 {
351 let h = (0.5 + 0.5 * (d2 - d1) / k).clamp(0.0, 1.0);
352 d1 * h + d2 * (1.0 - h) - k * h * (1.0 - h)
353 }
354
355 pub fn convex_polygon_2d(p: [f64; 2], verts: &[[f64; 2]]) -> f64 {
360 let n = verts.len();
361 if n == 0 {
362 return f64::INFINITY;
363 }
364 let mut min_dist = f64::INFINITY;
365 let mut crossings = 0i32;
367 for i in 0..n {
368 let a = verts[i];
369 let b = verts[(i + 1) % n];
370 let e = [b[0] - a[0], b[1] - a[1]];
371 let w = [p[0] - a[0], p[1] - a[1]];
372 let denom = e[0] * e[0] + e[1] * e[1];
374 let t = if denom < 1e-15 {
375 0.0
376 } else {
377 ((w[0] * e[0] + w[1] * e[1]) / denom).clamp(0.0, 1.0)
378 };
379 let closest = [a[0] + t * e[0], a[1] + t * e[1]];
380 let dx = p[0] - closest[0];
381 let dy = p[1] - closest[1];
382 let d = (dx * dx + dy * dy).sqrt();
383 if d < min_dist {
384 min_dist = d;
385 }
386 let a_above = a[1] > p[1];
389 let b_above = b[1] > p[1];
390 if a_above != b_above {
391 let t_cross = (p[1] - a[1]) / (b[1] - a[1]);
394 let x_cross = a[0] + t_cross * (b[0] - a[0]);
395 if x_cross > p[0] {
396 crossings += 1;
397 }
398 }
399 }
400 let inside = crossings % 2 == 1;
401 if inside { -min_dist } else { min_dist }
402 }
403}
404
405pub struct CapsuleCapsuleDist;
413
414impl CapsuleCapsuleDist {
415 pub fn query(
419 a_base: [f64; 3],
420 a_tip: [f64; 3],
421 a_radius: f64,
422 b_base: [f64; 3],
423 b_tip: [f64; 3],
424 b_radius: f64,
425 ) -> SpatialQueryResult {
426 let (seg_dist, wa, wb) = SegmentSegmentDist::query(a_base, a_tip, b_base, b_tip);
427 let combined_radius = a_radius + b_radius;
428 let dist = seg_dist - combined_radius;
429 let normal = if seg_dist > 1e-12 {
430 normalize3(sub3(wa, wb))
431 } else {
432 [0.0, 1.0, 0.0]
433 };
434 let witness_a = add3(wa, scale3(normalize3(sub3(wb, wa)).map(|x| x), -a_radius));
435 let witness_b = add3(wb, scale3(normalize3(sub3(wa, wb)).map(|x| x), -b_radius));
436 SpatialQueryResult::new(dist, witness_a, witness_b, normal)
437 }
438}
439
440pub struct KnnQuery {
449 pub points: Vec<[f64; 3]>,
451}
452
453impl KnnQuery {
454 pub fn new(points: Vec<[f64; 3]>) -> Self {
456 Self { points }
457 }
458
459 pub fn knn(&self, p: [f64; 3], k: usize) -> Vec<(usize, f64)> {
463 let mut dists: Vec<(usize, f64)> = self
464 .points
465 .iter()
466 .enumerate()
467 .map(|(i, &q)| (i, dist3(p, q)))
468 .collect();
469 dists.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
470 dists.truncate(k);
471 dists
472 }
473
474 pub fn range_query(&self, p: [f64; 3], r: f64) -> Vec<usize> {
476 self.points
477 .iter()
478 .enumerate()
479 .filter(|&(_, q)| dist3(p, *q) <= r)
480 .map(|(i, _)| i)
481 .collect()
482 }
483
484 pub fn ball_query(&self, center: [f64; 3], radius: f64) -> Vec<usize> {
486 self.range_query(center, radius)
487 }
488}
489
490#[derive(Debug, Clone, Copy)]
496pub struct Ray {
497 pub origin: [f64; 3],
499 pub direction: [f64; 3],
501}
502
503impl Ray {
504 pub fn new(origin: [f64; 3], direction: [f64; 3]) -> Self {
506 Self {
507 origin,
508 direction: normalize3(direction),
509 }
510 }
511
512 pub fn at(&self, t: f64) -> [f64; 3] {
514 add3(self.origin, scale3(self.direction, t))
515 }
516}
517
518pub struct RayQuery;
520
521impl RayQuery {
522 pub fn ray_aabb(ray: &Ray, lo: [f64; 3], hi: [f64; 3]) -> Option<f64> {
526 let mut t_min = f64::NEG_INFINITY;
527 let mut t_max = f64::INFINITY;
528 for i in 0..3 {
529 let inv_d = 1.0 / ray.direction[i];
530 let t1 = (lo[i] - ray.origin[i]) * inv_d;
531 let t2 = (hi[i] - ray.origin[i]) * inv_d;
532 let (t1, t2) = if inv_d >= 0.0 { (t1, t2) } else { (t2, t1) };
533 t_min = t_min.max(t1);
534 t_max = t_max.min(t2);
535 }
536 if t_max >= t_min && t_max >= 0.0 {
537 Some(t_min.max(0.0))
538 } else {
539 None
540 }
541 }
542
543 pub fn ray_sphere(ray: &Ray, center: [f64; 3], radius: f64) -> Option<f64> {
545 let oc = sub3(ray.origin, center);
546 let a = dot3(ray.direction, ray.direction);
547 let b = 2.0 * dot3(oc, ray.direction);
548 let c = dot3(oc, oc) - radius * radius;
549 let disc = b * b - 4.0 * a * c;
550 if disc < 0.0 {
551 return None;
552 }
553 let sqrt_disc = disc.sqrt();
554 let t1 = (-b - sqrt_disc) / (2.0 * a);
555 let t2 = (-b + sqrt_disc) / (2.0 * a);
556 if t1 >= 0.0 {
557 Some(t1)
558 } else if t2 >= 0.0 {
559 Some(t2)
560 } else {
561 None
562 }
563 }
564
565 pub fn ray_triangle(
569 ray: &Ray,
570 v0: [f64; 3],
571 v1: [f64; 3],
572 v2: [f64; 3],
573 ) -> Option<(f64, f64, f64)> {
574 let edge1 = sub3(v1, v0);
575 let edge2 = sub3(v2, v0);
576 let h = cross3(ray.direction, edge2);
577 let a = dot3(edge1, h);
578 if a.abs() < 1e-12 {
579 return None; }
581 let f = 1.0 / a;
582 let s = sub3(ray.origin, v0);
583 let u = f * dot3(s, h);
584 if !(0.0..=1.0).contains(&u) {
585 return None;
586 }
587 let q = cross3(s, edge1);
588 let v = f * dot3(ray.direction, q);
589 if v < 0.0 || u + v > 1.0 {
590 return None;
591 }
592 let t = f * dot3(edge2, q);
593 if t < 1e-9 {
594 return None;
595 }
596 Some((t, u, v))
597 }
598
599 pub fn ray_capsule(ray: &Ray, cap_a: [f64; 3], cap_b: [f64; 3], radius: f64) -> Option<f64> {
601 let ab = sub3(cap_b, cap_a);
603 let ao = sub3(ray.origin, cap_a);
604 let dir = ray.direction;
605
606 let ab_len2 = dot3(ab, ab);
607 if ab_len2 < 1e-15 {
608 return Self::ray_sphere(ray, cap_a, radius);
610 }
611 let ab_hat = scale3(ab, 1.0 / ab_len2.sqrt());
612
613 let d_perp = sub3(dir, scale3(ab_hat, dot3(dir, ab_hat)));
615 let ao_perp = sub3(ao, scale3(ab_hat, dot3(ao, ab_hat)));
616
617 let a_coef = dot3(d_perp, d_perp);
618 let b_coef = 2.0 * dot3(d_perp, ao_perp);
619 let c_coef = dot3(ao_perp, ao_perp) - radius * radius;
620
621 let mut t_min = f64::INFINITY;
622
623 if a_coef > 1e-15 {
624 let disc = b_coef * b_coef - 4.0 * a_coef * c_coef;
625 if disc >= 0.0 {
626 let sqrt_d = disc.sqrt();
627 for sign in &[-1.0_f64, 1.0_f64] {
628 let t = (-b_coef + sign * sqrt_d) / (2.0 * a_coef);
629 if t >= 0.0 {
630 let hit = ray.at(t);
631 let along = dot3(sub3(hit, cap_a), ab_hat);
632 if along >= 0.0 && along <= ab_len2.sqrt() && t < t_min {
633 t_min = t;
634 }
635 }
636 }
637 }
638 }
639
640 for ¢er in &[cap_a, cap_b] {
642 if let Some(t) = Self::ray_sphere(ray, center, radius)
643 && t < t_min
644 {
645 t_min = t;
646 }
647 }
648
649 if t_min < f64::INFINITY {
650 Some(t_min)
651 } else {
652 None
653 }
654 }
655}
656
657pub struct PointMeshDist {
665 pub vertices: Vec<[f64; 3]>,
667 pub indices: Vec<usize>,
669}
670
671impl PointMeshDist {
672 pub fn new(vertices: Vec<[f64; 3]>, indices: Vec<usize>) -> Self {
674 assert!(indices.len().is_multiple_of(3));
675 Self { vertices, indices }
676 }
677
678 pub fn n_triangles(&self) -> usize {
680 self.indices.len() / 3
681 }
682
683 pub fn closest_point(&self, p: [f64; 3]) -> (f64, usize, [f64; 3]) {
687 let mut min_dist_sq = f64::INFINITY;
688 let mut best_tri = 0;
689 let mut best_pt = p;
690 for tri in 0..self.n_triangles() {
691 let v0 = self.vertices[self.indices[3 * tri]];
692 let v1 = self.vertices[self.indices[3 * tri + 1]];
693 let v2 = self.vertices[self.indices[3 * tri + 2]];
694 let (q, _) = PointTriangleDist::closest_point(p, v0, v1, v2);
695 let d2 = dot3(sub3(p, q), sub3(p, q));
696 if d2 < min_dist_sq {
697 min_dist_sq = d2;
698 best_tri = tri;
699 best_pt = q;
700 }
701 }
702 (min_dist_sq.sqrt(), best_tri, best_pt)
703 }
704}
705
706pub struct MeshMeshDist;
712
713impl MeshMeshDist {
714 pub fn query(
718 verts_a: &[[f64; 3]],
719 idx_a: &[usize],
720 verts_b: &[[f64; 3]],
721 idx_b: &[usize],
722 ) -> (f64, usize, usize, [f64; 3], [f64; 3]) {
723 let na = idx_a.len() / 3;
724 let nb = idx_b.len() / 3;
725 let mut min_dist = f64::INFINITY;
726 let mut best = (0usize, 0usize, [0.0f64; 3], [0.0f64; 3]);
727
728 for ta in 0..na {
729 let v0a = verts_a[idx_a[3 * ta]];
730 let v1a = verts_a[idx_a[3 * ta + 1]];
731 let v2a = verts_a[idx_a[3 * ta + 2]];
732 let centroid_a = scale3(add3(add3(v0a, v1a), v2a), 1.0 / 3.0);
734 for tb in 0..nb {
735 let v0b = verts_b[idx_b[3 * tb]];
736 let v1b = verts_b[idx_b[3 * tb + 1]];
737 let v2b = verts_b[idx_b[3 * tb + 2]];
738 let (q_b, _) = PointTriangleDist::closest_point(centroid_a, v0b, v1b, v2b);
739 let (q_a, _) = PointTriangleDist::closest_point(q_b, v0a, v1a, v2a);
740 let d = dist3(q_a, q_b);
741 if d < min_dist {
742 min_dist = d;
743 best = (ta, tb, q_a, q_b);
744 }
745 }
746 }
747 (min_dist, best.0, best.1, best.2, best.3)
748 }
749}
750
751pub struct SweptVolumeDist;
757
758impl SweptVolumeDist {
759 pub fn capsule_trajectory_dist(
764 start_a: [f64; 3],
765 end_a: [f64; 3],
766 radius_a: f64,
767 start_b: [f64; 3],
768 end_b: [f64; 3],
769 radius_b: f64,
770 ) -> f64 {
771 let n = 8;
773 let mut min_dist = f64::INFINITY;
774 for i in 0..=n {
775 let t = i as f64 / n as f64;
776 let pa = lerp3(start_a, end_a, t);
777 let pb = lerp3(start_b, end_b, t);
778 let d = dist3(pa, pb) - radius_a - radius_b;
779 if d < min_dist {
780 min_dist = d;
781 }
782 }
783 min_dist
784 }
785}
786
787pub struct NarrowPhaseProximity {
795 pub activation_threshold: f64,
797 pub deactivation_threshold: f64,
799 pub active_pairs: Vec<(u32, u32)>,
801}
802
803impl NarrowPhaseProximity {
804 pub fn new(activation_threshold: f64, deactivation_threshold: f64) -> Self {
806 Self {
807 activation_threshold,
808 deactivation_threshold,
809 active_pairs: Vec::new(),
810 }
811 }
812
813 pub fn update(&mut self, distances: &[(u32, u32, f64)]) {
817 for &(a, b, d) in distances {
818 let pair = (a.min(b), a.max(b));
819 let already_active = self.active_pairs.contains(&pair);
820 if d <= self.activation_threshold && !already_active {
821 self.active_pairs.push(pair);
822 } else if d > self.deactivation_threshold && already_active {
823 self.active_pairs.retain(|&p| p != pair);
824 }
825 }
826 }
827
828 pub fn is_active(&self, a: u32, b: u32) -> bool {
830 let pair = (a.min(b), a.max(b));
831 self.active_pairs.contains(&pair)
832 }
833}
834
835#[cfg(test)]
840mod tests {
841 use super::*;
842
843 #[test]
844 fn test_closest_point_on_segment_midpoint() {
845 let a = [0.0, 0.0, 0.0];
846 let b = [2.0, 0.0, 0.0];
847 let p = [1.0, 1.0, 0.0];
848 let (q, t) = ProximityQuery::closest_point_on_segment(a, b, p);
849 assert!((q[0] - 1.0).abs() < 1e-12);
850 assert!(q[1].abs() < 1e-12);
851 assert!((t - 0.5).abs() < 1e-12);
852 }
853
854 #[test]
855 fn test_closest_point_on_segment_clamped_start() {
856 let a = [0.0, 0.0, 0.0];
857 let b = [1.0, 0.0, 0.0];
858 let p = [-1.0, 0.0, 0.0];
859 let (q, t) = ProximityQuery::closest_point_on_segment(a, b, p);
860 assert!((q[0] - 0.0).abs() < 1e-12);
861 assert!((t - 0.0).abs() < 1e-12);
862 }
863
864 #[test]
865 fn test_closest_point_on_segment_clamped_end() {
866 let a = [0.0, 0.0, 0.0];
867 let b = [1.0, 0.0, 0.0];
868 let p = [2.0, 0.0, 0.0];
869 let (q, t) = ProximityQuery::closest_point_on_segment(a, b, p);
870 assert!((q[0] - 1.0).abs() < 1e-12);
871 assert!((t - 1.0).abs() < 1e-12);
872 }
873
874 #[test]
875 fn test_closest_point_to_aabb() {
876 let lo = [0.0, 0.0, 0.0];
877 let hi = [1.0, 1.0, 1.0];
878 let p = [2.0, 0.5, 0.5];
879 let q = ProximityQuery::closest_point_to_aabb(lo, hi, p);
880 assert!((q[0] - 1.0).abs() < 1e-12);
881 assert!((q[1] - 0.5).abs() < 1e-12);
882 }
883
884 #[test]
885 fn test_point_in_convex_hull() {
886 let planes = [
888 ([1.0, 0.0, 0.0], 1.0),
889 ([-1.0, 0.0, 0.0], 0.0),
890 ([0.0, 1.0, 0.0], 1.0),
891 ([0.0, -1.0, 0.0], 0.0),
892 ([0.0, 0.0, 1.0], 1.0),
893 ([0.0, 0.0, -1.0], 0.0),
894 ];
895 assert!(ProximityQuery::point_in_convex_hull(
896 [0.5, 0.5, 0.5],
897 &planes
898 ));
899 assert!(!ProximityQuery::point_in_convex_hull(
900 [1.5, 0.5, 0.5],
901 &planes
902 ));
903 }
904
905 #[test]
906 fn test_segment_segment_dist_parallel() {
907 let p1 = [0.0, 0.0, 0.0];
908 let p2 = [1.0, 0.0, 0.0];
909 let p3 = [0.0, 1.0, 0.0];
910 let p4 = [1.0, 1.0, 0.0];
911 let (d, _, _) = SegmentSegmentDist::query(p1, p2, p3, p4);
912 assert!((d - 1.0).abs() < 1e-10);
913 }
914
915 #[test]
916 fn test_segment_segment_dist_crossing() {
917 let p1 = [-1.0, 0.0, 0.0];
918 let p2 = [1.0, 0.0, 0.0];
919 let p3 = [0.0, -1.0, 0.0];
920 let p4 = [0.0, 1.0, 0.0];
921 let (d, wa, wb) = SegmentSegmentDist::query(p1, p2, p3, p4);
922 assert!(d < 1e-10, "d={}", d);
923 assert!(wa[0].abs() < 1e-10);
924 assert!(wb[0].abs() < 1e-10);
925 }
926
927 #[test]
928 fn test_segment_segment_dist_skew() {
929 let p1 = [0.0, 0.0, 0.0];
930 let p2 = [1.0, 0.0, 0.0];
931 let p3 = [0.0, 0.0, 1.0];
932 let p4 = [1.0, 0.0, 1.0];
933 let (d, _, _) = SegmentSegmentDist::query(p1, p2, p3, p4);
934 assert!((d - 1.0).abs() < 1e-10);
935 }
936
937 #[test]
938 fn test_point_triangle_dist_inside() {
939 let v0 = [0.0, 0.0, 0.0];
940 let v1 = [1.0, 0.0, 0.0];
941 let v2 = [0.0, 1.0, 0.0];
942 let p = [0.25, 0.25, 1.0];
943 let (q, bary) = PointTriangleDist::closest_point(p, v0, v1, v2);
944 assert!((q[2] - 0.0).abs() < 1e-12);
945 let bary_sum = bary[0] + bary[1] + bary[2];
946 assert!((bary_sum - 1.0).abs() < 1e-10);
947 }
948
949 #[test]
950 fn test_point_triangle_dist_vertex() {
951 let v0 = [0.0, 0.0, 0.0];
952 let v1 = [1.0, 0.0, 0.0];
953 let v2 = [0.0, 1.0, 0.0];
954 let p = [-1.0, -1.0, 0.0];
955 let (q, _) = PointTriangleDist::closest_point(p, v0, v1, v2);
956 assert!((q[0] - 0.0).abs() < 1e-12);
957 assert!((q[1] - 0.0).abs() < 1e-12);
958 }
959
960 #[test]
961 fn test_sdf_sphere() {
962 let center = [0.0, 0.0, 0.0];
963 let d = SignedDistance::sphere([1.5, 0.0, 0.0], center, 1.0);
964 assert!((d - 0.5).abs() < 1e-12);
965 let d_inside = SignedDistance::sphere([0.5, 0.0, 0.0], center, 1.0);
966 assert!(d_inside < 0.0);
967 }
968
969 #[test]
970 fn test_sdf_box() {
971 let h = [1.0, 1.0, 1.0];
972 let d_outside = SignedDistance::box_sdf([2.0, 0.0, 0.0], h);
973 assert!((d_outside - 1.0).abs() < 1e-10);
974 let d_inside = SignedDistance::box_sdf([0.0, 0.0, 0.0], h);
975 assert!(d_inside < 0.0);
976 }
977
978 #[test]
979 fn test_sdf_union_intersection_subtraction() {
980 let d1 = 1.0;
981 let d2 = 2.0;
982 assert_eq!(SignedDistance::union(d1, d2), 1.0);
983 assert_eq!(SignedDistance::intersection(d1, d2), 2.0);
984 assert_eq!(SignedDistance::subtraction(d1, d2), 1.0);
985 }
986
987 #[test]
988 fn test_capsule_capsule_dist_touching() {
989 let a_base = [0.0, 0.0, 0.0];
990 let a_tip = [1.0, 0.0, 0.0];
991 let b_base = [2.5, 0.0, 0.0];
992 let b_tip = [3.5, 0.0, 0.0];
993 let res = CapsuleCapsuleDist::query(a_base, a_tip, 0.5, b_base, b_tip, 0.5);
994 assert!(res.distance >= -1e-6, "dist={}", res.distance);
995 }
996
997 #[test]
998 fn test_knn_query_k1() {
999 let pts = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1000 let knn = KnnQuery::new(pts);
1001 let result = knn.knn([0.1, 0.0, 0.0], 1);
1002 assert_eq!(result[0].0, 0);
1003 }
1004
1005 #[test]
1006 fn test_knn_range_query() {
1007 let pts: Vec<[f64; 3]> = (0..10).map(|i| [i as f64, 0.0, 0.0]).collect();
1008 let knn = KnnQuery::new(pts);
1009 let result = knn.range_query([5.0, 0.0, 0.0], 1.5);
1010 assert!(result.len() >= 2);
1011 }
1012
1013 #[test]
1014 fn test_ray_aabb_hit() {
1015 let ray = Ray::new([0.0, 0.0, -2.0], [0.0, 0.0, 1.0]);
1016 let lo = [-0.5, -0.5, -0.5];
1017 let hi = [0.5, 0.5, 0.5];
1018 let t = RayQuery::ray_aabb(&ray, lo, hi);
1019 assert!(t.is_some());
1020 assert!((t.unwrap() - 1.5).abs() < 1e-10);
1021 }
1022
1023 #[test]
1024 fn test_ray_aabb_miss() {
1025 let ray = Ray::new([0.0, 2.0, -2.0], [0.0, 0.0, 1.0]);
1026 let lo = [-0.5, -0.5, -0.5];
1027 let hi = [0.5, 0.5, 0.5];
1028 let t = RayQuery::ray_aabb(&ray, lo, hi);
1029 assert!(t.is_none());
1030 }
1031
1032 #[test]
1033 fn test_ray_sphere_hit() {
1034 let ray = Ray::new([0.0, 0.0, -3.0], [0.0, 0.0, 1.0]);
1035 let t = RayQuery::ray_sphere(&ray, [0.0, 0.0, 0.0], 1.0);
1036 assert!(t.is_some());
1037 assert!((t.unwrap() - 2.0).abs() < 1e-10);
1038 }
1039
1040 #[test]
1041 fn test_ray_triangle_hit() {
1042 let ray = Ray::new([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]);
1043 let v0 = [-1.0, -1.0, 0.0];
1044 let v1 = [1.0, -1.0, 0.0];
1045 let v2 = [0.0, 1.0, 0.0];
1046 let result = RayQuery::ray_triangle(&ray, v0, v1, v2);
1047 assert!(result.is_some());
1048 let (t, _, _) = result.unwrap();
1049 assert!((t - 1.0).abs() < 1e-10);
1050 }
1051
1052 #[test]
1053 fn test_ray_triangle_miss() {
1054 let ray = Ray::new([5.0, 5.0, -1.0], [0.0, 0.0, 1.0]);
1055 let v0 = [-1.0, -1.0, 0.0];
1056 let v1 = [1.0, -1.0, 0.0];
1057 let v2 = [0.0, 1.0, 0.0];
1058 let result = RayQuery::ray_triangle(&ray, v0, v1, v2);
1059 assert!(result.is_none());
1060 }
1061
1062 #[test]
1063 fn test_point_mesh_dist() {
1064 let verts = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1065 let idx = vec![0usize, 1, 2];
1066 let mesh = PointMeshDist::new(verts, idx);
1067 let (d, _tri, _q) = mesh.closest_point([0.25, 0.25, 1.0]);
1068 assert!((d - 1.0).abs() < 1e-10);
1069 }
1070
1071 #[test]
1072 fn test_narrow_phase_proximity_activate() {
1073 let mut npp = NarrowPhaseProximity::new(1.0, 2.0);
1074 npp.update(&[(0, 1, 0.5)]);
1075 assert!(npp.is_active(0, 1));
1076 }
1077
1078 #[test]
1079 fn test_narrow_phase_proximity_deactivate() {
1080 let mut npp = NarrowPhaseProximity::new(1.0, 2.0);
1081 npp.update(&[(0, 1, 0.5)]);
1082 assert!(npp.is_active(0, 1));
1083 npp.update(&[(0, 1, 3.0)]);
1084 assert!(!npp.is_active(0, 1));
1085 }
1086
1087 #[test]
1088 fn test_sdf_convex_polygon_inside() {
1089 let square = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
1090 let d = SignedDistance::convex_polygon_2d([0.5, 0.5], &square);
1091 assert!(d < 0.0);
1092 }
1093
1094 #[test]
1095 fn test_sdf_convex_polygon_outside() {
1096 let square = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
1097 let d = SignedDistance::convex_polygon_2d([2.0, 0.5], &square);
1098 assert!(d > 0.0);
1099 }
1100
1101 #[test]
1102 fn test_spatial_query_result_valid() {
1103 let r = SpatialQueryResult::new(1.0, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
1104 assert!(r.valid);
1105 assert!((r.distance - 1.0).abs() < 1e-12);
1106 }
1107}