Skip to main content

bevy_rapier2d/plugin/systems/
character_controller.rs

1use crate::control::CharacterCollision;
2use crate::dynamics::RapierRigidBodyHandle;
3use crate::geometry::RapierColliderHandle;
4use crate::plugin::context::systemparams::RAPIER_CONTEXT_EXPECT_ERROR;
5use crate::plugin::context::RapierContextEntityLink;
6use crate::plugin::RapierConfiguration;
7use crate::prelude::context::RapierContextColliders;
8use crate::prelude::context::RapierContextSimulation;
9use crate::prelude::context::RapierRigidBodySet;
10use crate::prelude::KinematicCharacterController;
11use crate::prelude::KinematicCharacterControllerOutput;
12use crate::utils;
13use bevy::prelude::*;
14use rapier::math::Pose;
15use rapier::parry::query::DefaultQueryDispatcher;
16use rapier::pipeline::QueryFilter;
17
18/// System responsible for applying the character controller translation to the underlying
19/// collider.
20pub fn update_character_controls(
21    mut commands: Commands,
22    config: Query<&RapierConfiguration>,
23    mut context_access: Query<(
24        &mut RapierContextSimulation,
25        &mut RapierContextColliders,
26        &mut RapierRigidBodySet,
27    )>,
28    mut character_controllers: Query<(
29        Entity,
30        &RapierContextEntityLink,
31        &mut KinematicCharacterController,
32        Option<&mut KinematicCharacterControllerOutput>,
33        Option<&RapierColliderHandle>,
34        Option<&RapierRigidBodyHandle>,
35        Option<&GlobalTransform>,
36    )>,
37    mut transforms: Query<&mut Transform>,
38) {
39    for (
40        entity,
41        rapier_context_link,
42        mut controller,
43        output,
44        collider_handle,
45        body_handle,
46        glob_transform,
47    ) in character_controllers.iter_mut()
48    {
49        if let (Some(raw_controller), Some(translation)) =
50            (controller.to_raw(), controller.translation)
51        {
52            let config = config
53                .get(rapier_context_link.0)
54                .expect("Could not get [`RapierConfiguration`]");
55            let (mut context, mut context_colliders, mut rigidbody_set) = context_access
56                .get_mut(rapier_context_link.0)
57                .expect(RAPIER_CONTEXT_EXPECT_ERROR);
58
59            let context = &mut *context;
60            let rigidbody_set = &mut *rigidbody_set;
61            let scaled_custom_shape =
62                controller
63                    .custom_shape
64                    .as_ref()
65                    .map(|(custom_shape, tra, rot)| {
66                        // TODO: avoid the systematic scale somehow?
67                        let mut scaled_shape = custom_shape.clone();
68                        scaled_shape.set_scale(custom_shape.scale, config.scaled_shape_subdivision);
69
70                        (scaled_shape, *tra, *rot)
71                    });
72
73            let parent_rigid_body = body_handle.map(|h| h.0).or_else(|| {
74                collider_handle
75                    .and_then(|h| context_colliders.colliders.get(h.0))
76                    .and_then(|c| c.parent())
77            });
78            let entity_to_move = parent_rigid_body
79                .and_then(|rb| rigidbody_set.rigid_body_entity(rb))
80                .unwrap_or(entity);
81
82            let (character_shape, character_pos) = if let Some((scaled_shape, tra, rot)) =
83                &scaled_custom_shape
84            {
85                let mut shape_pos: Pose = utils::pose_from(*tra, *rot);
86
87                if let Some(body) = body_handle.and_then(|h| rigidbody_set.bodies.get(h.0)) {
88                    shape_pos = body.position() * shape_pos
89                } else if let Some(gtransform) = glob_transform {
90                    shape_pos = utils::transform_to_iso(&gtransform.compute_transform()) * shape_pos
91                }
92
93                (&*scaled_shape.raw, shape_pos)
94            } else if let Some(collider) =
95                collider_handle.and_then(|h| context_colliders.colliders.get(h.0))
96            {
97                (collider.shape(), *collider.position())
98            } else {
99                continue;
100            };
101            let character_shape = character_shape.clone_dyn();
102
103            let exclude_collider = collider_handle.map(|h| h.0);
104
105            let character_mass = controller
106                .custom_mass
107                .or_else(|| {
108                    parent_rigid_body
109                        .and_then(|h| rigidbody_set.bodies.get(h))
110                        .map(|rb| rb.mass())
111                })
112                .unwrap_or(0.0);
113
114            let mut filter = QueryFilter {
115                flags: controller.filter_flags,
116                groups: controller.filter_groups.map(|g| g.into()),
117                exclude_collider: None,
118                exclude_rigid_body: None,
119                predicate: None,
120            };
121
122            if let Some(parent) = parent_rigid_body {
123                filter = filter.exclude_rigid_body(parent);
124            } else if let Some(excl_co) = exclude_collider {
125                filter = filter.exclude_collider(excl_co)
126            };
127
128            let collisions = &mut context.character_collisions_collector;
129            collisions.clear();
130
131            let mut query_pipeline = context.broad_phase.as_query_pipeline_mut(
132                &DefaultQueryDispatcher,
133                &mut rigidbody_set.bodies,
134                &mut context_colliders.colliders,
135                filter,
136            );
137            // TODO: add filter for charactercontroller.
138
139            let movement = raw_controller.move_shape(
140                context.integration_parameters.dt,
141                &query_pipeline.as_ref(),
142                &*character_shape,
143                &character_pos,
144                translation,
145                |c| collisions.push(c),
146            );
147
148            if controller.apply_impulse_to_dynamic_bodies {
149                raw_controller.solve_character_collision_impulses(
150                    context.integration_parameters.dt,
151                    &mut query_pipeline,
152                    &*character_shape,
153                    character_mass,
154                    collisions.iter(),
155                )
156            }
157
158            if let Ok(mut transform) = transforms.get_mut(entity_to_move) {
159                // TODO: take the parent’s GlobalTransform rotation into account?
160                transform.translation.x += movement.translation.x;
161                transform.translation.y += movement.translation.y;
162                #[cfg(feature = "dim3")]
163                {
164                    transform.translation.z += movement.translation.z;
165                }
166            }
167
168            let converted_collisions = context
169                .character_collisions_collector
170                .iter()
171                .filter_map(|c| CharacterCollision::from_raw(&context_colliders, c));
172
173            if let Some(mut output) = output {
174                output.desired_translation = controller.translation.unwrap();
175                output.effective_translation = movement.translation;
176                output.grounded = movement.grounded;
177                output.collisions.clear();
178                output.collisions.extend(converted_collisions);
179                output.is_sliding_down_slope = movement.is_sliding_down_slope;
180            } else {
181                commands
182                    .entity(entity)
183                    .insert(KinematicCharacterControllerOutput {
184                        desired_translation: controller.translation.unwrap(),
185                        effective_translation: movement.translation,
186                        grounded: movement.grounded,
187                        collisions: converted_collisions.collect(),
188                        is_sliding_down_slope: movement.is_sliding_down_slope,
189                    });
190            }
191
192            controller.translation = None;
193        }
194    }
195}