rg3d_core/math/
ray.rs

1// Clippy complains about normal mathematical symbols like A, B, C for quadratic equation.
2#![allow(clippy::many_single_char_names)]
3
4use crate::algebra::{Matrix4, Point3, Vector3};
5use crate::math::aabb::AxisAlignedBoundingBox;
6use crate::math::{is_point_inside_triangle, plane::Plane, solve_quadratic};
7
8#[derive(Copy, Clone, Debug)]
9pub struct Ray {
10    pub origin: Vector3<f32>,
11    pub dir: Vector3<f32>,
12}
13
14impl Default for Ray {
15    #[inline]
16    fn default() -> Self {
17        Ray {
18            origin: Vector3::new(0.0, 0.0, 0.0),
19            dir: Vector3::new(0.0, 0.0, 1.0),
20        }
21    }
22}
23
24/// Pair of ray equation parameters.
25#[derive(Clone, Debug, Copy)]
26pub struct IntersectionResult {
27    pub min: f32,
28    pub max: f32,
29}
30
31impl IntersectionResult {
32    #[inline]
33    pub fn from_slice(roots: &[f32]) -> Self {
34        let mut min = f32::MAX;
35        let mut max = -f32::MAX;
36        for n in roots {
37            min = min.min(*n);
38            max = max.max(*n);
39        }
40        Self { min, max }
41    }
42
43    #[inline]
44    pub fn from_set(results: &[Option<IntersectionResult>]) -> Option<Self> {
45        let mut result = None;
46        for v in results {
47            match result {
48                None => result = *v,
49                Some(ref mut result) => {
50                    if let Some(v) = v {
51                        result.merge(v.min);
52                        result.merge(v.max);
53                    }
54                }
55            }
56        }
57        result
58    }
59
60    /// Updates min and max ray equation parameters according to a new parameter -
61    /// expands range if `param` was outside of that range.
62    #[inline]
63    pub fn merge(&mut self, param: f32) {
64        if param < self.min {
65            self.min = param;
66        }
67        if param > self.max {
68            self.max = param;
69        }
70    }
71
72    #[inline]
73    pub fn merge_slice(&mut self, params: &[f32]) {
74        for param in params {
75            self.merge(*param)
76        }
77    }
78}
79
80pub enum CylinderKind {
81    Infinite,
82    Finite,
83    Capped,
84}
85
86impl Ray {
87    /// Creates ray from two points. May fail if begin == end.
88    #[inline]
89    pub fn from_two_points(begin: Vector3<f32>, end: Vector3<f32>) -> Self {
90        Ray {
91            origin: begin,
92            dir: end - begin,
93        }
94    }
95
96    #[inline]
97    pub fn new(origin: Vector3<f32>, dir: Vector3<f32>) -> Self {
98        Self { origin, dir }
99    }
100
101    /// Checks intersection with sphere. Returns two intersection points or none
102    /// if there was no intersection.
103    #[inline]
104    pub fn sphere_intersection_points(
105        &self,
106        position: &Vector3<f32>,
107        radius: f32,
108    ) -> Option<[Vector3<f32>; 2]> {
109        self.try_eval_points(self.sphere_intersection(position, radius))
110    }
111
112    #[inline]
113    pub fn sphere_intersection(
114        &self,
115        position: &Vector3<f32>,
116        radius: f32,
117    ) -> Option<IntersectionResult> {
118        let d = self.origin - *position;
119        let a = self.dir.dot(&self.dir);
120        let b = 2.0 * self.dir.dot(&d);
121        let c = d.dot(&d) - radius * radius;
122        solve_quadratic(a, b, c).map(|roots| IntersectionResult::from_slice(&roots))
123    }
124
125    /// Checks intersection with sphere.
126    #[inline]
127    pub fn is_intersect_sphere(&self, position: &Vector3<f32>, radius: f32) -> bool {
128        let d = self.origin - position;
129        let a = self.dir.dot(&self.dir);
130        let b = 2.0 * self.dir.dot(&d);
131        let c = d.dot(&d) - radius * radius;
132        let discriminant = b * b - 4.0 * a * c;
133        discriminant >= 0.0
134    }
135
136    /// Returns t factor (at pt=o+d*t equation) for projection of given point at ray
137    #[inline]
138    pub fn project_point(&self, point: &Vector3<f32>) -> f32 {
139        (point - self.origin).dot(&self.dir) / self.dir.norm_squared()
140    }
141
142    /// Returns point on ray which defined by pt=o+d*t equation.
143    #[inline]
144    pub fn get_point(&self, t: f32) -> Vector3<f32> {
145        self.origin + self.dir.scale(t)
146    }
147
148    #[inline]
149    pub fn box_intersection(
150        &self,
151        min: &Vector3<f32>,
152        max: &Vector3<f32>,
153    ) -> Option<IntersectionResult> {
154        let (mut tmin, mut tmax) = if self.dir.x >= 0.0 {
155            (
156                (min.x - self.origin.x) / self.dir.x,
157                (max.x - self.origin.x) / self.dir.x,
158            )
159        } else {
160            (
161                (max.x - self.origin.x) / self.dir.x,
162                (min.x - self.origin.x) / self.dir.x,
163            )
164        };
165
166        let (tymin, tymax) = if self.dir.y >= 0.0 {
167            (
168                (min.y - self.origin.y) / self.dir.y,
169                (max.y - self.origin.y) / self.dir.y,
170            )
171        } else {
172            (
173                (max.y - self.origin.y) / self.dir.y,
174                (min.y - self.origin.y) / self.dir.y,
175            )
176        };
177
178        if tmin > tymax || (tymin > tmax) {
179            return None;
180        }
181        if tymin > tmin {
182            tmin = tymin;
183        }
184        if tymax < tmax {
185            tmax = tymax;
186        }
187        let (tzmin, tzmax) = if self.dir.z >= 0.0 {
188            (
189                (min.z - self.origin.z) / self.dir.z,
190                (max.z - self.origin.z) / self.dir.z,
191            )
192        } else {
193            (
194                (max.z - self.origin.z) / self.dir.z,
195                (min.z - self.origin.z) / self.dir.z,
196            )
197        };
198
199        if (tmin > tzmax) || (tzmin > tmax) {
200            return None;
201        }
202        if tzmin > tmin {
203            tmin = tzmin;
204        }
205        if tzmax < tmax {
206            tmax = tzmax;
207        }
208        if tmin <= 1.0 && tmax >= 0.0 {
209            Some(IntersectionResult {
210                min: tmin,
211                max: tmax,
212            })
213        } else {
214            None
215        }
216    }
217
218    #[inline]
219    pub fn box_intersection_points(
220        &self,
221        min: &Vector3<f32>,
222        max: &Vector3<f32>,
223    ) -> Option<[Vector3<f32>; 2]> {
224        self.try_eval_points(self.box_intersection(min, max))
225    }
226
227    #[inline]
228    pub fn aabb_intersection(&self, aabb: &AxisAlignedBoundingBox) -> Option<IntersectionResult> {
229        self.box_intersection(&aabb.min, &aabb.max)
230    }
231
232    #[inline]
233    pub fn aabb_intersection_points(
234        &self,
235        aabb: &AxisAlignedBoundingBox,
236    ) -> Option<[Vector3<f32>; 2]> {
237        self.box_intersection_points(&aabb.min, &aabb.max)
238    }
239
240    /// Solves plane equation in order to find ray equation parameter.
241    /// There is no intersection if result < 0.
242    #[inline]
243    pub fn plane_intersection(&self, plane: &Plane) -> f32 {
244        let u = -(self.origin.dot(&plane.normal) + plane.d);
245        let v = self.dir.dot(&plane.normal);
246        u / v
247    }
248
249    #[inline]
250    pub fn plane_intersection_point(&self, plane: &Plane) -> Option<Vector3<f32>> {
251        let t = self.plane_intersection(plane);
252        if !(0.0..=1.0).contains(&t) {
253            None
254        } else {
255            Some(self.get_point(t))
256        }
257    }
258
259    #[inline]
260    pub fn triangle_intersection(
261        &self,
262        vertices: &[Vector3<f32>; 3],
263    ) -> Option<(f32, Vector3<f32>)> {
264        let ba = vertices[1] - vertices[0];
265        let ca = vertices[2] - vertices[0];
266        let plane = Plane::from_normal_and_point(&ba.cross(&ca), &vertices[0])?;
267
268        let t = self.plane_intersection(&plane);
269        if (0.0..=1.0).contains(&t) {
270            let point = self.get_point(t);
271            if is_point_inside_triangle(&point, vertices) {
272                return Some((t, point));
273            }
274        }
275        None
276    }
277
278    #[inline]
279    pub fn triangle_intersection_point(
280        &self,
281        vertices: &[Vector3<f32>; 3],
282    ) -> Option<Vector3<f32>> {
283        let ba = vertices[1] - vertices[0];
284        let ca = vertices[2] - vertices[0];
285        let plane = Plane::from_normal_and_point(&ba.cross(&ca), &vertices[0])?;
286
287        if let Some(point) = self.plane_intersection_point(&plane) {
288            if is_point_inside_triangle(&point, vertices) {
289                return Some(point);
290            }
291        }
292        None
293    }
294
295    /// Generic ray-cylinder intersection test.
296    ///
297    /// <https://mrl.nyu.edu/~dzorin/rend05/lecture2.pdf>
298    ///
299    ///  Infinite cylinder oriented along line pa + va * t:
300    ///      sqr_len(q - pa - dot(va, q - pa) * va) - r ^ 2 = 0
301    ///  where q - point on cylinder, substitute q with ray p + v * t:
302    ///     sqr_len(p - pa + vt - dot(va, p - pa + vt) * va) - r ^ 2 = 0
303    ///  reduce to A * t * t + B * t + C = 0 (quadratic equation), where:
304    ///     A = sqr_len(v - dot(v, va) * va)
305    ///     B = 2 * dot(v - dot(v, va) * va, dp - dot(dp, va) * va)
306    ///     C = sqr_len(dp - dot(dp, va) * va) - r ^ 2
307    ///     where dp = p - pa
308    ///  to find intersection points we have to solve quadratic equation
309    ///  to get root which will be t parameter of ray equation.
310    #[inline]
311    pub fn cylinder_intersection(
312        &self,
313        pa: &Vector3<f32>,
314        pb: &Vector3<f32>,
315        r: f32,
316        kind: CylinderKind,
317    ) -> Option<IntersectionResult> {
318        let va = (*pb - *pa)
319            .try_normalize(f32::EPSILON)
320            .unwrap_or_else(|| Vector3::new(0.0, 1.0, 0.0));
321        let vl = self.dir - va.scale(self.dir.dot(&va));
322        let dp = self.origin - *pa;
323        let dpva = dp - va.scale(dp.dot(&va));
324
325        let a = vl.norm_squared();
326        let b = 2.0 * vl.dot(&dpva);
327        let c = dpva.norm_squared() - r * r;
328
329        // Get roots for cylinder surfaces
330        if let Some(cylinder_roots) = solve_quadratic(a, b, c) {
331            match kind {
332                CylinderKind::Infinite => Some(IntersectionResult::from_slice(&cylinder_roots)),
333                CylinderKind::Capped => {
334                    let mut result = IntersectionResult::from_slice(&cylinder_roots);
335                    // In case of cylinder with caps we have to check intersection with caps
336                    for (cap_center, cap_normal) in [(pa, -va), (pb, va)].iter() {
337                        let cap_plane =
338                            Plane::from_normal_and_point(cap_normal, cap_center).unwrap();
339                        let t = self.plane_intersection(&cap_plane);
340                        if t > 0.0 {
341                            let intersection = self.get_point(t);
342                            if (*cap_center - intersection).norm_squared() <= r * r {
343                                // Point inside cap bounds
344                                result.merge(t);
345                            }
346                        }
347                    }
348                    result.merge_slice(&cylinder_roots);
349                    Some(result)
350                }
351                CylinderKind::Finite => {
352                    // In case of finite cylinder without caps we have to check that intersection
353                    // points on cylinder surface are between two planes of caps.
354                    let mut result = None;
355                    for root in cylinder_roots.iter() {
356                        let int_point = self.get_point(*root);
357                        if (int_point - *pa).dot(&va) >= 0.0 && (*pb - int_point).dot(&va) >= 0.0 {
358                            match &mut result {
359                                None => {
360                                    result = Some(IntersectionResult {
361                                        min: *root,
362                                        max: *root,
363                                    })
364                                }
365                                Some(result) => result.merge(*root),
366                            }
367                        }
368                    }
369                    result
370                }
371            }
372        } else {
373            // We have no roots, so no intersection.
374            None
375        }
376    }
377
378    #[inline]
379    pub fn try_eval_points(&self, result: Option<IntersectionResult>) -> Option<[Vector3<f32>; 2]> {
380        match result {
381            None => None,
382            Some(result) => {
383                let a = if result.min >= 0.0 && result.min <= 1.0 {
384                    Some(self.get_point(result.min))
385                } else {
386                    None
387                };
388
389                let b = if result.max >= 0.0 && result.max <= 1.0 {
390                    Some(self.get_point(result.max))
391                } else {
392                    None
393                };
394
395                match a {
396                    None => b.map(|b| [b, b]),
397                    Some(a) => match b {
398                        None => Some([a, a]),
399                        Some(b) => Some([a, b]),
400                    },
401                }
402            }
403        }
404    }
405
406    #[inline]
407    pub fn capsule_intersection(
408        &self,
409        pa: &Vector3<f32>,
410        pb: &Vector3<f32>,
411        radius: f32,
412    ) -> Option<[Vector3<f32>; 2]> {
413        // Dumb approach - check intersection with finite cylinder without caps,
414        // then check two sphere caps.
415        let cylinder = self.cylinder_intersection(pa, pb, radius, CylinderKind::Finite);
416        let cap_a = self.sphere_intersection(pa, radius);
417        let cap_b = self.sphere_intersection(pb, radius);
418        self.try_eval_points(IntersectionResult::from_set(&[cylinder, cap_a, cap_b]))
419    }
420
421    /// Transforms ray using given matrix. This method is useful when you need to
422    /// transform ray into some object space to simplify calculations. For example
423    /// you may have mesh with lots of triangles, and in one way you would take all
424    /// vertices, transform them into world space by some matrix, then do intersection
425    /// test in world space. This works, but too inefficient, much more faster would
426    /// be to put ray into object space and do intersection test in object space. This
427    /// removes vertex*matrix multiplication and significantly improves performance.
428    #[must_use = "Method does not modify ray, instead it returns transformed copy"]
429    #[inline]
430    pub fn transform(&self, mat: Matrix4<f32>) -> Self {
431        Self {
432            origin: mat.transform_point(&Point3::from(self.origin)).coords,
433            dir: mat.transform_vector(&self.dir),
434        }
435    }
436}
437
438#[cfg(test)]
439mod test {
440    use crate::math::ray::Ray;
441    use crate::math::Vector3;
442
443    #[test]
444    fn intersection() {
445        let triangle = [
446            Vector3::new(0.0, 0.5, 0.0),
447            Vector3::new(-0.5, -0.5, 0.0),
448            Vector3::new(0.5, -0.5, 0.0),
449        ];
450        let ray = Ray::from_two_points(Vector3::new(0.0, 0.0, -2.0), Vector3::new(0.0, 0.0, -1.0));
451        assert!(ray.triangle_intersection_point(&triangle).is_none());
452    }
453}