parry2d/query/ray/ray.rs
1//! Traits and structure needed to cast rays.
2
3use crate::math::{Isometry, Point, Real, Vector};
4use crate::shape::FeatureId;
5
6#[cfg(feature = "alloc")]
7use crate::partitioning::BvhLeafCost;
8#[cfg(feature = "rkyv")]
9use rkyv::{bytecheck, CheckBytes};
10
11/// A ray for ray-casting queries.
12///
13/// A ray is a half-infinite line starting at an origin point and extending
14/// infinitely in a direction. Rays are fundamental for visibility queries,
15/// shooting mechanics, and collision prediction.
16///
17/// # Structure
18///
19/// - **origin**: The starting point of the ray
20/// - **dir**: The direction vector (does NOT need to be normalized)
21///
22/// # Direction Vector
23///
24/// The direction can be any non-zero vector:
25/// - **Normalized**: `dir` with length 1.0 gives time-of-impact in world units
26/// - **Not normalized**: Time-of-impact is scaled by `dir.norm()`
27///
28/// Most applications use normalized directions for intuitive results.
29///
30/// # Use Cases
31///
32/// - **Shooting/bullets**: Check what a projectile hits
33/// - **Line of sight**: Check if one object can "see" another
34/// - **Mouse picking**: Select objects by clicking
35/// - **Laser beams**: Simulate light or laser paths
36/// - **Proximity sensing**: Detect obstacles in a direction
37///
38/// # Example
39///
40/// ```rust
41/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
42/// use parry3d::query::{Ray, RayCast};
43/// use parry3d::shape::Ball;
44/// use nalgebra::{Point3, Vector3, Isometry3};
45///
46/// // Create a ray from origin pointing along +X axis
47/// let ray = Ray::new(
48/// Point3::origin(),
49/// Vector3::new(1.0, 0.0, 0.0) // Normalized direction
50/// );
51///
52/// // Create a ball at position (5, 0, 0) with radius 1
53/// let ball = Ball::new(1.0);
54/// let ball_pos = Isometry3::translation(5.0, 0.0, 0.0);
55///
56/// // Cast the ray against the ball
57/// if let Some(toi) = ball.cast_ray(&ball_pos, &ray, 100.0, true) {
58/// // Ray hits at t=4.0 (center at 5.0 minus radius 1.0)
59/// assert_eq!(toi, 4.0);
60///
61/// // Compute the actual hit point
62/// let hit_point = ray.point_at(toi);
63/// assert_eq!(hit_point, Point3::new(4.0, 0.0, 0.0));
64/// }
65/// # }
66/// ```
67#[derive(Debug, Clone, Copy)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[cfg_attr(
70 feature = "rkyv",
71 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
72 archive(as = "Self")
73)]
74#[repr(C)]
75pub struct Ray {
76 /// Starting point of the ray.
77 ///
78 /// This is where the ray begins. Points along the ray are computed as
79 /// `origin + dir * t` for `t ≥ 0`.
80 pub origin: Point<Real>,
81
82 /// Direction vector of the ray.
83 ///
84 /// This vector points in the direction the ray travels. It does NOT need
85 /// to be normalized, but using a normalized direction makes time-of-impact
86 /// values represent actual distances.
87 pub dir: Vector<Real>,
88}
89
90impl Ray {
91 /// Creates a new ray from an origin point and direction vector.
92 ///
93 /// # Arguments
94 ///
95 /// * `origin` - The starting point of the ray
96 /// * `dir` - The direction vector (typically normalized but not required)
97 ///
98 /// # Example
99 ///
100 /// ```
101 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
102 /// use parry3d::query::Ray;
103 /// use nalgebra::{Point3, Vector3};
104 ///
105 /// // Horizontal ray pointing along +X axis
106 /// let ray = Ray::new(
107 /// Point3::new(0.0, 5.0, 0.0),
108 /// Vector3::new(1.0, 0.0, 0.0)
109 /// );
110 ///
111 /// // Ray starts at (0, 5, 0) and points along +X
112 /// assert_eq!(ray.origin, Point3::new(0.0, 5.0, 0.0));
113 /// assert_eq!(ray.dir, Vector3::new(1.0, 0.0, 0.0));
114 /// # }
115 /// ```
116 pub fn new(origin: Point<Real>, dir: Vector<Real>) -> Ray {
117 Ray { origin, dir }
118 }
119
120 /// Transforms this ray by the given isometry (translation + rotation).
121 ///
122 /// Both the origin and direction are transformed.
123 ///
124 /// # Example
125 ///
126 /// ```
127 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
128 /// use parry3d::query::Ray;
129 /// use nalgebra::{Isometry3, Point3, Vector3};
130 ///
131 /// let ray = Ray::new(Point3::origin(), Vector3::x());
132 ///
133 /// // Translate by (5, 0, 0)
134 /// let transform = Isometry3::translation(5.0, 0.0, 0.0);
135 /// let transformed = ray.transform_by(&transform);
136 ///
137 /// assert_eq!(transformed.origin, Point3::new(5.0, 0.0, 0.0));
138 /// assert_eq!(transformed.dir, Vector3::x());
139 /// # }
140 /// ```
141 #[inline]
142 pub fn transform_by(&self, m: &Isometry<Real>) -> Self {
143 Self::new(m * self.origin, m * self.dir)
144 }
145
146 /// Transforms this ray by the inverse of the given isometry.
147 ///
148 /// This is equivalent to transforming the ray to the local space of an object.
149 ///
150 /// # Example
151 ///
152 /// ```
153 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
154 /// use parry3d::query::Ray;
155 /// use nalgebra::{Isometry3, Point3, Vector3};
156 ///
157 /// let ray = Ray::new(Point3::new(10.0, 0.0, 0.0), Vector3::x());
158 ///
159 /// let transform = Isometry3::translation(5.0, 0.0, 0.0);
160 /// let local_ray = ray.inverse_transform_by(&transform);
161 ///
162 /// // Origin moved back by the translation
163 /// assert_eq!(local_ray.origin, Point3::new(5.0, 0.0, 0.0));
164 /// # }
165 /// ```
166 #[inline]
167 pub fn inverse_transform_by(&self, m: &Isometry<Real>) -> Self {
168 Self::new(
169 m.inverse_transform_point(&self.origin),
170 m.inverse_transform_vector(&self.dir),
171 )
172 }
173
174 /// Translates this ray by the given vector.
175 ///
176 /// Only the origin is moved; the direction remains unchanged.
177 ///
178 /// # Example
179 ///
180 /// ```
181 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
182 /// use parry3d::query::Ray;
183 /// use nalgebra::{Point3, Vector3};
184 ///
185 /// let ray = Ray::new(Point3::origin(), Vector3::x());
186 /// let translated = ray.translate_by(Vector3::new(10.0, 5.0, 0.0));
187 ///
188 /// assert_eq!(translated.origin, Point3::new(10.0, 5.0, 0.0));
189 /// assert_eq!(translated.dir, Vector3::x()); // Direction unchanged
190 /// # }
191 /// ```
192 #[inline]
193 pub fn translate_by(&self, v: Vector<Real>) -> Self {
194 Self::new(self.origin + v, self.dir)
195 }
196
197 /// Computes a point along the ray at parameter `t`.
198 ///
199 /// Returns `origin + dir * t`. For `t ≥ 0`, this gives points along the ray.
200 ///
201 /// # Arguments
202 ///
203 /// * `t` - The parameter (typically the time-of-impact from ray casting)
204 ///
205 /// # Example
206 ///
207 /// ```
208 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
209 /// use parry3d::query::Ray;
210 /// use nalgebra::{Point3, Vector3};
211 ///
212 /// let ray = Ray::new(
213 /// Point3::origin(),
214 /// Vector3::new(1.0, 0.0, 0.0)
215 /// );
216 ///
217 /// // Point at t=5.0
218 /// assert_eq!(ray.point_at(5.0), Point3::new(5.0, 0.0, 0.0));
219 ///
220 /// // Point at t=0.0 is the origin
221 /// assert_eq!(ray.point_at(0.0), ray.origin);
222 /// # }
223 /// ```
224 #[inline]
225 pub fn point_at(&self, t: Real) -> Point<Real> {
226 self.origin + self.dir * t
227 }
228}
229
230/// Result of a successful ray cast against a shape.
231///
232/// This structure contains all information about where and how a ray intersects
233/// a shape, including the time of impact, surface normal, and geometric feature hit.
234///
235/// # Fields
236///
237/// - **time_of_impact**: The `t` parameter where the ray hits (use with `ray.point_at(t)`)
238/// - **normal**: The surface normal at the hit point
239/// - **feature**: Which geometric feature was hit (vertex, edge, or face)
240///
241/// # Time of Impact
242///
243/// The time of impact is the parameter `t` in the ray equation `origin + dir * t`:
244/// - If `dir` is normalized: `t` represents the distance traveled
245/// - If `dir` is not normalized: `t` represents time (distance / speed)
246///
247/// # Normal Direction
248///
249/// The normal behavior depends on the ray origin and `solid` parameter:
250/// - **Outside solid shape**: Normal points outward from the surface
251/// - **Inside non-solid shape**: Normal points inward (toward the interior)
252/// - **At t=0.0**: Normal may be unreliable due to numerical precision
253///
254/// # Example
255///
256/// ```rust
257/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
258/// use parry3d::query::{Ray, RayCast};
259/// use parry3d::shape::Cuboid;
260/// use nalgebra::{Point3, Vector3, Isometry3};
261///
262/// let cuboid = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
263/// let ray = Ray::new(
264/// Point3::new(-5.0, 0.0, 0.0),
265/// Vector3::new(1.0, 0.0, 0.0)
266/// );
267///
268/// if let Some(intersection) = cuboid.cast_ray_and_get_normal(
269/// &Isometry3::identity(),
270/// &ray,
271/// 100.0,
272/// true
273/// ) {
274/// // Ray hits the -X face of the cuboid at x=-1.0
275/// assert_eq!(intersection.time_of_impact, 4.0); // Distance from -5 to -1
276///
277/// // Hit point is at (-1, 0, 0) - the surface of the cuboid
278/// let hit_point = ray.point_at(intersection.time_of_impact);
279/// assert_eq!(hit_point.x, -1.0);
280///
281/// // Normal points outward (in -X direction for the -X face)
282/// assert_eq!(intersection.normal, Vector3::new(-1.0, 0.0, 0.0));
283/// }
284/// # }
285/// ```
286#[derive(Copy, Clone, Debug)]
287#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
288#[cfg_attr(
289 feature = "rkyv",
290 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
291 archive(as = "Self")
292)]
293pub struct RayIntersection {
294 /// The time of impact (parameter `t`) where the ray hits the shape.
295 ///
296 /// The exact hit point can be computed with `ray.point_at(time_of_impact)`.
297 /// If the ray direction is normalized, this represents the distance traveled.
298 pub time_of_impact: Real,
299
300 /// The surface normal at the intersection point.
301 ///
302 /// - Typically points outward from the shape
303 /// - May point inward if ray origin is inside a non-solid shape
304 /// - May be unreliable if `time_of_impact` is exactly zero
305 ///
306 /// Note: This should be a unit vector but is not enforced by the type system yet.
307 // TODO: use a Unit<Vector> instead.
308 pub normal: Vector<Real>,
309
310 /// The geometric feature (vertex, edge, or face) that was hit.
311 ///
312 /// This can be used for more detailed collision response or to identify
313 /// exactly which part of the shape was struck.
314 pub feature: FeatureId,
315}
316
317impl RayIntersection {
318 #[inline]
319 /// Creates a new `RayIntersection`.
320 #[cfg(feature = "dim3")]
321 pub fn new(time_of_impact: Real, normal: Vector<Real>, feature: FeatureId) -> RayIntersection {
322 RayIntersection {
323 time_of_impact,
324 normal,
325 feature,
326 }
327 }
328
329 #[inline]
330 /// Creates a new `RayIntersection`.
331 #[cfg(feature = "dim2")]
332 pub fn new(time_of_impact: Real, normal: Vector<Real>, feature: FeatureId) -> RayIntersection {
333 RayIntersection {
334 time_of_impact,
335 normal,
336 feature,
337 }
338 }
339
340 #[inline]
341 pub fn transform_by(&self, transform: &Isometry<Real>) -> Self {
342 RayIntersection {
343 time_of_impact: self.time_of_impact,
344 normal: transform * self.normal,
345 feature: self.feature,
346 }
347 }
348}
349
350#[cfg(feature = "alloc")]
351impl BvhLeafCost for RayIntersection {
352 #[inline]
353 fn cost(&self) -> Real {
354 self.time_of_impact
355 }
356}
357
358/// Traits of objects which can be transformed and tested for intersection with a ray.
359pub trait RayCast {
360 /// Computes the time of impact between this transform shape and a ray.
361 fn cast_local_ray(&self, ray: &Ray, max_time_of_impact: Real, solid: bool) -> Option<Real> {
362 self.cast_local_ray_and_get_normal(ray, max_time_of_impact, solid)
363 .map(|inter| inter.time_of_impact)
364 }
365
366 /// Computes the time of impact, and normal between this transformed shape and a ray.
367 fn cast_local_ray_and_get_normal(
368 &self,
369 ray: &Ray,
370 max_time_of_impact: Real,
371 solid: bool,
372 ) -> Option<RayIntersection>;
373
374 /// Tests whether a ray intersects this transformed shape.
375 #[inline]
376 fn intersects_local_ray(&self, ray: &Ray, max_time_of_impact: Real) -> bool {
377 self.cast_local_ray(ray, max_time_of_impact, true).is_some()
378 }
379
380 /// Computes the time of impact between this transform shape and a ray.
381 fn cast_ray(
382 &self,
383 m: &Isometry<Real>,
384 ray: &Ray,
385 max_time_of_impact: Real,
386 solid: bool,
387 ) -> Option<Real> {
388 let ls_ray = ray.inverse_transform_by(m);
389 self.cast_local_ray(&ls_ray, max_time_of_impact, solid)
390 }
391
392 /// Computes the time of impact, and normal between this transformed shape and a ray.
393 fn cast_ray_and_get_normal(
394 &self,
395 m: &Isometry<Real>,
396 ray: &Ray,
397 max_time_of_impact: Real,
398 solid: bool,
399 ) -> Option<RayIntersection> {
400 let ls_ray = ray.inverse_transform_by(m);
401 self.cast_local_ray_and_get_normal(&ls_ray, max_time_of_impact, solid)
402 .map(|inter| inter.transform_by(m))
403 }
404
405 /// Tests whether a ray intersects this transformed shape.
406 #[inline]
407 fn intersects_ray(&self, m: &Isometry<Real>, ray: &Ray, max_time_of_impact: Real) -> bool {
408 let ls_ray = ray.inverse_transform_by(m);
409 self.intersects_local_ray(&ls_ray, max_time_of_impact)
410 }
411}