parry3d/query/ray/
ray_triangle.rs

1use crate::math::Real;
2#[cfg(feature = "dim2")]
3use crate::math::Vector;
4#[cfg(feature = "dim3")]
5use crate::math::Vector;
6use crate::query::{Ray, RayCast, RayIntersection};
7use crate::shape::{FeatureId, Triangle};
8
9impl RayCast for Triangle {
10    #[inline]
11    #[cfg(feature = "dim2")]
12    fn cast_local_ray_and_get_normal(
13        &self,
14        ray: &Ray,
15        max_time_of_impact: Real,
16        solid: bool,
17    ) -> Option<RayIntersection> {
18        let edges = self.edges();
19
20        if solid {
21            // Check if ray starts in triangle
22            let perp_sign1 = edges[0]
23                .scaled_direction()
24                .perp_dot(ray.origin - edges[0].a)
25                > 0.0;
26            let perp_sign2 = edges[1]
27                .scaled_direction()
28                .perp_dot(ray.origin - edges[1].a)
29                > 0.0;
30            let perp_sign3 = edges[2]
31                .scaled_direction()
32                .perp_dot(ray.origin - edges[2].a)
33                > 0.0;
34
35            if perp_sign1 == perp_sign2 && perp_sign1 == perp_sign3 {
36                return Some(RayIntersection::new(0.0, Vector::Y, FeatureId::Face(0)));
37            }
38        }
39
40        let mut best = None;
41        let mut smallest_toi = Real::MAX;
42
43        for edge in &edges {
44            if let Some(inter) = edge.cast_local_ray_and_get_normal(ray, max_time_of_impact, solid)
45            {
46                if inter.time_of_impact < smallest_toi {
47                    smallest_toi = inter.time_of_impact;
48                    best = Some(inter);
49                }
50            }
51        }
52
53        best
54    }
55
56    #[inline]
57    #[cfg(feature = "dim3")]
58    fn cast_local_ray_and_get_normal(
59        &self,
60        ray: &Ray,
61        max_time_of_impact: Real,
62        _: bool,
63    ) -> Option<RayIntersection> {
64        let inter = local_ray_intersection_with_triangle(self.a, self.b, self.c, ray)?.0;
65
66        if inter.time_of_impact <= max_time_of_impact {
67            Some(inter)
68        } else {
69            None
70        }
71    }
72}
73
74/// Computes the intersection between a triangle and a ray.
75///
76/// If an intersection is found, the time of impact, the normal and the barycentric coordinates of
77/// the intersection point are returned.
78#[cfg(feature = "dim3")]
79pub fn local_ray_intersection_with_triangle(
80    a: Vector,
81    b: Vector,
82    c: Vector,
83    ray: &Ray,
84) -> Option<(RayIntersection, Vector)> {
85    let ab = b - a;
86    let ac = c - a;
87
88    // normal
89    let n = ab.cross(ac);
90    let d = n.dot(ray.dir);
91
92    // the normal and the ray direction are parallel
93    if d == 0.0 {
94        return None;
95    }
96
97    let ap = ray.origin - a;
98    let t = ap.dot(n);
99
100    // the ray does not intersect the halfspace defined by the triangle
101    if (t < 0.0 && d < 0.0) || (t > 0.0 && d > 0.0) {
102        return None;
103    }
104
105    let fid = if d < 0.0 { 0 } else { 1 };
106
107    let d = d.abs();
108
109    //
110    // intersection: compute barycentric coordinates
111    //
112    let e = -ray.dir.cross(ap);
113
114    let mut v;
115    let mut w;
116    let time_of_impact;
117    let normal;
118
119    if t < 0.0 {
120        v = -ac.dot(e);
121
122        if v < 0.0 || v > d {
123            return None;
124        }
125
126        w = ab.dot(e);
127
128        if w < 0.0 || v + w > d {
129            return None;
130        }
131
132        let invd = 1.0 / d;
133        time_of_impact = -t * invd;
134        normal = -n.normalize();
135        v *= invd;
136        w *= invd;
137    } else {
138        v = ac.dot(e);
139
140        if v < 0.0 || v > d {
141            return None;
142        }
143
144        w = -ab.dot(e);
145
146        if w < 0.0 || v + w > d {
147            return None;
148        }
149
150        let invd = 1.0 / d;
151        time_of_impact = t * invd;
152        normal = n.normalize();
153        v *= invd;
154        w *= invd;
155    }
156
157    Some((
158        RayIntersection::new(time_of_impact, normal, FeatureId::Face(fid)),
159        Vector::new(-v - w + 1.0, v, w),
160    ))
161}