Skip to main content

bevy_rapier3d/control/
character_controller.rs

1use crate::control::ControllerFilterPredicate;
2use crate::geometry::{Collider, CollisionGroups, ShapeCastHit};
3use crate::math::{Real, Rot, Vect};
4use bevy::ecs::entity::EntityHashSet;
5use bevy::prelude::*;
6
7use crate::plugin::context::RapierContextColliders;
8pub use rapier::control::CharacterAutostep;
9pub use rapier::control::CharacterLength;
10use rapier::prelude::{ColliderSet, QueryFilterFlags};
11
12/// A collision between the character and its environment during its movement.
13#[derive(Copy, Clone, PartialEq, Debug)]
14pub struct CharacterCollision {
15    /// The entity hit by the character.
16    pub entity: Entity,
17    /// The position of the character when the collider was hit.
18    pub character_translation: Vect,
19    /// The rotation of the character when the collider was hit.
20    pub character_rotation: Rot,
21    /// The translation that was already applied to the character when the hit happens.
22    pub translation_applied: Vect,
23    /// The translations that was still waiting to be applied to the character when the hit happens.
24    pub translation_remaining: Vect,
25    /// Geometric information about the hit: the first shape is the obstacle (world-space witness
26    /// and normal), the second one the character (local-space witness and normal).
27    pub hit: ShapeCastHit,
28}
29
30impl CharacterCollision {
31    pub(crate) fn from_raw(
32        ctxt: &RapierContextColliders,
33        c: &rapier::control::CharacterCollision,
34    ) -> Option<Self> {
35        Self::from_raw_with_set(&ctxt.colliders, c, true)
36    }
37
38    pub(crate) fn from_raw_with_set(
39        colliders: &ColliderSet,
40        c: &rapier::control::CharacterCollision,
41        details_always_computed: bool,
42    ) -> Option<Self> {
43        RapierContextColliders::collider_entity_with_set(colliders, c.handle).map(|entity| {
44            CharacterCollision {
45                entity,
46                character_translation: c.character_pos.translation,
47                #[cfg(feature = "dim2")]
48                character_rotation: c.character_pos.rotation.angle(),
49                #[cfg(feature = "dim3")]
50                character_rotation: c.character_pos.rotation,
51                translation_applied: c.translation_applied,
52                translation_remaining: c.translation_remaining,
53                hit: ShapeCastHit::from_rapier(c.hit, details_always_computed),
54            }
55        })
56    }
57}
58
59/// Options for moving a shape using `RapierContext::move_shape`.
60#[derive(Clone, Debug, Copy, PartialEq)]
61pub struct MoveShapeOptions {
62    /// The direction that goes "up". Used to determine where the floor is, and the floor’s angle.
63    pub up: Vect,
64    /// A small gap to preserve between the character and its surroundings.
65    ///
66    /// This value should not be too large to avoid visual artifacts, but shouldn’t be too small
67    /// (must not be zero) to improve numerical stability of the character controller.
68    pub offset: CharacterLength,
69    /// Should the character try to slide against the floor if it hits it?
70    pub slide: bool,
71    /// Should the character automatically step over small obstacles?
72    pub autostep: Option<CharacterAutostep>,
73    /// The maximum angle (radians) between the floor’s normal and the `up` vector that the
74    /// character is able to climb.
75    pub max_slope_climb_angle: Real,
76    /// The minimum angle (radians) between the floor’s normal and the `up` vector before the
77    /// character starts to slide down automatically.
78    pub min_slope_slide_angle: Real,
79    /// Should the character apply forces to dynamic bodies in its path?
80    pub apply_impulse_to_dynamic_bodies: bool,
81    /// Should the character be automatically snapped to the ground if the distance between
82    /// the ground and its feet are smaller than the specified threshold?
83    pub snap_to_ground: Option<CharacterLength>,
84    /// Increase this number if your character appears to get stuck when sliding against surfaces.
85    ///
86    /// This is a small distance applied to the movement toward the contact normals of shapes hit
87    /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating
88    /// state during the sliding calculation.
89    ///
90    /// This value should remain fairly small since it can introduce artificial "bumps" when sliding
91    /// along a flat surface.
92    pub normal_nudge_factor: Real,
93}
94
95impl Default for MoveShapeOptions {
96    fn default() -> Self {
97        let def = rapier::control::KinematicCharacterController::default();
98        Self {
99            up: def.up,
100            offset: def.offset,
101            slide: def.slide,
102            autostep: def.autostep,
103            max_slope_climb_angle: def.max_slope_climb_angle,
104            min_slope_slide_angle: def.min_slope_slide_angle,
105            apply_impulse_to_dynamic_bodies: true,
106            snap_to_ground: def.snap_to_ground,
107            normal_nudge_factor: def.normal_nudge_factor,
108        }
109    }
110}
111
112/// A character controller for kinematic bodies and free-standing colliders.
113///
114/// The controller only computes translations: Rapier's character controller does not handle
115/// rotations, so the character's orientation must be updated separately (e.g. through its
116/// `Transform`).
117///
118/// The collider (or rigid-body) of the character itself is always excluded from the
119/// environment seen by the controller. Additional colliders can be excluded with
120/// [`Self::exclude_colliders`], [`Self::exclude_rigid_bodies`] and [`Self::filter_predicate`], or
121/// from every controller with the [`ControllerIgnored`](crate::control::ControllerIgnored)
122/// component.
123#[derive(Clone, Debug, Component)] // TODO: Reflect
124pub struct KinematicCharacterController {
125    /// The translations we desire the character to move by if it doesn’t meet any obstacle.
126    pub translation: Option<Vect>,
127    /// The shape, and its position, to be used instead of the shape of the collider attached to
128    /// the same entity is this `KinematicCharacterController`.
129    pub custom_shape: Option<(Collider, Vect, Rot)>,
130    /// The mass to be used for impulse of dynamic bodies. This replaces the mass of the rigid-body
131    /// potentially associated to the collider attached to the same entity as this
132    /// `KinematicCharacterController`.
133    ///
134    /// This field isn’t used if `Self::apply_impulse_to_dynamic_bodies` is set to `false`.
135    pub custom_mass: Option<Real>,
136    /// The direction that goes "up". Used to determine where the floor is, and the floor’s angle.
137    pub up: Vect,
138    /// A small gap to preserve between the character and its surroundings.
139    ///
140    /// This value should not be too large to avoid visual artifacts, but shouldn’t be too small
141    /// (must not be zero) to improve numerical stability of the character controller.
142    pub offset: CharacterLength,
143    /// Should the character try to slide against the floor if it hits it?
144    pub slide: bool,
145    /// Should the character automatically step over small obstacles?
146    pub autostep: Option<CharacterAutostep>,
147    /// The maximum angle (radians) between the floor’s normal and the `up` vector that the
148    /// character is able to climb.
149    pub max_slope_climb_angle: Real,
150    /// The minimum angle (radians) between the floor’s normal and the `up` vector before the
151    /// character starts to slide down automatically.
152    pub min_slope_slide_angle: Real,
153    /// Should the character apply forces to dynamic bodies in its path?
154    pub apply_impulse_to_dynamic_bodies: bool,
155    /// Should the character be automatically snapped to the ground if the distance between
156    /// the ground and its feet are smaller than the specified threshold?
157    pub snap_to_ground: Option<CharacterLength>,
158    /// Flags for filtering-out some categories of entities from the environment seen by the
159    /// character controller.
160    pub filter_flags: QueryFilterFlags,
161    /// Groups for filtering-out some colliders from the environment seen by the character
162    /// controller.
163    pub filter_groups: Option<CollisionGroups>,
164    /// Colliders (identified by their entity) ignored by the character controller.
165    pub exclude_colliders: EntityHashSet,
166    /// Rigid-bodies (identified by their entity) whose colliders are all ignored by the
167    /// character controller.
168    pub exclude_rigid_bodies: EntityHashSet,
169    /// If set, any collider for which this predicate returns `false` is ignored by the
170    /// character controller. It can't read ECS data: use the
171    /// [`ControllerIgnored`](crate::control::ControllerIgnored) component for exclusions decided
172    /// by systems.
173    pub filter_predicate: Option<ControllerFilterPredicate>,
174    /// Increase this number if your character appears to get stuck when sliding against surfaces.
175    ///
176    /// This is a small distance applied to the movement toward the contact normals of shapes hit
177    /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating
178    /// state during the sliding calculation.
179    ///
180    /// This value should remain fairly small since it can introduce artificial "bumps" when sliding
181    /// along a flat surface.
182    pub normal_nudge_factor: Real,
183}
184
185impl KinematicCharacterController {
186    pub(crate) fn to_raw(&self) -> Option<rapier::control::KinematicCharacterController> {
187        let autostep = self.autostep.map(|autostep| CharacterAutostep {
188            max_height: autostep.max_height,
189            min_width: autostep.min_width,
190            include_dynamic_bodies: autostep.include_dynamic_bodies,
191        });
192
193        if self.up.length_squared() < 1.0e-12 {
194            return None;
195        }
196
197        Some(rapier::control::KinematicCharacterController {
198            up: self.up.normalize(),
199            offset: self.offset,
200            slide: self.slide,
201            autostep,
202            max_slope_climb_angle: self.max_slope_climb_angle,
203            min_slope_slide_angle: self.min_slope_slide_angle,
204            snap_to_ground: self.snap_to_ground,
205            normal_nudge_factor: self.normal_nudge_factor,
206        })
207    }
208}
209
210impl Default for KinematicCharacterController {
211    fn default() -> Self {
212        let def = rapier::control::KinematicCharacterController::default();
213        Self {
214            translation: None,
215            custom_shape: None,
216            custom_mass: None,
217            up: def.up,
218            offset: def.offset,
219            slide: def.slide,
220            autostep: def.autostep,
221            max_slope_climb_angle: def.max_slope_climb_angle,
222            min_slope_slide_angle: def.min_slope_slide_angle,
223            apply_impulse_to_dynamic_bodies: true,
224            snap_to_ground: def.snap_to_ground,
225            filter_flags: QueryFilterFlags::default() | QueryFilterFlags::EXCLUDE_SENSORS,
226            filter_groups: None,
227            exclude_colliders: EntityHashSet::default(),
228            exclude_rigid_bodies: EntityHashSet::default(),
229            filter_predicate: None,
230            normal_nudge_factor: def.normal_nudge_factor,
231        }
232    }
233}
234
235/// The output of a character control.
236///
237/// This component is automatically added after the first execution of a character control
238/// based on the `KinematicCharacterController` component with its
239/// `KinematicCharacterController::translation` set to a value other than `None`.
240#[derive(Clone, PartialEq, Debug, Default, Component)]
241pub struct KinematicCharacterControllerOutput {
242    /// Indicates whether the shape is grounded after its kinematic movement.
243    pub grounded: bool,
244    /// The initial desired movement of the character if there were no obstacle.
245    pub desired_translation: Vect,
246    /// The translation calculated by the last character control step taking obstacles into account.
247    pub effective_translation: Vect,
248    /// Collisions between the character and obstacles found in its path.
249    pub collisions: Vec<CharacterCollision>,
250    /// Indicates whether the shape is sliding down a slope after its kinematic movement.
251    pub is_sliding_down_slope: bool,
252}
253
254/// The allowed movement computed by `RapierContext::move_shape`.
255pub struct MoveShapeOutput {
256    /// Indicates whether the shape is grounded after its kinematic movement.
257    pub grounded: bool,
258    /// The translation calculated by the last character control step taking obstacles into account.
259    pub effective_translation: Vect,
260    /// Indicates whether the shape is sliding down a slope after its kinematic movement.
261    pub is_sliding_down_slope: bool,
262}