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