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