1use fast_mesh_bvh::Aabb;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum Cull {
10 #[default]
12 None,
13 Back,
15 Front,
17}
18
19#[derive(Debug, Clone, Copy)]
21pub(crate) struct RayPre {
22 pub origin: [f32; 3],
23 pub dir: [f32; 3],
24 pub inv_dir: [f32; 3],
26 kx: usize,
27 ky: usize,
28 kz: usize,
29 sx: f32,
30 sy: f32,
31 sz: f32,
32}
33
34impl RayPre {
35 pub(crate) fn new(origin: [f32; 3], dir: [f32; 3]) -> RayPre {
36 let a = [dir[0].abs(), dir[1].abs(), dir[2].abs()];
37 let kz = if a[0] >= a[1] && a[0] >= a[2] {
38 0
39 } else if a[1] >= a[2] {
40 1
41 } else {
42 2
43 };
44 let mut kx = (kz + 1) % 3;
45 let mut ky = (kx + 1) % 3;
46 if dir[kz] < 0.0 {
47 std::mem::swap(&mut kx, &mut ky);
48 }
49 RayPre {
50 origin,
51 dir,
52 inv_dir: [1.0 / dir[0], 1.0 / dir[1], 1.0 / dir[2]],
53 kx,
54 ky,
55 kz,
56 sx: dir[kx] / dir[kz],
57 sy: dir[ky] / dir[kz],
58 sz: 1.0 / dir[kz],
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy)]
65pub(crate) struct TriHit {
66 pub t: f32,
68 pub barycentric: [f32; 3],
70}
71
72pub(crate) fn ray_triangle(
83 pre: &RayPre,
84 p: [[f32; 3]; 3],
85 cull: Cull,
86 min_t: f32,
87 max_t: f32,
88) -> Option<TriHit> {
89 let (kx, ky, kz) = (pre.kx, pre.ky, pre.kz);
90 let a = sub(p[0], pre.origin);
91 let b = sub(p[1], pre.origin);
92 let c = sub(p[2], pre.origin);
93 let ax = a[kx] - pre.sx * a[kz];
94 let ay = a[ky] - pre.sy * a[kz];
95 let bx = b[kx] - pre.sx * b[kz];
96 let by = b[ky] - pre.sy * b[kz];
97 let cx = c[kx] - pre.sx * c[kz];
98 let cy = c[ky] - pre.sy * c[kz];
99
100 let mut u = cx * by - cy * bx;
101 let mut v = ax * cy - ay * cx;
102 let mut w = bx * ay - by * ax;
103 if u == 0.0 || v == 0.0 || w == 0.0 {
104 let (ax, ay, bx, by, cx, cy) = (
105 f64::from(ax),
106 f64::from(ay),
107 f64::from(bx),
108 f64::from(by),
109 f64::from(cx),
110 f64::from(cy),
111 );
112 u = (cx * by - cy * bx) as f32;
113 v = (ax * cy - ay * cx) as f32;
114 w = (bx * ay - by * ax) as f32;
115 }
116
117 let inside = match cull {
118 Cull::None => (u >= 0.0 && v >= 0.0 && w >= 0.0) || (u <= 0.0 && v <= 0.0 && w <= 0.0),
119 Cull::Back => u >= 0.0 && v >= 0.0 && w >= 0.0,
120 Cull::Front => u <= 0.0 && v <= 0.0 && w <= 0.0,
121 };
122 if !inside {
123 return None;
124 }
125 let det = u + v + w;
126 if det == 0.0 {
127 return None;
128 }
129 let t_scaled = u * (pre.sz * a[kz]) + v * (pre.sz * b[kz]) + w * (pre.sz * c[kz]);
130 let t = t_scaled / det;
131 if !(t.is_finite() && min_t <= t && t <= max_t) {
134 return None;
135 }
136 let barycentric = [u / det, v / det, w / det];
139 if !barycentric.iter().all(|b| b.is_finite()) {
140 return None;
141 }
142 Some(TriHit { t, barycentric })
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum Feature {
148 Vertex(u8),
150 Edge(u8),
152 Face,
154}
155
156pub(crate) trait Real:
159 Copy
160 + PartialOrd
161 + std::ops::Add<Output = Self>
162 + std::ops::Sub<Output = Self>
163 + std::ops::Mul<Output = Self>
164 + std::ops::Div<Output = Self>
165{
166 const ZERO: Self;
167 const ONE: Self;
168 const SLIVER: Self;
170 fn is_nan(self) -> bool;
171 fn is_finite(self) -> bool;
172}
173
174macro_rules! impl_real {
175 ($t:ty) => {
176 impl Real for $t {
177 const ZERO: Self = 0.0;
178 const ONE: Self = 1.0;
179 const SLIVER: Self = 1e-5;
180 fn is_nan(self) -> bool {
181 <$t>::is_nan(self)
182 }
183 fn is_finite(self) -> bool {
184 <$t>::is_finite(self)
185 }
186 }
187 };
188}
189impl_real!(f32);
190impl_real!(f64);
191
192#[derive(Debug, Clone, Copy)]
194pub(crate) struct TriClosest<T = f32> {
195 pub point: [T; 3],
196 pub dist2: T,
198 pub barycentric: [T; 3],
200 pub feature: Feature,
201}
202
203fn dot<T: Real>(a: [T; 3], b: [T; 3]) -> T {
204 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
205}
206
207fn dist2<T: Real>(a: [T; 3], b: [T; 3]) -> T {
208 let d = sub(a, b);
209 dot(d, d)
210}
211
212fn lerp_point<T: Real>(a: [T; 3], b: [T; 3], t: T) -> [T; 3] {
214 std::array::from_fn(|k| a[k] + t * (b[k] - a[k]))
215}
216
217fn on_segment<T: Real>(q: [T; 3], p: [[T; 3]; 3], i: usize, j: usize, edge: u8) -> TriClosest<T> {
219 let ab = sub(p[j], p[i]);
220 let len2 = dot(ab, ab);
221 let t = if len2 > T::ZERO {
222 dot(sub(q, p[i]), ab) / len2
223 } else {
224 T::ZERO
225 };
226 let (t, feature) = if t.is_nan() || t <= T::ZERO {
227 (T::ZERO, Feature::Vertex(i as u8))
228 } else if t >= T::ONE {
229 (T::ONE, Feature::Vertex(j as u8))
230 } else {
231 (t, Feature::Edge(edge))
232 };
233 let mut barycentric = [T::ZERO; 3];
234 barycentric[i] = T::ONE - t;
235 barycentric[j] = t;
236 let point = lerp_point(p[i], p[j], t);
237 TriClosest {
238 point,
239 dist2: dist2(q, point),
240 barycentric,
241 feature,
242 }
243}
244
245pub(crate) fn closest_on_triangle<T: Real>(q: [T; 3], p: [[T; 3]; 3]) -> TriClosest<T> {
259 let [a, b, c] = p;
260 let ab = sub(b, a);
261 let ac = sub(c, a);
262 let ap = sub(q, a);
263 let bp = sub(q, b);
264 let cp = sub(q, c);
265 let (d1, d2) = (dot(ab, ap), dot(ac, ap));
266 let (d3, d4) = (dot(ab, bp), dot(ac, bp));
267 let (d5, d6) = (dot(ab, cp), dot(ac, cp));
268 let vc = d1 * d4 - d3 * d2;
269 let vb = d5 * d2 - d1 * d6;
270 let va = d3 * d6 - d5 * d4;
271 let sum = va + vb + vc;
272
273 let ericson = || -> TriClosest<T> {
274 let vertex = |k: u8| {
275 let mut barycentric = [T::ZERO; 3];
276 barycentric[k as usize] = T::ONE;
277 let point = p[k as usize];
278 TriClosest {
279 point,
280 dist2: dist2(q, point),
281 barycentric,
282 feature: Feature::Vertex(k),
283 }
284 };
285 let edge = |i: usize, j: usize, t: T, k: u8| {
286 let mut barycentric = [T::ZERO; 3];
287 barycentric[i] = T::ONE - t;
288 barycentric[j] = t;
289 let point = lerp_point(p[i], p[j], t);
290 TriClosest {
291 point,
292 dist2: dist2(q, point),
293 barycentric,
294 feature: Feature::Edge(k),
295 }
296 };
297
298 let z = T::ZERO;
299 if d1 <= z && d2 <= z {
300 return vertex(0);
301 }
302 if d3 >= z && d4 <= d3 {
303 return vertex(1);
304 }
305 if vc <= z && d1 >= z && d3 <= z {
306 return edge(0, 1, d1 / (d1 - d3), 0);
307 }
308 if d6 >= z && d5 <= d6 {
309 return vertex(2);
310 }
311 if vb <= z && d2 >= z && d6 <= z {
312 return edge(0, 2, d2 / (d2 - d6), 2);
313 }
314 if va <= z && d4 - d3 >= z && d5 - d6 >= z {
315 return edge(1, 2, (d4 - d3) / ((d4 - d3) + (d5 - d6)), 1);
316 }
317 let v = vb / sum;
318 let w = vc / sum;
319 let point = std::array::from_fn(|k| a[k] + ab[k] * v + ac[k] * w);
320 TriClosest {
321 point,
322 dist2: dist2(q, point),
323 barycentric: [T::ONE - v - w, v, w],
324 feature: Feature::Face,
325 }
326 };
327
328 let band = !sum.is_finite() || sum <= T::SLIVER * dot(ab, ab) * dot(ac, ac);
329 if !band {
330 return ericson();
331 }
332 let mut best = on_segment(q, p, 0, 1, 0);
333 for (i, j, edge) in [(1, 2, 1), (2, 0, 2)] {
334 let c = on_segment(q, p, i, j, edge);
335 if c.dist2 < best.dist2 {
336 best = c;
337 }
338 }
339 if sum > T::ZERO && sum.is_finite() {
340 let e = ericson();
341 if e.dist2 < best.dist2 {
342 best = e;
343 }
344 }
345 best
346}
347
348fn sub<T: Real>(a: [T; 3], b: [T; 3]) -> [T; 3] {
349 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
350}
351
352pub(crate) fn box_triangle(b: &Aabb, p: [[f32; 3]; 3]) -> bool {
366 if b.is_empty() {
367 return false;
368 }
369 let mut lo = [0.0f32; 3];
370 let mut hi = [0.0f32; 3];
371 for k in 0..3 {
372 let tmin = p[0][k].min(p[1][k]).min(p[2][k]);
373 let tmax = p[0][k].max(p[1][k]).max(p[2][k]);
374 lo[k] = b.min[k].max(tmin);
375 hi[k] = b.max[k].min(tmax);
376 if lo[k] > hi[k] {
378 return false;
379 }
380 }
381 let c: [f64; 3] = std::array::from_fn(|k| (f64::from(lo[k]) + f64::from(hi[k])) * 0.5);
382 let h: [f64; 3] = std::array::from_fn(|k| (f64::from(hi[k]) - f64::from(lo[k])) * 0.5);
383 let v: [[f64; 3]; 3] = p.map(|q| std::array::from_fn(|k| f64::from(q[k]) - c[k]));
384
385 for k in 0..3 {
387 let lo = v[0][k].min(v[1][k]).min(v[2][k]);
388 let hi = v[0][k].max(v[1][k]).max(v[2][k]);
389 if lo > h[k] || hi < -h[k] {
390 return false;
391 }
392 }
393
394 let e: [[f64; 3]; 3] = std::array::from_fn(|i| {
395 let (a, b) = (v[i], v[(i + 1) % 3]);
396 [b[0] - a[0], b[1] - a[1], b[2] - a[2]]
397 });
398 let separates = |axis: [f64; 3]| {
399 if axis == [0.0; 3] {
400 return false;
401 }
402 let d = v.map(|q| axis[0] * q[0] + axis[1] * q[1] + axis[2] * q[2]);
403 let r = h[0] * axis[0].abs() + h[1] * axis[1].abs() + h[2] * axis[2].abs();
404 d[0].min(d[1]).min(d[2]) > r || d[0].max(d[1]).max(d[2]) < -r
405 };
406
407 if separates(cross64(e[0], e[1])) {
409 return false;
410 }
411 for edge in e {
413 for k in 0..3 {
414 let mut unit = [0.0; 3];
415 unit[k] = 1.0;
416 if separates(cross64(edge, unit)) {
417 return false;
418 }
419 }
420 }
421 true
422}
423
424fn cross64(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
425 [
426 a[1] * b[2] - a[2] * b[1],
427 a[2] * b[0] - a[0] * b[2],
428 a[0] * b[1] - a[1] * b[0],
429 ]
430}
431
432pub(crate) fn sphere_triangle(center: [f32; 3], radius: f32, p: [[f32; 3]; 3]) -> bool {
435 closest_on_triangle(center, p).dist2 <= radius * radius
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 const T: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
443 const DOWN: [f32; 3] = [0.0, 0.0, -1.0];
444 const UP: [f32; 3] = [0.0, 0.0, 1.0];
445
446 fn cast(o: [f32; 3], d: [f32; 3], p: [[f32; 3]; 3], cull: Cull) -> Option<TriHit> {
447 ray_triangle(&RayPre::new(o, d), p, cull, 0.0, f32::INFINITY)
448 }
449
450 #[test]
451 fn hits_the_interior() {
452 let h = cast([0.25, 0.25, 1.0], DOWN, T, Cull::None).unwrap();
453 assert_eq!(h.t, 1.0);
454 let sum: f32 = h.barycentric.iter().sum();
455 assert!((sum - 1.0).abs() < 1e-6);
456 for (a, b) in h.barycentric.iter().zip([0.5, 0.25, 0.25]) {
457 assert!((a - b).abs() < 1e-6);
458 }
459 }
460
461 #[test]
462 fn misses_outside_and_behind() {
463 assert!(cast([2.0, 2.0, 1.0], DOWN, T, Cull::None).is_none());
464 assert!(cast([0.25, 0.25, -1.0], DOWN, T, Cull::None).is_none());
465 }
466
467 #[test]
468 fn culling() {
469 let o = [0.25, 0.25, 1.0];
470 assert!(cast(o, DOWN, T, Cull::None).is_some());
471 assert!(cast(o, DOWN, T, Cull::Back).is_some());
472 assert!(cast(o, DOWN, T, Cull::Front).is_none());
473 let u = [0.25, 0.25, -1.0];
474 assert!(cast(u, UP, T, Cull::None).is_some());
475 assert!(cast(u, UP, T, Cull::Front).is_some());
476 assert!(cast(u, UP, T, Cull::Back).is_none());
477 }
478
479 #[test]
480 fn range_is_closed() {
481 let pre = RayPre::new([0.25, 0.25, 1.0], DOWN);
482 assert!(ray_triangle(&pre, T, Cull::None, 1.0, f32::INFINITY).is_some());
483 assert!(ray_triangle(&pre, T, Cull::None, 0.0, 1.0).is_some());
484 assert!(ray_triangle(&pre, T, Cull::None, 0.0, 0.999).is_none());
485 }
486
487 #[test]
488 fn parallel_and_degenerate_miss() {
489 assert!(cast([0.25, 0.25, 0.0], [1.0, 0.0, 0.0], T, Cull::None).is_none());
490 let d = [[0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [2.0, 2.0, 0.0]];
491 assert!(cast([1.0, 1.0, 1.0], DOWN, d, Cull::None).is_none());
492 }
493
494 #[test]
495 fn subnormal_det_gives_finite_weights() {
496 let s = 1e-22;
499 let p = [[0.0, 0.0, 0.0], [s, 0.0, 0.0], [0.0, s, 0.0]];
500 let o = [s / 4.0, s / 4.0, 1.0];
501 let h = cast(o, DOWN, p, Cull::None).unwrap();
502 assert_eq!(h.t, 1.0);
503 assert!(h.barycentric.iter().all(|b| b.is_finite()));
504 let sum: f32 = h.barycentric.iter().sum();
505 assert!((sum - 1.0).abs() < 1e-6);
506 }
507
508 #[test]
509 fn overflowing_t_is_not_a_hit() {
510 let z = -3e38;
513 let p = [[-10.0, -10.0, z], [10.0, -10.0, z], [0.0, 10.0, z]];
514 let o = [0.0, 0.0, 0.0];
515 let pre = RayPre::new(o, DOWN);
516 let a = sub(p[0], o);
517 assert!((pre.sz * a[2]).is_finite());
518 assert_eq!(cast(o, DOWN, p, Cull::None).map(|h| h.t), None);
519 }
520
521 #[test]
522 fn shared_edge_is_watertight() {
523 let t2 = [[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]];
524 for i in 1..=1000 {
525 let s = i as f32 / 1001.0;
526 let o = [s, 1.0 - s, 1.0];
527 assert!(
528 cast(o, DOWN, T, Cull::None).is_some() || cast(o, DOWN, t2, Cull::None).is_some(),
529 "leak at s = {s}"
530 );
531 }
532 let t3 = [[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 0.0]];
534 let o = [1.0, 0.0, 1.0];
535 assert!(
536 [T, t2, t3]
537 .iter()
538 .any(|p| cast(o, DOWN, *p, Cull::None).is_some())
539 );
540 }
541
542 fn close(a: [f32; 3], b: [f32; 3]) -> bool {
543 a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
544 }
545
546 type VoronoiCase = ([f64; 3], Feature, [f64; 3], [f64; 3]);
549
550 const VORONOI: [VoronoiCase; 7] = [
553 (
554 [-1.0, -1.0, 0.0],
555 Feature::Vertex(0),
556 [0.0, 0.0, 0.0],
557 [1.0, 0.0, 0.0],
558 ),
559 (
560 [2.0, -1.0, 0.0],
561 Feature::Vertex(1),
562 [1.0, 0.0, 0.0],
563 [0.0, 1.0, 0.0],
564 ),
565 (
566 [-1.0, 2.0, 0.0],
567 Feature::Vertex(2),
568 [0.0, 1.0, 0.0],
569 [0.0, 0.0, 1.0],
570 ),
571 (
572 [0.5, -1.0, 0.0],
573 Feature::Edge(0),
574 [0.5, 0.0, 0.0],
575 [0.5, 0.5, 0.0],
576 ),
577 (
578 [1.0, 1.0, 0.0],
579 Feature::Edge(1),
580 [0.5, 0.5, 0.0],
581 [0.0, 0.5, 0.5],
582 ),
583 (
584 [-1.0, 0.5, 0.0],
585 Feature::Edge(2),
586 [0.0, 0.5, 0.0],
587 [0.5, 0.0, 0.5],
588 ),
589 (
590 [0.25, 0.25, 3.0],
591 Feature::Face,
592 [0.25, 0.25, 0.0],
593 [0.5, 0.25, 0.25],
594 ),
595 ];
596
597 #[test]
598 fn voronoi_regions() {
599 for (q, feature, point, bary) in VORONOI {
600 let (q, point, bary) = (
601 q.map(|v| v as f32),
602 point.map(|v| v as f32),
603 bary.map(|v| v as f32),
604 );
605 let c = closest_on_triangle(q, T);
606 assert_eq!(c.feature, feature, "{q:?}");
607 assert!(close(c.point, point), "{q:?}: {:?}", c.point);
608 assert!(close(c.barycentric, bary), "{q:?}: {:?}", c.barycentric);
609 }
610 assert_eq!(closest_on_triangle([0.25, 0.25, 3.0], T).dist2, 9.0);
611 }
612
613 #[test]
614 fn degenerate_triangle_uses_segments() {
615 let line: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [2.0, 2.0, 0.0]];
616 let c = closest_on_triangle([1.0, 0.0, 0.0], line);
617 assert!(close(c.point, [0.5, 0.5, 0.0]));
618 assert_eq!(c.dist2, 0.5);
619 assert!(matches!(c.feature, Feature::Edge(_)));
620 assert!(c.barycentric.iter().all(|b| b.is_finite()));
621
622 let dot: [[f32; 3]; 3] = [[1.0, 1.0, 1.0]; 3];
623 let c = closest_on_triangle([0.0, 0.0, 0.0], dot);
624 assert_eq!(c.feature, Feature::Vertex(0));
625 assert_eq!(c.dist2, 3.0);
626 }
627
628 fn segment_distance(q: [f32; 3], p: [[f32; 3]; 3]) -> f64 {
630 let v = |a: [f32; 3]| a.map(f64::from);
631 let q = v(q);
632 (0..3)
633 .map(|i| {
634 let (a, b) = (v(p[i]), v(p[(i + 1) % 3]));
635 let ab: Vec<f64> = (0..3).map(|k| b[k] - a[k]).collect();
636 let len2: f64 = ab.iter().map(|x| x * x).sum();
637 let t = if len2 > 0.0 {
638 ((0..3).map(|k| (q[k] - a[k]) * ab[k]).sum::<f64>() / len2).clamp(0.0, 1.0)
639 } else {
640 0.0
641 };
642 (0..3)
643 .map(|k| (q[k] - a[k] - t * ab[k]).powi(2))
644 .sum::<f64>()
645 .sqrt()
646 })
647 .fold(f64::INFINITY, f64::min)
648 }
649
650 #[test]
651 fn near_collinear_sliver_is_not_far_off() {
652 let p = [
653 [0.5389, -0.9795, -0.2304],
654 [0.4449, -1.3181, -0.0153],
655 [0.1759, -2.2879, 0.6010],
656 ];
657 let q = [-1.531, -0.251, 1.715];
658 let c = closest_on_triangle(q, p);
659 let got = f64::from(c.dist2).sqrt();
660 assert!((got - segment_distance(q, p)).abs() < 1e-4, "{got}");
661 }
662
663 #[test]
664 fn random_slivers_stay_close_and_on_the_triangle() {
665 let mut rng = crate::test_util::Lcg(0x511_7e75);
666 let mut r = |lo: f32, hi: f32| lo + (hi - lo) * rng.next();
667 for _ in 0..5000 {
668 let a = [r(-2.0, 2.0), r(-2.0, 2.0), r(-2.0, 2.0)];
669 let b = [r(-2.0, 2.0), r(-2.0, 2.0), r(-2.0, 2.0)];
670 let s = r(-1.0, 3.0);
671 let c = std::array::from_fn(|k| a[k] + s * (b[k] - a[k]));
672 let q = [r(-3.0, 3.0), r(-3.0, 3.0), r(-3.0, 3.0)];
673 let p = [a, b, c];
674 let got = closest_on_triangle(q, p);
675 let d = f64::from(got.dist2).sqrt();
676 assert!(d <= segment_distance(q, p) + 1e-4, "{p:?} {q:?}: {d}");
677 let w = got.barycentric;
679 assert!(w.iter().all(|x| *x >= -1e-4), "{w:?}");
680 assert!((w.iter().sum::<f32>() - 1.0).abs() < 1e-4);
681 for k in 0..3 {
682 let m = w[0] * a[k] + w[1] * b[k] + w[2] * c[k];
683 assert!((m - got.point[k]).abs() < 1e-4, "{p:?} {q:?}");
684 }
685 }
686 }
687
688 #[test]
689 fn distant_query_on_small_sliver_stays_accurate() {
690 let p = [[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1e-4, 0.0]];
694 for dist in [10.0f32, 50.0] {
695 let q = [0.5, 0.5, dist];
696 let c = closest_on_triangle(q, p);
697 let got = f64::from(c.dist2).sqrt();
698 assert!((got - segment_distance(q, p)).abs() < 1e-4, "{dist}: {got}");
699 let w = c.barycentric;
701 assert!(w.iter().all(|x| *x >= -1e-4), "{w:?}");
702 assert!((w.iter().sum::<f32>() - 1.0).abs() < 1e-4);
703 let m: [f32; 3] =
704 std::array::from_fn(|k| w[0] * p[0][k] + w[1] * p[1][k] + w[2] * p[2][k]);
705 for (got, want) in m.iter().zip(c.point.iter()) {
706 assert!((got - want).abs() < 1e-3, "{dist}");
707 }
708 }
709 }
710
711 #[test]
712 fn box_triangle_cases() {
713 let b = |min, max| Aabb::new(min, max);
714 assert!(box_triangle(&b([0.2, 0.2, -1.0], [0.3, 0.3, 1.0]), T));
716 assert!(!box_triangle(&b([2.0, 2.0, -1.0], [3.0, 3.0, 1.0]), T));
718 assert!(!box_triangle(&b([0.0, 0.0, 0.1], [1.0, 1.0, 1.0]), T));
720 assert!(!box_triangle(&b([0.6, 0.6, -1.0], [1.0, 1.0, 1.0]), T));
723 assert!(box_triangle(&b([1.0, 0.0, 0.0], [2.0, 1.0, 1.0]), T));
725 assert!(!box_triangle(&Aabb::EMPTY, T));
727 }
728
729 #[test]
730 fn box_triangle_unbounded_and_huge_boxes() {
731 let b = |min, max| Aabb::new(min, max);
732 let inf = f32::INFINITY;
733 for big in [inf, 1e30, f32::MAX] {
735 assert!(!box_triangle(&b([0.6, 0.6, -big], [big, big, big]), T));
736 assert!(!box_triangle(&b([0.6, 0.6, -1.0], [big, big, 1.0]), T));
737 assert!(box_triangle(&b([0.2, 0.2, -big], [big, big, big]), T));
739 assert!(box_triangle(&b([0.2, 0.2, -1.0], [big, big, 1.0]), T));
740 assert!(box_triangle(&b([-big; 3], [big; 3]), T));
741 }
742 assert!(!box_triangle(&b([0.6, 0.6, -inf], [inf, inf, inf]), T));
744 assert!(box_triangle(&b([-inf, -inf, -inf], [0.1, 0.1, inf]), T));
745 assert!(!box_triangle(&b([-inf, -inf, 0.5], [inf, inf, inf]), T));
746 assert!(!box_triangle(&b([-inf, -inf, -inf], [inf, inf, -0.5]), T));
747 assert!(box_triangle(&b([-inf, -inf, 0.0], [inf, inf, 0.0]), T));
748 assert!(box_triangle(&b([1.0, 0.0, 0.0], [inf, inf, inf]), T));
750 assert!(!box_triangle(&b([1.0001, 0.0, 0.0], [inf, inf, inf]), T));
751 }
752
753 fn clip_oracle(p: [[f64; 3]; 3], min: [f64; 3], max: [f64; 3]) -> bool {
757 let mut poly: Vec<[f64; 3]> = p.to_vec();
758 for k in 0..3 {
759 for upper in [false, true] {
760 let dist = |v: &[f64; 3]| if upper { max[k] - v[k] } else { v[k] - min[k] };
761 let input = std::mem::take(&mut poly);
762 for i in 0..input.len() {
763 let (a, b) = (input[i], input[(i + 1) % input.len()]);
764 let (da, db) = (dist(&a), dist(&b));
765 if da >= 0.0 {
766 poly.push(a);
767 }
768 if (da >= 0.0) != (db >= 0.0) {
769 let s = da / (da - db);
770 poly.push(std::array::from_fn(|j| a[j] + (b[j] - a[j]) * s));
771 }
772 }
773 if poly.is_empty() {
774 return false;
775 }
776 }
777 }
778 true
779 }
780
781 #[test]
782 fn box_triangle_matches_the_clipping_oracle() {
783 use crate::test_util::Lcg;
784 let mut rng = Lcg(0xb0c5);
785 let (mut yes, mut no, mut skipped) = (0, 0, 0);
786 for _ in 0..4000 {
787 let p: [[f32; 3]; 3] =
788 std::array::from_fn(|_| std::array::from_fn(|_| rng.next() * 2.0 - 1.0));
789 let mut min = [0.0f32; 3];
790 let mut max = [0.0f32; 3];
791 for k in 0..3 {
792 let c = rng.next() * 3.0 - 1.5;
793 let h = 0.05 + rng.next() * 0.8;
794 min[k] = c - h;
795 max[k] = c + h;
796 match (rng.next() * 12.0) as u32 {
798 0 => min[k] = f32::NEG_INFINITY,
799 1 => max[k] = f32::INFINITY,
800 _ => {}
801 }
802 }
803 let pd = p.map(|v| v.map(f64::from));
804 let widen = |eps: f64| {
805 clip_oracle(
806 pd,
807 min.map(|v| f64::from(v) - eps),
808 max.map(|v| f64::from(v) + eps),
809 )
810 };
811 let (loose, tight) = (widen(1e-9), widen(-1e-9));
812 if loose != tight {
813 skipped += 1;
814 continue;
815 }
816 assert_eq!(
817 box_triangle(&Aabb::new(min, max), p),
818 loose,
819 "triangle {p:?}, box {min:?}..{max:?}"
820 );
821 if loose {
822 yes += 1;
823 } else {
824 no += 1;
825 }
826 }
827 assert!(
828 yes > 500 && no > 500,
829 "yes {yes}, no {no}, skipped {skipped}"
830 );
831 assert!(skipped < 100, "skipped {skipped}");
832 }
833
834 #[test]
835 fn sphere_triangle_cases() {
836 assert!(sphere_triangle([0.25, 0.25, 1.0], 1.0, T));
837 assert!(!sphere_triangle([0.25, 0.25, 1.0], 0.999, T));
838 }
839
840 #[test]
841 fn f64_matches_f32_on_simple_cases() {
842 let t: [[f64; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
843 for (q, feature, point, bary) in VORONOI {
844 let c = closest_on_triangle(q, t);
845 assert_eq!(c.feature, feature, "{q:?}");
846 assert_eq!(c.point, point, "{q:?}");
847 assert_eq!(c.barycentric, bary, "{q:?}");
848 }
849 assert_eq!(closest_on_triangle([0.25, 0.25, 3.0], t).dist2, 9.0);
850 }
851
852 #[test]
853 fn ordinary_triangle_keeps_interior_result() {
854 let p: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [4.0, 0.0, 0.0], [1.0, 3.0, 1.0]];
855 let q = [1.5, 1.0, 5.0];
856 let c = closest_on_triangle(q, p);
857 assert_eq!(c.feature, Feature::Face);
858 let w = c.barycentric;
859 for (k, &x) in c.point.iter().enumerate() {
860 let m = w[0] * p[0][k] + w[1] * p[1][k] + w[2] * p[2][k];
861 assert!((m - x).abs() < 1e-5);
862 }
863 let d = sub(q, c.point);
865 assert!(dot(d, sub(p[1], p[0])).abs() < 1e-4);
866 assert!(dot(d, sub(p[2], p[0])).abs() < 1e-4);
867 }
868}