avian2d/spatial_query/
shape_caster.rs

1use crate::prelude::*;
2use bevy::{
3    ecs::{
4        entity::{EntityMapper, MapEntities},
5        lifecycle::HookContext,
6        world::DeferredWorld,
7    },
8    prelude::*,
9};
10use parry::{query::ShapeCastOptions, shape::CompositeShapeRef};
11
12/// A component used for [shapecasting](spatial_query#shapecasting).
13///
14/// **Shapecasting** is a type of [spatial query](spatial_query) where a shape travels along a straight
15/// line and computes hits with colliders. This is often used to determine how far an object can move
16/// in a direction before it hits something.
17///
18/// Each shapecast is defined by a `shape` (a [`Collider`]), its local `shape_rotation`, a local `origin` and
19/// a local `direction`. The [`ShapeCaster`] will find each hit and add them to the [`ShapeHits`] component in
20/// the order of distance.
21///
22/// Computing lots of hits can be expensive, especially against complex geometry, so the maximum number of hits
23/// is one by default. This can be configured through the `max_hits` property.
24///
25/// The [`ShapeCaster`] is the easiest way to handle simple shapecasting. If you want more control and don't want
26/// to perform shapecasts on every frame, consider using the [`SpatialQuery`] system parameter.
27///
28/// # Example
29///
30/// ```
31/// # #[cfg(feature = "2d")]
32/// # use avian2d::prelude::*;
33/// # #[cfg(feature = "3d")]
34/// use avian3d::prelude::*;
35/// use bevy::prelude::*;
36///
37/// # #[cfg(all(feature = "3d", feature = "f32"))]
38/// fn setup(mut commands: Commands) {
39///     // Spawn a shape caster with a ball shape moving right starting from the origin
40///     commands.spawn(ShapeCaster::new(
41#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.5),")]
42#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.5),")]
43///         Vec3::ZERO,
44///         Quat::default(),
45///         Dir3::X,
46///     ));
47/// }
48///
49/// fn print_hits(query: Query<(&ShapeCaster, &ShapeHits)>) {
50///     for (shape_caster, hits) in &query {
51///         for hit in hits.iter() {
52///             println!("Hit entity {}", hit.entity);
53///         }
54///     }
55/// }
56/// ```
57#[derive(Component, Clone, Debug, Reflect)]
58#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
59#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
60#[reflect(Debug, Component)]
61#[component(on_add = on_add_shape_caster)]
62#[require(ShapeHits)]
63pub struct ShapeCaster {
64    /// Controls if the shape caster is enabled.
65    pub enabled: bool,
66
67    /// The shape being cast represented as a [`Collider`].
68    #[reflect(ignore)]
69    pub shape: Collider,
70
71    /// The local origin of the shape relative to the [`Position`] and [`Rotation`]
72    /// of the shape caster entity or its parent.
73    ///
74    /// To get the global origin, use the `global_origin` method.
75    pub origin: Vector,
76
77    /// The global origin of the shape.
78    global_origin: Vector,
79
80    /// The local rotation of the shape being cast relative to the [`Rotation`]
81    /// of the shape caster entity or its parent. Expressed in radians.
82    ///
83    /// To get the global shape rotation, use the `global_shape_rotation` method.
84    #[cfg(feature = "2d")]
85    pub shape_rotation: Scalar,
86
87    /// The local rotation of the shape being cast relative to the [`Rotation`]
88    /// of the shape caster entity or its parent.
89    ///
90    /// To get the global shape rotation, use the `global_shape_rotation` method.
91    #[cfg(feature = "3d")]
92    pub shape_rotation: Quaternion,
93
94    /// The global rotation of the shape.
95    #[cfg(feature = "2d")]
96    global_shape_rotation: Scalar,
97
98    /// The global rotation of the shape.
99    #[cfg(feature = "3d")]
100    global_shape_rotation: Quaternion,
101
102    /// The local direction of the shapecast relative to the [`Rotation`] of the shape caster entity or its parent.
103    ///
104    /// To get the global direction, use the `global_direction` method.
105    pub direction: Dir,
106
107    /// The global direction of the shapecast.
108    global_direction: Dir,
109
110    /// The maximum number of hits allowed. By default this is one and only the first hit is returned.
111    pub max_hits: u32,
112
113    /// The maximum distance the shape can travel.
114    ///
115    /// By default, this is infinite.
116    #[doc(alias = "max_time_of_impact")]
117    pub max_distance: Scalar,
118
119    /// The separation distance at which the shapes will be considered as impacting.
120    ///
121    /// If the shapes are separated by a distance smaller than `target_distance` at the origin of the cast,
122    /// the computed contact points and normals are only reliable if [`ShapeCaster::compute_contact_on_penetration`]
123    /// is set to `true`.
124    ///
125    /// By default, this is `0.0`, so the shapes will only be considered as impacting when they first touch.
126    pub target_distance: Scalar,
127
128    /// If `true`, contact points and normals will be calculated even when the cast distance is `0.0`.
129    ///
130    /// The default is `true`.
131    pub compute_contact_on_penetration: bool,
132
133    /// If `true` *and* the shape is travelling away from the object that was hit,
134    /// the cast will ignore any impact that happens at the cast origin.
135    ///
136    /// The default is `false`.
137    pub ignore_origin_penetration: bool,
138
139    /// If true, the shape caster ignores hits against its own [`Collider`]. This is the default.
140    pub ignore_self: bool,
141
142    /// Rules that determine which colliders are taken into account in the shape cast.
143    pub query_filter: SpatialQueryFilter,
144}
145
146impl Default for ShapeCaster {
147    fn default() -> Self {
148        Self {
149            enabled: true,
150            #[cfg(feature = "2d")]
151            shape: Collider::circle(0.0),
152            #[cfg(feature = "3d")]
153            shape: Collider::sphere(0.0),
154            origin: Vector::ZERO,
155            global_origin: Vector::ZERO,
156            #[cfg(feature = "2d")]
157            shape_rotation: 0.0,
158            #[cfg(feature = "3d")]
159            shape_rotation: Quaternion::IDENTITY,
160            #[cfg(feature = "2d")]
161            global_shape_rotation: 0.0,
162            #[cfg(feature = "3d")]
163            global_shape_rotation: Quaternion::IDENTITY,
164            direction: Dir::X,
165            global_direction: Dir::X,
166            max_hits: 1,
167            max_distance: Scalar::MAX,
168            target_distance: 0.0,
169            compute_contact_on_penetration: true,
170            ignore_origin_penetration: false,
171            ignore_self: true,
172            query_filter: SpatialQueryFilter::default(),
173        }
174    }
175}
176
177impl ShapeCaster {
178    /// Creates a new [`ShapeCaster`] with a given shape, origin, shape rotation and direction.
179    #[cfg(feature = "2d")]
180    pub fn new(
181        shape: impl Into<Collider>,
182        origin: Vector,
183        shape_rotation: Scalar,
184        direction: Dir,
185    ) -> Self {
186        Self {
187            shape: shape.into(),
188            origin,
189            shape_rotation,
190            direction,
191            ..default()
192        }
193    }
194    #[cfg(feature = "3d")]
195    /// Creates a new [`ShapeCaster`] with a given shape, origin, shape rotation and direction.
196    pub fn new(
197        shape: impl Into<Collider>,
198        origin: Vector,
199        shape_rotation: Quaternion,
200        direction: Dir,
201    ) -> Self {
202        Self {
203            shape: shape.into(),
204            origin,
205            shape_rotation,
206            direction,
207            ..default()
208        }
209    }
210
211    /// Sets the ray origin.
212    pub fn with_origin(mut self, origin: Vector) -> Self {
213        self.origin = origin;
214        self
215    }
216
217    /// Sets the ray direction.
218    pub fn with_direction(mut self, direction: Dir) -> Self {
219        self.direction = direction;
220        self
221    }
222
223    /// Sets the separation distance at which the shapes will be considered as impacting.
224    ///
225    /// If the shapes are separated by a distance smaller than `target_distance` at the origin of the cast,
226    /// the computed contact points and normals are only reliable if [`ShapeCaster::compute_contact_on_penetration`]
227    /// is set to `true`.
228    ///
229    /// By default, this is `0.0`, so the shapes will only be considered as impacting when they first touch.
230    pub fn with_target_distance(mut self, target_distance: Scalar) -> Self {
231        self.target_distance = target_distance;
232        self
233    }
234
235    /// Sets if contact points and normals should be calculated even when the cast distance is `0.0`.
236    ///
237    /// The default is `true`.
238    pub fn with_compute_contact_on_penetration(mut self, compute_contact: bool) -> Self {
239        self.compute_contact_on_penetration = compute_contact;
240        self
241    }
242
243    /// Controls how the shapecast behaves when the shape is already penetrating a [collider](Collider)
244    /// at the shape origin.
245    ///
246    /// If set to `true` **and** the shape is being cast in a direction where it will eventually stop penetrating,
247    /// the shapecast will not stop immediately, and will instead continue until another hit.\
248    /// If set to false, the shapecast will stop immediately and return the hit. This is the default.
249    pub fn with_ignore_origin_penetration(mut self, ignore: bool) -> Self {
250        self.ignore_origin_penetration = ignore;
251        self
252    }
253
254    /// Sets if the shape caster should ignore hits against its own [`Collider`].
255    ///
256    /// The default is `true`.
257    pub fn with_ignore_self(mut self, ignore: bool) -> Self {
258        self.ignore_self = ignore;
259        self
260    }
261
262    /// Sets the maximum distance the shape can travel.
263    pub fn with_max_distance(mut self, max_distance: Scalar) -> Self {
264        self.max_distance = max_distance;
265        self
266    }
267
268    /// Sets the maximum number of allowed hits.
269    pub fn with_max_hits(mut self, max_hits: u32) -> Self {
270        self.max_hits = max_hits;
271        self
272    }
273
274    /// Sets the shape caster's [query filter](SpatialQueryFilter) that controls which colliders
275    /// should be included or excluded by shapecasts.
276    pub fn with_query_filter(mut self, query_filter: SpatialQueryFilter) -> Self {
277        self.query_filter = query_filter;
278        self
279    }
280
281    /// Enables the [`ShapeCaster`].
282    pub fn enable(&mut self) {
283        self.enabled = true;
284    }
285
286    /// Disables the [`ShapeCaster`].
287    pub fn disable(&mut self) {
288        self.enabled = false;
289    }
290
291    /// Returns the global origin of the ray.
292    pub fn global_origin(&self) -> Vector {
293        self.global_origin
294    }
295
296    /// Returns the global rotation of the shape.
297    #[cfg(feature = "2d")]
298    pub fn global_shape_rotation(&self) -> Scalar {
299        self.global_shape_rotation
300    }
301
302    /// Returns the global rotation of the shape.
303    #[cfg(feature = "3d")]
304    pub fn global_shape_rotation(&self) -> Quaternion {
305        self.global_shape_rotation
306    }
307
308    /// Returns the global direction of the ray.
309    pub fn global_direction(&self) -> Dir {
310        self.global_direction
311    }
312
313    /// Sets the global origin of the ray.
314    pub(crate) fn set_global_origin(&mut self, global_origin: Vector) {
315        self.global_origin = global_origin;
316    }
317
318    /// Sets the global rotation of the shape.
319    #[cfg(feature = "2d")]
320    pub(crate) fn set_global_shape_rotation(&mut self, global_rotation: Scalar) {
321        self.global_shape_rotation = global_rotation;
322    }
323
324    /// Sets the global rotation of the shape.
325    #[cfg(feature = "3d")]
326    pub(crate) fn set_global_shape_rotation(&mut self, global_rotation: Quaternion) {
327        self.global_shape_rotation = global_rotation;
328    }
329
330    /// Sets the global direction of the ray.
331    pub(crate) fn set_global_direction(&mut self, global_direction: Dir) {
332        self.global_direction = global_direction;
333    }
334
335    pub(crate) fn cast(
336        &self,
337        caster_entity: Entity,
338        hits: &mut ShapeHits,
339        query_pipeline: &SpatialQueryPipeline,
340    ) {
341        // TODO: This clone is here so that the excluded entities in the original `query_filter` aren't modified.
342        //       We could remove this if shapecasting could compute multiple hits without just doing casts in a loop.
343        //       See https://github.com/Jondolf/avian/issues/403.
344        let mut query_filter = self.query_filter.clone();
345
346        if self.ignore_self {
347            query_filter.excluded_entities.insert(caster_entity);
348        }
349
350        hits.clear();
351
352        let shape_cast_options = ShapeCastOptions {
353            max_time_of_impact: self.max_distance,
354            target_distance: self.target_distance,
355            stop_at_penetration: !self.ignore_origin_penetration,
356            compute_impact_geometry_on_penetration: self.compute_contact_on_penetration,
357        };
358
359        let shape_rotation: Rotation;
360        #[cfg(feature = "2d")]
361        {
362            shape_rotation = Rotation::radians(self.global_shape_rotation());
363        }
364        #[cfg(feature = "3d")]
365        {
366            shape_rotation = Rotation::from(self.global_shape_rotation());
367        }
368
369        let shape_isometry = make_isometry(self.global_origin(), shape_rotation);
370        let shape_direction = self.global_direction().adjust_precision().into();
371
372        while hits.len() < self.max_hits as usize {
373            let composite = query_pipeline.as_composite_shape_internal(&query_filter);
374            let pipeline_shape = CompositeShapeRef(&composite);
375
376            let hit = pipeline_shape
377                .cast_shape(
378                    query_pipeline.dispatcher.as_ref(),
379                    &shape_isometry,
380                    &shape_direction,
381                    self.shape.shape_scaled().as_ref(),
382                    shape_cast_options,
383                )
384                .map(|(index, hit)| ShapeHitData {
385                    entity: query_pipeline.proxies[index as usize].entity,
386                    distance: hit.time_of_impact,
387                    point1: hit.witness1.into(),
388                    point2: hit.witness2.into(),
389                    normal1: hit.normal1.into(),
390                    normal2: hit.normal2.into(),
391                });
392
393            if let Some(hit) = hit {
394                query_filter.excluded_entities.insert(hit.entity);
395                hits.push(hit);
396            } else {
397                return;
398            }
399        }
400    }
401}
402
403fn on_add_shape_caster(mut world: DeferredWorld, ctx: HookContext) {
404    let shape_caster = world.get::<ShapeCaster>(ctx.entity).unwrap();
405    let max_hits = if shape_caster.max_hits == u32::MAX {
406        10
407    } else {
408        shape_caster.max_hits as usize
409    };
410
411    // Initialize capacity for hits
412    world.get_mut::<ShapeHits>(ctx.entity).unwrap().0 = Vec::with_capacity(max_hits);
413}
414
415/// Configuration for a shape cast.
416#[derive(Clone, Debug, PartialEq, Reflect)]
417#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
418#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
419#[reflect(Debug, PartialEq)]
420pub struct ShapeCastConfig {
421    /// The maximum distance the shape can travel.
422    ///
423    /// By default, this is infinite.
424    #[doc(alias = "max_time_of_impact")]
425    pub max_distance: Scalar,
426
427    /// The separation distance at which the shapes will be considered as impacting.
428    ///
429    /// If the shapes are separated by a distance smaller than `target_distance` at the origin of the cast,
430    /// the computed contact points and normals are only reliable if [`ShapeCastConfig::compute_contact_on_penetration`]
431    /// is set to `true`.
432    ///
433    /// By default, this is `0.0`, so the shapes will only be considered as impacting when they first touch.
434    pub target_distance: Scalar,
435
436    /// If `true`, contact points and normals will be calculated even when the cast distance is `0.0`.
437    ///
438    /// The default is `true`.
439    pub compute_contact_on_penetration: bool,
440
441    /// If `true` *and* the shape is travelling away from the object that was hit,
442    /// the cast will ignore any impact that happens at the cast origin.
443    ///
444    /// The default is `false`.
445    pub ignore_origin_penetration: bool,
446}
447
448impl Default for ShapeCastConfig {
449    fn default() -> Self {
450        Self::DEFAULT
451    }
452}
453
454impl ShapeCastConfig {
455    /// The default [`ShapeCastConfig`] configuration.
456    pub const DEFAULT: Self = Self {
457        max_distance: Scalar::MAX,
458        target_distance: 0.0,
459        compute_contact_on_penetration: true,
460        ignore_origin_penetration: false,
461    };
462
463    /// Creates a new [`ShapeCastConfig`] with a given maximum distance the shape can travel.
464    #[inline]
465    pub const fn from_max_distance(max_distance: Scalar) -> Self {
466        Self {
467            max_distance,
468            target_distance: 0.0,
469            compute_contact_on_penetration: true,
470            ignore_origin_penetration: false,
471        }
472    }
473
474    /// Creates a new [`ShapeCastConfig`] with a given separation distance at which
475    /// the shapes will be considered as impacting.
476    #[inline]
477    pub const fn from_target_distance(target_distance: Scalar) -> Self {
478        Self {
479            max_distance: Scalar::MAX,
480            target_distance,
481            compute_contact_on_penetration: true,
482            ignore_origin_penetration: false,
483        }
484    }
485
486    /// Sets the maximum distance the shape can travel.
487    #[inline]
488    pub const fn with_max_distance(mut self, max_distance: Scalar) -> Self {
489        self.max_distance = max_distance;
490        self
491    }
492
493    /// Sets the separation distance at which the shapes will be considered as impacting.
494    #[inline]
495    pub const fn with_target_distance(mut self, target_distance: Scalar) -> Self {
496        self.target_distance = target_distance;
497        self
498    }
499}
500
501/// Contains the hits of a shape cast by a [`ShapeCaster`]. The hits are in the order of distance.
502///
503/// The maximum number of hits depends on the value of `max_hits` in [`ShapeCaster`]. By default only
504/// one hit is computed, as shapecasting for many results can be expensive.
505///
506/// # Example
507///
508/// ```
509#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
510#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
511/// use bevy::prelude::*;
512///
513/// fn print_hits(query: Query<&ShapeHits, With<ShapeCaster>>) {
514///     for hits in &query {
515///         for hit in hits {
516///             println!("Hit entity {} with distance {}", hit.entity, hit.distance);
517///         }
518///     }
519/// }
520/// ```
521#[derive(Component, Clone, Debug, Default, Deref, DerefMut, PartialEq, Reflect)]
522#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
523#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
524#[reflect(Component, Debug, Default, PartialEq)]
525pub struct ShapeHits(pub Vec<ShapeHitData>);
526
527impl IntoIterator for ShapeHits {
528    type Item = ShapeHitData;
529    type IntoIter = alloc::vec::IntoIter<ShapeHitData>;
530
531    fn into_iter(self) -> Self::IntoIter {
532        self.0.into_iter()
533    }
534}
535
536impl<'a> IntoIterator for &'a ShapeHits {
537    type Item = &'a ShapeHitData;
538    type IntoIter = core::slice::Iter<'a, ShapeHitData>;
539
540    fn into_iter(self) -> Self::IntoIter {
541        self.0.iter()
542    }
543}
544
545impl<'a> IntoIterator for &'a mut ShapeHits {
546    type Item = &'a mut ShapeHitData;
547    type IntoIter = core::slice::IterMut<'a, ShapeHitData>;
548
549    fn into_iter(self) -> Self::IntoIter {
550        self.0.iter_mut()
551    }
552}
553
554impl MapEntities for ShapeHits {
555    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
556        for hit in self {
557            hit.map_entities(entity_mapper);
558        }
559    }
560}
561
562/// Data related to a hit during a [shapecast](spatial_query#shapecasting).
563#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
564#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
565#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
566#[reflect(Debug, PartialEq)]
567pub struct ShapeHitData {
568    /// The entity of the collider that was hit by the shape.
569    pub entity: Entity,
570
571    /// How far the shape travelled before the initial hit.
572    #[doc(alias = "time_of_impact")]
573    pub distance: Scalar,
574
575    /// The closest point on the shape that was hit, expressed in world space.
576    ///
577    /// If the shapes are penetrating or the target distance is greater than zero,
578    /// this will be different from `point2`.
579    pub point1: Vector,
580
581    /// The closest point on the shape that was cast, expressed in world space.
582    ///
583    /// If the shapes are penetrating or the target distance is greater than zero,
584    /// this will be different from `point1`.
585    pub point2: Vector,
586
587    /// The outward surface normal on the hit shape at `point1`, expressed in world space.
588    pub normal1: Vector,
589
590    /// The outward surface normal on the cast shape at `point2`, expressed in world space.
591    pub normal2: Vector,
592}
593
594impl MapEntities for ShapeHitData {
595    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
596        self.entity = entity_mapper.get_mapped(self.entity);
597    }
598}