1use fast_mesh_bvh::{Aabb, Bvh, NodeKind};
4use fast_mesh_store::{Mesh, TriangleId, VertexId};
5
6use crate::ray::points;
7use crate::tri::{box_triangle, sphere_triangle};
8use crate::{Appender, QueryError, Result, check};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum Region {
14 Box(Aabb),
16 Sphere {
18 center: [f32; 3],
20 radius: f32,
22 },
23}
24
25const SPHERE_PAD: f32 = 1e-22;
41
42impl Region {
43 fn validate(&self) -> Result<()> {
44 if let Region::Sphere { center, radius } = self {
45 if !center.iter().all(|c| c.is_finite()) {
46 return Err(QueryError::InvalidInput("sphere center must be finite"));
47 }
48 if !(radius.is_finite() && *radius >= 0.0) {
49 return Err(QueryError::InvalidInput(
50 "sphere radius must be finite and not negative",
51 ));
52 }
53 }
54 Ok(())
55 }
56
57 fn bounds(&self) -> Aabb {
82 match *self {
83 Region::Box(b) => b,
84 Region::Sphere { center, radius } => {
85 let r = radius * (1.0 + 4.0 * f32::EPSILON) + SPHERE_PAD;
86 Aabb::new(
87 center.map(|c| (c - r).next_down()),
88 center.map(|c| (c + r).next_up()),
89 )
90 }
91 }
92 }
93
94 fn contains_point(&self, q: [f32; 3]) -> bool {
95 match *self {
96 Region::Box(b) => (0..3).all(|k| b.min[k] <= q[k] && q[k] <= b.max[k]),
97 Region::Sphere { center, radius } => {
98 let d: f32 = (0..3)
99 .map(|k| (q[k] - center[k]) * (q[k] - center[k]))
100 .sum();
101 d <= radius * radius
102 }
103 }
104 }
105
106 fn meets_triangle(&self, p: [[f32; 3]; 3]) -> bool {
107 match *self {
108 Region::Box(b) => box_triangle(&b, p),
109 Region::Sphere { center, radius } => sphere_triangle(center, radius, p),
110 }
111 }
112}
113
114fn candidates(
117 bvh: &Bvh,
118 region: &Region,
119 mut leaf: impl FnMut(TriangleId) -> Result<()>,
120) -> Result<()> {
121 let bounds = region.bounds();
122 let mut failed = None;
123 bvh.traverse(|_, node| {
124 if failed.is_some() || !node.bounds.overlaps(&bounds) {
125 return false;
126 }
127 if let NodeKind::Leaf { triangles } = node.kind {
128 for &id in triangles {
129 if let Err(e) = leaf(id) {
130 failed = Some(e);
131 return false;
132 }
133 }
134 }
135 true
136 });
137 failed.map_or(Ok(()), Err)
138}
139
140pub fn triangles_in(
155 mesh: &Mesh,
156 bvh: &Bvh,
157 region: &Region,
158 out: &mut Vec<TriangleId>,
159) -> Result<usize> {
160 region.validate()?;
161 check(mesh, bvh)?;
162 let mut ap = Appender::new(out);
163 candidates(bvh, region, |id| {
164 if region.meets_triangle(points(mesh, id)) {
165 ap.push(id)?;
166 }
167 Ok(())
168 })?;
169 Ok(ap.count())
170}
171
172pub fn vertices_in(
187 mesh: &Mesh,
188 bvh: &Bvh,
189 region: &Region,
190 out: &mut Vec<VertexId>,
191) -> Result<usize> {
192 region.validate()?;
193 check(mesh, bvh)?;
194 let mut ap = Appender::new(out);
195 let positions = mesh.positions();
196 let mut compact_at = 64;
199 candidates(bvh, region, |id| {
200 for v in mesh.triangles()[id as usize] {
201 if region.contains_point(positions[v as usize]) {
202 ap.push(v)?;
203 }
204 }
205 if ap.count() >= compact_at {
206 compact_at = 2 * ap.sort_dedup_appended().max(1);
207 }
208 Ok(())
209 })?;
210 Ok(ap.sort_dedup_appended())
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::ray::points;
217 use crate::test_util::{Lcg, build, wavy};
218 use crate::tri::{box_triangle, sphere_triangle};
219 use crate::{QueryError, fail_allocations_after};
220 use fast_mesh_bvh::{Aabb, Bvh, Quality};
221 use fast_mesh_store::{Mesh, TriangleId, VertexId};
222
223 fn contains(region: &Region, q: [f32; 3]) -> bool {
224 match region {
225 Region::Box(b) => (0..3).all(|k| b.min[k] <= q[k] && q[k] <= b.max[k]),
226 Region::Sphere { center, radius } => {
227 let d: f32 = (0..3)
228 .map(|k| (q[k] - center[k]) * (q[k] - center[k]))
229 .sum();
230 d <= radius * radius
231 }
232 }
233 }
234
235 fn brute_triangles(mesh: &Mesh, region: &Region) -> Vec<TriangleId> {
236 (0..mesh.triangle_slots() as TriangleId)
237 .filter(|&id| mesh.is_triangle_alive(id))
238 .filter(|&id| match region {
239 Region::Box(b) => box_triangle(b, points(mesh, id)),
240 Region::Sphere { center, radius } => {
241 sphere_triangle(*center, *radius, points(mesh, id))
242 }
243 })
244 .collect()
245 }
246
247 fn brute_vertices(mesh: &Mesh, region: &Region) -> Vec<VertexId> {
248 let mut v: Vec<VertexId> = (0..mesh.triangle_slots() as TriangleId)
249 .filter(|&id| mesh.is_triangle_alive(id))
250 .flat_map(|id| mesh.triangles()[id as usize])
251 .filter(|&v| mesh.is_vertex_alive(v) && contains(region, mesh.positions()[v as usize]))
252 .collect();
253 v.sort_unstable();
254 v.dedup();
255 v
256 }
257
258 fn regions() -> Vec<Region> {
260 let mut rng = Lcg(0x4e61);
261 let mut out = Vec::new();
262 for _ in 0..200 {
263 let c = [
264 rng.next() * 18.0 - 1.0,
265 rng.next() * 18.0 - 1.0,
266 rng.next() * 4.0 - 1.0,
267 ];
268 let h = [
269 0.1 + rng.next() * 3.0,
270 0.1 + rng.next() * 3.0,
271 0.1 + rng.next() * 2.0,
272 ];
273 out.push(Region::Box(Aabb::new(
274 std::array::from_fn(|k| c[k] - h[k]),
275 std::array::from_fn(|k| c[k] + h[k]),
276 )));
277 out.push(Region::Sphere {
278 center: c,
279 radius: 0.1 + rng.next() * 3.0,
280 });
281 }
282 out
283 }
284
285 fn tri_mesh() -> (Mesh, Bvh) {
286 let p = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
287 let mesh = Mesh::from_buffers(&p, &[0, 1, 2]).unwrap();
288 let bvh = build(&mesh, Quality::Sah);
289 (mesh, bvh)
290 }
291
292 #[test]
293 fn triangles_match_brute_force() {
294 let mesh = wavy(16);
295 for quality in [Quality::Sah, Quality::Fast] {
296 let bvh = build(&mesh, quality);
297 let mut nonempty = 0;
298 for region in regions() {
299 let mut out = Vec::new();
300 let n = triangles_in(&mesh, &bvh, ®ion, &mut out).unwrap();
301 assert_eq!(n, out.len());
302 out.sort_unstable();
303 assert_eq!(out, brute_triangles(&mesh, ®ion), "{region:?}");
304 nonempty += usize::from(n > 0);
305 }
306 assert!(nonempty > 100, "only {nonempty} regions hit");
307 }
308 }
309
310 #[test]
311 fn bounding_box_overlap_alone_is_not_enough() {
312 let (mesh, bvh) = tri_mesh();
313 let regions = [
316 Region::Box(Aabb::new([0.6, 0.6, -1.0], [1.0, 1.0, 1.0])),
317 Region::Sphere {
318 center: [0.9, 0.9, 0.0],
319 radius: 0.5,
320 },
321 ];
322 for region in regions {
323 let mut out = Vec::new();
324 assert_eq!(triangles_in(&mesh, &bvh, ®ion, &mut out), Ok(0));
325 assert!(out.is_empty());
326 }
327 }
328
329 #[test]
330 fn vertices_match_brute_force() {
331 let mesh = wavy(16);
332 for quality in [Quality::Sah, Quality::Fast] {
333 let bvh = build(&mesh, quality);
334 let mut nonempty = 0;
335 for region in regions() {
336 let mut out = Vec::new();
337 let n = vertices_in(&mesh, &bvh, ®ion, &mut out).unwrap();
338 assert_eq!(n, out.len());
339 assert!(out.windows(2).all(|w| w[0] < w[1]), "{region:?}");
340 assert_eq!(out, brute_vertices(&mesh, ®ion), "{region:?}");
341 nonempty += usize::from(n > 0);
342 }
343 assert!(nonempty > 100, "only {nonempty} regions hit");
344 }
345 }
346
347 #[test]
348 fn sphere_pruning_box_covers_f32_containment_at_ulp_scale() {
349 let mut rng = Lcg(0x51de);
353 let mut found = None;
354 'search: for _ in 0..20_000 {
355 let center = [rng.next() * 10.0 - 5.0, 0.0, 0.0];
356 let radius = 0.1 + rng.next() * 3.0;
357 let region = Region::Sphere { center, radius };
358 let plain_hi = center[0] + radius;
359 let mut x = plain_hi;
360 for _ in 0..4 {
361 x = x.next_up();
362 if contains(®ion, [x, 0.0, 0.0]) {
363 found = Some((region, x));
364 break 'search;
365 }
366 }
367 }
368 let (region, x) = found.expect("no point just outside the unpadded box is contained");
369 let Region::Sphere { center, radius } = region else {
370 unreachable!()
371 };
372 assert!(x > center[0] + radius);
373 assert!(contains(®ion, [x, 0.0, 0.0]));
374 assert!(region.bounds().max[0] >= x);
375
376 let p = [x, 0.0, 0.0, x + 1.0, 5.0, 0.0, x + 1.0, 0.0, 5.0];
379 let mesh = Mesh::from_buffers(&p, &[0, 1, 2]).unwrap();
380 for quality in [Quality::Sah, Quality::Fast] {
381 let bvh = build(&mesh, quality);
382 let mut out = Vec::new();
383 assert_eq!(vertices_in(&mesh, &bvh, ®ion, &mut out), Ok(1));
384 assert_eq!(out, vec![0]);
385 }
386 }
387
388 #[test]
389 fn sphere_with_underflowing_square_keeps_contained_vertices() {
390 let center = [0.0, 0.0, 0.0];
394 let radius = 1e-23f32;
395 assert_eq!(radius * radius, 0.0);
396 let region = Region::Sphere { center, radius };
397 let q = [2e-23f32, 0.0, 0.0];
398 assert!(contains(®ion, q));
399 let p = [q[0], q[1], q[2], q[0], 1e-23, 0.0, q[0], 0.0, 1e-23];
400 let mesh = Mesh::from_buffers(&p, &[0, 1, 2]).unwrap();
401 for quality in [Quality::Sah, Quality::Fast] {
402 let bvh = build(&mesh, quality);
403 let mut out = Vec::new();
404 assert_eq!(vertices_in(&mesh, &bvh, ®ion, &mut out), Ok(3));
405 assert_eq!(out, vec![0, 1, 2]);
406 }
407 }
408
409 #[test]
410 fn sphere_pruning_box_covers_sphere_triangle_at_ulp_scale() {
411 let mut rng = Lcg(0x7a19);
419 for scale in [1.0f32, 10.0, 100.0, 1000.0] {
420 let center = [scale, -0.5 * scale, 0.25 * scale];
421 for radius in [0.5, 1.0, 3.0] {
422 let region = Region::Sphere { center, radius };
423 let bounds = region.bounds();
424 for ax in 0..3 {
425 for _ in 0..1500 {
426 let span = [1.0f32, 3.0, 10.0, 30.0][(rng.next() * 4.0) as usize % 4];
427 let mut p = [[0.0f32; 3]; 3];
428 for v in p.iter_mut() {
429 for k in 0..3 {
430 v[k] = if k == ax {
431 let t = rng.next();
435 bounds.max[ax] + t * t * radius
436 } else {
437 center[k] + (rng.next() * 2.0 - 1.0) * span * radius
438 };
439 }
440 }
441 if rng.next() < 0.5 {
442 let s = rng.next();
444 let (a, b) = (p[0], p[1]);
445 for (c, (&x, &y)) in p[2].iter_mut().zip(a.iter().zip(b.iter())) {
446 *c = x + s * (y - x);
447 }
448 }
449 if !p.iter().all(|v| v[ax] > bounds.max[ax]) {
450 continue;
451 }
452 assert!(
453 !sphere_triangle(center, radius, p),
454 "pruned but accepted: c={center:?} r={radius} ax={ax} p={p:?}"
455 );
456 }
457 }
458 }
459 }
460 }
461
462 #[test]
463 fn vertices_in_peak_stays_near_the_result() {
464 let mesh = wavy(64);
469 let bvh = build(&mesh, Quality::Sah);
470 let region = Region::Box(Aabb::new([-1.0; 3], [70.0; 3]));
471 let mut out = Vec::new();
472 let n = vertices_in(&mesh, &bvh, ®ion, &mut out).unwrap();
473 assert_eq!(n, 65 * 65);
474 assert_eq!(out.len(), n);
475 assert!(
476 out.capacity() <= 5 * n,
477 "capacity {} for {n} vertices",
478 out.capacity()
479 );
480 }
481
482 #[test]
483 fn vertices_in_sorts_only_the_appended_range() {
484 let mesh = wavy(4);
485 let bvh = build(&mesh, Quality::Sah);
486 let region = Region::Box(Aabb::new([-1.0; 3], [5.0; 3]));
487 let mut out = vec![999];
488 let n = vertices_in(&mesh, &bvh, ®ion, &mut out).unwrap();
489 assert_eq!(n, 25);
490 assert_eq!(out[0], 999);
491 assert_eq!(out[1..], (0..25).collect::<Vec<_>>()[..]);
492 }
495
496 #[test]
497 fn isolated_vertex_is_not_found() {
498 let (mut mesh, mut bvh) = tri_mesh();
499 let mut tx = mesh.begin_edit();
500 let ids = tx.add_vertices(&[[0.5, 0.25, 0.0]]).unwrap();
501 let p = tx.commit().unwrap();
502 bvh.update(&mut mesh, p.changes()).unwrap();
503 let region = Region::Box(Aabb::new([0.4, 0.2, -1.0], [0.6, 0.3, 1.0]));
504 let mut out = Vec::new();
505 assert_eq!(vertices_in(&mesh, &bvh, ®ion, &mut out), Ok(0));
506 assert!(!out.contains(&ids[0]));
507 let region = Region::Box(Aabb::new([-1.0; 3], [2.0; 3]));
509 assert_eq!(vertices_in(&mesh, &bvh, ®ion, &mut out), Ok(3));
510 assert_eq!(out, vec![0, 1, 2]);
511 }
512
513 #[test]
514 fn empty_box_returns_nothing() {
515 let mesh = wavy(4);
516 let bvh = build(&mesh, Quality::Sah);
517 let region = Region::Box(Aabb::EMPTY);
518 let mut out = Vec::new();
519 assert_eq!(triangles_in(&mesh, &bvh, ®ion, &mut out), Ok(0));
520 let mut verts = Vec::new();
521 assert_eq!(vertices_in(&mesh, &bvh, ®ion, &mut verts), Ok(0));
522 assert!(out.is_empty() && verts.is_empty());
523 }
524
525 #[test]
526 fn empty_mesh_returns_nothing() {
527 let check_empty = |mesh: &Mesh, bvh: &Bvh| {
528 for region in [
529 Region::Box(Aabb::new([-9.0; 3], [9.0; 3])),
530 Region::Sphere {
531 center: [0.0; 3],
532 radius: 9.0,
533 },
534 ] {
535 let mut t = Vec::new();
536 assert_eq!(triangles_in(mesh, bvh, ®ion, &mut t), Ok(0));
537 let mut v = Vec::new();
538 assert_eq!(vertices_in(mesh, bvh, ®ion, &mut v), Ok(0));
539 assert!(t.is_empty() && v.is_empty());
540 }
541 };
542 let mesh = Mesh::new();
543 let bvh = build(&mesh, Quality::Sah);
544 check_empty(&mesh, &bvh);
545
546 let mut mesh = wavy(4);
547 let mut bvh = build(&mesh, Quality::Sah);
548 let all: Vec<TriangleId> = (0..mesh.triangle_slots() as TriangleId).collect();
549 let mut tx = mesh.begin_edit();
550 tx.remove_triangles(&all).unwrap();
551 let p = tx.commit().unwrap();
552 bvh.update(&mut mesh, p.changes()).unwrap();
553 check_empty(&mesh, &bvh);
554 }
555
556 #[test]
557 fn invalid_input() {
558 let (mesh, bvh) = tri_mesh();
559 let bad = [
560 Region::Sphere {
561 center: [0.0; 3],
562 radius: -1.0,
563 },
564 Region::Sphere {
565 center: [0.0; 3],
566 radius: f32::NAN,
567 },
568 Region::Sphere {
569 center: [f32::NAN, 0.0, 0.0],
570 radius: 1.0,
571 },
572 ];
573 for region in bad {
574 let mut t = vec![5];
575 assert!(matches!(
576 triangles_in(&mesh, &bvh, ®ion, &mut t),
577 Err(QueryError::InvalidInput(_))
578 ));
579 let mut v = vec![5];
580 assert!(matches!(
581 vertices_in(&mesh, &bvh, ®ion, &mut v),
582 Err(QueryError::InvalidInput(_))
583 ));
584 assert_eq!((t, v), (vec![5], vec![5]));
585 }
586 }
587
588 #[test]
589 fn stale_and_mismatch_are_rejected() {
590 let (mut mesh, bvh) = tri_mesh();
591 let (other, _) = tri_mesh();
592 let region = Region::Box(Aabb::new([-1.0; 3], [2.0; 3]));
593 let mut out = Vec::new();
594 assert_eq!(
595 triangles_in(&other, &bvh, ®ion, &mut out),
596 Err(QueryError::MeshMismatch)
597 );
598 let mut tx = mesh.begin_edit();
599 tx.set_positions(&[0], &[[0.0, 0.0, 1.0]]).unwrap();
600 tx.commit().unwrap();
601 assert_eq!(
602 triangles_in(&mesh, &bvh, ®ion, &mut out),
603 Err(QueryError::StaleBvh)
604 );
605 let mut verts = Vec::new();
606 assert_eq!(
607 vertices_in(&mesh, &bvh, ®ion, &mut verts),
608 Err(QueryError::StaleBvh)
609 );
610 }
611
612 #[test]
613 fn oom_restores_out() {
614 let (mesh, bvh) = tri_mesh();
615 let region = Region::Box(Aabb::new([-1.0; 3], [2.0; 3]));
616 let mut t = vec![7];
617 fail_allocations_after(Some(0));
618 let r = triangles_in(&mesh, &bvh, ®ion, &mut t);
619 fail_allocations_after(None);
620 assert_eq!(r, Err(QueryError::OutOfMemory));
621 assert_eq!(t, vec![7]);
622
623 let mut v = vec![7];
624 fail_allocations_after(Some(0));
625 let r = vertices_in(&mesh, &bvh, ®ion, &mut v);
626 fail_allocations_after(None);
627 assert_eq!(r, Err(QueryError::OutOfMemory));
628 assert_eq!(v, vec![7]);
629 }
630}