Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/ray.rs
9351 views
1
use crate::{
2
ops,
3
primitives::{InfinitePlane3d, Plane2d},
4
Dir2, Dir3, Vec2, Vec3,
5
};
6
7
#[cfg(feature = "bevy_reflect")]
8
use bevy_reflect::Reflect;
9
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
10
use 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
)]
24
pub struct Ray2d {
25
/// The origin of the ray.
26
pub origin: Vec2,
27
/// The direction of the ray.
28
pub direction: Dir2,
29
}
30
31
impl 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
)]
81
pub struct Ray3d {
82
/// The origin of the ray.
83
pub origin: Vec3,
84
/// The direction of the ray.
85
pub direction: Dir3,
86
}
87
88
impl 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)]
131
mod 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
}
217
218