1use fast_mesh_bvh::{Aabb, Bvh, NodeKind};
4use fast_mesh_store::{Mesh, TriangleId, par};
5
6use crate::tri::{RayPre, TriHit, ray_triangle};
7use crate::{Appender, Cull, QueryError, Result, check};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Ray {
12 pub origin: [f32; 3],
14 pub dir: [f32; 3],
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct RayOptions {
28 pub min_t: f32,
30 pub max_t: f32,
32 pub cull: Cull,
34}
35
36impl Default for RayOptions {
37 fn default() -> Self {
38 RayOptions {
39 min_t: 0.0,
40 max_t: f32::INFINITY,
41 cull: Cull::None,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
48pub struct RayHit {
49 pub triangle: TriangleId,
51 pub t: f32,
53 pub point: [f32; 3],
55 pub barycentric: [f32; 3],
57 pub normal: [f32; 3],
60}
61
62const GAMMA3: f32 = 3.0 * (f32::EPSILON / 2.0) / (1.0 - 3.0 * (f32::EPSILON / 2.0));
64const GROW: f32 = 1.0 + 2.0 * GAMMA3;
66const SHRINK: f32 = 1.0 - 2.0 * GAMMA3;
68
69pub(crate) fn ray_box(pre: &RayPre, b: &Aabb, min_t: f32, max_t: f32) -> Option<f32> {
84 let mut enter = f32::NEG_INFINITY;
85 let mut exit = f32::INFINITY;
86 for i in 0..3 {
87 let inv = pre.inv_dir[i];
88 let (near, far) = if inv < 0.0 {
89 (b.max[i], b.min[i])
90 } else {
91 (b.min[i], b.max[i])
92 };
93 enter = enter.max((near - pre.origin[i]) * inv);
94 exit = exit.min((far - pre.origin[i]) * inv);
95 }
96 let enter = enter * if enter < 0.0 { GROW } else { SHRINK };
97 let exit = exit * if exit > 0.0 { GROW } else { SHRINK };
98 (enter <= exit && enter <= max_t && exit >= min_t).then_some(enter.max(min_t))
99}
100
101fn validate(origin: [f32; 3], dir: [f32; 3]) -> Result<()> {
102 if !origin.iter().all(|c| c.is_finite()) {
103 return Err(QueryError::InvalidInput("ray origin must be finite"));
104 }
105 if !dir.iter().all(|c| c.is_finite()) {
106 return Err(QueryError::InvalidInput("ray direction must be finite"));
107 }
108 if dir == [0.0; 3] {
109 return Err(QueryError::InvalidInput("ray direction must not be zero"));
110 }
111 Ok(())
112}
113
114fn validate_opts(opts: &RayOptions) -> Result<()> {
115 if opts.min_t.is_nan() || opts.max_t.is_nan() || opts.min_t > opts.max_t {
116 return Err(QueryError::InvalidInput(
117 "min_t and max_t must not be NaN, and min_t must not exceed max_t",
118 ));
119 }
120 Ok(())
121}
122
123pub(crate) fn points(mesh: &Mesh, id: TriangleId) -> [[f32; 3]; 3] {
124 let positions = mesh.positions();
125 mesh.triangles()[id as usize].map(|v| positions[v as usize])
126}
127
128fn make_hit(pre: &RayPre, p: [[f32; 3]; 3], triangle: TriangleId, h: TriHit) -> RayHit {
129 let point = std::array::from_fn(|i| pre.origin[i] + h.t * pre.dir[i]);
130 let e1: [f64; 3] = std::array::from_fn(|i| f64::from(p[1][i]) - f64::from(p[0][i]));
132 let e2: [f64; 3] = std::array::from_fn(|i| f64::from(p[2][i]) - f64::from(p[0][i]));
133 let n = [
134 e1[1] * e2[2] - e1[2] * e2[1],
135 e1[2] * e2[0] - e1[0] * e2[2],
136 e1[0] * e2[1] - e1[1] * e2[0],
137 ];
138 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
139 RayHit {
140 triangle,
141 t: h.t,
142 point,
143 barycentric: h.barycentric,
144 normal: n.map(|c| (c / len) as f32),
145 }
146}
147
148fn walk(
153 bvh: &Bvh,
154 pre: &RayPre,
155 opts: &RayOptions,
156 bound: impl Fn() -> Option<f32>,
157 mut leaf: impl FnMut(TriangleId),
158) {
159 bvh.traverse_by_key(
160 |b| ray_box(pre, b, opts.min_t, opts.max_t),
161 |_, node, entry| {
162 let Some(max_t) = bound() else {
165 return false;
166 };
167 if entry > max_t {
168 return false;
169 }
170 if let NodeKind::Leaf { triangles } = node.kind {
171 for &id in triangles {
172 leaf(id);
173 }
174 }
175 true
176 },
177 );
178}
179
180fn closest(
181 mesh: &Mesh,
182 bvh: &Bvh,
183 origin: [f32; 3],
184 dir: [f32; 3],
185 opts: &RayOptions,
186) -> Option<RayHit> {
187 let pre = RayPre::new(origin, dir);
188 let best: std::cell::Cell<Option<(TriangleId, TriHit)>> = std::cell::Cell::new(None);
189 let best_t = || best.get().map_or(opts.max_t, |(_, h)| h.t);
190 walk(
191 bvh,
192 &pre,
193 opts,
194 || Some(best_t()),
197 |id| {
198 let Some(h) = ray_triangle(&pre, points(mesh, id), opts.cull, opts.min_t, best_t())
199 else {
200 return;
201 };
202 let better = match best.get() {
203 None => true,
204 Some((bid, bh)) => h.t < bh.t || (h.t == bh.t && id < bid),
205 };
206 if better {
207 best.set(Some((id, h)));
208 }
209 },
210 );
211 best.get()
212 .map(|(id, h)| make_hit(&pre, points(mesh, id), id, h))
213}
214
215pub fn raycast(mesh: &Mesh, bvh: &Bvh, ray: &Ray, opts: &RayOptions) -> Result<Option<RayHit>> {
227 validate(ray.origin, ray.dir)?;
228 validate_opts(opts)?;
229 check(mesh, bvh)?;
230 Ok(closest(mesh, bvh, ray.origin, ray.dir, opts))
231}
232
233pub fn raycast_any(mesh: &Mesh, bvh: &Bvh, ray: &Ray, opts: &RayOptions) -> Result<Option<RayHit>> {
240 validate(ray.origin, ray.dir)?;
241 validate_opts(opts)?;
242 check(mesh, bvh)?;
243 let pre = RayPre::new(ray.origin, ray.dir);
244 let found: std::cell::Cell<Option<(TriangleId, TriHit)>> = std::cell::Cell::new(None);
245 walk(
246 bvh,
247 &pre,
248 opts,
249 || found.get().is_none().then_some(opts.max_t),
250 |id| {
251 if found.get().is_none()
252 && let Some(h) =
253 ray_triangle(&pre, points(mesh, id), opts.cull, opts.min_t, opts.max_t)
254 {
255 found.set(Some((id, h)));
256 }
257 },
258 );
259 Ok(found
260 .get()
261 .map(|(id, h)| make_hit(&pre, points(mesh, id), id, h)))
262}
263
264pub fn raycast_all(
282 mesh: &Mesh,
283 bvh: &Bvh,
284 ray: &Ray,
285 opts: &RayOptions,
286 out: &mut Vec<RayHit>,
287) -> Result<usize> {
288 validate(ray.origin, ray.dir)?;
289 validate_opts(opts)?;
290 check(mesh, bvh)?;
291 let pre = RayPre::new(ray.origin, ray.dir);
292 let mut ap = Appender::new(out);
293 let failed: std::cell::Cell<Option<QueryError>> = std::cell::Cell::new(None);
294 walk(
295 bvh,
296 &pre,
297 opts,
298 || failed.get().is_none().then_some(opts.max_t),
299 |id| {
300 if failed.get().is_some() {
301 return;
302 }
303 let p = points(mesh, id);
304 if let Some(h) = ray_triangle(&pre, p, opts.cull, opts.min_t, opts.max_t)
305 && let Err(e) = ap.push(make_hit(&pre, p, id, h))
306 {
307 failed.set(Some(e));
308 }
309 },
310 );
311 if let Some(e) = failed.get() {
312 return Err(e);
313 }
314 ap.appended()
315 .sort_unstable_by(|a, b| a.t.total_cmp(&b.t).then(a.triangle.cmp(&b.triangle)));
316 Ok(ap.count())
317}
318
319pub fn raycast_many(
327 mesh: &Mesh,
328 bvh: &Bvh,
329 origins: &[[f32; 3]],
330 dirs: &[[f32; 3]],
331 opts: &RayOptions,
332 out: &mut [Option<RayHit>],
333) -> Result<()> {
334 if origins.len() != dirs.len() || origins.len() != out.len() {
335 return Err(QueryError::InvalidInput(
336 "origins, dirs and out must have the same length",
337 ));
338 }
339 for (&o, &d) in origins.iter().zip(dirs) {
340 validate(o, d)?;
341 }
342 validate_opts(opts)?;
343 check(mesh, bvh)?;
344 par::for_each_chunk_mut(out, QUERY_CHUNK, PAR_MIN_QUERIES, |i, slots| {
345 let rays = origins[i * QUERY_CHUNK..]
346 .iter()
347 .zip(&dirs[i * QUERY_CHUNK..]);
348 for ((&o, &d), slot) in rays.zip(slots) {
349 *slot = closest(mesh, bvh, o, d, opts);
350 }
351 });
352 Ok(())
353}
354
355pub(crate) const PAR_MIN_QUERIES: usize = 256;
365
366pub(crate) const QUERY_CHUNK: usize = 64;
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::test_util::{Lcg, build, wavy};
373 use crate::tri::{RayPre, ray_triangle};
374 use crate::{QueryError, fail_allocations_after};
375 use fast_mesh_bvh::{BuildOptions, Bvh, Quality};
376 use fast_mesh_store::{Mesh, TriangleId};
377
378 const T: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
379 const DOWN: [f32; 3] = [0.0, 0.0, -1.0];
380
381 fn one_triangle(p: &[f32; 9]) -> (Mesh, Bvh) {
382 let mesh = Mesh::from_buffers(p, &[0, 1, 2]).unwrap();
383 let bvh = build(&mesh, Quality::Sah);
384 (mesh, bvh)
385 }
386
387 fn brute_all(mesh: &Mesh, ray: &Ray, opts: &RayOptions) -> Vec<(TriangleId, f32)> {
389 let pre = RayPre::new(ray.origin, ray.dir);
390 let mut hits = Vec::new();
391 for id in 0..mesh.triangle_slots() as TriangleId {
392 if !mesh.is_triangle_alive(id) {
393 continue;
394 }
395 if let Some(h) = ray_triangle(&pre, points(mesh, id), opts.cull, opts.min_t, opts.max_t)
396 {
397 hits.push((id, h.t));
398 }
399 }
400 hits.sort_by(|a, b| a.1.total_cmp(&b.1).then(a.0.cmp(&b.0)));
401 hits
402 }
403
404 fn brute_closest(mesh: &Mesh, ray: &Ray, opts: &RayOptions) -> Option<(TriangleId, f32)> {
405 brute_all(mesh, ray, opts).first().copied()
406 }
407
408 fn rays(n: u32) -> Vec<Ray> {
410 let mut rng = Lcg(0x5eed);
411 let span = n as f32 + 2.0;
412 (0..500)
413 .map(|_| Ray {
414 origin: [rng.next() * span - 1.0, rng.next() * span - 1.0, 5.0],
415 dir: [
416 rng.next() * 0.6 - 0.3,
417 rng.next() * 0.6 - 0.3,
418 -1.0 - rng.next(),
419 ],
420 })
421 .collect()
422 }
423
424 fn shallow_rays(n: u32) -> Vec<Ray> {
427 let mut rng = Lcg(0xab1e);
428 (0..500)
429 .map(|_| {
430 let a = rng.next() * std::f32::consts::TAU;
431 Ray {
432 origin: [
433 rng.next() * n as f32,
434 rng.next() * n as f32,
435 0.5 + rng.next(),
436 ],
437 dir: [a.cos(), a.sin(), 0.2 * rng.next() - 0.1],
438 }
439 })
440 .collect()
441 }
442
443 fn opts() -> RayOptions {
444 RayOptions::default()
445 }
446
447 #[test]
448 fn options_default() {
449 let o = RayOptions::default();
450 assert_eq!(o.min_t, 0.0);
451 assert_eq!(o.max_t, f32::INFINITY);
452 assert_eq!(o.cull, Cull::None);
453 }
454
455 #[test]
456 fn closest_matches_brute_force() {
457 let mesh = wavy(16);
458 for quality in [Quality::Sah, Quality::Fast] {
459 let bvh = build(&mesh, quality);
460 let mut hits = 0;
461 for ray in rays(16).into_iter().chain(shallow_rays(16)) {
462 let got = raycast(&mesh, &bvh, &ray, &opts())
463 .unwrap()
464 .map(|h| (h.triangle, h.t));
465 assert_eq!(got, brute_closest(&mesh, &ray, &opts()), "{ray:?}");
466 hits += usize::from(got.is_some());
467 }
468 assert!(hits > 300, "only {hits} rays hit");
469 }
470 }
471
472 #[test]
473 fn all_matches_brute_force_sorted() {
474 let mesh = wavy(16);
475 let bvh = build(&mesh, Quality::Sah);
476 let sentinel = RayHit {
477 triangle: 999,
478 t: -1.0,
479 point: [0.0; 3],
480 barycentric: [0.0; 3],
481 normal: [0.0; 3],
482 };
483 let mut multi = 0;
484 for ray in rays(16).into_iter().chain(shallow_rays(16)) {
485 let mut out = vec![sentinel];
486 let n = raycast_all(&mesh, &bvh, &ray, &opts(), &mut out).unwrap();
487 assert_eq!(n, out.len() - 1);
488 assert_eq!(out[0], sentinel);
489 let got: Vec<_> = out[1..].iter().map(|h| (h.triangle, h.t)).collect();
490 assert_eq!(got, brute_all(&mesh, &ray, &opts()), "{ray:?}");
491 multi += usize::from(n > 1);
492 }
493 assert!(multi > 10, "only {multi} rays hit more than once");
494 }
495
496 #[test]
497 fn any_hits_iff_closest_hits() {
498 let mesh = wavy(16);
499 let bvh = build(&mesh, Quality::Sah);
500 for ray in rays(16).into_iter().chain(shallow_rays(16)) {
501 let any = raycast_any(&mesh, &bvh, &ray, &opts()).unwrap();
502 let closest = raycast(&mesh, &bvh, &ray, &opts()).unwrap();
503 assert_eq!(any.is_some(), closest.is_some());
504 if let Some(h) = any {
505 let pre = RayPre::new(ray.origin, ray.dir);
506 let real = ray_triangle(
507 &pre,
508 points(&mesh, h.triangle),
509 Cull::None,
510 0.0,
511 f32::INFINITY,
512 );
513 assert_eq!(real.map(|r| r.t), Some(h.t));
514 }
515 }
516 }
517
518 #[test]
519 fn shared_edge_tie_takes_smallest_id() {
520 let mesh = wavy(4);
522 let bvh = build(&mesh, Quality::Sah);
523 let ray = Ray {
524 origin: [0.5, 0.5, 10.0],
525 dir: DOWN,
526 };
527 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
528 assert_eq!((h.triangle, h.t), (0, 9.0));
529 let mut out = Vec::new();
530 assert_eq!(raycast_all(&mesh, &bvh, &ray, &opts(), &mut out), Ok(2));
531 assert_eq!(
532 out.iter().map(|h| (h.triangle, h.t)).collect::<Vec<_>>(),
533 [(0, 9.0), (1, 9.0)]
534 );
535 }
536
537 #[test]
538 fn ray_through_a_fan_vertex_hits_every_triangle() {
539 let mut p = vec![0.0, 0.0, 0.0];
541 for i in 0..6 {
542 let a = i as f32 * std::f32::consts::TAU / 6.0;
543 p.extend([a.cos(), a.sin(), 0.0]);
544 }
545 let idx: Vec<u32> = (0..6).flat_map(|i| [0, 1 + i, 1 + (i + 1) % 6]).collect();
546 let mesh = Mesh::from_buffers(&p, &idx).unwrap();
547 let bvh = build(&mesh, Quality::Sah);
548 let ray = Ray {
549 origin: [0.0, 0.0, 2.0],
550 dir: DOWN,
551 };
552 let mut out = Vec::new();
553 assert_eq!(raycast_all(&mesh, &bvh, &ray, &opts(), &mut out), Ok(6));
554 assert!(out.iter().all(|h| h.t == 2.0));
555 let ids: Vec<_> = out.iter().map(|h| h.triangle).collect();
556 assert_eq!(ids, [0, 1, 2, 3, 4, 5]);
557 }
558
559 #[test]
560 fn shared_edge_tie_with_reversed_ids_takes_smallest_id() {
561 let p = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0];
564 let mesh = Mesh::from_buffers(&p, &[0, 2, 3, 0, 1, 2]).unwrap();
565 for quality in [Quality::Sah, Quality::Fast] {
566 let opts1 = BuildOptions {
567 quality,
568 max_leaf_size: 1,
569 ..Default::default()
570 };
571 let bvh = Bvh::build(&mesh, &opts1).unwrap();
572 let ray = Ray {
573 origin: [0.5, 0.5, 1.0],
574 dir: DOWN,
575 };
576 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
577 assert_eq!((h.triangle, h.t), (0, 1.0));
578 }
579 }
580
581 #[test]
582 fn hit_fields() {
583 let (mesh, bvh) = one_triangle(&T);
584 let ray = Ray {
585 origin: [0.25, 0.25, 1.0],
586 dir: DOWN,
587 };
588 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
589 assert_eq!(h.triangle, 0);
590 assert_eq!(h.t, 1.0);
591 for (a, b) in h.point.iter().zip([0.25, 0.25, 0.0]) {
592 assert!((a - b).abs() < 1e-6);
593 }
594 for (a, b) in h.normal.iter().zip([0.0, 0.0, 1.0]) {
595 assert!((a - b).abs() < 1e-6);
596 }
597 for (a, b) in h.barycentric.iter().zip([0.5, 0.25, 0.25]) {
598 assert!((a - b).abs() < 1e-6);
599 }
600 }
601
602 #[test]
603 fn t_is_in_units_of_dir() {
604 let (mesh, bvh) = one_triangle(&T);
605 let ray = Ray {
606 origin: [0.25, 0.25, 1.0],
607 dir: [0.0, 0.0, -2.0],
608 };
609 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
610 assert_eq!(h.t, 0.5);
611 for (a, b) in h.point.iter().zip([0.25, 0.25, 0.0]) {
612 assert!((a - b).abs() < 1e-6);
613 }
614 }
615
616 #[test]
617 fn ray_starting_on_the_surface_hits_at_zero() {
618 let (mesh, bvh) = one_triangle(&T);
619 let ray = Ray {
620 origin: [0.25, 0.25, 0.0],
621 dir: DOWN,
622 };
623 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
624 assert_eq!(h.t, 0.0);
625 }
626
627 #[test]
628 fn axis_aligned_ray_on_a_box_face_still_hits() {
629 let p = [1.0, 0.25, 0.25, 1.0, 1.0, 0.25, 1.0, 0.25, 1.0];
632 let (mesh, bvh) = one_triangle(&p);
633 let ray = Ray {
634 origin: [0.0, 0.25, 0.25],
635 dir: [1.0, 0.0, 0.0],
636 };
637 let h = raycast(&mesh, &bvh, &ray, &opts()).unwrap().unwrap();
638 assert_eq!(h.t, 1.0);
639 assert!(raycast_any(&mesh, &bvh, &ray, &opts()).unwrap().is_some());
640 let ray = Ray {
642 origin: [0.0, 0.25, 0.25],
643 dir: [1.0, -0.0, -0.0],
644 };
645 assert_eq!(
646 raycast(&mesh, &bvh, &ray, &opts()).unwrap().map(|h| h.t),
647 Some(1.0)
648 );
649 }
650
651 #[test]
652 fn negative_min_t_finds_hits_behind_the_origin() {
653 let mesh = wavy(8);
654 let bvh = build(&mesh, Quality::Sah);
655 let ray = Ray {
656 origin: [3.3, 4.6, -5.0],
657 dir: DOWN,
658 };
659 let o = RayOptions {
660 min_t: f32::NEG_INFINITY,
661 ..opts()
662 };
663 let got = raycast(&mesh, &bvh, &ray, &o)
664 .unwrap()
665 .map(|h| (h.triangle, h.t));
666 assert!(got.is_some());
667 assert_eq!(got, brute_closest(&mesh, &ray, &o));
668 assert_eq!(raycast(&mesh, &bvh, &ray, &opts()), Ok(None));
669 }
670
671 #[test]
672 fn max_t_and_cull_are_honored() {
673 let mesh = wavy(16);
674 let bvh = build(&mesh, Quality::Sah);
675 for o in [
676 RayOptions {
677 min_t: 3.5,
678 max_t: 4.5,
679 cull: Cull::None,
680 },
681 RayOptions {
682 cull: Cull::Back,
683 ..opts()
684 },
685 RayOptions {
686 cull: Cull::Front,
687 ..opts()
688 },
689 ] {
690 for ray in rays(16).into_iter().chain(shallow_rays(16)) {
691 let got = raycast(&mesh, &bvh, &ray, &o)
692 .unwrap()
693 .map(|h| (h.triangle, h.t));
694 assert_eq!(got, brute_closest(&mesh, &ray, &o));
695 let mut out = Vec::new();
696 raycast_all(&mesh, &bvh, &ray, &o, &mut out).unwrap();
697 let all: Vec<_> = out.iter().map(|h| (h.triangle, h.t)).collect();
698 assert_eq!(all, brute_all(&mesh, &ray, &o));
699 }
700 }
701 }
702
703 #[test]
704 fn empty_mesh_returns_nothing() {
705 let ray = Ray {
706 origin: [0.5, 0.5, 5.0],
707 dir: DOWN,
708 };
709 let check_empty = |mesh: &Mesh, bvh: &Bvh| {
710 assert_eq!(raycast(mesh, bvh, &ray, &opts()), Ok(None));
711 assert_eq!(raycast_any(mesh, bvh, &ray, &opts()), Ok(None));
712 let mut out = Vec::new();
713 assert_eq!(raycast_all(mesh, bvh, &ray, &opts(), &mut out), Ok(0));
714 assert!(out.is_empty());
715 };
716 let mesh = Mesh::new();
717 let bvh = build(&mesh, Quality::Sah);
718 check_empty(&mesh, &bvh);
719
720 let mut mesh = wavy(4);
721 let mut bvh = build(&mesh, Quality::Sah);
722 let all: Vec<TriangleId> = (0..mesh.triangle_slots() as TriangleId).collect();
723 let mut tx = mesh.begin_edit();
724 tx.remove_triangles(&all).unwrap();
725 let p = tx.commit().unwrap();
726 bvh.update(&mut mesh, p.changes()).unwrap();
727 check_empty(&mesh, &bvh);
728 }
729
730 #[test]
731 fn invalid_input() {
732 let (mesh, bvh) = one_triangle(&T);
733 let good = Ray {
734 origin: [0.25, 0.25, 1.0],
735 dir: DOWN,
736 };
737 let bad_rays = [
738 Ray {
739 dir: [0.0; 3],
740 ..good
741 },
742 Ray {
743 dir: [0.0, f32::INFINITY, -1.0],
744 ..good
745 },
746 Ray {
747 origin: [f32::NAN, 0.0, 0.0],
748 ..good
749 },
750 ];
751 let bad_opts = [
752 RayOptions {
753 min_t: 2.0,
754 max_t: 1.0,
755 ..opts()
756 },
757 RayOptions {
758 min_t: f32::NAN,
759 ..opts()
760 },
761 ];
762 let cases = bad_rays
763 .iter()
764 .map(|r| (*r, opts()))
765 .chain(bad_opts.iter().map(|o| (good, *o)));
766 for (ray, o) in cases {
767 let is_invalid =
768 |r: Result<Option<RayHit>>| matches!(r, Err(QueryError::InvalidInput(_)));
769 assert!(is_invalid(raycast(&mesh, &bvh, &ray, &o)));
770 assert!(is_invalid(raycast_any(&mesh, &bvh, &ray, &o)));
771 let mut out = Vec::new();
772 assert!(matches!(
773 raycast_all(&mesh, &bvh, &ray, &o, &mut out),
774 Err(QueryError::InvalidInput(_))
775 ));
776 let mut many = [None];
777 assert!(matches!(
778 raycast_many(&mesh, &bvh, &[ray.origin], &[ray.dir], &o, &mut many),
779 Err(QueryError::InvalidInput(_))
780 ));
781 }
782 let mut out = [None, None];
783 assert!(matches!(
784 raycast_many(
785 &mesh,
786 &bvh,
787 &[good.origin; 2],
788 &[good.dir],
789 &opts(),
790 &mut out
791 ),
792 Err(QueryError::InvalidInput(_))
793 ));
794 let mut out = [None];
795 assert!(matches!(
796 raycast_many(
797 &mesh,
798 &bvh,
799 &[good.origin; 2],
800 &[good.dir; 2],
801 &opts(),
802 &mut out
803 ),
804 Err(QueryError::InvalidInput(_))
805 ));
806 }
807
808 #[test]
809 fn nan_max_t_is_invalid() {
810 let (mesh, bvh) = one_triangle(&T);
811 let ray = Ray {
812 origin: [0.25, 0.25, 1.0],
813 dir: DOWN,
814 };
815 let o = RayOptions {
816 max_t: f32::NAN,
817 ..opts()
818 };
819 assert!(matches!(
820 raycast(&mesh, &bvh, &ray, &o),
821 Err(QueryError::InvalidInput(_))
822 ));
823 assert!(matches!(
824 raycast_any(&mesh, &bvh, &ray, &o),
825 Err(QueryError::InvalidInput(_))
826 ));
827 let mut out = Vec::new();
828 assert!(matches!(
829 raycast_all(&mesh, &bvh, &ray, &o, &mut out),
830 Err(QueryError::InvalidInput(_))
831 ));
832 let mut many = [None];
833 assert!(matches!(
834 raycast_many(&mesh, &bvh, &[ray.origin], &[ray.dir], &o, &mut many),
835 Err(QueryError::InvalidInput(_))
836 ));
837 }
838
839 #[test]
840 fn invalid_input_wins_over_stale() {
841 let (mut mesh, bvh) = one_triangle(&T);
842 let mut tx = mesh.begin_edit();
843 tx.set_positions(&[0], &[[0.0, 0.0, 1.0]]).unwrap();
844 tx.commit().unwrap();
845 let ray = Ray {
846 origin: [0.25, 0.25, 1.0],
847 dir: [0.0; 3],
848 };
849 assert!(matches!(
850 raycast(&mesh, &bvh, &ray, &opts()),
851 Err(QueryError::InvalidInput(_))
852 ));
853 }
854
855 #[test]
856 fn stale_and_mismatch_are_rejected() {
857 let (mut mesh, bvh) = one_triangle(&T);
858 let (other, _) = one_triangle(&T);
859 let ray = Ray {
860 origin: [0.25, 0.25, 1.0],
861 dir: DOWN,
862 };
863 let mut out = Vec::new();
864 let mut many = [None];
865 assert_eq!(
866 raycast(&other, &bvh, &ray, &opts()),
867 Err(QueryError::MeshMismatch)
868 );
869
870 let mut tx = mesh.begin_edit();
871 tx.set_positions(&[0], &[[0.0, 0.0, 1.0]]).unwrap();
872 tx.commit().unwrap();
873 let stale = Err(QueryError::StaleBvh);
874 assert_eq!(raycast(&mesh, &bvh, &ray, &opts()), stale);
875 assert_eq!(raycast_any(&mesh, &bvh, &ray, &opts()), stale);
876 assert_eq!(
877 raycast_all(&mesh, &bvh, &ray, &opts(), &mut out),
878 Err(QueryError::StaleBvh)
879 );
880 assert_eq!(
881 raycast_many(&mesh, &bvh, &[ray.origin], &[ray.dir], &opts(), &mut many),
882 Err(QueryError::StaleBvh)
883 );
884 }
885
886 #[test]
887 fn raycast_many_matches_raycast() {
888 let mesh = wavy(16);
889 let bvh = build(&mesh, Quality::Sah);
890 let rs = rays(16);
891 let origins: Vec<_> = rs.iter().map(|r| r.origin).collect();
892 let dirs: Vec<_> = rs.iter().map(|r| r.dir).collect();
893 let mut out = vec![None; rs.len()];
894 raycast_many(&mesh, &bvh, &origins, &dirs, &opts(), &mut out).unwrap();
895 for (ray, got) in rs.iter().zip(&out) {
896 assert_eq!(*got, raycast(&mesh, &bvh, ray, &opts()).unwrap());
897 }
898 }
899
900 #[test]
901 fn raycast_all_oom_restores_out() {
902 let (mesh, bvh) = one_triangle(&T);
903 let ray = Ray {
904 origin: [0.25, 0.25, 1.0],
905 dir: DOWN,
906 };
907 let sentinel = RayHit {
908 triangle: 7,
909 t: 0.0,
910 point: [0.0; 3],
911 barycentric: [0.0; 3],
912 normal: [0.0; 3],
913 };
914 let mut out = vec![sentinel];
915 fail_allocations_after(Some(0));
916 let r = raycast_all(&mesh, &bvh, &ray, &opts(), &mut out);
917 fail_allocations_after(None);
918 assert_eq!(r, Err(QueryError::OutOfMemory));
919 assert_eq!(out, vec![sentinel]);
920 }
921
922 fn fan() -> (Mesh, Bvh) {
924 let mut p = vec![0.0, 0.0, 0.0];
925 for k in 0..6 {
926 let a = std::f64::consts::TAU * f64::from(k) / 6.0;
927 p.extend([a.cos() as f32, a.sin() as f32, 0.0]);
928 }
929 let mut idx = Vec::new();
930 for k in 0..6 {
931 idx.extend([0, 1 + k, 1 + (k + 1) % 6]);
932 }
933 let mesh = Mesh::from_buffers(&p, &idx).unwrap();
934 let bvh = build(&mesh, Quality::Sah);
935 (mesh, bvh)
936 }
937
938 #[test]
939 fn shared_vertex_and_edge_report_one_hit_per_triangle() {
940 let (mesh, bvh) = fan();
944 let mut out = Vec::new();
945 let down = Ray {
946 origin: [0.0, 0.0, 1.0],
947 dir: DOWN,
948 };
949 let n = raycast_all(&mesh, &bvh, &down, &opts(), &mut out).unwrap();
950 assert_eq!(n, 6);
951 assert!(out.iter().all(|h| h.t == 1.0));
952 assert_eq!(
953 out.iter().map(|h| h.triangle).collect::<Vec<_>>(),
954 vec![0, 1, 2, 3, 4, 5]
955 );
956 let edge = Ray {
958 origin: [0.5, 0.0, 1.0],
959 dir: DOWN,
960 };
961 out.clear();
962 let n = raycast_all(&mesh, &bvh, &edge, &opts(), &mut out).unwrap();
963 assert_eq!(n, 2);
964 assert!(out.iter().all(|h| h.t == 1.0));
965 assert_eq!(
966 out.iter().map(|h| h.triangle).collect::<Vec<_>>(),
967 vec![0, 5]
968 );
969 }
970
971 #[test]
972 fn box_enter_never_exceeds_triangle_t_at_large_coords() {
973 let mut rng = Lcg(0x19e7);
979 let mut hits = 0;
980 for scale in [1e4f32, 1e7, 1e10] {
981 for _ in 0..600 {
982 let base = [
983 (rng.next() * 2.0 - 1.0) * scale,
984 (rng.next() * 2.0 - 1.0) * scale,
985 (rng.next() * 2.0 - 1.0) * scale,
986 ];
987 let corner = |rng: &mut Lcg| -> [f32; 3] {
988 std::array::from_fn(|k| base[k] + (rng.next() * 2.0 - 1.0))
989 };
990 let (e0, e1, a, b) = (
991 corner(&mut rng),
992 corner(&mut rng),
993 corner(&mut rng),
994 corner(&mut rng),
995 );
996 let mp: [f32; 3] = if rng.next() < 0.5 {
997 std::array::from_fn(|k| (e0[k] + e1[k]) / 2.0)
999 } else {
1000 std::array::from_fn(|k| (e0[k] + e1[k] + a[k]) / 3.0)
1002 };
1003 let dh: [f32; 3] = std::array::from_fn(|_| rng.next() * 2.0 - 1.0);
1004 let len = (dh[0] * dh[0] + dh[1] * dh[1] + dh[2] * dh[2]).sqrt();
1005 if len == 0.0 {
1006 continue;
1007 }
1008 let dist = 1.0 + rng.next() * 4.0;
1009 let origin = std::array::from_fn(|k| mp[k] - dh[k] / len * dist);
1010 let dir = std::array::from_fn(|k| dh[k] / len);
1011 let pre = RayPre::new(origin, dir);
1012 for p in [[e0, e1, a], [e0, e1, b]] {
1013 let Some(h) = ray_triangle(&pre, p, Cull::None, 0.0, f32::INFINITY) else {
1014 continue;
1015 };
1016 hits += 1;
1017 let (mn, mx) = p.iter().fold(
1018 ([f32::INFINITY; 3], [f32::NEG_INFINITY; 3]),
1019 |(mn, mx), q| {
1020 (
1021 std::array::from_fn(|k| mn[k].min(q[k])),
1022 std::array::from_fn(|k| mx[k].max(q[k])),
1023 )
1024 },
1025 );
1026 let b = fast_mesh_bvh::Aabb::new(mn, mx);
1027 let e = ray_box(&pre, &b, 0.0, f32::INFINITY)
1028 .expect("box of a hit triangle is culled");
1029 assert!(e <= h.t, "enter {e} above t {} at scale {scale:e}", h.t);
1030 }
1031 }
1032 }
1033 assert!(hits > 1000, "only {hits} hits");
1034 }
1035
1036 #[test]
1037 fn subnormal_direction_components() {
1038 let (mesh, bvh) = one_triangle(&T);
1042 let o = [0.25, 0.25, 1.0];
1043 let tiny = Ray {
1044 origin: o,
1045 dir: [1e-40, 0.0, 0.0],
1046 };
1047 assert_eq!(raycast(&mesh, &bvh, &tiny, &opts()), Ok(None));
1048 assert_eq!(raycast_any(&mesh, &bvh, &tiny, &opts()), Ok(None));
1049 let mut out = Vec::new();
1050 assert_eq!(raycast_all(&mesh, &bvh, &tiny, &opts(), &mut out), Ok(0));
1051 let sideways = Ray {
1052 origin: o,
1053 dir: [1e-40, 0.0, -1.0],
1054 };
1055 assert_eq!(
1056 raycast(&mesh, &bvh, &sideways, &opts())
1057 .unwrap()
1058 .map(|h| h.t),
1059 Some(1.0)
1060 );
1061 }
1062
1063 #[test]
1064 fn ray_box_is_conservative_and_clamps_entry() {
1065 use fast_mesh_bvh::Aabb;
1066 let b = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1067 let pre = RayPre::new([0.5, 0.5, 3.0], DOWN);
1068 let e = ray_box(&pre, &b, 0.0, f32::INFINITY).unwrap();
1069 assert!(e <= 2.0 && e > 1.99);
1070 assert_eq!(ray_box(&pre, &b, 0.0, 1.5), None);
1071 assert_eq!(ray_box(&pre, &b, 3.5, f32::INFINITY), None);
1072 let pre = RayPre::new([0.5, 0.5, 0.5], DOWN);
1074 assert_eq!(ray_box(&pre, &b, 0.0, f32::INFINITY), Some(0.0));
1075 let pre = RayPre::new([0.5, 0.5, 3.0], [0.0, 0.0, 1.0]);
1077 assert_eq!(ray_box(&pre, &b, 0.0, f32::INFINITY), None);
1078 let flat = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]);
1080 let pre = RayPre::new([-1.0, 0.5, 0.0], [1.0, 0.0, 0.0]);
1081 assert!(ray_box(&pre, &flat, 0.0, f32::INFINITY).is_some());
1082 }
1083}