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