ifc_lite_geometry/kernel/broadphase/
nearest.rs1use super::{aabb_contains, Aabb, Bvh};
5
6impl Bvh {
7 pub fn nearest_point_bounded(
16 &self,
17 point: [f64; 3],
18 radius: f64,
19 remaining: &mut usize,
20 ambiguity: f64,
21 candidates: &mut Vec<(u32, f64)>,
22 mut distance_squared: impl FnMut(u32) -> f64,
23 ) -> Result<Option<(u32, f64)>, &'static str> {
24 if !point.iter().all(|v| v.is_finite()) || !radius.is_finite() || radius < 0.
25 || !ambiguity.is_finite() || ambiguity < 0. || !(radius + ambiguity).is_finite() {
26 return Err("BVH query requires a finite point and nonnegative radius");
27 }
28 candidates.clear();
29 if self.root == u32::MAX { return Ok(None); }
30 let mut pending = [0_u32; 64];
31 pending[0] = self.root;
32 let mut count = 1;
33 let mut best: Option<(u32, u32, f64)> = None;
34 let mut limit = radius + ambiguity;
35 while count > 0 {
36 count -= 1;
37 let index = pending[count];
38 charge(remaining)?;
39 let node = &self.nodes[index as usize];
40 if !aabb_contains(point, &node.aabb, self.pad + limit) { continue; }
41 if node.tri != u32::MAX {
42 charge(remaining)?;
43 let d2 = distance_squared(node.tri);
44 if !d2.is_finite() || d2 < 0. {
45 return Err("BVH primitive distance must be finite and nonnegative");
46 }
47 if d2.sqrt() > limit { continue; }
48 candidates.push((node.tri, d2));
49 if best.is_none_or(|(_, old_index, old)| d2 < old || (d2 == old && index < old_index)) {
50 best = Some((node.tri, index, d2));
51 limit = d2.sqrt() + ambiguity;
52 }
53 } else {
54 if count + 2 > pending.len() { return Err("BVH query stack budget exceeded"); }
55 let left = box_distance(point, &self.nodes[node.left as usize].aabb);
56 let right = box_distance(point, &self.nodes[node.right as usize].aabb);
57 let (near, far) = if left <= right { (node.left, node.right) } else { (node.right, node.left) };
59 pending[count] = far;
60 pending[count + 1] = near;
61 count += 2;
62 }
63 }
64 *remaining = remaining.checked_sub(candidates.len()).ok_or("BVH query work budget exhausted")?;
65 candidates.retain(|(_, d2)| d2.sqrt() <= limit);
66 Ok(best.filter(|(_, _, d2)| d2.sqrt() <= radius).map(|(id, _, d2)| (id, d2)))
67 }
68}
69fn charge(remaining: &mut usize) -> Result<(), &'static str> {
70 *remaining = remaining.checked_sub(1).ok_or("BVH query work budget exhausted")?;
71 Ok(())
72}
73fn box_distance(point: [f64; 3], bounds: &Aabb) -> f64 {
74 (0..3).map(|axis| {
75 let d = (bounds.0[axis] - point[axis]).max(point[axis] - bounds.1[axis]).max(0.);
76 d * d
77 }).sum()
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 fn d2(a: [f64; 3], b: [f64; 3]) -> f64 {
84 (0..3).map(|axis| (a[axis] - b[axis]).powi(2)).sum()
85 }
86 #[test]
87 fn transfer_4381_nearest_and_band_match_independent_distances() {
88 let points: Vec<_> = (0..125).map(|i| [f64::from(i % 5), f64::from((i / 5) % 5), f64::from(i / 25)]).collect();
89 let tree = Bvh::build(&points.iter().map(|p| [*p; 3]).collect::<Vec<_>>());
90 for query in [[0.1, 0.2, 0.3], [2.5, 2.5, 2.5], [-2., 1., 3.], [4., 4., 4.]] {
91 let mut order = Vec::new();
92 tree.point_candidates(query, 10., &mut order);
93 let mut expected = None;
94 for id in order {
95 let distance = d2(points[id as usize], query);
96 if expected.is_none_or(|(_, old)| distance < old) { expected = Some((id, distance)); }
97 }
98 let mut candidates = Vec::new();
99 let actual = tree.nearest_point_bounded(query, 10., &mut 1000, 0.4, &mut candidates,
100 |id| d2(points[id as usize], query)).unwrap();
101 assert_eq!(actual, expected, "nearest and original left-to-right tie precedence");
102 let nearest = expected.unwrap().1.sqrt();
103 let expected_band: Vec<_> = points.iter().enumerate().filter_map(|(id, point)| {
104 let distance = d2(*point, query);
105 (distance.sqrt() <= nearest + 0.4).then_some((id as u32, distance))
106 }).collect();
107 candidates.sort_by_key(|(id, _)| *id);
108 assert_eq!(candidates, expected_band, "no near-tie observation may be pruned");
109 }
110 }
111 #[test]
112 fn transfer_4381_nearest_refuses_budget_and_invalid_distance() {
113 let tree = Bvh::build(&[[[0.; 3]; 3], [[2.; 3]; 3]]);
114 assert!(tree.nearest_point_bounded([0.; 3], 1., &mut 0, 0., &mut Vec::new(), |_| 0.).is_err());
115 assert!(tree.nearest_point_bounded([0.; 3], 1., &mut 100, 0., &mut Vec::new(), |_| f64::NAN).is_err());
116 assert!(tree.nearest_point_bounded([0.; 3], 1., &mut 100, -1., &mut Vec::new(), |_| 0.).is_err());
117 assert_eq!(tree.nearest_point_bounded([8.; 3], 1., &mut 100, 0., &mut Vec::new(), |_| 108.).unwrap(), None);
118 assert_eq!(Bvh::build(&[]).nearest_point_bounded([0.; 3], 1., &mut 0, 0., &mut Vec::new(), |_| 0.).unwrap(), None);
119 }
120}