avian3d/character_controller/
move_and_slide.rs

1//! Contains the *move and slide* algorithm and utilities for kinematic character controllers.
2//!
3//! See the documentation of [`MoveAndSlide`] for more information.
4
5pub use super::velocity_project::*;
6
7use crate::{collision::collider::contact_query::contact_manifolds, prelude::*};
8use bevy::{ecs::system::SystemParam, prelude::*};
9use core::time::Duration;
10
11/// Needed to improve stability when `n.dot(dir)` happens to be very close to zero.
12const DOT_EPSILON: Scalar = 0.005;
13
14/// Cosine of 5 degrees.
15#[allow(clippy::excessive_precision)]
16pub const COS_5_DEGREES: Scalar = 0.99619469809;
17
18/// A [`SystemParam`] for the *move and slide* algorithm, also known as *collide and slide* or *step slide*.
19///
20/// Move and slide is the core movement and collision algorithm used by most kinematic character controllers.
21/// It attempts to move a shape along a desired velocity vector, sliding along any colliders that are hit on the way.
22///
23/// # Algorithm
24///
25/// At a high level, the algorithm works as follows:
26///
27/// 1. Sweep the shape along the desired velocity vector.
28/// 2. If no collision is detected, move the full distance.
29/// 3. If a collision is detected:
30///    - Move up to the point of collision.
31///    - Project the remaining velocity onto the contact surfaces to obtain a new sliding velocity.
32/// 4. Repeat with the new sliding velocity until movement is complete.
33///
34/// The algorithm also includes depenetration passes before and after movement to improve stability
35/// and ensure that the shape does not intersect any colliders.
36///
37/// # Configuration
38///
39/// [`MoveAndSlideConfig`] allows configuring various aspects of the algorithm.
40/// See its documentation for more information.
41///
42/// Additionally, [`move_and_slide`](MoveAndSlide::move_and_slide) can be given a callback that is called
43/// for each contact surface that is detected during movement. This allows for custom handling of collisions,
44/// such as triggering events, or modifying movement based on specific colliders.
45///
46/// # Other Utilities
47///
48/// In addition to the main `move_and_slide` method, this system parameter also provides utilities for:
49///
50/// - Performing shape casts optimized for movement via [`cast_move`](MoveAndSlide::cast_move).
51/// - Depenetrating shapes that are intersecting colliders via [`depenetrate`](MoveAndSlide::depenetrate).
52/// - Performing intersection tests via [`intersections`](MoveAndSlide::intersections).
53/// - Projecting velocities to slide along contact planes via [`project_velocity`](MoveAndSlide::project_velocity).
54///
55/// These methods are used internally by the move and slide algorithm, but can also be used independently
56/// for custom movement and collision handling.
57///
58/// # Resources
59///
60/// Some useful resources for learning more about the move and slide algorithm include:
61///
62/// - [*Collide And Slide - \*Actually Decent\* Character Collision From Scratch*](https://youtu.be/YR6Q7dUz2uk) by [Poke Dev](https://www.youtube.com/@poke_gamedev) (video)
63/// - [`PM_SlideMove`](https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/game/bg_slidemove.c#L45) in Quake III Arena (source code)
64///
65/// Note that while the high-level concepts are similar across different implementations, details may vary.
66#[derive(SystemParam)]
67#[doc(alias = "CollideAndSlide")]
68#[doc(alias = "StepSlide")]
69pub struct MoveAndSlide<'w, 's> {
70    /// The [`SpatialQuery`] system parameter used to perform shape casts and other geometric queries.
71    pub spatial_query: SpatialQuery<'w, 's>,
72    /// The [`Query`] used to query for colliders.
73    pub colliders: Query<
74        'w,
75        's,
76        (
77            &'static Collider,
78            &'static Position,
79            &'static Rotation,
80            Option<&'static CollisionLayers>,
81        ),
82        (With<ColliderOf>, Without<Sensor>),
83    >,
84    /// A units-per-meter scaling factor that adjusts some thresholds and tolerances
85    /// to the scale of the world for better behavior.
86    pub length_unit: Res<'w, PhysicsLengthUnit>,
87}
88
89/// Configuration for [`MoveAndSlide::move_and_slide`].
90#[derive(Clone, Debug, PartialEq, Reflect)]
91#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
92#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
93#[reflect(Debug, PartialEq)]
94pub struct MoveAndSlideConfig {
95    /// How many iterations to use when moving the character.
96    ///
97    /// A single iteration consists of:
98    ///
99    /// - Moving the character as far as possible in the desired velocity direction.
100    /// - Modifying the velocity to slide along any contact surfaces.
101    ///
102    /// Increasing this allows the character to slide along more surfaces in a single frame,
103    /// which can help with complex geometry and high speeds, but increases computation time.
104    pub move_and_slide_iterations: usize,
105
106    /// How many iterations to use when performing depenetration.
107    ///
108    /// Depenetration is an iterative process that solves penetrations for all planes,
109    /// until we either reached [`MoveAndSlideConfig::move_and_slide_iterations`]
110    /// or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
111    ///
112    /// To disable depenetration, set this to `0`.
113    pub depenetration_iterations: usize,
114
115    /// The target error to achieve when performing depenetration.
116    ///
117    /// Depenetration is an iterative process that solves penetrations for all planes,
118    /// until we either reached [`MoveAndSlideConfig::move_and_slide_iterations`]
119    /// or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
120    ///
121    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
122    pub max_depenetration_error: Scalar,
123
124    /// The maximum penetration depth that is allowed for a contact to be resolved during depenetration.
125    ///
126    /// This is used to reject invalid contacts that have an excessively high penetration depth,
127    /// which can lead to clipping through geometry. This may be removed in the future once the
128    /// collision errors in the underlying collision detection system are fixed.
129    ///
130    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
131    pub penetration_rejection_threshold: Scalar,
132
133    /// A minimal distance to always keep between the collider and any other colliders.
134    ///
135    /// This exists to improve numerical stability and ensure that the collider never intersects anything.
136    /// Set this to a small enough value that you don't see visual artifacts but have good stability.
137    ///
138    /// Increase the value if you notice your character getting stuck in geometry.
139    /// Decrease it when you notice jittering, especially around V-shaped walls.
140    ///
141    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
142    pub skin_width: Scalar,
143
144    /// The initial planes to consider for the move and slide algorithm.
145    ///
146    /// This will be expanded during the algorithm with contact planes, but you can also initialize it
147    /// with some predefined planes that the algorithm should never move against.
148    ///
149    /// A common use case is adding the ground plane when a character controller is standing or walking on the ground.
150    pub planes: Vec<Dir>,
151
152    /// The dot product threshold to consider two planes as similar when pruning nearly parallel planes.
153    /// The comparison used is `n1.dot(n2) >= plane_similarity_dot_threshold`.
154    ///
155    /// This is used to reduce the number of planes considered during move and slide,
156    /// which can improve performance for dense geomtry. However, setting this value too high
157    /// can lead to unwanted behavior, as it may discard important planes.
158    ///
159    /// The default value of [`COS_5_DEGREES`] (≈0.996) corresponds to a 5 degree angle between the planes.
160    pub plane_similarity_dot_threshold: Scalar,
161
162    /// The maximum number of planes to solve while performing move and slide.
163    ///
164    /// If the number of planes exceeds this value, the algorithm will stop collecting new planes.
165    /// This is a safety measure to prevent excessive computation time for dense geometry.
166    pub max_planes: usize,
167}
168
169impl Default for MoveAndSlideConfig {
170    fn default() -> Self {
171        let default_depen_cfg = DepenetrationConfig::default();
172        Self {
173            move_and_slide_iterations: 4,
174            depenetration_iterations: default_depen_cfg.depenetration_iterations,
175            max_depenetration_error: default_depen_cfg.max_depenetration_error,
176            penetration_rejection_threshold: default_depen_cfg.penetration_rejection_threshold,
177            skin_width: default_depen_cfg.skin_width,
178            planes: Vec::new(),
179            plane_similarity_dot_threshold: COS_5_DEGREES,
180            max_planes: 20,
181        }
182    }
183}
184
185/// Configuration for [`MoveAndSlide::depenetrate`].
186#[derive(Clone, Debug, PartialEq, Reflect)]
187#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
188#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
189#[reflect(Debug, PartialEq)]
190pub struct DepenetrationConfig {
191    /// How many iterations to use when performing depenetration.
192    ///
193    /// Depenetration is an iterative process that solves penetrations for all planes,
194    /// until we either reached [`MoveAndSlideConfig::move_and_slide_iterations`]
195    /// or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
196    ///
197    /// To disable depenetration, set this to `0`.
198    pub depenetration_iterations: usize,
199
200    /// The target error to achieve when performing depenetration.
201    ///
202    /// Depenetration is an iterative process that solves penetrations for all planes,
203    /// until we either reached [`MoveAndSlideConfig::move_and_slide_iterations`]
204    /// or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
205    ///
206    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
207    pub max_depenetration_error: Scalar,
208
209    /// The maximum penetration depth that is allowed for a contact to be resolved during depenetration.
210    ///
211    /// This is used to reject invalid contacts that have an excessively high penetration depth,
212    /// which can lead to clipping through geometry. This may be removed in the future once the
213    /// collision errors in the underlying collision detection system are fixed.
214    ///
215    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
216    pub penetration_rejection_threshold: Scalar,
217
218    /// A minimal distance to always keep between the collider and any other colliders.
219    ///
220    /// This exists to improve numerical stability and ensure that the collider never intersects anything.
221    /// Set this to a small enough value that you don't see visual artifacts but have good stability.
222    ///
223    /// Increase the value if you notice your character getting stuck in geometry.
224    /// Decrease it when you notice jittering, especially around V-shaped walls.
225    ///
226    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
227    pub skin_width: Scalar,
228}
229
230impl Default for DepenetrationConfig {
231    fn default() -> Self {
232        Self {
233            depenetration_iterations: 16,
234            max_depenetration_error: 0.0001,
235            penetration_rejection_threshold: 0.5,
236            skin_width: 0.01,
237        }
238    }
239}
240
241impl From<&MoveAndSlideConfig> for DepenetrationConfig {
242    fn from(config: &MoveAndSlideConfig) -> Self {
243        Self {
244            depenetration_iterations: config.depenetration_iterations,
245            max_depenetration_error: config.max_depenetration_error,
246            penetration_rejection_threshold: config.penetration_rejection_threshold,
247            skin_width: config.skin_width,
248        }
249    }
250}
251
252/// Output from [`MoveAndSlide::move_and_slide`].
253#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
254#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
255#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
256#[reflect(Debug, PartialEq)]
257pub struct MoveAndSlideOutput {
258    /// The final position of the character after move and slide.
259    ///
260    /// Set your [`Transform::translation`] to this value.
261    pub position: Vector,
262
263    /// The final velocity of the character after move and slide.
264    ///
265    /// This corresponds to the remaining velocity after the algorithm has slid along all contact surfaces.
266    /// For example, if the character is trying to move to the right, but there is a ramp in its path,
267    /// the projected velocity will point up the ramp, with reduced magnitude.
268    ///
269    /// It is useful to store this value or apply it to [`LinearVelocity`] and use it as the input velocity
270    /// for the next frame's call to [`MoveAndSlide::move_and_slide`].
271    ///
272    /// Note that if you apply this to [`LinearVelocity`], it is recommended to use [`CustomPositionIntegration`].
273    /// This ways, the character's position is only updated via the move and slide algorithm,
274    /// and not also by the physics integrator.
275    pub projected_velocity: Vector,
276}
277
278/// Data related to a hit during [`MoveAndSlide::move_and_slide`].
279#[derive(Debug, PartialEq)]
280pub struct MoveAndSlideHitData<'a> {
281    /// The entity of the collider that was hit by the shape.
282    pub entity: Entity,
283
284    /// The maximum distance that is safe to move in the given direction so that the collider
285    /// still keeps a distance of `skin_width` to the other colliders.
286    ///
287    /// This is `0.0` when any of the following is true:
288    ///
289    /// - The collider started off intersecting another collider.
290    /// - The collider is moving toward another collider that is already closer than `skin_width`.
291    ///
292    /// If you want to know the real distance to the next collision, use [`Self::collision_distance`].
293    pub distance: Scalar,
294
295    /// The hit point on the shape that was hit, expressed in world space.
296    pub point: Vector,
297
298    /// The outward surface normal on the hit shape at `point`, expressed in world space.
299    pub normal: &'a mut Dir,
300
301    /// The position of the collider at the time of the move and slide iteration.
302    pub position: &'a mut Vector,
303
304    /// The velocity of the collider at the time of the move and slide iteration.
305    pub velocity: &'a mut Vector,
306
307    /// The raw distance to the next collision, not respecting skin width.
308    /// To move the shape, use [`Self::distance`] instead.
309    #[doc(alias = "time_of_impact")]
310    pub collision_distance: Scalar,
311}
312
313impl<'a> MoveAndSlideHitData<'a> {
314    /// Whether the collider started off already intersecting another collider when it was cast.
315    ///
316    /// Note that this will be `false` if the collider was closer than `skin_width`, but not physically intersecting.
317    pub fn intersects(&self) -> bool {
318        self.collision_distance == 0.0
319    }
320}
321
322/// Indicates how to handle a hit detected during [`MoveAndSlide::move_and_slide`].
323///
324/// This is returned by the `on_hit` callback provided to [`MoveAndSlide::move_and_slide`].
325#[derive(Debug, PartialEq)]
326pub enum MoveAndSlideHitResponse {
327    /// Accept the hit and continue the move and slide algorithm.
328    Accept,
329
330    /// Ignore the hit and continue the move and slide algorithm.
331    ///
332    /// Note that the shape will still be moved up to the point of collision,
333    /// but the velocity will not be modified to slide along the surface.
334    Ignore,
335
336    /// Ignore the hit and abort the move and slide algorithm.
337    ///
338    /// Note that the shape will still be moved up to the point of collision,
339    /// but no further movement or velocity modification will be performed.
340    Abort,
341}
342
343/// Data related to a hit during [`MoveAndSlide::cast_move`].
344#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
345#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
346#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
347#[reflect(Debug, PartialEq)]
348pub struct MoveHitData {
349    /// The entity of the collider that was hit by the shape.
350    pub entity: Entity,
351
352    /// The maximum distance that is safe to move in the given direction so that the collider
353    /// still keeps a distance of `skin_width` to the other colliders.
354    ///
355    /// This is `0.0` when any of the following is true:
356    ///
357    /// - The collider started off intersecting another collider.
358    /// - The collider is moving toward another collider that is already closer than `skin_width`.
359    ///
360    /// If you want to know the real distance to the next collision, use [`Self::collision_distance`].
361    #[doc(alias = "time_of_impact")]
362    pub distance: Scalar,
363
364    /// The closest point on the shape that was hit, expressed in world space.
365    ///
366    /// If the shapes are penetrating or the target distance is greater than zero,
367    /// this will be different from `point2`.
368    pub point1: Vector,
369
370    /// The closest point on the shape that was cast, expressed in world space.
371    ///
372    /// If the shapes are penetrating or the target distance is greater than zero,
373    /// this will be different from `point1`.
374    pub point2: Vector,
375
376    /// The outward surface normal on the hit shape at `point1`, expressed in world space.
377    pub normal1: Vector,
378
379    /// The outward surface normal on the cast shape at `point2`, expressed in world space.
380    pub normal2: Vector,
381
382    /// The raw distance to the next collision, not respecting skin width.
383    /// To move the shape, use [`Self::distance`] instead.
384    #[doc(alias = "time_of_impact")]
385    pub collision_distance: Scalar,
386}
387
388impl MoveHitData {
389    /// Whether the collider started off already intersecting another collider when it was cast.
390    ///
391    /// Note that this will be `false` if the collider was closer than `skin_width`, but not physically intersecting.
392    pub fn intersects(self) -> bool {
393        self.collision_distance == 0.0
394    }
395}
396
397impl<'w, 's> MoveAndSlide<'w, 's> {
398    /// Moves a shape along a given velocity vector, sliding along any colliders that are hit on the way.
399    ///
400    /// See [`MoveAndSlide`] for an overview of the algorithm.
401    ///
402    /// # Arguments
403    ///
404    /// - `shape`: The shape being cast represented as a [`Collider`].
405    /// - `shape_position`: Where the shape is cast from.
406    /// - `shape_rotation`: The rotation of the shape being cast.
407    /// - `velocity`: The initial velocity vector along which to move the shape. This will be modified to reflect sliding along surfaces.
408    /// - `delta_time`: The duration over which to move the shape. `velocity * delta_time` gives the total desired movement vector.
409    /// - `config`: A [`MoveAndSlideConfig`] that determines the behavior of the move and slide. [`MoveAndSlideConfig::default()`] should be a good start for most cases.
410    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query. It is highly recommended to exclude the entity holding the collider itself,
411    ///   otherwise the character will collide with itself.
412    /// - `on_hit`: A callback that is called when a collider is hit as part of the move and slide iterations. The returned [`MoveAndSlideHitResponse`] determines how to handle the hit.
413    ///   If you don't have any special handling per collision, you can pass `|_| MoveAndSlideHitResponse::Accept`.
414    ///
415    /// # Example
416    ///
417    /// ```
418    /// use bevy::prelude::*;
419    /// use std::collections::HashSet;
420    #[cfg_attr(
421        feature = "2d",
422        doc = "use avian2d::{prelude::*, math::{Vector, AdjustPrecision as _, AsF32 as _}};"
423    )]
424    #[cfg_attr(
425        feature = "3d",
426        doc = "use avian3d::{prelude::*, math::{Vector, AdjustPrecision as _, AsF32 as _}};"
427    )]
428    ///
429    /// #[derive(Component)]
430    /// struct CharacterController {
431    ///     velocity: Vector,
432    /// }
433    ///
434    /// fn perform_move_and_slide(
435    ///     player: Single<(Entity, &Collider, &mut CharacterController, &mut Transform)>,
436    ///     move_and_slide: MoveAndSlide,
437    ///     time: Res<Time>
438    /// ) {
439    ///     let (entity, collider, mut controller, mut transform) = player.into_inner();
440    ///     let velocity = controller.velocity + Vector::X * 10.0;
441    ///     let filter = SpatialQueryFilter::from_excluded_entities([entity]);
442    ///     let mut collisions = HashSet::new();
443    ///     let out = move_and_slide.move_and_slide(
444    ///         collider,
445    #[cfg_attr(
446        feature = "2d",
447        doc = "         transform.translation.xy().adjust_precision(),"
448    )]
449    #[cfg_attr(
450        feature = "3d",
451        doc = "         transform.translation.adjust_precision(),"
452    )]
453    #[cfg_attr(
454        feature = "2d",
455        doc = "         transform.rotation.to_euler(EulerRot::XYZ).2.adjust_precision(),"
456    )]
457    #[cfg_attr(
458        feature = "3d",
459        doc = "         transform.rotation.adjust_precision(),"
460    )]
461    ///         velocity,
462    ///         time.delta(),
463    ///         &MoveAndSlideConfig::default(),
464    ///         &filter,
465    ///         |hit| {
466    ///             collisions.insert(hit.entity);
467    ///             MoveAndSlideHitResponse::Accept
468    ///         },
469    ///     );
470    #[cfg_attr(
471        feature = "2d",
472        doc = "     transform.translation = out.position.f32().extend(0.0);"
473    )]
474    #[cfg_attr(
475        feature = "3d",
476        doc = "     transform.translation = out.position.f32();"
477    )]
478    ///     controller.velocity = out.projected_velocity;
479    ///     info!("Colliding with entities: {:?}", collisions);
480    /// }
481    /// ```
482    #[must_use]
483    #[doc(alias = "collide_and_slide")]
484    #[doc(alias = "step_slide")]
485    pub fn move_and_slide(
486        &self,
487        shape: &Collider,
488        shape_position: Vector,
489        shape_rotation: RotationValue,
490        mut velocity: Vector,
491        delta_time: Duration,
492        config: &MoveAndSlideConfig,
493        filter: &SpatialQueryFilter,
494        mut on_hit: impl FnMut(MoveAndSlideHitData) -> MoveAndSlideHitResponse,
495    ) -> MoveAndSlideOutput {
496        let mut position = shape_position;
497        let mut time_left = {
498            #[cfg(feature = "f32")]
499            {
500                delta_time.as_secs_f32()
501            }
502            #[cfg(feature = "f64")]
503            {
504                delta_time.as_secs_f64()
505            }
506        };
507        let skin_width = self.length_unit.0 * config.skin_width;
508
509        // Initial depenetration pass
510        let depenetration_offset =
511            self.depenetrate(shape, position, shape_rotation, &config.into(), filter);
512        position += depenetration_offset;
513
514        // Main move and slide loop:
515        // 1. Sweep the shape along the velocity vector
516        // 2. If we hit something, move up to the hit point
517        // 3. Collect contact planes
518        // 4. Project velocity to slide along contact planes
519        // 5. Repeat until we run out of iterations or time
520        for _ in 0..config.move_and_slide_iterations {
521            let sweep = time_left * velocity;
522            let Some((vel_dir, distance)) = Dir::new_and_length(sweep.f32()).ok() else {
523                // No movement left
524                break;
525            };
526            let distance = distance.adjust_precision();
527
528            const MIN_DISTANCE: Scalar = 1e-4;
529            if distance < MIN_DISTANCE {
530                break;
531            }
532
533            // Sweep the shape along the velocity vector.
534            let Some(sweep_hit) =
535                self.cast_move(shape, position, shape_rotation, sweep, skin_width, filter)
536            else {
537                // No collision, move the full distance.
538                position += sweep;
539                break;
540            };
541            let point = sweep_hit.point2;
542
543            // Move up to the hit point.
544            time_left -= time_left * (sweep_hit.distance / distance);
545            position += vel_dir.adjust_precision() * sweep_hit.distance;
546
547            // Initialize velocity clipping planes with the user-defined planes.
548            // This often includes a ground plane.
549            let mut planes: Vec<Dir> = config.planes.clone();
550
551            // We need to add the sweep hit's plane explicitly, as `contact_manifolds` sometimes returns nothing
552            // due to a Parry bug. Otherwise, `contact_manifolds` would pick up this normal anyways.
553            // TODO: Remove this once the collision bug is fixed.
554            let mut first_normal = Dir::new_unchecked(sweep_hit.normal1.f32());
555            let hit_response = on_hit(MoveAndSlideHitData {
556                entity: sweep_hit.entity,
557                point,
558                normal: &mut first_normal,
559                collision_distance: sweep_hit.collision_distance,
560                distance: sweep_hit.distance,
561                position: &mut position,
562                velocity: &mut velocity,
563            });
564
565            if hit_response == MoveAndSlideHitResponse::Accept {
566                planes.push(first_normal);
567            } else if hit_response == MoveAndSlideHitResponse::Abort {
568                break;
569            }
570
571            // Collect contact planes.
572            let mut aborted = false;
573            self.intersections(
574                shape,
575                position,
576                shape_rotation,
577                // Use a slightly larger skin width to ensure we catch all contacts for velocity clipping.
578                // Depenetration still uses just the normal skin width.
579                skin_width * 2.0,
580                filter,
581                |contact_point, mut normal| {
582                    // Check if this plane is nearly parallel to an existing one.
583                    // This can help prune redundant planes for velocity clipping.
584                    for existing_normal in planes.iter_mut() {
585                        if normal.dot(**existing_normal) as Scalar
586                            >= config.plane_similarity_dot_threshold
587                        {
588                            // Keep the most blocking version of the plane.
589                            let n_dot_v = normal.adjust_precision().dot(velocity);
590                            let existing_n_dot_v = existing_normal.adjust_precision().dot(velocity);
591                            if n_dot_v < existing_n_dot_v {
592                                *existing_normal = normal;
593                            }
594                            return true;
595                        }
596                    }
597
598                    if planes.len() >= config.max_planes {
599                        return false;
600                    }
601
602                    // Call the user-defined hit callback.
603                    let hit_response = on_hit(MoveAndSlideHitData {
604                        entity: sweep_hit.entity,
605                        point: contact_point.point,
606                        normal: &mut normal,
607                        collision_distance: sweep_hit.collision_distance,
608                        distance: sweep_hit.distance,
609                        position: &mut position,
610                        velocity: &mut velocity,
611                    });
612
613                    match hit_response {
614                        MoveAndSlideHitResponse::Accept => {
615                            // Add the contact plane for velocity clipping.
616                            planes.push(normal);
617                            true
618                        }
619                        MoveAndSlideHitResponse::Ignore => true,
620                        MoveAndSlideHitResponse::Abort => {
621                            aborted = true;
622                            false
623                        }
624                    }
625                },
626            );
627
628            // Project velocity to slide along contact planes.
629            velocity = Self::project_velocity(velocity, &planes);
630
631            if aborted {
632                break;
633            }
634        }
635
636        // Final depenetration pass
637        // TODO: We could get the intersections from the last iteration and avoid re-querying them here.
638        let depenetration_offset =
639            self.depenetrate(shape, position, shape_rotation, &config.into(), filter);
640        position += depenetration_offset;
641
642        MoveAndSlideOutput {
643            position,
644            projected_velocity: velocity,
645        }
646    }
647
648    /// A [shape cast](spatial_query#shapecasting) optimized for movement. Use this if you want to move a collider
649    /// with a given velocity and stop so that it keeps a distance of `skin_width` from the first collider on its path.
650    ///
651    /// This operation is most useful when you ensure that the character is not intersecting any colliders before moving.
652    /// To do this, call [`MoveAndSlide::depenetrate`] and add the resulting offset vector to the character's position
653    /// before calling this method. See the example below.
654    ///
655    /// It is often useful to clip the velocity afterwards so that it no longer points into the contact plane using [`Self::project_velocity`].
656    ///
657    /// # Arguments
658    ///
659    /// - `shape`: The shape being cast represented as a [`Collider`].
660    /// - `shape_position`: Where the shape is cast from.
661    /// - `shape_rotation`: The rotation of the shape being cast.
662    /// - `movement`: The direction and magnitude of the movement. If this is [`Vector::ZERO`], this method can still return `Some(MoveHitData)` if the shape started off intersecting a collider.
663    /// - `skin_width`: A [`ShapeCastConfig`] that determines the behavior of the cast.
664    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query. It is highly recommended to exclude the entity holding the collider itself,
665    ///   otherwise the character will collide with itself.
666    ///
667    /// # Returns
668    ///
669    /// - `Some(MoveHitData)` if the shape hit a collider on the way, or started off intersecting a collider.
670    /// - `None` if the shape is able to move the full distance without hitting a collider.
671    ///
672    /// # Example
673    ///
674    /// ```
675    /// use bevy::prelude::*;
676    #[cfg_attr(
677        feature = "2d",
678        doc = "use avian2d::{prelude::*, math::{Vector, Dir, AdjustPrecision as _, AsF32 as _}};"
679    )]
680    #[cfg_attr(
681        feature = "3d",
682        doc = "use avian3d::{prelude::*, math::{Vector, Dir, AdjustPrecision as _, AsF32 as _}};"
683    )]
684    ///
685    /// #[derive(Component)]
686    /// struct CharacterController {
687    ///     velocity: Vector,
688    /// }
689    ///
690    /// fn perform_cast_move(
691    ///     player: Single<(Entity, &Collider, &mut CharacterController, &mut Transform)>,
692    ///     move_and_slide: MoveAndSlide,
693    ///     time: Res<Time>
694    /// ) {
695    ///     let (entity, collider, mut controller, mut transform) = player.into_inner();
696    ///     let filter = SpatialQueryFilter::from_excluded_entities([entity]);
697    ///     let config = MoveAndSlideConfig::default();
698    ///
699    ///     // Ensure that the character is not intersecting with any colliders.
700    ///     let offset = move_and_slide.depenetrate(
701    ///         collider,
702    #[cfg_attr(
703        feature = "2d",
704        doc = "         transform.translation.xy().adjust_precision(),"
705    )]
706    #[cfg_attr(
707        feature = "3d",
708        doc = "         transform.translation.adjust_precision(),"
709    )]
710    #[cfg_attr(
711        feature = "2d",
712        doc = "         transform.rotation.to_euler(EulerRot::XYZ).2.adjust_precision(),"
713    )]
714    #[cfg_attr(
715        feature = "3d",
716        doc = "         transform.rotation.adjust_precision(),"
717    )]
718    ///         &((&config).into()),
719    ///         &filter,
720    ///     );
721    #[cfg_attr(
722        feature = "2d",
723        doc = "     transform.translation += offset.f32().extend(0.0);"
724    )]
725    #[cfg_attr(feature = "3d", doc = "     transform.translation += offset.f32();")]
726    ///     let velocity = controller.velocity;
727    ///
728    ///     let hit = move_and_slide.cast_move(
729    ///         collider,
730    #[cfg_attr(
731        feature = "2d",
732        doc = "         transform.translation.xy().adjust_precision(),"
733    )]
734    #[cfg_attr(
735        feature = "3d",
736        doc = "         transform.translation.adjust_precision(),"
737    )]
738    #[cfg_attr(
739        feature = "2d",
740        doc = "         transform.rotation.to_euler(EulerRot::XYZ).2.adjust_precision(),"
741    )]
742    #[cfg_attr(
743        feature = "3d",
744        doc = "         transform.rotation.adjust_precision(),"
745    )]
746    ///         velocity * time.delta_secs().adjust_precision(),
747    ///         config.skin_width,
748    ///         &filter,
749    ///     );
750    ///     if let Some(hit) = hit {
751    ///         // We collided with something on the way. Advance as much as possible.
752    #[cfg_attr(
753        feature = "2d",
754        doc = "         transform.translation += (velocity.normalize_or_zero() * hit.distance).extend(0.0).f32();"
755    )]
756    #[cfg_attr(
757        feature = "3d",
758        doc = "         transform.translation += (velocity.normalize_or_zero() * hit.distance).f32();"
759    )]
760    ///         // Then project the velocity to make sure it no longer points towards the contact plane.
761    ///         controller.velocity =
762    ///             MoveAndSlide::project_velocity(velocity, &[Dir::new_unchecked(hit.normal1.f32())])
763    ///     } else {
764    ///         // We traveled the full distance without colliding.
765    #[cfg_attr(
766        feature = "2d",
767        doc = "         transform.translation += velocity.extend(0.0).f32();"
768    )]
769    #[cfg_attr(
770        feature = "3d",
771        doc = "         transform.translation += velocity.f32();"
772    )]
773    ///     }
774    /// }
775    /// ```
776    ///
777    /// # Related methods
778    ///
779    /// - [`SpatialQuery::cast_shape`]
780    #[must_use]
781    #[doc(alias = "sweep")]
782    pub fn cast_move(
783        &self,
784        shape: &Collider,
785        shape_position: Vector,
786        shape_rotation: RotationValue,
787        movement: Vector,
788        skin_width: Scalar,
789        filter: &SpatialQueryFilter,
790    ) -> Option<MoveHitData> {
791        let (direction, distance) = Dir::new_and_length(movement.f32()).unwrap_or((Dir::X, 0.0));
792        let distance = distance.adjust_precision() + skin_width;
793        let shape_hit = self.spatial_query.cast_shape_predicate(
794            shape,
795            shape_position,
796            shape_rotation,
797            direction,
798            &ShapeCastConfig {
799                ignore_origin_penetration: true,
800                ..ShapeCastConfig::from_max_distance(distance)
801            },
802            filter,
803            // Make sure we don't hit sensors.
804            // TODO: Replace this when spatial queries support excluding sensors directly.
805            &|entity| self.colliders.contains(entity),
806        )?;
807        let safe_distance = if distance == 0.0 {
808            0.0
809        } else {
810            Self::pull_back(shape_hit, direction, skin_width)
811        };
812        Some(MoveHitData {
813            distance: safe_distance,
814            collision_distance: distance,
815            entity: shape_hit.entity,
816            point1: shape_hit.point1,
817            point2: shape_hit.point2,
818            normal1: shape_hit.normal1,
819            normal2: shape_hit.normal2,
820        })
821    }
822
823    /// Returns a [`ShapeHitData::distance`] that is reduced such that the hit distance is at least `skin_width`.
824    /// The result will never be negative, so if the hit is already closer than `skin_width`, the returned distance will be zero.
825    #[must_use]
826    fn pull_back(hit: ShapeHitData, dir: Dir, skin_width: Scalar) -> Scalar {
827        let dot = dir.adjust_precision().dot(-hit.normal1).max(DOT_EPSILON);
828        let skin_distance = skin_width / dot;
829        (hit.distance - skin_distance).max(0.0)
830    }
831
832    /// Moves a collider so that it no longer intersects any other collider and keeps a minimum distance
833    /// of [`DepenetrationConfig::skin_width`] scaled by the [`PhysicsLengthUnit`].
834    ///
835    /// Depenetration is an iterative process that solves penetrations for all planes, until we either reached
836    /// [`MoveAndSlideConfig::move_and_slide_iterations`] or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
837    /// If the maximum number of iterations was reached before the error is below the threshold, the current best attempt is returned,
838    /// in which case the collider may still be intersecting with other colliders.
839    ///
840    /// This method is equivalent to calling [`Self::depenetrate_intersections`] with the results of [`Self::intersections`].
841    ///
842    /// # Arguments
843    ///
844    /// - `shape`: The shape that intersections are tested against represented as a [`Collider`].
845    /// - `shape_position`: The position of the shape.
846    /// - `shape_rotation`: The rotation of the shape.
847    /// - `config`: A [`DepenetrationConfig`] that determines the behavior of the depenetration. [`DepenetrationConfig::default()`] should be a good start for most cases.
848    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
849    ///
850    /// # Example
851    ///
852    /// ```
853    /// use bevy::prelude::*;
854    #[cfg_attr(
855        feature = "2d",
856        doc = "use avian2d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};"
857    )]
858    #[cfg_attr(
859        feature = "3d",
860        doc = "use avian3d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};"
861    )]
862    /// fn depenetrate_player(
863    ///     player: Single<(Entity, &Collider, &mut Transform)>,
864    ///     move_and_slide: MoveAndSlide,
865    ///     time: Res<Time>
866    /// ) {
867    ///     let (entity, collider, mut transform) = player.into_inner();
868    ///     let filter = SpatialQueryFilter::from_excluded_entities([entity]);
869    ///
870    ///     let offset = move_and_slide.depenetrate(
871    ///         collider,
872    #[cfg_attr(
873        feature = "2d",
874        doc = "         transform.translation.xy().adjust_precision(),"
875    )]
876    #[cfg_attr(
877        feature = "3d",
878        doc = "         transform.translation.adjust_precision(),"
879    )]
880    #[cfg_attr(
881        feature = "2d",
882        doc = "         transform.rotation.to_euler(EulerRot::XYZ).2.adjust_precision(),"
883    )]
884    #[cfg_attr(
885        feature = "3d",
886        doc = "         transform.rotation.adjust_precision(),"
887    )]
888    ///         &DepenetrationConfig::default(),
889    ///         &filter,
890    ///     );
891    #[cfg_attr(
892        feature = "2d",
893        doc = "     transform.translation += offset.f32().extend(0.0);"
894    )]
895    #[cfg_attr(feature = "3d", doc = "     transform.translation += offset.f32();")]
896    /// }
897    /// ```
898    ///
899    /// See also [`MoveAndSlide::cast_move`] for a typical usage scenario.
900    ///
901    /// # Related methods
902    ///
903    /// - [`MoveAndSlide::intersections`]
904    /// - [`MoveAndSlide::depenetrate_intersections`]
905    pub fn depenetrate(
906        &self,
907        shape: &Collider,
908        shape_position: Vector,
909        shape_rotation: RotationValue,
910        config: &DepenetrationConfig,
911        filter: &SpatialQueryFilter,
912    ) -> Vector {
913        if config.depenetration_iterations == 0 {
914            // Depenetration disabled
915            return Vector::ZERO;
916        }
917
918        let mut intersections = Vec::new();
919        self.intersections(
920            shape,
921            shape_position,
922            shape_rotation,
923            self.length_unit.0 * config.skin_width,
924            filter,
925            |contact_point, normal| {
926                intersections.push((
927                    normal,
928                    contact_point.penetration + self.length_unit.0 * config.skin_width,
929                ));
930                true
931            },
932        );
933        self.depenetrate_intersections(config, &intersections)
934    }
935
936    /// Manual version of [`MoveAndSlide::depenetrate`].
937    ///
938    /// Moves a collider so that it no longer intersects any other collider and keeps a minimum distance
939    /// of [`DepenetrationConfig::skin_width`] scaled by the [`PhysicsLengthUnit`]. The intersections
940    /// should be provided as a list of contact plane normals and penetration distances, which can be obtained
941    /// via [`MoveAndSlide::intersections`].
942    ///
943    /// Depenetration is an iterative process that solves penetrations for all planes, until we either reached
944    /// [`MoveAndSlideConfig::move_and_slide_iterations`] or the accumulated error is less than [`MoveAndSlideConfig::max_depenetration_error`].
945    /// If the maximum number of iterations was reached before the error is below the threshold, the current best attempt is returned,
946    /// in which case the collider may still be intersecting with other colliders.
947    ///
948    /// # Arguments
949    ///
950    /// - `config`: A [`DepenetrationConfig`] that determines the behavior of the depenetration. [`DepenetrationConfig::default()`] should be a good start for most cases.
951    /// - `intersections`: A list of contact plane normals and penetration distances representing the intersections to resolve.
952    ///
953    /// # Returns
954    ///
955    /// A displacement vector that can be added to the `shape_position` to resolve the intersections,
956    /// or the best attempt if the max iterations were reached before a solution was found.
957    ///
958    /// # Example
959    ///
960    /// ```
961    /// use bevy::prelude::*;
962    #[cfg_attr(
963        feature = "2d",
964        doc = "use avian2d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};"
965    )]
966    #[cfg_attr(
967        feature = "3d",
968        doc = "use avian3d::{prelude::*, character_controller::move_and_slide::DepenetrationConfig, math::{AdjustPrecision as _, AsF32 as _}};"
969    )]
970    /// fn depenetrate_player_manually(
971    ///     player: Single<(Entity, &Collider, &mut Transform)>,
972    ///     move_and_slide: MoveAndSlide,
973    ///     time: Res<Time>
974    /// ) {
975    ///     let (entity, collider, mut transform) = player.into_inner();
976    ///     let filter = SpatialQueryFilter::from_excluded_entities([entity]);
977    ///     let config = DepenetrationConfig::default();
978    ///
979    ///     let mut intersections = Vec::new();
980    ///     move_and_slide.intersections(
981    ///         collider,
982    #[cfg_attr(
983        feature = "2d",
984        doc = "         transform.translation.xy().adjust_precision(),"
985    )]
986    #[cfg_attr(
987        feature = "3d",
988        doc = "         transform.translation.adjust_precision(),"
989    )]
990    #[cfg_attr(
991        feature = "2d",
992        doc = "         transform.rotation.to_euler(EulerRot::XYZ).2.adjust_precision(),"
993    )]
994    #[cfg_attr(
995        feature = "3d",
996        doc = "         transform.rotation.adjust_precision(),"
997    )]
998    ///         config.skin_width,
999    ///         &filter,
1000    ///         |contact_point, normal| {
1001    ///             intersections.push((normal, contact_point.penetration + config.skin_width));
1002    ///             true
1003    ///         },
1004    ///     );
1005    ///     let offset = move_and_slide.depenetrate_intersections(&config, &intersections);
1006    #[cfg_attr(
1007        feature = "2d",
1008        doc = "     transform.translation += offset.f32().extend(0.0);"
1009    )]
1010    #[cfg_attr(feature = "3d", doc = "     transform.translation += offset.f32();")]
1011    /// }
1012    /// ```
1013    ///
1014    /// # Related methods
1015    ///
1016    /// - [`MoveAndSlide::intersections`]
1017    /// - [`MoveAndSlide::depenetrate`]
1018    #[must_use]
1019    pub fn depenetrate_intersections(
1020        &self,
1021        config: &DepenetrationConfig,
1022        intersections: &[(Dir, Scalar)],
1023    ) -> Vector {
1024        let mut fixup = Vector::ZERO;
1025
1026        // Gauss-Seidel style iterative depenetration
1027        for _ in 0..config.depenetration_iterations {
1028            let mut total_error = 0.0;
1029
1030            for (normal, dist) in intersections {
1031                if *dist > self.length_unit.0 * config.penetration_rejection_threshold {
1032                    continue;
1033                }
1034                let normal = normal.adjust_precision();
1035                let error = (dist - fixup.dot(normal)).max(0.0);
1036                total_error += error;
1037                fixup += error * normal;
1038            }
1039
1040            if total_error < self.length_unit.0 * config.max_depenetration_error {
1041                break;
1042            }
1043        }
1044
1045        fixup
1046    }
1047
1048    /// An [intersection test](spatial_query#intersection-tests) that calls a callback for each [`Collider`] found
1049    /// that is closer to the given `shape` with a given position and rotation than `prediction_distance`.
1050    ///
1051    /// # Arguments
1052    ///
1053    /// - `shape`: The shape that intersections are tested against represented as a [`Collider`].
1054    /// - `shape_position`: The position of the shape.
1055    /// - `shape_rotation`: The rotation of the shape.
1056    /// - `filter`: A [`SpatialQueryFilter`] that determines which colliders are taken into account in the query.
1057    /// - `prediction_distance`: An extra margin applied to the [`Collider`].
1058    /// - `callback`: A callback that is called for each intersection found. The callback receives the deepest contact point and the contact normal.
1059    ///   Returning `false` will stop further processing of intersections.
1060    ///
1061    /// # Example
1062    ///
1063    /// See [`MoveAndSlide::depenetrate_intersections`] for a typical usage scenario.
1064    ///
1065    /// # Related methods
1066    ///
1067    /// - [`MoveAndSlide::depenetrate_intersections`]
1068    /// - [`MoveAndSlide::depenetrate`]
1069    pub fn intersections(
1070        &self,
1071        shape: &Collider,
1072        shape_position: Vector,
1073        shape_rotation: RotationValue,
1074        prediction_distance: Scalar,
1075        filter: &SpatialQueryFilter,
1076        mut callback: impl FnMut(&ContactPoint, Dir) -> bool,
1077    ) {
1078        let expanded_aabb = shape
1079            .aabb(shape_position, shape_rotation)
1080            .grow(Vector::splat(prediction_distance));
1081        let aabb_intersections = self
1082            .spatial_query
1083            .aabb_intersections_with_aabb(expanded_aabb);
1084
1085        'outer: for intersection_entity in aabb_intersections {
1086            let Ok((intersection_collider, intersection_pos, intersection_rot, layers)) =
1087                self.colliders.get(intersection_entity)
1088            else {
1089                continue;
1090            };
1091            let layers = layers.copied().unwrap_or_default();
1092            if !filter.test(intersection_entity, layers) {
1093                continue;
1094            }
1095            let mut manifolds = Vec::new();
1096            contact_manifolds(
1097                shape,
1098                shape_position,
1099                shape_rotation,
1100                intersection_collider,
1101                *intersection_pos,
1102                *intersection_rot,
1103                prediction_distance,
1104                &mut manifolds,
1105            );
1106            for manifold in manifolds {
1107                let Some(deepest) = manifold.find_deepest_contact() else {
1108                    continue;
1109                };
1110
1111                let normal = Dir::new_unchecked(-manifold.normal.f32());
1112
1113                if !callback(deepest, normal) {
1114                    // Abort further processing.
1115                    break 'outer;
1116                }
1117            }
1118        }
1119    }
1120
1121    /// Projects input velocity `v` onto the planes defined by the given `normals`.
1122    /// This ensures that `velocity` does not point into any of the planes, but along them.
1123    ///
1124    /// This is often used after [`MoveAndSlide::cast_move`] to ensure a character moved that way
1125    /// does not try to continue moving into colliding geometry.
1126    #[must_use]
1127    pub fn project_velocity(v: Vector, normals: &[Dir]) -> Vector {
1128        project_velocity(v, normals)
1129    }
1130}