1type Tri = [[f64; 3]; 3];
16type Aabb = ([f64; 3], [f64; 3]);
17
18pub fn tri_aabb(t: &Tri) -> Aabb {
19 let mut lo = t[0];
20 let mut hi = t[0];
21 for p in t.iter().skip(1) {
22 for k in 0..3 {
23 lo[k] = lo[k].min(p[k]);
24 hi[k] = hi[k].max(p[k]);
25 }
26 }
27 (lo, hi)
28}
29
30fn overlap(a: &Aabb, b: &Aabb) -> bool {
31 (0..3).all(|k| a.0[k] <= b.1[k] && b.0[k] <= a.1[k])
32}
33
34fn aabb_contains(p: [f64; 3], bb: &Aabb, pad: f64) -> bool {
36 (0..3).all(|k| p[k] >= bb.0[k] - pad && p[k] <= bb.1[k] + pad)
37}
38
39fn seg_hits_aabb(p: [f64; 3], far: [f64; 3], bb: &Aabb, pad: f64) -> bool {
43 let (mut tmin, mut tmax) = (0.0f64, 1.0f64);
44 for k in 0..3 {
45 let (lo, hi) = (bb.0[k] - pad, bb.1[k] + pad);
46 let d = far[k] - p[k];
47 if d.abs() <= f64::MIN_POSITIVE {
48 if p[k] < lo || p[k] > hi {
50 return false;
51 }
52 } else {
53 let inv = 1.0 / d;
54 let (mut t0, mut t1) = ((lo - p[k]) * inv, (hi - p[k]) * inv);
55 if t0 > t1 {
56 std::mem::swap(&mut t0, &mut t1);
57 }
58 tmin = tmin.max(t0);
59 tmax = tmax.min(t1);
60 if tmin > tmax {
61 return false;
62 }
63 }
64 }
65 true
66}
67
68struct Node {
69 aabb: Aabb,
70 tri: u32, left: u32,
72 right: u32,
73}
74
75pub struct Bvh {
76 nodes: Vec<Node>,
77 root: u32,
78 pad: f64,
86}
87
88impl Bvh {
89 pub fn build(tris: &[Tri]) -> Bvh {
90 let mut items: Vec<(u32, Aabb, [f64; 3])> = tris
91 .iter()
92 .enumerate()
93 .map(|(i, t)| {
94 let bb = tri_aabb(t);
95 let c = [
96 0.5 * (bb.0[0] + bb.1[0]),
97 0.5 * (bb.0[1] + bb.1[1]),
98 0.5 * (bb.0[2] + bb.1[2]),
99 ];
100 (i as u32, bb, c)
101 })
102 .collect();
103 let mut nodes = Vec::new();
104 let root = if items.is_empty() {
105 u32::MAX
106 } else {
107 build_node(&mut nodes, &mut items)
108 };
109 let pad = if root == u32::MAX {
110 0.0
111 } else {
112 let (lo, hi) = nodes[root as usize].aabb;
113 let diag = ((hi[0] - lo[0]).powi(2) + (hi[1] - lo[1]).powi(2) + (hi[2] - lo[2]).powi(2))
114 .sqrt();
115 (diag * 1.0e-9).max(1.0e-12)
116 };
117 Bvh { nodes, root, pad }
118 }
119
120 pub fn ray_candidates(&self, p: [f64; 3], far: [f64; 3], out: &mut Vec<u32>) {
125 if self.root != u32::MAX {
126 self.descend_ray(self.root, p, far, out);
127 }
128 }
129
130 fn descend_ray(&self, idx: u32, p: [f64; 3], far: [f64; 3], out: &mut Vec<u32>) {
131 let n = &self.nodes[idx as usize];
132 if !seg_hits_aabb(p, far, &n.aabb, self.pad) {
133 return;
134 }
135 if n.tri != u32::MAX {
136 out.push(n.tri);
137 } else {
138 self.descend_ray(n.left, p, far, out);
139 self.descend_ray(n.right, p, far, out);
140 }
141 }
142
143 pub fn point_candidates(&self, p: [f64; 3], radius: f64, out: &mut Vec<u32>) {
148 if self.root != u32::MAX {
149 self.descend_point(self.root, p, radius, out);
150 }
151 }
152
153 fn descend_point(&self, idx: u32, p: [f64; 3], radius: f64, out: &mut Vec<u32>) {
154 let n = &self.nodes[idx as usize];
155 if !aabb_contains(p, &n.aabb, self.pad + radius) {
156 return;
157 }
158 if n.tri != u32::MAX {
159 out.push(n.tri);
160 } else {
161 self.descend_point(n.left, p, radius, out);
162 self.descend_point(n.right, p, radius, out);
163 }
164 }
165
166 pub fn query(&self, q: &Aabb, out: &mut Vec<u32>) {
168 if self.root != u32::MAX {
169 self.descend(self.root, q, out);
170 }
171 }
172
173 fn descend(&self, idx: u32, q: &Aabb, out: &mut Vec<u32>) {
174 let n = &self.nodes[idx as usize];
175 if !overlap(&n.aabb, q) {
176 return;
177 }
178 if n.tri != u32::MAX {
179 out.push(n.tri);
180 } else {
181 self.descend(n.left, q, out);
182 self.descend(n.right, q, out);
183 }
184 }
185}
186
187fn bounds(items: &[(u32, Aabb, [f64; 3])]) -> Aabb {
188 let mut lo = items[0].1 .0;
189 let mut hi = items[0].1 .1;
190 for it in &items[1..] {
191 for k in 0..3 {
192 lo[k] = lo[k].min(it.1 .0[k]);
193 hi[k] = hi[k].max(it.1 .1[k]);
194 }
195 }
196 (lo, hi)
197}
198
199fn build_node(nodes: &mut Vec<Node>, items: &mut [(u32, Aabb, [f64; 3])]) -> u32 {
200 let bb = bounds(items);
201 if items.len() == 1 {
202 let idx = nodes.len() as u32;
203 nodes.push(Node { aabb: bb, tri: items[0].0, left: 0, right: 0 });
204 return idx;
205 }
206 let mut span = [0.0f64; 3];
208 let (mut clo, mut chi) = (items[0].2, items[0].2);
209 for it in items.iter() {
210 for k in 0..3 {
211 clo[k] = clo[k].min(it.2[k]);
212 chi[k] = chi[k].max(it.2[k]);
213 }
214 }
215 for k in 0..3 {
216 span[k] = chi[k] - clo[k];
217 }
218 let axis = if span[0] >= span[1] && span[0] >= span[2] {
219 0
220 } else if span[1] >= span[2] {
221 1
222 } else {
223 2
224 };
225 items.sort_by(|a, b| a.2[axis].total_cmp(&b.2[axis]));
228 let mid = items.len() / 2;
229 let (l, r) = items.split_at_mut(mid);
230 let left = build_node(nodes, l);
231 let right = build_node(nodes, r);
232 let idx = nodes.len() as u32;
233 nodes.push(Node { aabb: bb, tri: u32::MAX, left, right });
234 idx
235}
236
237pub fn candidate_pairs(a: &[Tri], b: &[Tri]) -> Vec<(usize, usize)> {
240 let bvh = Bvh::build(b);
241 let mut pairs = Vec::new();
242 let mut cand = Vec::new();
243 for (i, ta) in a.iter().enumerate() {
244 cand.clear();
245 bvh.query(&tri_aabb(ta), &mut cand);
246 for &j in &cand {
247 pairs.push((i, j as usize));
248 }
249 }
250 pairs.sort_unstable();
251 pairs
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 fn brute(a: &[Tri], b: &[Tri]) -> Vec<(usize, usize)> {
259 let mut p = Vec::new();
260 for (i, ta) in a.iter().enumerate() {
261 for (j, tb) in b.iter().enumerate() {
262 if overlap(&tri_aabb(ta), &tri_aabb(tb)) {
263 p.push((i, j));
264 }
265 }
266 }
267 p.sort_unstable();
268 p
269 }
270
271 #[test]
272 fn ray_and_point_candidates_are_conservative_supersets() {
273 let tris: Vec<Tri> = (0..60)
277 .map(|i| {
278 let (x, y, z) = (i as f64 * 0.7, (i % 7) as f64 * 1.3, (i % 5) as f64 * 0.9);
279 [[x, y, z], [x + 0.4, y, z], [x, y + 0.5, z + 0.3]]
280 })
281 .collect();
282 let bvh = Bvh::build(&tris);
283 let rays = [
284 ([0.0, 0.0, 0.0], [40.0, 9.0, 4.0]),
285 ([5.0, 3.0, 1.0], [5.0001, 3.0, 100.0]),
286 ([-2.0, -2.0, -2.0], [42.0, 12.0, 6.0]),
287 ];
288 for (p, far) in rays {
289 let mut cand = Vec::new();
290 bvh.ray_candidates(p, far, &mut cand);
291 let cset: std::collections::HashSet<u32> = cand.into_iter().collect();
292 for (i, t) in tris.iter().enumerate() {
293 if seg_hits_aabb(p, far, &tri_aabb(t), 0.0) {
294 assert!(cset.contains(&(i as u32)), "ray missed candidate {i}");
295 }
296 }
297 }
298 for p in [[5.2, 3.0, 0.1], [0.1, 0.0, 0.0], [41.0, 7.0, 3.6]] {
299 let mut cand = Vec::new();
300 bvh.point_candidates(p, 0.0, &mut cand);
301 let cset: std::collections::HashSet<u32> = cand.into_iter().collect();
302 for (i, t) in tris.iter().enumerate() {
303 if aabb_contains(p, &tri_aabb(t), 0.0) {
304 assert!(cset.contains(&(i as u32)), "point missed candidate {i}");
305 }
306 }
307 }
308 }
309
310 #[test]
311 fn bvh_candidate_pairs_match_brute_force() {
312 let mk = |dx: f64| -> Vec<Tri> {
315 (0..20)
316 .map(|i| {
317 let x = dx + i as f64 * 0.3;
318 [[x, 0., 0.], [x + 0.5, 0., 0.], [x, 0.5, 0.4]]
319 })
320 .collect()
321 };
322 let a = mk(0.0);
323 let b = mk(1.7);
324 assert_eq!(candidate_pairs(&a, &b), brute(&a, &b));
325 let far = mk(1000.0);
327 assert!(candidate_pairs(&a, &far).is_empty());
328 }
329}