bevy_rapier2d/control/
character_controller.rs

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