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#[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 pub origin: Vec2,
22 pub direction: Dir2,
24}
25
26impl Ray2d {
27 #[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 #[inline]
42 pub fn get_point(&self, distance: f32) -> Vec2 {
43 self.origin + *self.direction * distance
44 }
45
46 #[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#[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 pub origin: Vec3,
71 pub direction: Dir3,
73}
74
75impl Ray3d {
76 #[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 #[inline]
91 pub fn get_point(&self, distance: f32) -> Vec3 {
92 self.origin + *self.direction * distance
93 }
94
95 #[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 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 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 assert!(ray
144 .intersect_plane(Vec2::X, Plane2d::new(Vec2::X))
145 .is_none());
146
147 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 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 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 assert!(ray
184 .intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
185 .is_none());
186
187 assert!(ray
189 .intersect_plane(
190 Vec3::X,
191 InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
192 )
193 .is_none());
194 }
195}