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}