Skip to main content

bevy_shape/
ray.rs

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