bevy_math/
ray.rs

1use crate::{
2    primitives::{InfinitePlane3d, Plane2d},
3    Dir2, Dir3, Vec2, Vec3,
4};
5
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::Reflect;
8#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
9use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
10
11/// An infinite half-line starting at `origin` and going in `direction` in 2D space.
12#[derive(Clone, Copy, Debug, PartialEq)]
13#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
14#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
15#[cfg_attr(
16    all(feature = "serialize", feature = "bevy_reflect"),
17    reflect(Deserialize, Serialize)
18)]
19pub struct Ray2d {
20    /// The origin of the ray.
21    pub origin: Vec2,
22    /// The direction of the ray.
23    pub direction: Dir2,
24}
25
26impl Ray2d {
27    /// Create a new `Ray2d` from a given origin and direction
28    ///
29    /// # Panics
30    ///
31    /// Panics if the given `direction` is zero (or very close to zero), or non-finite.
32    #[inline]
33    pub fn new(origin: Vec2, direction: Vec2) -> Self {
34        Self {
35            origin,
36            direction: Dir2::new(direction).expect("ray direction must be nonzero and finite"),
37        }
38    }
39
40    /// Get a point at a given distance along the ray
41    #[inline]
42    pub fn get_point(&self, distance: f32) -> Vec2 {
43        self.origin + *self.direction * distance
44    }
45
46    /// Get the distance to a plane if the ray intersects it
47    #[inline]
48    pub fn intersect_plane(&self, plane_origin: Vec2, plane: Plane2d) -> Option<f32> {
49        let denominator = plane.normal.dot(*self.direction);
50        if denominator.abs() > f32::EPSILON {
51            let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
52            if distance > f32::EPSILON {
53                return Some(distance);
54            }
55        }
56        None
57    }
58}
59
60/// An infinite half-line starting at `origin` and going in `direction` in 3D space.
61#[derive(Clone, Copy, Debug, PartialEq)]
62#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
63#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
64#[cfg_attr(
65    all(feature = "serialize", feature = "bevy_reflect"),
66    reflect(Deserialize, Serialize)
67)]
68pub struct Ray3d {
69    /// The origin of the ray.
70    pub origin: Vec3,
71    /// The direction of the ray.
72    pub direction: Dir3,
73}
74
75impl Ray3d {
76    /// Create a new `Ray3d` from a given origin and direction
77    ///
78    /// # Panics
79    ///
80    /// Panics if the given `direction` is zero (or very close to zero), or non-finite.
81    #[inline]
82    pub fn new(origin: Vec3, direction: Vec3) -> Self {
83        Self {
84            origin,
85            direction: Dir3::new(direction).expect("ray direction must be nonzero and finite"),
86        }
87    }
88
89    /// Get a point at a given distance along the ray
90    #[inline]
91    pub fn get_point(&self, distance: f32) -> Vec3 {
92        self.origin + *self.direction * distance
93    }
94
95    /// Get the distance to a plane if the ray intersects it
96    #[inline]
97    pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option<f32> {
98        let denominator = plane.normal.dot(*self.direction);
99        if denominator.abs() > f32::EPSILON {
100            let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
101            if distance > f32::EPSILON {
102                return Some(distance);
103            }
104        }
105        None
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn intersect_plane_2d() {
115        let ray = Ray2d::new(Vec2::ZERO, Vec2::Y);
116
117        // Orthogonal, and test that an inverse plane_normal has the same result
118        assert_eq!(
119            ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::Y)),
120            Some(1.0)
121        );
122        assert_eq!(
123            ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::NEG_Y)),
124            Some(1.0)
125        );
126        assert!(ray
127            .intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::Y))
128            .is_none());
129        assert!(ray
130            .intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::NEG_Y))
131            .is_none());
132
133        // Diagonal
134        assert_eq!(
135            ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::ONE)),
136            Some(1.0)
137        );
138        assert!(ray
139            .intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::ONE))
140            .is_none());
141
142        // Parallel
143        assert!(ray
144            .intersect_plane(Vec2::X, Plane2d::new(Vec2::X))
145            .is_none());
146
147        // Parallel with simulated rounding error
148        assert!(ray
149            .intersect_plane(Vec2::X, Plane2d::new(Vec2::X + Vec2::Y * f32::EPSILON))
150            .is_none());
151    }
152
153    #[test]
154    fn intersect_plane_3d() {
155        let ray = Ray3d::new(Vec3::ZERO, Vec3::Z);
156
157        // Orthogonal, and test that an inverse plane_normal has the same result
158        assert_eq!(
159            ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)),
160            Some(1.0)
161        );
162        assert_eq!(
163            ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)),
164            Some(1.0)
165        );
166        assert!(ray
167            .intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z))
168            .is_none());
169        assert!(ray
170            .intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::NEG_Z))
171            .is_none());
172
173        // Diagonal
174        assert_eq!(
175            ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)),
176            Some(1.0)
177        );
178        assert!(ray
179            .intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE))
180            .is_none());
181
182        // Parallel
183        assert!(ray
184            .intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
185            .is_none());
186
187        // Parallel with simulated rounding error
188        assert!(ray
189            .intersect_plane(
190                Vec3::X,
191                InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
192            )
193            .is_none());
194    }
195}