mesh_geometry/queries/
ray_triangle.rs

1//! Ray-triangle intersection queries for mesh-geometry.
2
3use crate::{Float, Point3, Vec3};
4
5/// A 3D ray: origin + t·dir, with dir not necessarily normalized.
6#[derive(Debug, Clone, Copy)]
7pub struct Ray<T: Float> {
8    /// Ray origin point
9    pub origin: Point3<T>,
10    /// Ray direction vector (not necessarily normalized)
11    pub dir: Vec3<T>,
12}
13
14/// If the ray intersects the triangle (a,b,c), returns `Some((t,u,v))`
15/// where intersection = origin + t·dir, and (u,v) are barycentric coords.
16/// Otherwise returns `None`.
17pub fn ray_intersects_triangle<T: Float>(
18    ray: Ray<T>,
19    a: Point3<T>,
20    b: Point3<T>,
21    c: Point3<T>,
22) -> Option<(T, T, T)> {
23    let epsilon = T::from(1e-8).unwrap();
24    let edge1 = b - a;
25    let edge2 = c - a;
26    let pvec = ray.dir.cross(edge2);
27    let det = edge1.dot(pvec);
28    if det.abs() < epsilon {
29        return None; // parallel
30    }
31    let inv_det = det.recip();
32    let tvec = ray.origin - a;
33    let u = tvec.dot(pvec) * inv_det;
34    if u < T::zero() || u > T::one() {
35        return None;
36    }
37    let qvec = tvec.cross(edge1);
38    let v = ray.dir.dot(qvec) * inv_det;
39    if v < T::zero() || u + v > T::one() {
40        return None;
41    }
42    let t = edge2.dot(qvec) * inv_det;
43    if t > epsilon {
44        Some((t, u, v))
45    } else {
46        None
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::{Point3, Vec3};
54
55    #[test]
56    fn hit_center() {
57        let ray = Ray {
58            origin: Point3::new(0.1_f64, 0.1, -1.0),
59            dir: Vec3::new(0.0, 0.0, 1.0),
60        };
61        let a = Point3::new(0.0, 0.0, 0.0);
62        let b = Point3::new(1.0, 0.0, 0.0);
63        let c = Point3::new(0.0, 1.0, 0.0);
64        let hit = ray_intersects_triangle(ray, a, b, c).unwrap();
65        assert!((hit.0 - 1.0).abs() < 1e-8);
66        // hit.1, hit.2 are barycentric coords; should sum < 1
67        assert!(hit.1 + hit.2 < 1.0);
68    }
69
70    #[test]
71    fn miss_ray() {
72        let ray = Ray {
73            origin: Point3::new(2.0, 2.0, -1.0),
74            dir: Vec3::new(0.0, 0.0, 1.0),
75        };
76        assert!(ray_intersects_triangle(
77            ray,
78            Point3::new(0., 0., 0.),
79            Point3::new(1., 0., 0.),
80            Point3::new(0., 1., 0.)
81        )
82        .is_none());
83    }
84}