rapier3d/control/
character_controller.rs

1use crate::geometry::{ColliderHandle, ContactManifold, Shape, ShapeCastHit};
2use crate::math::{Isometry, Point, Real, UnitVector, Vector};
3use crate::pipeline::{QueryFilterFlags, QueryPipeline, QueryPipelineMut};
4use crate::utils;
5use na::{RealField, Vector2};
6use parry::bounding_volume::BoundingVolume;
7use parry::math::Translation;
8use parry::query::details::ShapeCastOptions;
9use parry::query::{DefaultQueryDispatcher, PersistentQueryDispatcher};
10
11#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
12#[derive(Copy, Clone, Debug, PartialEq)]
13/// A length measure used for various options of a character controller.
14pub enum CharacterLength {
15    /// The length is specified relative to some of the character shape’s size.
16    ///
17    /// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
18    /// for a shape with a height equal to 20.0 will result in a maximum step height
19    /// of `0.1 * 20.0 = 2.0`.
20    Relative(Real),
21    /// The length is specified as an absolute value, independent from the character shape’s size.
22    ///
23    /// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
24    /// for a shape with a height equal to 20.0 will result in a maximum step height
25    /// of `0.1` (the shape height is ignored in for this value).
26    Absolute(Real),
27}
28
29impl CharacterLength {
30    /// Returns `self` with its value changed by the closure `f` if `self` is the `Self::Absolute`
31    /// variant.
32    pub fn map_absolute(self, f: impl FnOnce(Real) -> Real) -> Self {
33        if let Self::Absolute(value) = self {
34            Self::Absolute(f(value))
35        } else {
36            self
37        }
38    }
39
40    /// Returns `self` with its value changed by the closure `f` if `self` is the `Self::Relative`
41    /// variant.
42    pub fn map_relative(self, f: impl FnOnce(Real) -> Real) -> Self {
43        if let Self::Relative(value) = self {
44            Self::Relative(f(value))
45        } else {
46            self
47        }
48    }
49
50    fn eval(self, value: Real) -> Real {
51        match self {
52            Self::Relative(x) => value * x,
53            Self::Absolute(x) => x,
54        }
55    }
56}
57
58#[derive(Debug)]
59struct HitInfo {
60    toi: ShapeCastHit,
61    is_wall: bool,
62    is_nonslip_slope: bool,
63}
64
65/// Configuration for the auto-stepping character controller feature.
66#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
67#[derive(Copy, Clone, Debug, PartialEq)]
68pub struct CharacterAutostep {
69    /// The maximum step height a character can automatically step over.
70    pub max_height: CharacterLength,
71    /// The minimum width of free space that must be available after stepping on a stair.
72    pub min_width: CharacterLength,
73    /// Can the character automatically step over dynamic bodies too?
74    pub include_dynamic_bodies: bool,
75}
76
77impl Default for CharacterAutostep {
78    fn default() -> Self {
79        Self {
80            max_height: CharacterLength::Relative(0.25),
81            min_width: CharacterLength::Relative(0.5),
82            include_dynamic_bodies: true,
83        }
84    }
85}
86
87#[derive(Debug)]
88struct HitDecomposition {
89    normal_part: Vector<Real>,
90    horizontal_tangent: Vector<Real>,
91    vertical_tangent: Vector<Real>,
92    // NOTE: we don’t store the penetration part since we don’t really need it
93    //       for anything.
94}
95
96impl HitDecomposition {
97    pub fn unconstrained_slide_part(&self) -> Vector<Real> {
98        self.normal_part + self.horizontal_tangent + self.vertical_tangent
99    }
100}
101
102/// A collision between the character and its environment during its movement.
103#[derive(Copy, Clone, Debug)]
104pub struct CharacterCollision {
105    /// The collider hit by the character.
106    pub handle: ColliderHandle,
107    /// The position of the character when the collider was hit.
108    pub character_pos: Isometry<Real>,
109    /// The translation that was already applied to the character when the hit happens.
110    pub translation_applied: Vector<Real>,
111    /// The translations that was still waiting to be applied to the character when the hit happens.
112    pub translation_remaining: Vector<Real>,
113    /// Geometric information about the hit.
114    pub hit: ShapeCastHit,
115}
116
117/// A character controller for kinematic bodies.
118#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
119#[derive(Copy, Clone, Debug)]
120pub struct KinematicCharacterController {
121    /// The direction that goes "up". Used to determine where the floor is, and the floor’s angle.
122    pub up: UnitVector<Real>,
123    /// A small gap to preserve between the character and its surroundings.
124    ///
125    /// This value should not be too large to avoid visual artifacts, but shouldn’t be too small
126    /// (must not be zero) to improve numerical stability of the character controller.
127    pub offset: CharacterLength,
128    /// Should the character try to slide against the floor if it hits it?
129    pub slide: bool,
130    /// Should the character automatically step over small obstacles? (disabled by default)
131    ///
132    /// Note that autostepping is currently a very computationally expensive feature, so it
133    /// is disabled by default.
134    pub autostep: Option<CharacterAutostep>,
135    /// The maximum angle (radians) between the floor’s normal and the `up` vector that the
136    /// character is able to climb.
137    pub max_slope_climb_angle: Real,
138    /// The minimum angle (radians) between the floor’s normal and the `up` vector before the
139    /// character starts to slide down automatically.
140    pub min_slope_slide_angle: Real,
141    /// Should the character be automatically snapped to the ground if the distance between
142    /// the ground and its feed are smaller than the specified threshold?
143    pub snap_to_ground: Option<CharacterLength>,
144    /// Increase this number if your character appears to get stuck when sliding against surfaces.
145    ///
146    /// This is a small distance applied to the movement toward the contact normals of shapes hit
147    /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating
148    /// state during the sliding calculation.
149    ///
150    /// This value should remain fairly small since it can introduce artificial "bumps" when sliding
151    /// along a flat surface.
152    pub normal_nudge_factor: Real,
153}
154
155impl Default for KinematicCharacterController {
156    fn default() -> Self {
157        Self {
158            up: Vector::y_axis(),
159            offset: CharacterLength::Relative(0.01),
160            slide: true,
161            autostep: None,
162            max_slope_climb_angle: Real::frac_pi_4(),
163            min_slope_slide_angle: Real::frac_pi_4(),
164            snap_to_ground: Some(CharacterLength::Relative(0.2)),
165            normal_nudge_factor: 1.0e-4,
166        }
167    }
168}
169
170/// The effective movement computed by the character controller.
171#[derive(Debug)]
172pub struct EffectiveCharacterMovement {
173    /// The movement to apply.
174    pub translation: Vector<Real>,
175    /// Is the character touching the ground after applying `EffectiveKineamticMovement::translation`?
176    pub grounded: bool,
177    /// Is the character sliding down a slope due to slope angle being larger than `min_slope_slide_angle`?
178    pub is_sliding_down_slope: bool,
179}
180
181impl KinematicCharacterController {
182    fn check_and_fix_penetrations(&self) {
183        /*
184        // 1/ Check if the body is grounded and if there are penetrations.
185        let mut grounded = false;
186        let mut penetrating = false;
187
188        let mut contacts = vec![];
189
190        let aabb = shape
191            .compute_aabb(shape_pos)
192            .loosened(self.offset);
193        queries.colliders_with_aabb_intersecting_aabb(&aabb, |handle| {
194            // TODO: apply the filter.
195            if let Some(collider) = colliders.get(*handle) {
196                if let Ok(Some(contact)) = parry::query::contact(
197                    &shape_pos,
198                    shape,
199                    collider.position(),
200                    collider.shape(),
201                    self.offset,
202                ) {
203                    contacts.push((contact, collider));
204                }
205            }
206
207            true
208        });
209         */
210    }
211
212    /// Computes the possible movement for a shape.
213    #[profiling::function]
214    pub fn move_shape(
215        &self,
216        dt: Real,
217        queries: &QueryPipeline,
218        character_shape: &dyn Shape,
219        character_pos: &Isometry<Real>,
220        desired_translation: Vector<Real>,
221        mut events: impl FnMut(CharacterCollision),
222    ) -> EffectiveCharacterMovement {
223        let mut result = EffectiveCharacterMovement {
224            translation: Vector::zeros(),
225            grounded: false,
226            is_sliding_down_slope: false,
227        };
228        let dims = self.compute_dims(character_shape);
229
230        // 1. Check and fix penetrations.
231        self.check_and_fix_penetrations();
232
233        let mut translation_remaining = desired_translation;
234
235        let grounded_at_starting_pos = self.detect_grounded_status_and_apply_friction(
236            dt,
237            queries,
238            character_shape,
239            character_pos,
240            &dims,
241            None,
242            None,
243        );
244
245        let mut max_iters = 20;
246        let mut kinematic_friction_translation = Vector::zeros();
247        let offset = self.offset.eval(dims.y);
248        let mut is_moving = false;
249
250        while let Some((translation_dir, translation_dist)) =
251            UnitVector::try_new_and_get(translation_remaining, 1.0e-5)
252        {
253            if max_iters == 0 {
254                break;
255            } else {
256                max_iters -= 1;
257            }
258            is_moving = true;
259
260            // 2. Cast towards the movement direction.
261            if let Some((handle, hit)) = queries.cast_shape(
262                &(Translation::from(result.translation) * character_pos),
263                &translation_dir,
264                character_shape,
265                ShapeCastOptions {
266                    target_distance: offset,
267                    stop_at_penetration: false,
268                    max_time_of_impact: translation_dist,
269                    compute_impact_geometry_on_penetration: true,
270                },
271            ) {
272                // We hit something, compute and apply the allowed interference-free translation.
273                let allowed_dist = hit.time_of_impact;
274                let allowed_translation = *translation_dir * allowed_dist;
275                result.translation += allowed_translation;
276                translation_remaining -= allowed_translation;
277
278                events(CharacterCollision {
279                    handle,
280                    character_pos: Translation::from(result.translation) * character_pos,
281                    translation_applied: result.translation,
282                    translation_remaining,
283                    hit,
284                });
285
286                let hit_info = self.compute_hit_info(hit);
287
288                // Try to go upstairs.
289                if !self.handle_stairs(
290                    *queries,
291                    character_shape,
292                    &(Translation::from(result.translation) * character_pos),
293                    &dims,
294                    handle,
295                    &hit_info,
296                    &mut translation_remaining,
297                    &mut result,
298                ) {
299                    // No stairs, try to move along slopes.
300                    translation_remaining = self.handle_slopes(
301                        &hit_info,
302                        &desired_translation,
303                        &translation_remaining,
304                        self.normal_nudge_factor,
305                        &mut result,
306                    );
307                }
308            } else {
309                // No interference along the path.
310                result.translation += translation_remaining;
311                translation_remaining.fill(0.0);
312                result.grounded = self.detect_grounded_status_and_apply_friction(
313                    dt,
314                    queries,
315                    character_shape,
316                    &(Translation::from(result.translation) * character_pos),
317                    &dims,
318                    None,
319                    None,
320                );
321                break;
322            }
323            result.grounded = self.detect_grounded_status_and_apply_friction(
324                dt,
325                queries,
326                character_shape,
327                &(Translation::from(result.translation) * character_pos),
328                &dims,
329                Some(&mut kinematic_friction_translation),
330                Some(&mut translation_remaining),
331            );
332
333            if !self.slide {
334                break;
335            }
336        }
337        // When not moving, `detect_grounded_status_and_apply_friction` is not reached
338        // so we call it explicitly here.
339        if !is_moving {
340            result.grounded = self.detect_grounded_status_and_apply_friction(
341                dt,
342                queries,
343                character_shape,
344                &(Translation::from(result.translation) * character_pos),
345                &dims,
346                None,
347                None,
348            );
349        }
350        // If needed, and if we are not already grounded, snap to the ground.
351        if grounded_at_starting_pos {
352            self.snap_to_ground(
353                queries,
354                character_shape,
355                &(Translation::from(result.translation) * character_pos),
356                &dims,
357                &mut result,
358            );
359        }
360
361        // Return the result.
362        result
363    }
364
365    fn snap_to_ground(
366        &self,
367        queries: &QueryPipeline,
368        character_shape: &dyn Shape,
369        character_pos: &Isometry<Real>,
370        dims: &Vector2<Real>,
371        result: &mut EffectiveCharacterMovement,
372    ) -> Option<(ColliderHandle, ShapeCastHit)> {
373        if let Some(snap_distance) = self.snap_to_ground {
374            if result.translation.dot(&self.up) < -1.0e-5 {
375                let snap_distance = snap_distance.eval(dims.y);
376                let offset = self.offset.eval(dims.y);
377                if let Some((hit_handle, hit)) = queries.cast_shape(
378                    character_pos,
379                    &-self.up,
380                    character_shape,
381                    ShapeCastOptions {
382                        target_distance: offset,
383                        stop_at_penetration: false,
384                        max_time_of_impact: snap_distance,
385                        compute_impact_geometry_on_penetration: true,
386                    },
387                ) {
388                    // Apply the snap.
389                    result.translation -= *self.up * hit.time_of_impact;
390                    result.grounded = true;
391                    return Some((hit_handle, hit));
392                }
393            }
394        }
395
396        None
397    }
398
399    fn predict_ground(&self, up_extends: Real) -> Real {
400        self.offset.eval(up_extends) + 0.05
401    }
402
403    #[profiling::function]
404    fn detect_grounded_status_and_apply_friction(
405        &self,
406        dt: Real,
407        queries: &QueryPipeline,
408        character_shape: &dyn Shape,
409        character_pos: &Isometry<Real>,
410        dims: &Vector2<Real>,
411        mut kinematic_friction_translation: Option<&mut Vector<Real>>,
412        mut translation_remaining: Option<&mut Vector<Real>>,
413    ) -> bool {
414        let prediction = self.predict_ground(dims.y);
415
416        // TODO: allow custom dispatchers.
417        let dispatcher = DefaultQueryDispatcher;
418
419        let mut manifolds: Vec<ContactManifold> = vec![];
420        let character_aabb = character_shape
421            .compute_aabb(character_pos)
422            .loosened(prediction);
423
424        let mut grounded = false;
425
426        'outer: for (_, collider) in queries.intersect_aabb_conservative(character_aabb) {
427            manifolds.clear();
428            let pos12 = character_pos.inv_mul(collider.position());
429            let _ = dispatcher.contact_manifolds(
430                &pos12,
431                character_shape,
432                collider.shape(),
433                prediction,
434                &mut manifolds,
435                &mut None,
436            );
437
438            if let (Some(kinematic_friction_translation), Some(translation_remaining)) = (
439                kinematic_friction_translation.as_deref_mut(),
440                translation_remaining.as_deref_mut(),
441            ) {
442                let init_kinematic_friction_translation = *kinematic_friction_translation;
443                let kinematic_parent = collider
444                    .parent
445                    .and_then(|p| queries.bodies.get(p.handle))
446                    .filter(|rb| rb.is_kinematic());
447
448                for m in &manifolds {
449                    if self.is_grounded_at_contact_manifold(m, character_pos, dims) {
450                        grounded = true;
451                    }
452
453                    if let Some(kinematic_parent) = kinematic_parent {
454                        let mut num_active_contacts = 0;
455                        let mut manifold_center = Point::origin();
456                        let normal = -(character_pos * m.local_n1);
457
458                        for contact in &m.points {
459                            if contact.dist <= prediction {
460                                num_active_contacts += 1;
461                                let contact_point = collider.position() * contact.local_p2;
462                                let target_vel = kinematic_parent.velocity_at_point(&contact_point);
463
464                                let normal_target_mvt = target_vel.dot(&normal) * dt;
465                                let normal_current_mvt = translation_remaining.dot(&normal);
466
467                                manifold_center += contact_point.coords;
468                                *translation_remaining +=
469                                    normal * (normal_target_mvt - normal_current_mvt);
470                            }
471                        }
472
473                        if num_active_contacts > 0 {
474                            let target_vel = kinematic_parent.velocity_at_point(
475                                &(manifold_center / num_active_contacts as Real),
476                            );
477                            let tangent_platform_mvt =
478                                (target_vel - normal * target_vel.dot(&normal)) * dt;
479                            kinematic_friction_translation.zip_apply(
480                                &tangent_platform_mvt,
481                                |y, x| {
482                                    if x.abs() > (*y).abs() {
483                                        *y = x;
484                                    }
485                                },
486                            );
487                        }
488                    }
489                }
490
491                *translation_remaining +=
492                    *kinematic_friction_translation - init_kinematic_friction_translation;
493            } else {
494                for m in &manifolds {
495                    if self.is_grounded_at_contact_manifold(m, character_pos, dims) {
496                        grounded = true;
497                        break 'outer; // We can stop the search early.
498                    }
499                }
500            }
501        }
502        grounded
503    }
504
505    fn is_grounded_at_contact_manifold(
506        &self,
507        manifold: &ContactManifold,
508        character_pos: &Isometry<Real>,
509        dims: &Vector2<Real>,
510    ) -> bool {
511        let normal = -(character_pos * manifold.local_n1);
512
513        // For the controller to be grounded, the angle between the contact normal and the up vector
514        // has to be smaller than acos(1.0e-3) = 89.94 degrees.
515        if normal.dot(&self.up) >= 1.0e-3 {
516            let prediction = self.predict_ground(dims.y);
517            for contact in &manifold.points {
518                if contact.dist <= prediction {
519                    return true;
520                }
521            }
522        }
523        false
524    }
525
526    fn handle_slopes(
527        &self,
528        hit: &HitInfo,
529        movement_input: &Vector<Real>,
530        translation_remaining: &Vector<Real>,
531        normal_nudge_factor: Real,
532        result: &mut EffectiveCharacterMovement,
533    ) -> Vector<Real> {
534        let [_vertical_input, horizontal_input] = self.split_into_components(movement_input);
535        let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi);
536        let decomp = self.decompose_hit(translation_remaining, &hit.toi);
537
538        // An object is trying to slip if the tangential movement induced by its vertical movement
539        // points downward.
540        let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0;
541        // An object is slipping if its vertical movement points downward.
542        let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0;
543
544        // An object is trying to climb if its vertical input motion points upward.
545        let climbing_intent = self.up.dot(&_vertical_input) > 0.0;
546        // An object is climbing if the tangential movement induced by its vertical movement points upward.
547        let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0;
548
549        let allowed_movement = if hit.is_wall && climbing && !climbing_intent {
550            // Can’t climb the slope, remove the vertical tangent motion induced by the forward motion.
551            decomp.horizontal_tangent + decomp.normal_part
552        } else if hit.is_nonslip_slope && slipping && !slipping_intent {
553            // Prevent the vertical movement from sliding down.
554            decomp.horizontal_tangent + decomp.normal_part
555        } else {
556            // Let it slide (including climbing the slope).
557            result.is_sliding_down_slope = true;
558            decomp.unconstrained_slide_part()
559        };
560
561        allowed_movement + *hit.toi.normal1 * normal_nudge_factor
562    }
563
564    fn split_into_components(&self, translation: &Vector<Real>) -> [Vector<Real>; 2] {
565        let vertical_translation = *self.up * (self.up.dot(translation));
566        let horizontal_translation = *translation - vertical_translation;
567        [vertical_translation, horizontal_translation]
568    }
569
570    fn compute_hit_info(&self, toi: ShapeCastHit) -> HitInfo {
571        let angle_with_floor = self.up.angle(&toi.normal1);
572        let is_ceiling = self.up.dot(&toi.normal1) < 0.0;
573        let is_wall = angle_with_floor >= self.max_slope_climb_angle && !is_ceiling;
574        let is_nonslip_slope = angle_with_floor <= self.min_slope_slide_angle;
575
576        HitInfo {
577            toi,
578            is_wall,
579            is_nonslip_slope,
580        }
581    }
582
583    fn decompose_hit(&self, translation: &Vector<Real>, hit: &ShapeCastHit) -> HitDecomposition {
584        let dist_to_surface = translation.dot(&hit.normal1);
585        let normal_part;
586        let penetration_part;
587
588        if dist_to_surface < 0.0 {
589            normal_part = Vector::zeros();
590            penetration_part = dist_to_surface * *hit.normal1;
591        } else {
592            penetration_part = Vector::zeros();
593            normal_part = dist_to_surface * *hit.normal1;
594        }
595
596        let tangent = translation - normal_part - penetration_part;
597        #[cfg(feature = "dim3")]
598        let horizontal_tangent_dir = hit.normal1.cross(&self.up);
599        #[cfg(feature = "dim2")]
600        let horizontal_tangent_dir = Vector::zeros();
601
602        let horizontal_tangent_dir = horizontal_tangent_dir
603            .try_normalize(1.0e-5)
604            .unwrap_or_default();
605        let horizontal_tangent = tangent.dot(&horizontal_tangent_dir) * horizontal_tangent_dir;
606        let vertical_tangent = tangent - horizontal_tangent;
607
608        HitDecomposition {
609            normal_part,
610            horizontal_tangent,
611            vertical_tangent,
612        }
613    }
614
615    fn compute_dims(&self, character_shape: &dyn Shape) -> Vector2<Real> {
616        let extents = character_shape.compute_local_aabb().extents();
617        let up_extent = extents.dot(&self.up.abs());
618        let side_extent = (extents - (*self.up).abs() * up_extent).norm();
619        Vector2::new(side_extent, up_extent)
620    }
621
622    #[profiling::function]
623    fn handle_stairs(
624        &self,
625        mut queries: QueryPipeline,
626        character_shape: &dyn Shape,
627        character_pos: &Isometry<Real>,
628        dims: &Vector2<Real>,
629        stair_handle: ColliderHandle,
630        hit: &HitInfo,
631        translation_remaining: &mut Vector<Real>,
632        result: &mut EffectiveCharacterMovement,
633    ) -> bool {
634        let Some(autostep) = self.autostep else {
635            return false;
636        };
637
638        // Only try to autostep on walls.
639        if !hit.is_wall {
640            return false;
641        }
642
643        let offset = self.offset.eval(dims.y);
644        let min_width = autostep.min_width.eval(dims.x) + offset;
645        let max_height = autostep.max_height.eval(dims.y) + offset;
646
647        if !autostep.include_dynamic_bodies {
648            if queries
649                .colliders
650                .get(stair_handle)
651                .and_then(|co| co.parent)
652                .and_then(|p| queries.bodies.get(p.handle))
653                .map(|b| b.is_dynamic())
654                == Some(true)
655            {
656                // The "stair" is a dynamic body, which the user wants to ignore.
657                return false;
658            }
659
660            queries.filter.flags |= QueryFilterFlags::EXCLUDE_DYNAMIC;
661        }
662
663        let shifted_character_pos = Translation::from(*self.up * max_height) * character_pos;
664
665        let Some(horizontal_dir) = (*translation_remaining
666            - *self.up * translation_remaining.dot(&self.up))
667        .try_normalize(1.0e-5) else {
668            return false;
669        };
670
671        if queries
672            .cast_shape(
673                character_pos,
674                &self.up,
675                character_shape,
676                ShapeCastOptions {
677                    target_distance: offset,
678                    stop_at_penetration: false,
679                    max_time_of_impact: max_height,
680                    compute_impact_geometry_on_penetration: true,
681                },
682            )
683            .is_some()
684        {
685            // We can’t go up.
686            return false;
687        }
688
689        if queries
690            .cast_shape(
691                &shifted_character_pos,
692                &horizontal_dir,
693                character_shape,
694                ShapeCastOptions {
695                    target_distance: offset,
696                    stop_at_penetration: false,
697                    max_time_of_impact: min_width,
698                    compute_impact_geometry_on_penetration: true,
699                },
700            )
701            .is_some()
702        {
703            // We don’t have enough room on the stair to stay on it.
704            return false;
705        }
706
707        // Check that we are not getting into a ramp that is too steep
708        // after stepping.
709        if let Some((_, hit)) = queries.cast_shape(
710            &(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
711            &-self.up,
712            character_shape,
713            ShapeCastOptions {
714                target_distance: offset,
715                stop_at_penetration: false,
716                max_time_of_impact: max_height,
717                compute_impact_geometry_on_penetration: true,
718            },
719        ) {
720            let [vertical_slope_translation, horizontal_slope_translation] = self
721                .split_into_components(translation_remaining)
722                .map(|remaining| subtract_hit(remaining, &hit));
723
724            let slope_translation = horizontal_slope_translation + vertical_slope_translation;
725
726            let angle_with_floor = self.up.angle(&hit.normal1);
727            let climbing = self.up.dot(&slope_translation) >= 0.0;
728
729            if climbing && angle_with_floor > self.max_slope_climb_angle {
730                return false; // The target ramp is too steep.
731            }
732        }
733
734        // We can step, we need to find the actual step height.
735        let step_height = max_height
736            - queries
737                .cast_shape(
738                    &(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
739                    &-self.up,
740                    character_shape,
741                    ShapeCastOptions {
742                        target_distance: offset,
743                        stop_at_penetration: false,
744                        max_time_of_impact: max_height,
745                        compute_impact_geometry_on_penetration: true,
746                    },
747                )
748                .map(|hit| hit.1.time_of_impact)
749                .unwrap_or(max_height);
750
751        // Remove the step height from the vertical part of the self.
752        let step = *self.up * step_height;
753        *translation_remaining -= step;
754
755        // Advance the collider on the step horizontally, to make sure further
756        // movement won’t just get stuck on its edge.
757        let horizontal_nudge =
758            horizontal_dir * horizontal_dir.dot(translation_remaining).min(min_width);
759        *translation_remaining -= horizontal_nudge;
760
761        result.translation += step + horizontal_nudge;
762        true
763    }
764
765    /// For the given collisions between a character and its environment, this method will apply
766    /// impulses to the rigid-bodies surrounding the character shape at the time of the collisions.
767    /// Note that the impulse calculation is only approximate as it is not based on a global
768    /// constraints resolution scheme.
769    #[profiling::function]
770    pub fn solve_character_collision_impulses<'a>(
771        &self,
772        dt: Real,
773        queries: &mut QueryPipelineMut,
774        character_shape: &dyn Shape,
775        character_mass: Real,
776        collisions: impl IntoIterator<Item = &'a CharacterCollision>,
777    ) {
778        for collision in collisions {
779            self.solve_single_character_collision_impulse(
780                dt,
781                queries,
782                character_shape,
783                character_mass,
784                collision,
785            );
786        }
787    }
788
789    /// For the given collision between a character and its environment, this method will apply
790    /// impulses to the rigid-bodies surrounding the character shape at the time of the collision.
791    /// Note that the impulse calculation is only approximate as it is not based on a global
792    /// constraints resolution scheme.
793    #[profiling::function]
794    fn solve_single_character_collision_impulse(
795        &self,
796        dt: Real,
797        queries: &mut QueryPipelineMut,
798        character_shape: &dyn Shape,
799        character_mass: Real,
800        collision: &CharacterCollision,
801    ) {
802        let extents = character_shape.compute_local_aabb().extents();
803        let up_extent = extents.dot(&self.up.abs());
804        let movement_to_transfer =
805            *collision.hit.normal1 * collision.translation_remaining.dot(&collision.hit.normal1);
806        let prediction = self.predict_ground(up_extent);
807
808        // TODO: allow custom dispatchers.
809        let dispatcher = DefaultQueryDispatcher;
810
811        let mut manifolds: Vec<ContactManifold> = vec![];
812        let character_aabb = character_shape
813            .compute_aabb(&collision.character_pos)
814            .loosened(prediction);
815
816        for (_, collider) in queries.as_ref().intersect_aabb_conservative(character_aabb) {
817            if let Some(parent) = collider.parent {
818                if let Some(body) = queries.bodies.get(parent.handle) {
819                    if body.is_dynamic() {
820                        manifolds.clear();
821                        let pos12 = collision.character_pos.inv_mul(collider.position());
822                        let prev_manifolds_len = manifolds.len();
823                        let _ = dispatcher.contact_manifolds(
824                            &pos12,
825                            character_shape,
826                            collider.shape(),
827                            prediction,
828                            &mut manifolds,
829                            &mut None,
830                        );
831
832                        for m in &mut manifolds[prev_manifolds_len..] {
833                            m.data.rigid_body2 = Some(parent.handle);
834                            m.data.normal = collision.character_pos * m.local_n1;
835                        }
836                    }
837                }
838            }
839        }
840
841        let velocity_to_transfer = movement_to_transfer * utils::inv(dt);
842
843        for manifold in &manifolds {
844            let body_handle = manifold.data.rigid_body2.unwrap();
845            let body = &mut queries.bodies[body_handle];
846
847            for pt in &manifold.points {
848                if pt.dist <= prediction {
849                    let body_mass = body.mass();
850                    let contact_point = body.position() * pt.local_p2;
851                    let delta_vel_per_contact = (velocity_to_transfer
852                        - body.velocity_at_point(&contact_point))
853                    .dot(&manifold.data.normal);
854                    let mass_ratio = body_mass * character_mass / (body_mass + character_mass);
855
856                    body.apply_impulse_at_point(
857                        manifold.data.normal * delta_vel_per_contact.max(0.0) * mass_ratio,
858                        contact_point,
859                        true,
860                    );
861                }
862            }
863        }
864    }
865}
866
867fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
868    let surface_correction = (-translation).dot(&hit.normal1).max(0.0);
869    // This fixes some instances of moving through walls
870    let surface_correction = surface_correction * (1.0 + 1.0e-5);
871    translation + *hit.normal1 * surface_correction
872}
873
874#[cfg(all(feature = "dim3", feature = "f32"))]
875#[cfg(test)]
876mod test {
877    use crate::{
878        control::{CharacterLength, KinematicCharacterController},
879        prelude::*,
880    };
881
882    #[test]
883    fn character_controller_climb_test() {
884        let mut colliders = ColliderSet::new();
885        let mut impulse_joints = ImpulseJointSet::new();
886        let mut multibody_joints = MultibodyJointSet::new();
887        let mut pipeline = PhysicsPipeline::new();
888        let mut bf = BroadPhaseBvh::new();
889        let mut nf = NarrowPhase::new();
890        let mut islands = IslandManager::new();
891
892        let mut bodies = RigidBodySet::new();
893
894        let gravity = Vector::y() * -9.81;
895
896        let ground_size = 100.0;
897        let ground_height = 0.1;
898        /*
899         * Create a flat ground
900         */
901        let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
902        let floor_handle = bodies.insert(rigid_body);
903        let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
904        colliders.insert_with_parent(collider, floor_handle, &mut bodies);
905
906        /*
907         * Create a slope we can climb.
908         */
909        let slope_angle = 0.2;
910        let slope_size = 2.0;
911        let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size)
912            .translation(vector![0.1 + slope_size, -ground_height + 0.4, 0.0])
913            .rotation(Vector::z() * slope_angle);
914        colliders.insert(collider);
915
916        /*
917         * Create a slope we can’t climb.
918         */
919        let impossible_slope_angle = 0.6;
920        let impossible_slope_size = 2.0;
921        let collider = ColliderBuilder::cuboid(slope_size, ground_height, ground_size)
922            .translation(vector![
923                0.1 + slope_size * 2.0 + impossible_slope_size - 0.9,
924                -ground_height + 1.7,
925                0.0
926            ])
927            .rotation(Vector::z() * impossible_slope_angle);
928        colliders.insert(collider);
929
930        let integration_parameters = IntegrationParameters::default();
931
932        // Initialize character which can climb
933        let mut character_body_can_climb = RigidBodyBuilder::kinematic_position_based()
934            .additional_mass(1.0)
935            .build();
936        character_body_can_climb.set_translation(Vector::new(0.6, 0.5, 0.0), false);
937        let character_handle_can_climb = bodies.insert(character_body_can_climb);
938
939        let collider = ColliderBuilder::ball(0.5).build();
940        colliders.insert_with_parent(collider.clone(), character_handle_can_climb, &mut bodies);
941
942        // Initialize character which cannot climb
943        let mut character_body_cannot_climb = RigidBodyBuilder::kinematic_position_based()
944            .additional_mass(1.0)
945            .build();
946        character_body_cannot_climb.set_translation(Vector::new(-0.6, 0.5, 0.0), false);
947        let character_handle_cannot_climb = bodies.insert(character_body_cannot_climb);
948
949        let collider = ColliderBuilder::ball(0.5).build();
950        let character_shape = collider.shape();
951        colliders.insert_with_parent(collider.clone(), character_handle_cannot_climb, &mut bodies);
952
953        for i in 0..200 {
954            // Step once
955            pipeline.step(
956                &gravity,
957                &integration_parameters,
958                &mut islands,
959                &mut bf,
960                &mut nf,
961                &mut bodies,
962                &mut colliders,
963                &mut impulse_joints,
964                &mut multibody_joints,
965                &mut CCDSolver::new(),
966                &(),
967                &(),
968            );
969
970            let mut update_character_controller =
971                |controller: KinematicCharacterController, handle: RigidBodyHandle| {
972                    let character_body = bodies.get(handle).unwrap();
973                    // Use a closure to handle or collect the collisions while
974                    // the character is being moved.
975                    let mut collisions = vec![];
976                    let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle);
977                    let query_pipeline = bf.as_query_pipeline(
978                        nf.query_dispatcher(),
979                        &bodies,
980                        &colliders,
981                        filter_character_controller,
982                    );
983                    let effective_movement = controller.move_shape(
984                        integration_parameters.dt,
985                        &query_pipeline,
986                        character_shape,
987                        character_body.position(),
988                        Vector::new(0.1, -0.1, 0.0),
989                        |collision| collisions.push(collision),
990                    );
991                    let character_body = bodies.get_mut(handle).unwrap();
992                    let translation = character_body.translation();
993                    assert!(
994                        effective_movement.grounded,
995                        "movement should be grounded at all times for current setup (iter: {}), pos: {}.",
996                        i,
997                        translation + effective_movement.translation
998                    );
999                    character_body.set_next_kinematic_translation(
1000                        translation + effective_movement.translation,
1001                    );
1002                };
1003
1004            let character_controller_cannot_climb = KinematicCharacterController {
1005                max_slope_climb_angle: impossible_slope_angle - 0.001,
1006                ..Default::default()
1007            };
1008            let character_controller_can_climb = KinematicCharacterController {
1009                max_slope_climb_angle: impossible_slope_angle + 0.001,
1010                ..Default::default()
1011            };
1012            update_character_controller(
1013                character_controller_cannot_climb,
1014                character_handle_cannot_climb,
1015            );
1016            update_character_controller(character_controller_can_climb, character_handle_can_climb);
1017        }
1018        let character_body = bodies.get(character_handle_can_climb).unwrap();
1019        assert!(character_body.translation().x > 6.0);
1020        assert!(character_body.translation().y > 3.0);
1021        let character_body = bodies.get(character_handle_cannot_climb).unwrap();
1022        assert!(character_body.translation().x < 4.0);
1023        assert!(dbg!(character_body.translation().y) < 2.0);
1024    }
1025
1026    #[test]
1027    fn character_controller_ground_detection() {
1028        let mut colliders = ColliderSet::new();
1029        let mut impulse_joints = ImpulseJointSet::new();
1030        let mut multibody_joints = MultibodyJointSet::new();
1031        let mut pipeline = PhysicsPipeline::new();
1032        let mut bf = BroadPhaseBvh::new();
1033        let mut nf = NarrowPhase::new();
1034        let mut islands = IslandManager::new();
1035
1036        let mut bodies = RigidBodySet::new();
1037
1038        let gravity = Vector::y() * -9.81;
1039
1040        let ground_size = 1001.0;
1041        let ground_height = 1.0;
1042        /*
1043         * Create a flat ground
1044         */
1045        let rigid_body =
1046            RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height / 2f32, 0.0]);
1047        let floor_handle = bodies.insert(rigid_body);
1048        let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
1049        colliders.insert_with_parent(collider, floor_handle, &mut bodies);
1050
1051        let integration_parameters = IntegrationParameters::default();
1052
1053        // Initialize character with snap to ground
1054        let character_controller_snap = KinematicCharacterController {
1055            snap_to_ground: Some(CharacterLength::Relative(0.2)),
1056            ..Default::default()
1057        };
1058        let mut character_body_snap = RigidBodyBuilder::kinematic_position_based()
1059            .additional_mass(1.0)
1060            .build();
1061        character_body_snap.set_translation(Vector::new(0.6, 0.5, 0.0), false);
1062        let character_handle_snap = bodies.insert(character_body_snap);
1063
1064        let collider = ColliderBuilder::ball(0.5).build();
1065        colliders.insert_with_parent(collider.clone(), character_handle_snap, &mut bodies);
1066
1067        // Initialize character without snap to ground
1068        let character_controller_no_snap = KinematicCharacterController {
1069            snap_to_ground: None,
1070            ..Default::default()
1071        };
1072        let mut character_body_no_snap = RigidBodyBuilder::kinematic_position_based()
1073            .additional_mass(1.0)
1074            .build();
1075        character_body_no_snap.set_translation(Vector::new(-0.6, 0.5, 0.0), false);
1076        let character_handle_no_snap = bodies.insert(character_body_no_snap);
1077
1078        let collider = ColliderBuilder::ball(0.5).build();
1079        let character_shape = collider.shape();
1080        colliders.insert_with_parent(collider.clone(), character_handle_no_snap, &mut bodies);
1081
1082        for i in 0..10000 {
1083            // Step once
1084            pipeline.step(
1085                &gravity,
1086                &integration_parameters,
1087                &mut islands,
1088                &mut bf,
1089                &mut nf,
1090                &mut bodies,
1091                &mut colliders,
1092                &mut impulse_joints,
1093                &mut multibody_joints,
1094                &mut CCDSolver::new(),
1095                &(),
1096                &(),
1097            );
1098
1099            let mut update_character_controller =
1100                |controller: KinematicCharacterController, handle: RigidBodyHandle| {
1101                    let character_body = bodies.get(handle).unwrap();
1102                    // Use a closure to handle or collect the collisions while
1103                    // the character is being moved.
1104                    let mut collisions = vec![];
1105                    let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle);
1106                    let query_pipeline = bf.as_query_pipeline(
1107                        nf.query_dispatcher(),
1108                        &bodies,
1109                        &colliders,
1110                        filter_character_controller,
1111                    );
1112                    let effective_movement = controller.move_shape(
1113                        integration_parameters.dt,
1114                        &query_pipeline,
1115                        character_shape,
1116                        character_body.position(),
1117                        Vector::new(0.1, -0.1, 0.1),
1118                        |collision| collisions.push(collision),
1119                    );
1120                    let character_body = bodies.get_mut(handle).unwrap();
1121                    let translation = character_body.translation();
1122                    assert!(
1123                        effective_movement.grounded,
1124                        "movement should be grounded at all times for current setup (iter: {}), pos: {}.",
1125                        i,
1126                        translation + effective_movement.translation
1127                    );
1128                    character_body.set_next_kinematic_translation(
1129                        translation + effective_movement.translation,
1130                    );
1131                };
1132
1133            update_character_controller(character_controller_no_snap, character_handle_no_snap);
1134            update_character_controller(character_controller_snap, character_handle_snap);
1135        }
1136
1137        let character_body = bodies.get_mut(character_handle_no_snap).unwrap();
1138        let translation = character_body.translation();
1139
1140        // accumulated numerical errors make the test go less far than it should,
1141        // but it's expected.
1142        assert!(
1143            translation.x >= 997.0,
1144            "actual translation.x:{}",
1145            translation.x
1146        );
1147        assert!(
1148            translation.z >= 997.0,
1149            "actual translation.z:{}",
1150            translation.z
1151        );
1152
1153        let character_body = bodies.get_mut(character_handle_snap).unwrap();
1154        let translation = character_body.translation();
1155        assert!(
1156            translation.x >= 997.0,
1157            "actual translation.x:{}",
1158            translation.x
1159        );
1160        assert!(
1161            translation.z >= 997.0,
1162            "actual translation.z:{}",
1163            translation.z
1164        );
1165    }
1166}