1use ogeom_core::{Exact, OgeomResult, Predicates, Sign, ogeom_bail};
40use ogeom_math::{Aabb, Point, Vector};
41use ogeom_topo::{Model, Shape, Triangulation};
42
43use crate::discretize::Deflection;
44
45#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct Hit {
48 pub triangle: u32,
50 pub point: Point,
52 pub distance: f64,
55 pub barycentric: [f64; 3],
58}
59
60type P = [f64; 3];
61
62#[derive(Debug, Clone, Copy)]
64struct Node {
65 low: P,
66 high: P,
67 first: u32,
70 count: u32,
72}
73
74#[derive(Debug, Clone)]
80pub struct Bvh {
81 nodes: Vec<Node>,
82 corners: Vec<[P; 3]>,
84 order: Vec<u32>,
86 slot: Vec<u32>,
88}
89
90const LEAF_MOST: usize = 8;
92
93const BINS: usize = 16;
95
96pub(crate) const BATCH: usize = 256;
98
99impl Bvh {
100 pub fn build(mesh: &Triangulation) -> OgeomResult<Self> {
113 let mut corners = Vec::with_capacity(mesh.triangles.len());
114 for triangle in &mesh.triangles {
115 let mut three = [[0.0; 3]; 3];
116 for (corner, &k) in three.iter_mut().zip(triangle) {
117 let Some(p) = mesh.positions.get(k as usize) else {
118 ogeom_bail!(
119 Range,
120 "a triangle names vertex {k} of {}",
121 mesh.positions.len()
122 );
123 };
124 if !(p.x.is_finite() && p.y.is_finite() && p.z.is_finite()) {
125 ogeom_bail!(Numeric, "a triangle's corner {k} is not finite");
126 }
127 *corner = [p.x, p.y, p.z];
128 }
129 corners.push(three);
130 }
131 if u32::try_from(corners.len()).is_err() {
132 ogeom_bail!(Range, "a mesh of more than four billion triangles");
133 }
134 Ok(Self::over(&corners))
135 }
136
137 pub fn of_shape(
149 model: &Model,
150 shape: &Shape,
151 deflection: Deflection,
152 tol: ogeom_core::Tolerances,
153 ) -> OgeomResult<Self> {
154 Self::build(&crate::triangulate::triangulate(
155 model, shape, deflection, tol,
156 )?)
157 }
158
159 fn over(corners: &[[P; 3]]) -> Self {
160 let mut order: Vec<u32> = (0..corners.len())
161 .map(|i| u32::try_from(i).unwrap_or(u32::MAX))
162 .collect();
163 let boxes: Vec<(P, P)> = corners.iter().map(box_of).collect();
164 let nodes = build_nodes(&boxes, &mut order);
165 let mut slot = vec![0_u32; order.len()];
166 for (at, &t) in order.iter().enumerate() {
167 slot[t as usize] = u32::try_from(at).unwrap_or(u32::MAX);
168 }
169 Self {
170 nodes,
171 corners: order.iter().map(|&t| corners[t as usize]).collect(),
172 order,
173 slot,
174 }
175 }
176
177 #[must_use]
179 pub fn triangle_count(&self) -> usize {
180 self.order.len()
181 }
182
183 #[must_use]
185 pub fn is_empty(&self) -> bool {
186 self.order.is_empty()
187 }
188
189 #[must_use]
191 pub fn triangle(&self, triangle: u32) -> Option<[Point; 3]> {
192 let slot = *self.slot.get(triangle as usize)?;
193 Some(self.corners[slot as usize].map(|[x, y, z]| Point::new(x, y, z)))
194 }
195
196 #[must_use]
198 pub fn bounds(&self) -> Aabb {
199 self.nodes.first().map_or(Aabb::EMPTY, |root| {
200 Aabb::of_corners(point(root.low), point(root.high))
201 })
202 }
203
204 #[must_use]
207 pub fn nearest(&self, p: Point, max: f64) -> Option<Hit> {
208 self.nearest_with(p, max, &mut Vec::new(), |_| true)
209 }
210
211 #[must_use]
213 pub fn nearest_many(&self, points: &[Point], max: f64) -> Vec<Option<Hit>> {
214 batched(
215 points,
216 |&p| p,
217 |&p, stack| self.nearest_with(p, max, stack, |_| true),
218 )
219 }
220
221 #[must_use]
226 pub fn nearest_where(&self, p: Point, max: f64, keep: impl Fn(u32) -> bool) -> Option<Hit> {
227 self.nearest_with(p, max, &mut Vec::new(), keep)
228 }
229
230 #[must_use]
233 pub fn nearest_many_where(
234 &self,
235 points: &[Point],
236 max: f64,
237 keep: impl Fn(usize, u32) -> bool + Sync,
238 ) -> Vec<Option<Hit>> {
239 let queries: Vec<usize> = (0..points.len()).collect();
240 batched(
241 &queries,
242 |&i| points[i],
243 |&i, stack| self.nearest_with(points[i], max, stack, |t| keep(i, t)),
244 )
245 }
246
247 fn nearest_with(
248 &self,
249 p: Point,
250 max: f64,
251 stack: &mut Vec<(u32, f64)>,
252 keep: impl Fn(u32) -> bool,
253 ) -> Option<Hit> {
254 let p = [p.x, p.y, p.z];
255 let reach = max * max;
256 if self.nodes.is_empty() || !p.iter().all(|c| c.is_finite()) || max.is_nan() || max < 0.0 {
257 return None;
258 }
259 let mut best: Option<(f64, u32, usize)> = None;
262 let mut bound = reach;
263 stack.clear();
264 let root = box_gap(&self.nodes[0], p);
265 if root > bound {
266 return None;
267 }
268 stack.push((0, root));
269 while let Some((at, near)) = stack.pop() {
270 if near > bound {
271 continue;
272 }
273 let node = &self.nodes[at as usize];
274 if node.count > 0 {
275 let first = node.first as usize;
276 for slot in first..first + node.count as usize {
277 let triangle = self.order[slot];
278 if !keep(triangle) {
279 continue;
280 }
281 let (_, _, d2) = closest(p, &self.corners[slot]);
282 let better = match best {
283 None => d2 <= bound,
284 Some((held, index, _)) => d2 < held || (d2 == held && triangle < index),
285 };
286 if better {
287 best = Some((d2, triangle, slot));
288 bound = d2;
289 }
290 }
291 continue;
292 }
293 let (left, right) = (at + 1, node.first);
294 let gl = box_gap(&self.nodes[left as usize], p);
295 let gr = box_gap(&self.nodes[right as usize], p);
296 let (near, far) = if gl <= gr {
298 ((left, gl), (right, gr))
299 } else {
300 ((right, gr), (left, gl))
301 };
302 if far.1 <= bound {
303 stack.push(far);
304 }
305 if near.1 <= bound {
306 stack.push(near);
307 }
308 }
309 let (_, triangle, slot) = best?;
310 let (q, barycentric, d2) = closest(p, &self.corners[slot]);
311 Some(Hit {
312 triangle,
313 point: point(q),
314 distance: d2.sqrt(),
315 barycentric,
316 })
317 }
318
319 #[must_use]
322 pub fn within_ball(&self, centre: Point, radius: f64) -> Vec<u32> {
323 let c = [centre.x, centre.y, centre.z];
324 let reach = radius * radius;
325 let mut found = Vec::new();
326 if self.nodes.is_empty()
327 || !c.iter().all(|v| v.is_finite())
328 || radius.is_nan()
329 || radius < 0.0
330 {
331 return found;
332 }
333 let mut stack = vec![0_u32];
334 while let Some(at) = stack.pop() {
335 let node = &self.nodes[at as usize];
336 if box_gap(node, c) > reach {
337 continue;
338 }
339 if node.count > 0 {
340 let first = node.first as usize;
341 for slot in first..first + node.count as usize {
342 if closest(c, &self.corners[slot]).2 <= reach {
343 found.push(self.order[slot]);
344 }
345 }
346 } else {
347 stack.push(node.first);
348 stack.push(at + 1);
349 }
350 }
351 found.sort_unstable();
352 found
353 }
354
355 #[must_use]
361 pub fn triangles_where(&self, test: impl Fn(&Aabb) -> bool) -> Vec<u32> {
362 let mut found = Vec::new();
363 if self.nodes.is_empty() {
364 return found;
365 }
366 let mut stack = vec![0_u32];
367 while let Some(at) = stack.pop() {
368 let node = &self.nodes[at as usize];
369 if !test(&Aabb::of_corners(point(node.low), point(node.high))) {
370 continue;
371 }
372 if node.count > 0 {
373 let first = node.first as usize;
374 for slot in first..first + node.count as usize {
375 let (low, high) = box_of(&self.corners[slot]);
376 if test(&Aabb::of_corners(point(low), point(high))) {
377 found.push(self.order[slot]);
378 }
379 }
380 } else {
381 stack.push(node.first);
382 stack.push(at + 1);
383 }
384 }
385 found.sort_unstable();
386 found
387 }
388
389 #[must_use]
400 pub fn ray(&self, origin: Point, direction: Vector, max: f64, both_sides: bool) -> Option<Hit> {
401 let shear = Shear::new(origin, direction)?;
402 self.ray_with(&shear, max, both_sides, &mut Vec::new())
403 }
404
405 pub fn rays(
413 &self,
414 origins: &[Point],
415 directions: &[Vector],
416 max: f64,
417 both_sides: bool,
418 ) -> OgeomResult<Vec<Option<Hit>>> {
419 if origins.len() != directions.len() {
420 ogeom_bail!(
421 Dimension,
422 "{} ray origins against {} directions",
423 origins.len(),
424 directions.len()
425 );
426 }
427 let pairs: Vec<(Point, Vector)> = origins
428 .iter()
429 .copied()
430 .zip(directions.iter().copied())
431 .collect();
432 Ok(batched(
433 &pairs,
434 |&(o, _)| o,
435 |&(o, d), stack| {
436 let shear = Shear::new(o, d)?;
437 self.ray_with(&shear, max, both_sides, stack)
438 },
439 ))
440 }
441
442 #[must_use]
445 pub fn hits_along(
446 &self,
447 origin: Point,
448 direction: Vector,
449 max: f64,
450 both_sides: bool,
451 ) -> Vec<Hit> {
452 let mut hits = Vec::new();
453 let Some(shear) = Shear::new(origin, direction) else {
454 return hits;
455 };
456 if self.nodes.is_empty() || max.is_nan() || max < 0.0 {
457 return hits;
458 }
459 let mut stack = vec![0_u32];
460 while let Some(at) = stack.pop() {
461 let node = &self.nodes[at as usize];
462 if shear.box_depth(node).is_none_or(|near| near > max) {
463 continue;
464 }
465 if node.count > 0 {
466 let first = node.first as usize;
467 for slot in first..first + node.count as usize {
468 if let Some(hit) = shear.triangle(&self.corners[slot], max, both_sides) {
469 hits.push(Hit {
470 triangle: self.order[slot],
471 ..hit
472 });
473 }
474 }
475 } else {
476 stack.push(node.first);
477 stack.push(at + 1);
478 }
479 }
480 hits.sort_by(|a, b| {
481 a.distance
482 .total_cmp(&b.distance)
483 .then(a.triangle.cmp(&b.triangle))
484 });
485 hits
486 }
487
488 fn ray_with(
489 &self,
490 shear: &Shear,
491 max: f64,
492 both_sides: bool,
493 stack: &mut Vec<(u32, f64)>,
494 ) -> Option<Hit> {
495 if self.nodes.is_empty() || max.is_nan() || max < 0.0 {
496 return None;
497 }
498 let mut best: Option<Hit> = None;
499 let mut bound = max;
500 stack.clear();
501 let near = shear.box_depth(&self.nodes[0])?;
502 if near > bound {
503 return None;
504 }
505 stack.push((0, near));
506 while let Some((at, near)) = stack.pop() {
507 if near > bound {
508 continue;
509 }
510 let node = &self.nodes[at as usize];
511 if node.count > 0 {
512 let first = node.first as usize;
513 for slot in first..first + node.count as usize {
514 let Some(hit) = shear.triangle(&self.corners[slot], bound, both_sides) else {
515 continue;
516 };
517 let triangle = self.order[slot];
518 let better = best.is_none_or(|held| {
519 hit.distance < held.distance
520 || (hit.distance == held.distance && triangle < held.triangle)
521 });
522 if better {
523 bound = hit.distance;
524 best = Some(Hit { triangle, ..hit });
525 }
526 }
527 continue;
528 }
529 let (left, right) = (at + 1, node.first);
530 let dl = shear.box_depth(&self.nodes[left as usize]);
531 let dr = shear.box_depth(&self.nodes[right as usize]);
532 let (first, second) = match (dl, dr) {
533 (Some(l), Some(r)) if r < l => (Some((right, r)), Some((left, l))),
534 (l, r) => (l.map(|l| (left, l)), r.map(|r| (right, r))),
535 };
536 for (index, depth) in [second, first].into_iter().flatten() {
537 if depth <= bound {
538 stack.push((index, depth));
539 }
540 }
541 }
542 best
543 }
544}
545
546pub(crate) fn batched<T: Sync, R: Send + Copy + Default>(
553 queries: &[T],
554 place: impl Fn(&T) -> Point,
555 answer: impl Fn(&T, &mut Vec<(u32, f64)>) -> R + Sync,
556) -> Vec<R> {
557 let places: Vec<Point> = queries.iter().map(place).collect();
558 let bounds = places
559 .iter()
560 .filter(|p| p.x.is_finite() && p.y.is_finite() && p.z.is_finite())
561 .fold(Aabb::EMPTY, |acc, &p| acc.with_point(p));
562 let mut order: Vec<(u64, usize)> = places
563 .iter()
564 .enumerate()
565 .map(|(i, &p)| (morton(&bounds, p), i))
566 .collect();
567 order.sort_unstable();
568 let chunks: Vec<&[(u64, usize)]> = order.chunks(BATCH).collect();
569 let answered = ogeom_core::parallel::map_ordered(&chunks, |_, chunk| {
570 let mut stack = Vec::new();
571 chunk
572 .iter()
573 .map(|&(_, i)| answer(&queries[i], &mut stack))
574 .collect::<Vec<R>>()
575 });
576 let mut out = vec![R::default(); queries.len()];
577 for (&(_, i), r) in order.iter().zip(answered.into_iter().flatten()) {
578 out[i] = r;
579 }
580 out
581}
582
583fn morton(bounds: &Aabb, p: Point) -> u64 {
586 let (Some(low), Some(high)) = (bounds.low(), bounds.high()) else {
587 return 0;
588 };
589 let cell = |v: f64, lo: f64, hi: f64| -> u64 {
590 let span = hi - lo;
591 let unit = if span > 0.0 { (v - lo) / span } else { 0.0 };
592 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
593 let at = (unit.clamp(0.0, 1.0) * 2_097_151.0) as u64;
594 let mut x = at & 0x1f_ffff;
596 x = (x | x << 32) & 0x1f_0000_0000_ffff;
597 x = (x | x << 16) & 0x1f_0000_ff00_00ff;
598 x = (x | x << 8) & 0x100f_00f0_0f00_f00f;
599 x = (x | x << 4) & 0x10c3_0c30_c30c_30c3;
600 x = (x | x << 2) & 0x1249_2492_4924_9249;
601 x
602 };
603 cell(p.x, low.x, high.x) | cell(p.y, low.y, high.y) << 1 | cell(p.z, low.z, high.z) << 2
604}
605
606pub(crate) fn point(p: P) -> Point {
607 Point::new(p[0], p[1], p[2])
608}
609
610fn box_of(t: &[P; 3]) -> (P, P) {
611 let mut low = t[0];
612 let mut high = t[0];
613 for c in &t[1..] {
614 for axis in 0..3 {
615 low[axis] = low[axis].min(c[axis]);
616 high[axis] = high[axis].max(c[axis]);
617 }
618 }
619 (low, high)
620}
621
622pub(crate) fn gap(low: &P, high: &P, p: P) -> f64 {
625 let mut sum = 0.0;
626 for axis in 0..3 {
627 let g = if p[axis] < low[axis] {
628 low[axis] - p[axis]
629 } else if p[axis] > high[axis] {
630 p[axis] - high[axis]
631 } else {
632 0.0
633 };
634 sum += g * g;
635 }
636 sum
637}
638
639fn box_gap(node: &Node, p: P) -> f64 {
640 gap(&node.low, &node.high, p)
641}
642
643pub(crate) fn distance2(a: P, b: P) -> f64 {
645 let mut sum = 0.0;
646 for axis in 0..3 {
647 let d = a[axis] - b[axis];
648 sum += d * d;
649 }
650 sum
651}
652
653fn sub(a: P, b: P) -> P {
654 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
655}
656
657fn dot(a: P, b: P) -> f64 {
658 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
659}
660
661fn along(a: P, d: P, s: f64) -> P {
662 [a[0] + d[0] * s, a[1] + d[1] * s, a[2] + d[2] * s]
663}
664
665pub(crate) fn distance2_to(p: Point, t: [Point; 3]) -> f64 {
669 closest([p.x, p.y, p.z], &t.map(|q| [q.x, q.y, q.z])).2
670}
671
672fn closest(p: P, t: &[P; 3]) -> (P, [f64; 3], f64) {
680 let (q, w) = closest_unclamped(p, t);
681 let (low, high) = box_of(t);
682 let q = [0, 1, 2].map(|axis| q[axis].clamp(low[axis], high[axis]));
683 (q, w, distance2(q, p))
684}
685
686fn closest_unclamped(p: P, [a, b, c]: &[P; 3]) -> (P, [f64; 3]) {
687 let (a, b, c) = (*a, *b, *c);
688 let ab = sub(b, a);
689 let ac = sub(c, a);
690 let ap = sub(p, a);
691 let d1 = dot(ab, ap);
692 let d2 = dot(ac, ap);
693 if d1 <= 0.0 && d2 <= 0.0 {
694 return (a, [1.0, 0.0, 0.0]);
695 }
696 let bp = sub(p, b);
697 let d3 = dot(ab, bp);
698 let d4 = dot(ac, bp);
699 if d3 >= 0.0 && d4 <= d3 {
700 return (b, [0.0, 1.0, 0.0]);
701 }
702 let vc = d1 * d4 - d3 * d2;
703 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 && d1 - d3 > 0.0 {
704 let v = d1 / (d1 - d3);
705 return (along(a, ab, v), [1.0 - v, v, 0.0]);
706 }
707 let cp = sub(p, c);
708 let d5 = dot(ab, cp);
709 let d6 = dot(ac, cp);
710 if d6 >= 0.0 && d5 <= d6 {
711 return (c, [0.0, 0.0, 1.0]);
712 }
713 let vb = d5 * d2 - d1 * d6;
714 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 && d2 - d6 > 0.0 {
715 let w = d2 / (d2 - d6);
716 return (along(a, ac, w), [1.0 - w, 0.0, w]);
717 }
718 let va = d3 * d6 - d5 * d4;
719 let (e, f) = (d4 - d3, d5 - d6);
720 if va <= 0.0 && e >= 0.0 && f >= 0.0 && e + f > 0.0 {
721 let w = e / (e + f);
722 return (along(b, sub(c, b), w), [0.0, 1.0 - w, w]);
723 }
724 let sum = va + vb + vc;
725 if sum > 0.0 && sum.is_finite() {
726 let v = vb / sum;
727 let w = vc / sum;
728 if v >= 0.0 && w >= 0.0 && v + w <= 1.0 {
729 return (along(along(a, ab, v), ac, w), [1.0 - v - w, v, w]);
730 }
731 }
732 nearest_edge(p, a, b, c)
733}
734
735fn nearest_edge(p: P, a: P, b: P, c: P) -> (P, [f64; 3]) {
738 let on = |from: P, to: P| -> (P, f64) {
739 let d = sub(to, from);
740 let length2 = dot(d, d);
741 let s = if length2 > 0.0 {
742 (dot(sub(p, from), d) / length2).clamp(0.0, 1.0)
743 } else {
744 0.0
745 };
746 (along(from, d, s), s)
747 };
748 let (qab, sab) = on(a, b);
749 let (qbc, sbc) = on(b, c);
750 let (qca, sca) = on(c, a);
751 let mut best = (qab, [1.0 - sab, sab, 0.0]);
752 let mut held = distance2(qab, p);
753 for (q, w) in [(qbc, [0.0, 1.0 - sbc, sbc]), (qca, [sca, 0.0, 1.0 - sca])] {
754 let d = distance2(q, p);
755 if d < held {
756 held = d;
757 best = (q, w);
758 }
759 }
760 best
761}
762
763#[derive(Debug, Clone, Copy)]
768struct Shear {
769 origin: P,
770 axes: [usize; 3],
771 lean: [f64; 2],
772 scale: f64,
773}
774
775impl Shear {
776 fn new(origin: Point, direction: Vector) -> Option<Self> {
777 let length = direction.magnitude();
778 let o = [origin.x, origin.y, origin.z];
779 if !(length > 0.0 && length.is_finite()) || !o.iter().all(|v| v.is_finite()) {
780 return None;
781 }
782 let d = [
783 direction.x / length,
784 direction.y / length,
785 direction.z / length,
786 ];
787 let mut kz = 0;
788 for axis in 1..3 {
789 if d[axis].abs() > d[kz].abs() {
790 kz = axis;
791 }
792 }
793 let mut kx = (kz + 1) % 3;
794 let mut ky = (kx + 1) % 3;
795 if d[kz] < 0.0 {
796 core::mem::swap(&mut kx, &mut ky);
797 }
798 Some(Self {
799 origin: o,
800 axes: [kx, ky, kz],
801 lean: [d[kx] / d[kz], d[ky] / d[kz]],
802 scale: 1.0 / d[kz],
803 })
804 }
805
806 fn transform(&self, p: P) -> P {
808 let [kx, ky, kz] = self.axes;
809 let dz = p[kz] - self.origin[kz];
810 [
811 (p[kx] - self.origin[kx]) - self.lean[0] * dz,
812 (p[ky] - self.origin[ky]) - self.lean[1] * dz,
813 self.scale * dz,
814 ]
815 }
816
817 fn box_depth(&self, node: &Node) -> Option<f64> {
824 let [kx, ky, kz] = self.axes;
825 let z0 = node.low[kz] - self.origin[kz];
826 let z1 = node.high[kz] - self.origin[kz];
827 let lateral = |k: usize, lean: f64| -> bool {
828 let (s0, s1) = (lean * z0, lean * z1);
829 let least = (node.low[k] - self.origin[k]) - s0.max(s1);
830 let most = (node.high[k] - self.origin[k]) - s0.min(s1);
831 least <= 0.0 && most >= 0.0
832 };
833 if !lateral(kx, self.lean[0]) || !lateral(ky, self.lean[1]) {
834 return None;
835 }
836 let (d0, d1) = (self.scale * z0, self.scale * z1);
837 let (near, far) = if d0 <= d1 { (d0, d1) } else { (d1, d0) };
838 (far >= 0.0).then_some(near.max(0.0))
839 }
840
841 fn triangle(&self, corners: &[P; 3], max: f64, both_sides: bool) -> Option<Hit> {
844 let [a, b, c] = corners.map(|p| self.transform(p));
845 let origin = [0.0, 0.0];
846 let signs = [
847 Exact::orient2d([c[0], c[1]], [b[0], b[1]], origin),
848 Exact::orient2d([a[0], a[1]], [c[0], c[1]], origin),
849 Exact::orient2d([b[0], b[1]], [a[0], a[1]], origin),
850 ];
851 let any = |s: Sign| signs.contains(&s);
852 if (any(Sign::Negative) && any(Sign::Positive))
853 || !(any(Sign::Negative) || any(Sign::Positive))
854 {
855 return None;
856 }
857 let facing = any(Sign::Positive);
858 if !both_sides && !facing {
859 return None;
860 }
861 let raw = [
865 c[0] * b[1] - c[1] * b[0],
866 a[0] * c[1] - a[1] * c[0],
867 b[0] * a[1] - b[1] * a[0],
868 ];
869 let weights: [f64; 3] = [0, 1, 2].map(|i| match signs[i] {
870 Sign::Zero => 0.0,
871 Sign::Positive if raw[i] > 0.0 => raw[i],
872 Sign::Negative if raw[i] < 0.0 => raw[i],
873 _ => 0.0,
874 });
875 let det = weights[0] + weights[1] + weights[2];
876 let barycentric = if det == 0.0 || !det.is_finite() {
877 [1.0 / 3.0; 3]
878 } else {
879 weights.map(|w| w / det)
880 };
881 let depth = barycentric[0] * a[2] + barycentric[1] * b[2] + barycentric[2] * c[2];
882 let least = a[2].min(b[2]).min(c[2]);
883 let most = a[2].max(b[2]).max(c[2]);
884 let depth = depth.clamp(least, most);
885 if !(depth >= 0.0 && depth <= max) {
886 return None;
887 }
888 let [pa, pb, pc] = *corners;
889 let at = [0, 1, 2]
890 .map(|k| barycentric[0] * pa[k] + barycentric[1] * pb[k] + barycentric[2] * pc[k]);
891 Some(Hit {
892 triangle: 0,
893 point: point(at),
894 distance: depth,
895 barycentric,
896 })
897 }
898}
899
900fn node_of(boxes: &[(P, P)], range: &mut [u32], start: usize) -> (Node, Option<usize>) {
906 let centre = |t: u32| -> P {
907 let (low, high) = boxes[t as usize];
908 [0, 1, 2].map(|k| 0.5 * (low[k] + high[k]))
909 };
910 let (mut low, mut high) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
911 let (mut clow, mut chigh) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
912 for &t in range.iter() {
913 let (l, h) = boxes[t as usize];
914 let c = centre(t);
915 for k in 0..3 {
916 low[k] = low[k].min(l[k]);
917 high[k] = high[k].max(h[k]);
918 clow[k] = clow[k].min(c[k]);
919 chigh[k] = chigh[k].max(c[k]);
920 }
921 }
922 let count = range.len();
923 let leaf = Node {
924 low,
925 high,
926 first: u32::try_from(start).unwrap_or(u32::MAX),
927 count: u32::try_from(count).unwrap_or(u32::MAX),
928 };
929 if count <= 2 {
930 return (leaf, None);
931 }
932 let mid = match sah_split(boxes, range, &clow, &chigh, area(&low, &high)) {
933 Some(split) if split.pays || count > LEAF_MOST => {
934 let bin_of = binner(&clow, &chigh, split.axis);
935 partition(range, |t| bin_of(centre(t)[split.axis]) <= split.bin)
936 }
937 None if count > LEAF_MOST => count / 2,
938 _ => return (leaf, None),
939 };
940 (
941 Node {
942 first: 0,
943 count: 0,
944 ..leaf
945 },
946 Some(mid),
947 )
948}
949
950fn build_serial(boxes: &[(P, P)], order: &mut [u32], start: usize, base: usize) -> Vec<Node> {
954 let mut nodes: Vec<Node> = Vec::new();
955 if order.is_empty() {
956 return nodes;
957 }
958 let mut pending: Vec<(usize, usize, Option<usize>)> = vec![(0, order.len(), None)];
960 while let Some((from, to, parent)) = pending.pop() {
961 let index = nodes.len();
962 if let Some(parent) = parent {
963 nodes[parent].first = u32::try_from(base + index).unwrap_or(u32::MAX);
964 }
965 let (node, mid) = node_of(boxes, &mut order[from..to], start + from);
966 nodes.push(node);
967 if let Some(mid) = mid {
968 pending.push((from + mid, to, Some(index)));
969 pending.push((from, from + mid, None));
970 }
971 }
972 nodes
973}
974
975const SUBTREE: usize = 1 << 14;
978
979enum Piece {
982 Node(Node, usize),
983 Range(usize, usize),
984}
985
986fn build_top(boxes: &[(P, P)], order: &mut [u32], start: usize, pieces: &mut Vec<Piece>) {
987 let end = start + order.len();
988 if order.len() <= SUBTREE {
989 pieces.push(Piece::Range(start, end));
990 return;
991 }
992 let (node, mid) = node_of(boxes, order, start);
993 let Some(mid) = mid else {
994 pieces.push(Piece::Node(node, 0));
995 return;
996 };
997 let at = pieces.len();
998 pieces.push(Piece::Node(node, 0));
999 let (left, right) = order.split_at_mut(mid);
1000 build_top(boxes, left, start, pieces);
1001 let right_piece = pieces.len();
1002 if let Piece::Node(_, r) = &mut pieces[at] {
1003 *r = right_piece;
1004 }
1005 build_top(boxes, right, start + mid, pieces);
1006}
1007
1008fn build_nodes(boxes: &[(P, P)], order: &mut [u32]) -> Vec<Node> {
1015 if order.len() <= SUBTREE {
1016 return build_serial(boxes, order, 0, 0);
1017 }
1018 let mut pieces = Vec::new();
1019 build_top(boxes, order, 0, &mut pieces);
1020 let ranges: Vec<(usize, usize)> = pieces
1021 .iter()
1022 .filter_map(|p| match p {
1023 Piece::Range(a, b) => Some((*a, *b)),
1024 Piece::Node(..) => None,
1025 })
1026 .collect();
1027 let built = ogeom_core::parallel::map_ordered(&ranges, |_, &(a, b)| {
1028 let mut part = order[a..b].to_vec();
1029 let nodes = build_serial(boxes, &mut part, a, 0);
1030 (nodes, part)
1031 });
1032 let mut sizes = Vec::with_capacity(pieces.len());
1034 let mut built_at = 0;
1035 for p in &pieces {
1036 sizes.push(match p {
1037 Piece::Node(..) => 1,
1038 Piece::Range(..) => {
1039 built_at += 1;
1040 built[built_at - 1].0.len()
1041 }
1042 });
1043 }
1044 let mut offsets = Vec::with_capacity(pieces.len());
1045 let mut total = 0;
1046 for size in &sizes {
1047 offsets.push(total);
1048 total += size;
1049 }
1050 let mut nodes = Vec::with_capacity(total);
1051 let mut built = built.into_iter();
1052 for (k, p) in pieces.iter().enumerate() {
1053 match p {
1054 Piece::Node(node, right) => nodes.push(Node {
1055 first: if node.count == 0 {
1056 u32::try_from(offsets[*right]).unwrap_or(u32::MAX)
1057 } else {
1058 node.first
1059 },
1060 ..*node
1061 }),
1062 Piece::Range(a, b) => {
1063 let Some((part_nodes, part)) = built.next() else {
1064 continue;
1065 };
1066 order[*a..*b].copy_from_slice(&part);
1067 let shift = u32::try_from(offsets[k]).unwrap_or(u32::MAX);
1068 nodes.extend(part_nodes.into_iter().map(|n| Node {
1069 first: if n.count == 0 {
1070 n.first + shift
1071 } else {
1072 n.first
1073 },
1074 ..n
1075 }));
1076 }
1077 }
1078 }
1079 nodes
1080}
1081
1082struct Split {
1084 axis: usize,
1085 bin: usize,
1086 pays: bool,
1088}
1089
1090fn area(low: &P, high: &P) -> f64 {
1091 let d = [0, 1, 2].map(|k| (high[k] - low[k]).max(0.0));
1092 d[0] * d[1] + d[1] * d[2] + d[2] * d[0]
1093}
1094
1095fn binner(clow: &P, chigh: &P, axis: usize) -> impl Fn(f64) -> usize {
1097 let low = clow[axis];
1098 let scale = bin_count_f64() / (chigh[axis] - low);
1099 move |c: f64| {
1100 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1101 let bin = ((c - low) * scale) as usize;
1102 bin.min(BINS - 1)
1103 }
1104}
1105
1106#[allow(clippy::cast_precision_loss)]
1107fn bin_count_f64() -> f64 {
1108 BINS as f64
1109}
1110
1111fn sah_split(boxes: &[(P, P)], range: &[u32], clow: &P, chigh: &P, whole: f64) -> Option<Split> {
1117 let mut best: Option<(f64, usize, usize)> = None;
1118 for axis in 0..3 {
1119 if chigh[axis] <= clow[axis] {
1120 continue;
1121 }
1122 let bin_of = binner(clow, chigh, axis);
1123 let mut counts = [0_usize; BINS];
1124 let mut lows = [[f64::INFINITY; 3]; BINS];
1125 let mut highs = [[f64::NEG_INFINITY; 3]; BINS];
1126 for &t in range {
1127 let (l, h) = boxes[t as usize];
1128 let b = bin_of(0.5 * (l[axis] + h[axis]));
1129 counts[b] += 1;
1130 for k in 0..3 {
1131 lows[b][k] = lows[b][k].min(l[k]);
1132 highs[b][k] = highs[b][k].max(h[k]);
1133 }
1134 }
1135 let mut right = [(0.0, 0_usize); BINS];
1137 let (mut low, mut high, mut n) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3], 0);
1138 for b in (1..BINS).rev() {
1139 for k in 0..3 {
1140 low[k] = low[k].min(lows[b][k]);
1141 high[k] = high[k].max(highs[b][k]);
1142 }
1143 n += counts[b];
1144 right[b - 1] = (if n > 0 { area(&low, &high) } else { 0.0 }, n);
1145 }
1146 let (mut low, mut high, mut n) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3], 0);
1147 for b in 0..BINS - 1 {
1148 for k in 0..3 {
1149 low[k] = low[k].min(lows[b][k]);
1150 high[k] = high[k].max(highs[b][k]);
1151 }
1152 n += counts[b];
1153 let (ra, rn) = right[b];
1154 if n == 0 || rn == 0 {
1155 continue;
1156 }
1157 #[allow(clippy::cast_precision_loss)]
1158 let cost = area(&low, &high) * n as f64 + ra * rn as f64;
1159 if best.is_none_or(|(held, _, _)| cost < held) {
1160 best = Some((cost, axis, b));
1161 }
1162 }
1163 }
1164 let (cost, axis, bin) = best?;
1165 #[allow(clippy::cast_precision_loss)]
1166 let leaf = range.len() as f64;
1167 Some(Split {
1168 axis,
1169 bin,
1170 pays: whole <= 0.0 || 1.0 + cost / whole < leaf,
1171 })
1172}
1173
1174fn partition(range: &mut [u32], left: impl Fn(u32) -> bool) -> usize {
1177 let mut kept = 0;
1178 let mut rest = Vec::new();
1179 for i in 0..range.len() {
1180 let t = range[i];
1181 if left(t) {
1182 range[kept] = t;
1183 kept += 1;
1184 } else {
1185 rest.push(t);
1186 }
1187 }
1188 range[kept..].copy_from_slice(&rest);
1189 kept
1190}
1191
1192#[cfg(test)]
1193#[allow(clippy::unwrap_used, clippy::expect_used, clippy::cast_precision_loss)]
1194pub(crate) mod tests {
1195 use super::*;
1196
1197 pub(crate) struct Seeded(pub(crate) u64);
1200
1201 impl Seeded {
1202 pub(crate) fn next(&mut self) -> u64 {
1203 let mut x = self.0;
1204 x ^= x << 13;
1205 x ^= x >> 7;
1206 x ^= x << 17;
1207 self.0 = x;
1208 x
1209 }
1210
1211 pub(crate) fn range(&mut self, lo: f64, hi: f64) -> f64 {
1213 #[allow(clippy::cast_precision_loss)]
1214 let unit = (self.next() >> 11) as f64 / (1_u64 << 53) as f64;
1215 lo + (hi - lo) * unit
1216 }
1217
1218 pub(crate) fn below(&mut self, n: usize) -> usize {
1219 usize::try_from(self.next() % n as u64).unwrap()
1220 }
1221
1222 pub(crate) fn point(&mut self, lo: f64, hi: f64) -> Point {
1223 Point::new(self.range(lo, hi), self.range(lo, hi), self.range(lo, hi))
1224 }
1225 }
1226
1227 #[test]
1228 fn a_tree_built_in_pieces_is_the_tree_built_whole() {
1229 let mut rng = Seeded(0x51ed_270b_d3c4_9a17);
1232 let mut boxes = Vec::new();
1233 for i in 0..20 * SUBTREE {
1234 let c = if i % 7 == 0 && i > 0 {
1235 boxes.last().map_or([0.0; 3], |b: &(P, P)| b.0)
1236 } else {
1237 let p = rng.point(-50.0, 50.0);
1238 [p.x, p.y, p.z]
1239 };
1240 let r = rng.range(0.0, 0.5);
1241 boxes.push((c.map(|v| v - r), c.map(|v| v + r)));
1242 }
1243 let all: Vec<u32> = (0..u32::try_from(boxes.len()).unwrap()).collect();
1244 let (mut whole, mut pieces) = (all.clone(), all);
1245 let one = build_serial(&boxes, &mut whole, 0, 0);
1246 let many = build_nodes(&boxes, &mut pieces);
1247 assert_eq!(whole, pieces);
1248 let key = |n: &Node| (n.low, n.high, n.first, n.count);
1249 assert_eq!(one.len(), many.len());
1250 assert!(one.iter().zip(&many).all(|(a, b)| key(a) == key(b)));
1251 }
1252
1253 fn corners_of(mesh: &Triangulation) -> Vec<[P; 3]> {
1254 mesh.triangles
1255 .iter()
1256 .map(|t| {
1257 t.map(|k| {
1258 let p = mesh.positions[k as usize];
1259 [p.x, p.y, p.z]
1260 })
1261 })
1262 .collect()
1263 }
1264
1265 fn brute_nearest(corners: &[[P; 3]], p: Point, max: f64) -> Option<Hit> {
1266 let q = [p.x, p.y, p.z];
1267 let reach = max * max;
1268 let mut best: Option<(f64, usize)> = None;
1269 for (i, t) in corners.iter().enumerate() {
1270 let d2 = closest(q, t).2;
1271 if d2 <= reach && best.is_none_or(|(held, _)| d2 < held) {
1272 best = Some((d2, i));
1273 }
1274 }
1275 let (_, i) = best?;
1276 let (at, barycentric, d2) = closest(q, &corners[i]);
1277 Some(Hit {
1278 triangle: u32::try_from(i).unwrap(),
1279 point: point(at),
1280 distance: d2.sqrt(),
1281 barycentric,
1282 })
1283 }
1284
1285 fn brute_ball(corners: &[[P; 3]], c: Point, r: f64) -> Vec<u32> {
1286 let q = [c.x, c.y, c.z];
1287 (0..corners.len())
1288 .filter(|&i| closest(q, &corners[i]).2 <= r * r)
1289 .map(|i| u32::try_from(i).unwrap())
1290 .collect()
1291 }
1292
1293 fn brute_ray(corners: &[[P; 3]], o: Point, d: Vector, max: f64, both: bool) -> Option<Hit> {
1294 let shear = Shear::new(o, d)?;
1295 let mut best: Option<Hit> = None;
1296 for (i, t) in corners.iter().enumerate() {
1297 if let Some(hit) = shear.triangle(t, max, both)
1298 && best.is_none_or(|held| hit.distance < held.distance)
1299 {
1300 best = Some(Hit {
1301 triangle: u32::try_from(i).unwrap(),
1302 ..hit
1303 });
1304 }
1305 }
1306 best
1307 }
1308
1309 fn torus(around: u32, across: u32, rng: &mut Seeded) -> Triangulation {
1312 let mut mesh = Triangulation::new();
1313 for i in 0..around {
1314 for j in 0..across {
1315 let u = std::f64::consts::TAU * f64::from(i) / f64::from(around);
1316 let v = std::f64::consts::TAU * f64::from(j) / f64::from(across);
1317 let lump = 1.0 + 0.05 * rng.range(-1.0, 1.0);
1318 let r = (2.0 + 0.7 * v.cos()) * lump;
1319 mesh.positions
1320 .push(Point::new(r * u.cos(), r * u.sin(), 0.7 * v.sin() * lump));
1321 }
1322 }
1323 let at = |i: u32, j: u32| (i % around) * across + (j % across);
1324 for i in 0..around {
1325 for j in 0..across {
1326 let (a, b, c, d) = (at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1));
1327 mesh.triangles.push([a, b, c]);
1328 mesh.triangles.push([a, c, d]);
1329 }
1330 }
1331 mesh
1332 }
1333
1334 fn soup(count: usize, rng: &mut Seeded) -> Triangulation {
1337 let mut mesh = Triangulation::new();
1338 for i in 0..count {
1339 let a = rng.point(-1.0, 1.0);
1340 let size = if i % 3 == 0 { 0.5 } else { 0.05 };
1341 let b = a + (rng.point(-1.0, 1.0) - Point::ORIGIN) * size;
1342 let c = a + (rng.point(-1.0, 1.0) - Point::ORIGIN) * size;
1343 let corners = match i % 17 {
1344 3 => [a, b, b],
1345 5 => [a, b, a + (b - a) * 0.25],
1346 7 => [a, a, a],
1347 _ => [a, b, c],
1348 };
1349 let first = u32::try_from(mesh.positions.len()).unwrap();
1350 mesh.positions.extend(corners);
1351 mesh.triangles.push([first, first + 1, first + 2]);
1352 }
1353 mesh
1354 }
1355
1356 fn direction(rng: &mut Seeded) -> Vector {
1357 loop {
1358 let v = rng.point(-1.0, 1.0) - Point::ORIGIN;
1359 if v.magnitude() > 0.1 {
1360 return v;
1361 }
1362 }
1363 }
1364
1365 #[test]
1368 fn every_query_equals_brute_force_on_random_meshes() {
1369 let mut rng = Seeded(0x05ee_d577);
1370 let mut meshes = Vec::new();
1371 for _ in 0..5 {
1372 meshes.push(torus(
1373 12 + u32::try_from(rng.below(12)).unwrap(),
1374 8,
1375 &mut rng,
1376 ));
1377 meshes.push(soup(150 + rng.below(250), &mut rng));
1378 }
1379 let per = 100_000 / meshes.len();
1380 let mut found = [0_usize; 4];
1381 for mesh in &meshes {
1382 let bvh = Bvh::build(mesh).unwrap();
1383 let corners = corners_of(mesh);
1384 let target = |rng: &mut Seeded, kind: usize| -> Point {
1386 let t = &corners[rng.below(corners.len())];
1387 let [a, b, c] = t.map(point);
1388 match kind {
1389 0 => a,
1390 1 => a + (b - a) * rng.range(0.0, 1.0),
1391 2 => {
1392 let (u, v) = (rng.range(0.0, 1.0), rng.range(0.0, 1.0));
1393 let (u, v) = if u + v > 1.0 {
1394 (1.0 - u, 1.0 - v)
1395 } else {
1396 (u, v)
1397 };
1398 a + (b - a) * u + (c - a) * v + (rng.point(-0.05, 0.05) - Point::ORIGIN)
1399 }
1400 _ => rng.point(-3.5, 3.5),
1401 }
1402 };
1403 for q in 0..per {
1404 let p = target(&mut rng, q % 4);
1405 let max = if q % 4 == 0 {
1406 f64::INFINITY
1407 } else {
1408 rng.range(0.0, 1.5)
1409 };
1410 let near = bvh.nearest(p, max);
1411 assert_eq!(near, brute_nearest(&corners, p, max), "{p:?} {max}");
1412 found[0] += usize::from(near.is_some());
1413 let r = rng.range(0.0, 0.6);
1414 let ball = bvh.within_ball(p, r);
1415 assert_eq!(ball, brute_ball(&corners, p, r), "{p:?} {r}");
1416 found[1] += usize::from(!ball.is_empty());
1417 let o = rng.point(-3.5, 3.5);
1419 let aim = target(&mut rng, (q / 4) % 3);
1420 let d = if aim == o {
1421 direction(&mut rng)
1422 } else {
1423 aim - o
1424 };
1425 let max = if q % 3 == 0 {
1426 f64::INFINITY
1427 } else {
1428 rng.range(0.0, 6.0)
1429 };
1430 for both in [false, true] {
1431 let hit = bvh.ray(o, d, max, both);
1432 assert_eq!(
1433 hit,
1434 brute_ray(&corners, o, d, max, both),
1435 "{o:?} {d:?} {max} {both}"
1436 );
1437 found[2 + usize::from(both)] += usize::from(hit.is_some());
1438 }
1439 }
1440 }
1441 assert!(found.iter().all(|&n| n > 25_000), "{found:?}");
1443 }
1444
1445 #[test]
1450 fn filtered_nearest_equals_brute_force_over_the_kept_triangles() {
1451 let mut rng = Seeded(0x00f1_17e2);
1452 let mut checked = 0_usize;
1453 for round in 0..6 {
1454 let mesh = if round % 2 == 0 {
1455 torus(14 + u32::try_from(rng.below(10)).unwrap(), 8, &mut rng)
1456 } else {
1457 soup(150 + rng.below(250), &mut rng)
1458 };
1459 let bvh = Bvh::build(&mesh).unwrap();
1460 let corners = corners_of(&mesh);
1461 let facing: Vec<P> = corners
1462 .iter()
1463 .map(|[a, b, c]| {
1464 let (u, v) = (sub(*b, *a), sub(*c, *a));
1465 [
1466 u[1] * v[2] - u[2] * v[1],
1467 u[2] * v[0] - u[0] * v[2],
1468 u[0] * v[1] - u[1] * v[0],
1469 ]
1470 })
1471 .collect();
1472 let points: Vec<Point> = (0..4000).map(|_| rng.point(-3.5, 3.5)).collect();
1473 let aims: Vec<P> = (0..points.len())
1474 .map(|_| {
1475 let d = direction(&mut rng);
1476 [d.x, d.y, d.z]
1477 })
1478 .collect();
1479 let max = if round < 3 { f64::INFINITY } else { 1.0 };
1480 let filters: [&(dyn Fn(usize, u32) -> bool + Sync); 3] = [
1481 &|_, t| t % 2 == 1,
1482 &|i, t| dot(facing[t as usize], aims[i]) > 0.0,
1483 &|_, _| false,
1484 ];
1485 for keep in filters {
1486 let brute: Vec<Option<Hit>> = points
1487 .iter()
1488 .enumerate()
1489 .map(|(i, &p)| {
1490 let kept: Vec<usize> = (0..corners.len())
1491 .filter(|&t| keep(i, u32::try_from(t).unwrap()))
1492 .collect();
1493 let picked: Vec<[P; 3]> = kept.iter().map(|&t| corners[t]).collect();
1494 brute_nearest(&picked, p, max).map(|hit| Hit {
1495 triangle: u32::try_from(kept[hit.triangle as usize]).unwrap(),
1496 ..hit
1497 })
1498 })
1499 .collect();
1500 let single: Vec<Option<Hit>> = points
1501 .iter()
1502 .enumerate()
1503 .map(|(i, &p)| bvh.nearest_where(p, max, |t| keep(i, t)))
1504 .collect();
1505 assert_eq!(single, brute);
1506 for threads in [1, 3, 8] {
1507 ogeom_core::parallel::set_threads(threads);
1508 assert_eq!(bvh.nearest_many_where(&points, max, keep), brute);
1509 }
1510 ogeom_core::parallel::set_threads(0);
1511 checked += brute.iter().filter(|h| h.is_some()).count();
1512 }
1513 }
1514 assert!(checked > 20_000, "{checked}");
1515 }
1516
1517 #[test]
1520 fn batches_answer_as_single_queries_at_any_thread_count() {
1521 let mut rng = Seeded(77);
1522 let mesh = torus(30, 12, &mut rng);
1523 let bvh = Bvh::build(&mesh).unwrap();
1524 let points: Vec<Point> = (0..3000).map(|_| rng.point(-3.0, 3.0)).collect();
1525 let dirs: Vec<Vector> = (0..3000).map(|_| direction(&mut rng)).collect();
1526 let near: Vec<Option<Hit>> = points.iter().map(|&p| bvh.nearest(p, 1.0)).collect();
1527 let rays: Vec<Option<Hit>> = points
1528 .iter()
1529 .zip(&dirs)
1530 .map(|(&p, &d)| bvh.ray(p, d, f64::INFINITY, true))
1531 .collect();
1532 for threads in [1, 3, 8] {
1533 ogeom_core::parallel::set_threads(threads);
1534 assert_eq!(bvh.nearest_many(&points, 1.0), near);
1535 assert_eq!(bvh.rays(&points, &dirs, f64::INFINITY, true).unwrap(), rays);
1536 }
1537 ogeom_core::parallel::set_threads(0);
1538 assert!(bvh.rays(&points, &dirs[1..], 1.0, true).is_err());
1539 }
1540
1541 #[test]
1545 fn rays_through_edges_and_vertices_do_not_slip_through() {
1546 let mut rng = Seeded(9);
1547 let mesh = torus(40, 16, &mut rng);
1548 let bvh = Bvh::build(&mesh).unwrap();
1549 let mut aimed = 0;
1550 for t in &mesh.triangles {
1551 let [a, b, _] = t.map(|k| mesh.positions[k as usize]);
1552 let mid = Point::new((a.x + b.x) / 2.0, (a.y + b.y) / 2.0, 0.0);
1555 let core = (mid - Point::ORIGIN) / (mid - Point::ORIGIN).magnitude() * 2.0;
1556 let inside = Point::ORIGIN + core;
1557 for target in [a, b, a + (b - a) * 0.5, a + (b - a) * 0.25] {
1558 let hit = bvh.ray(inside, target - inside, f64::INFINITY, true);
1560 assert!(
1561 hit.is_some(),
1562 "a ray from {inside:?} through {target:?} slipped"
1563 );
1564 aimed += 1;
1565 }
1566 }
1567 assert_eq!(aimed, 4 * mesh.triangles.len());
1568 let mut grid = Triangulation::new();
1572 for i in 0..5_u32 {
1573 for j in 0..5_u32 {
1574 grid.positions
1575 .push(Point::new(f64::from(i) * 0.25, f64::from(j) * 0.25, 0.0));
1576 }
1577 }
1578 for i in 0..4 {
1579 for j in 0..4 {
1580 let (a, b, c, d) = (
1581 i * 5 + j,
1582 (i + 1) * 5 + j,
1583 (i + 1) * 5 + j + 1,
1584 i * 5 + j + 1,
1585 );
1586 grid.triangles.push([a, b, c]);
1587 grid.triangles.push([a, c, d]);
1588 }
1589 }
1590 let flat = Bvh::build(&grid).unwrap();
1591 for i in 0..=8 {
1592 for j in 0..=8 {
1593 let at = Point::new(f64::from(i) * 0.125, f64::from(j) * 0.125, 1.0);
1594 let hit = flat
1595 .ray(at, Vector::new(0.0, 0.0, -1.0), 2.0, false)
1596 .expect("a ray onto the grid hits it");
1597 assert_eq!(hit.distance, 1.0);
1598 let corners = corners_of(&grid);
1600 let least = brute_ray(&corners, at, Vector::new(0.0, 0.0, -1.0), 2.0, false);
1601 assert_eq!(Some(hit), least);
1602 }
1603 }
1604 }
1605
1606 #[test]
1609 fn a_ray_meets_the_front_or_both_sides_at_the_closed_form_depth() {
1610 let mut mesh = Triangulation::new();
1611 mesh.positions = vec![
1612 Point::new(0.0, 0.0, 0.0),
1613 Point::new(4.0, 0.0, 0.0),
1614 Point::new(0.0, 4.0, 0.0),
1615 ];
1616 mesh.triangles = vec![[0, 1, 2]];
1617 let bvh = Bvh::build(&mesh).unwrap();
1618 let down = bvh
1619 .ray(
1620 Point::new(1.0, 2.0, 3.0),
1621 Vector::new(0.0, 0.0, -2.0),
1622 10.0,
1623 false,
1624 )
1625 .unwrap();
1626 assert_eq!(down.distance, 3.0);
1627 assert_eq!(down.point, Point::new(1.0, 2.0, 0.0));
1628 assert_eq!(down.barycentric, [0.25, 0.25, 0.5]);
1629 let up = Vector::new(0.0, 0.0, 1.0);
1630 assert!(
1631 bvh.ray(Point::new(1.0, 2.0, -3.0), up, 10.0, false)
1632 .is_none()
1633 );
1634 assert_eq!(
1635 bvh.ray(Point::new(1.0, 2.0, -3.0), up, 10.0, true)
1636 .unwrap()
1637 .distance,
1638 3.0
1639 );
1640 for (o, d) in [
1642 (Point::new(1.0, 1.0, 1.0), Vector::new(0.1, -0.2, -1.0)),
1643 (Point::new(1.5, 1.0, 1.0), Vector::new(-1.0, 0.0, -0.9)),
1644 (Point::new(1.0, 2.0, 1.0), Vector::new(0.3, -1.0, -0.8)),
1645 (Point::new(3.0, 0.5, 1.0), Vector::new(-1.0, 0.1, -0.5)),
1646 (Point::new(0.5, 3.0, 1.0), Vector::new(0.0, -1.0, -0.5)),
1647 (Point::new(0.2, 1.0, 1.0), Vector::new(1.0, 0.2, -0.6)),
1648 (Point::new(1.0, 0.2, 1.0), Vector::new(0.2, 1.0, -0.6)),
1649 ] {
1650 assert!(bvh.ray(o, d, 10.0, false).is_some(), "{d:?}");
1651 let below = Point::new(o.x, o.y, -o.z);
1652 let rising = Vector::new(d.x, d.y, -d.z);
1653 assert!(bvh.ray(below, rising, 10.0, false).is_none(), "{rising:?}");
1654 assert!(bvh.ray(below, rising, 10.0, true).is_some(), "{rising:?}");
1655 }
1656 assert!(
1658 bvh.ray(Point::new(1.0, 2.0, 3.0), -up, 2.9, false)
1659 .is_none()
1660 );
1661 assert!(bvh.ray(Point::new(1.0, 2.0, 3.0), up, 10.0, true).is_none());
1662 assert_eq!(
1664 bvh.hits_along(Point::new(1.0, 2.0, 3.0), -up, 10.0, true)
1665 .len(),
1666 1
1667 );
1668 }
1669
1670 #[test]
1673 fn queries_on_vertices_and_edges_land_on_them() {
1674 let mut rng = Seeded(31);
1675 let mesh = torus(16, 8, &mut rng);
1676 let bvh = Bvh::build(&mesh).unwrap();
1677 for (k, &p) in mesh.positions.iter().enumerate() {
1678 let hit = bvh.nearest(p, f64::INFINITY).unwrap();
1679 assert_eq!(hit.distance, 0.0);
1680 assert_eq!(hit.point, p);
1681 let k = u32::try_from(k).unwrap();
1682 let least = mesh.triangles.iter().position(|t| t.contains(&k)).unwrap();
1683 assert_eq!(hit.triangle as usize, least);
1684 assert!(bvh.within_ball(p, 0.0).contains(&hit.triangle));
1685 }
1686 for t in &mesh.triangles {
1687 let [a, b, _] = t.map(|k| mesh.positions[k as usize]);
1688 let on = a + (b - a) * 0.5;
1689 let hit = bvh.nearest(on, f64::INFINITY).unwrap();
1690 assert!(hit.distance <= 1e-14, "{}", hit.distance);
1691 }
1692 }
1693
1694 #[test]
1697 fn degenerate_triangles_answer_as_their_segments_and_points() {
1698 let mut mesh = Triangulation::new();
1699 mesh.positions = vec![
1700 Point::new(0.0, 0.0, 0.0),
1701 Point::new(2.0, 0.0, 0.0),
1702 Point::new(2.0, 0.0, 0.0),
1703 Point::new(50.0, 50.0, 50.0),
1704 Point::new(1.0, 0.0, 0.0),
1705 ];
1706 mesh.triangles = vec![[0, 1, 2], [0, 4, 1], [3, 3, 3]];
1709 let bvh = Bvh::build(&mesh).unwrap();
1710 let hit = bvh
1711 .nearest(Point::new(1.5, 3.0, 4.0), f64::INFINITY)
1712 .unwrap();
1713 assert_eq!(hit.triangle, 0);
1714 assert_eq!(hit.distance, 5.0);
1715 assert_eq!(hit.point, Point::new(1.5, 0.0, 0.0));
1716 let hit = bvh
1717 .nearest(Point::new(50.0, 50.0, 52.0), f64::INFINITY)
1718 .unwrap();
1719 assert_eq!((hit.triangle, hit.distance), (2, 2.0));
1720 assert_eq!(bvh.within_ball(Point::new(1.0, 1.0, 0.0), 1.0), vec![0, 1]);
1721 assert!(
1724 bvh.ray(
1725 Point::new(1.0, 0.0, 1.0),
1726 Vector::new(0.0, 0.0, -1.0),
1727 5.0,
1728 true
1729 )
1730 .is_none()
1731 );
1732 }
1733
1734 #[test]
1735 fn an_empty_mesh_answers_nothing() {
1736 let bvh = Bvh::build(&Triangulation::new()).unwrap();
1737 assert!(bvh.is_empty());
1738 assert!(bvh.bounds().is_empty());
1739 let p = Point::new(0.0, 0.0, 0.0);
1740 assert!(bvh.nearest(p, f64::INFINITY).is_none());
1741 assert!(bvh.within_ball(p, 1e9).is_empty());
1742 assert!(
1743 bvh.ray(p, Vector::new(1.0, 0.0, 0.0), f64::INFINITY, true)
1744 .is_none()
1745 );
1746 assert!(
1747 bvh.hits_along(p, Vector::new(1.0, 0.0, 0.0), 1.0, true)
1748 .is_empty()
1749 );
1750 assert!(bvh.triangles_where(|_| true).is_empty());
1751 assert_eq!(bvh.nearest_many(&[p, p], 1.0), vec![None, None]);
1752 assert!(bvh.triangle(0).is_none());
1753 }
1754
1755 #[test]
1758 fn a_negative_or_undefined_reach_finds_nothing() {
1759 let mut mesh = Triangulation::new();
1760 mesh.positions = vec![
1761 Point::new(0.0, 0.0, 0.0),
1762 Point::new(1.0, 0.0, 0.0),
1763 Point::new(0.0, 1.0, 0.0),
1764 ];
1765 mesh.triangles = vec![[0, 1, 2]];
1766 let bvh = Bvh::build(&mesh).unwrap();
1767 let above = Point::new(0.2, 0.2, 1.0);
1768 assert!(bvh.nearest(above, 1.0).is_some());
1769 for bad in [-1.0, -2.0, f64::NAN] {
1770 assert!(bvh.nearest(above, bad).is_none());
1771 assert!(bvh.within_ball(above, bad).is_empty());
1772 assert!(
1773 bvh.ray(above, Vector::new(0.0, 0.0, -1.0), bad, true)
1774 .is_none()
1775 );
1776 }
1777 }
1778
1779 #[test]
1780 fn a_mesh_naming_a_missing_or_infinite_corner_is_refused() {
1781 let mut mesh = Triangulation::new();
1782 mesh.positions = vec![Point::new(0.0, 0.0, 0.0); 2];
1783 mesh.triangles = vec![[0, 1, 2]];
1784 assert!(Bvh::build(&mesh).is_err());
1785 mesh.positions.push(Point::new(f64::NAN, 0.0, 0.0));
1786 assert!(Bvh::build(&mesh).is_err());
1787 }
1788
1789 #[test]
1792 fn a_shape_s_tessellation_indexes_as_its_triangulation() {
1793 use ogeom_core::Tolerances;
1794 let tol = Tolerances::millimetres();
1795 let mut model = Model::new();
1796 let solid =
1797 ogeom_algo::make_box(&mut model, ogeom_math::Frame::WORLD, (2.0, 3.0, 4.0), tol)
1798 .unwrap()
1799 .shape;
1800 let deflection = Deflection::default();
1801 let bvh = Bvh::of_shape(&model, &solid, deflection, tol).unwrap();
1802 let mesh = crate::triangulate::triangulate(&model, &solid, deflection, tol).unwrap();
1803 assert_eq!(bvh.triangle_count(), mesh.triangles.len());
1804 for (i, t) in mesh.triangles.iter().enumerate() {
1805 let corners = t.map(|k| mesh.positions[k as usize]);
1806 assert_eq!(bvh.triangle(u32::try_from(i).unwrap()), Some(corners));
1807 }
1808 let hit = bvh
1809 .nearest(Point::new(1.0, 1.5, 9.0), f64::INFINITY)
1810 .unwrap();
1811 assert_eq!(hit.distance, 5.0);
1812 assert_eq!(hit.point, Point::new(1.0, 1.5, 4.0));
1813 let found = bvh.triangles_where(|b| b.contains(Point::new(1.0, 1.5, 4.0)));
1814 assert!(found.contains(&hit.triangle));
1815 }
1816}