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}