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}