bevy_math/
ray.rs

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