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