Skip to main content

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