Skip to main content

bevy_rapier3d/plugin/systems/
mod.rs

1//! Systems responsible for interfacing our Bevy components with the Rapier physics engine.
2
3mod character_controller;
4mod collider;
5mod joint;
6mod multiple_rapier_contexts;
7mod pid_controller;
8mod remove;
9mod rigid_body;
10mod soft_body;
11#[cfg(feature = "dim3")]
12mod vehicle_controller;
13mod writeback;
14
15pub use character_controller::*;
16pub use collider::*;
17pub use joint::*;
18pub use multiple_rapier_contexts::*;
19pub use pid_controller::*;
20pub use remove::*;
21pub use rigid_body::*;
22pub use soft_body::*;
23#[cfg(feature = "dim3")]
24pub use vehicle_controller::*;
25pub use writeback::*;
26
27use crate::dynamics::{
28    RapierRigidBodyHandle, RigidBodyDisabled, SoftBodyDisabled, TransformInterpolation,
29};
30use crate::geometry::ColliderDisabled;
31use crate::pipeline::{CollisionEvent, ContactForceEvent, PhysicsQuarantineEvent};
32use crate::plugin::context::SimulationToRenderTime;
33use crate::plugin::{RapierConfiguration, TimestepMode};
34use crate::prelude::{BevyPhysicsHooks, BevyPhysicsHooksAdapter};
35use bevy::ecs::system::{StaticSystemParam, SystemParamItem};
36use bevy::prelude::*;
37
38use super::context::{
39    RapierContextColliders, RapierContextJoints, RapierContextSimulation, RapierRigidBodySet,
40};
41
42/// System responsible for advancing the physics simulation, and updating the internal state
43/// for scene queries.
44pub fn step_simulation<Hooks>(
45    mut commands: Commands,
46    mut context: Query<(
47        Entity,
48        &mut RapierContextSimulation,
49        &mut RapierContextColliders,
50        &mut RapierContextJoints,
51        &mut RapierRigidBodySet,
52        &RapierConfiguration,
53        &mut SimulationToRenderTime,
54    )>,
55    timestep_mode: Res<TimestepMode>,
56    hooks: StaticSystemParam<Hooks>,
57    time: Res<Time>,
58    mut collision_events: MessageWriter<CollisionEvent>,
59    mut contact_force_events: MessageWriter<ContactForceEvent>,
60    mut quarantine_events: MessageWriter<PhysicsQuarantineEvent>,
61    mut interpolation_query: Query<(&RapierRigidBodyHandle, &mut TransformInterpolation)>,
62) where
63    Hooks: 'static + BevyPhysicsHooks,
64    for<'w, 's> SystemParamItem<'w, 's, Hooks>: BevyPhysicsHooks,
65{
66    let hooks_adapter = BevyPhysicsHooksAdapter::new(hooks.into_inner());
67
68    for (
69        context_entity,
70        mut context,
71        mut context_colliders,
72        mut joints,
73        mut rigidbody_set,
74        config,
75        mut sim_to_render_time,
76    ) in context.iter_mut()
77    {
78        let context = &mut *context;
79        let context_colliders = &mut *context_colliders;
80
81        if config.physics_pipeline_active {
82            context.set_num_threads(config.num_threads);
83            context.step_simulation(
84                context_colliders,
85                &mut joints,
86                &mut rigidbody_set,
87                config.gravity,
88                config.simulation_mode,
89                *timestep_mode,
90                Some((&collision_events, &contact_force_events)),
91                &hooks_adapter,
92                &time,
93                &mut sim_to_render_time,
94                Some(&mut interpolation_query),
95            );
96        } else {
97            rigidbody_set.propagate_modified_body_positions_to_colliders(context_colliders);
98        }
99        context.send_bevy_events(&mut collision_events, &mut contact_force_events);
100        // Mirror the quarantine on the entities so that removing the component re-enables them.
101        for entity in &context.quarantined_bodies {
102            commands.entity(*entity).try_insert(RigidBodyDisabled);
103        }
104        for entity in &context.quarantined_colliders {
105            commands.entity(*entity).try_insert(ColliderDisabled);
106        }
107        for entity in &context.quarantined_soft_bodies {
108            commands.entity(*entity).try_insert(SoftBodyDisabled);
109        }
110        context.send_quarantine_events(context_entity, &mut quarantine_events);
111    }
112}
113
114#[cfg(test)]
115#[allow(missing_docs)]
116pub mod tests {
117    use bevy::time::TimePlugin;
118    use rapier::geometry::CollisionEventFlags;
119    use std::f32::consts::PI;
120
121    use super::*;
122    use crate::{
123        plugin::{NoUserData, RapierPhysicsPlugin},
124        prelude::{Collider, CollidingEntities, RigidBody},
125        utils,
126    };
127
128    #[test]
129    fn colliding_entities_updates() {
130        let mut app = App::new();
131        app.add_message::<CollisionEvent>()
132            .add_systems(Update, update_colliding_entities);
133
134        let entity1 = app.world_mut().spawn(CollidingEntities::default()).id();
135        let entity2 = app.world_mut().spawn(CollidingEntities::default()).id();
136
137        let mut collision_events = app
138            .world_mut()
139            .get_resource_mut::<Messages<CollisionEvent>>()
140            .unwrap();
141        collision_events.write(CollisionEvent::Started(
142            entity1,
143            entity2,
144            CollisionEventFlags::SENSOR,
145        ));
146
147        app.update();
148
149        let colliding_entities1 = app
150            .world()
151            .entity(entity1)
152            .get::<CollidingEntities>()
153            .unwrap();
154        assert_eq!(
155            colliding_entities1.len(),
156            1,
157            "There should be one colliding entity"
158        );
159        assert_eq!(
160            colliding_entities1.iter().next().unwrap(),
161            entity2,
162            "Colliding entity should be equal to the second entity"
163        );
164
165        let colliding_entities2 = app
166            .world()
167            .entity(entity2)
168            .get::<CollidingEntities>()
169            .unwrap();
170        assert_eq!(
171            colliding_entities2.len(),
172            1,
173            "There should be one colliding entity"
174        );
175        assert_eq!(
176            colliding_entities2.iter().next().unwrap(),
177            entity1,
178            "Colliding entity should be equal to the first entity"
179        );
180
181        let mut collision_events = app
182            .world_mut()
183            .get_resource_mut::<Messages<CollisionEvent>>()
184            .unwrap();
185        collision_events.write(CollisionEvent::Stopped(
186            entity1,
187            entity2,
188            CollisionEventFlags::SENSOR,
189        ));
190
191        app.update();
192
193        let colliding_entities1 = app
194            .world()
195            .entity(entity1)
196            .get::<CollidingEntities>()
197            .unwrap();
198        assert!(
199            colliding_entities1.is_empty(),
200            "Colliding entity should be removed from the CollidingEntities component when the collision ends"
201        );
202
203        let colliding_entities2 = app
204            .world()
205            .entity(entity2)
206            .get::<CollidingEntities>()
207            .unwrap();
208        assert!(
209            colliding_entities2.is_empty(),
210            "Colliding entity should be removed from the CollidingEntities component when the collision ends"
211        );
212    }
213
214    #[test]
215    fn transform_propagation() {
216        let mut app = App::new();
217        app.add_plugins((
218            TransformPlugin,
219            TimePlugin,
220            RapierPhysicsPlugin::<NoUserData>::default(),
221        ));
222        app.finish();
223
224        let zero = (Transform::default(), Transform::default());
225
226        let different = (
227            Transform {
228                translation: Vec3::X * 10.0,
229                rotation: Quat::from_rotation_x(PI),
230                ..Default::default()
231            },
232            Transform {
233                translation: Vec3::Y * 10.0,
234                rotation: Quat::from_rotation_x(PI),
235                ..Default::default()
236            },
237        );
238
239        let same = (different.0, different.0);
240
241        for (child_transform, parent_transform) in [zero, same, different] {
242            let child = app
243                .world_mut()
244                .spawn((child_transform, RigidBody::Fixed, Collider::ball(1.0)))
245                .id();
246
247            app.world_mut()
248                .spawn(parent_transform)
249                .add_children(&[child]);
250
251            app.update();
252
253            let world = app.world_mut();
254            let rigidbody_set = world
255                .query::<&RapierRigidBodySet>()
256                .iter(world)
257                .next()
258                .unwrap();
259            let child_transform = world.entity(child).get::<GlobalTransform>().unwrap();
260            let child_handle = rigidbody_set.entity2body[&child];
261            let child_body = rigidbody_set.bodies.get(child_handle).unwrap();
262            let body_transform = utils::iso_to_transform(child_body.position());
263
264            fn transforms_approx_equal(
265                a: &GlobalTransform,
266                b: &GlobalTransform,
267                epsilon: f32,
268            ) -> bool {
269                a.translation().abs_diff_eq(b.translation(), epsilon)
270                    && a.scale().abs_diff_eq(b.scale(), epsilon)
271                    && a.rotation().abs_diff_eq(b.rotation(), epsilon)
272            }
273            assert!(
274                transforms_approx_equal(
275                    &GlobalTransform::from(body_transform),
276                    child_transform,
277                    1.0e-5,
278                ),
279                "Collider transforms should have have equal global rotation and translation"
280            );
281        }
282    }
283
284    #[test]
285    fn transform_propagation2() {
286        let mut app = App::new();
287        app.add_plugins((
288            TransformPlugin,
289            TimePlugin,
290            RapierPhysicsPlugin::<NoUserData>::default(),
291        ));
292        app.finish();
293
294        let zero = (Transform::default(), Transform::default());
295
296        let different = (
297            Transform {
298                translation: Vec3::X * 10.0,
299                // NOTE: in 2D the test will fail if the rotation is wrt. an axis
300                //       other than Z because 2D physics objects can’t rotate wrt.
301                //       other axes.
302                rotation: Quat::from_rotation_z(PI),
303                ..Default::default()
304            },
305            Transform {
306                translation: Vec3::Y * 10.0,
307                rotation: Quat::from_rotation_z(PI),
308                ..Default::default()
309            },
310        );
311
312        let same = (different.0, different.0);
313
314        for (child_transform, parent_transform) in [zero, same, different] {
315            let child = app
316                .world_mut()
317                .spawn((child_transform, Collider::ball(1.0)))
318                .id();
319
320            let parent = app
321                .world_mut()
322                .spawn((parent_transform, RigidBody::Fixed))
323                .add_children(&[child])
324                .id();
325
326            app.update();
327
328            let child_transform = app
329                .world()
330                .entity(child)
331                .get::<GlobalTransform>()
332                .unwrap()
333                .compute_transform();
334            let world = app.world_mut();
335            let (rigidbody_set, context_colliders) = world
336                .query::<(&RapierRigidBodySet, &RapierContextColliders)>()
337                .iter(world)
338                .next()
339                .unwrap();
340            let parent_handle = rigidbody_set.entity2body[&parent];
341            let parent_body = rigidbody_set.bodies.get(parent_handle).unwrap();
342            let child_collider_handle = parent_body.colliders()[0];
343            let child_collider = context_colliders
344                .colliders
345                .get(child_collider_handle)
346                .unwrap();
347            let body_transform = utils::iso_to_transform(child_collider.position());
348            approx::assert_relative_eq!(
349                body_transform.translation,
350                child_transform.translation,
351                epsilon = 1.0e-5
352            );
353
354            // Adjust signs to account for the quaternion’s double covering.
355            let comparison_child_rotation =
356                if body_transform.rotation.w * child_transform.rotation.w < 0.0 {
357                    -child_transform.rotation
358                } else {
359                    child_transform.rotation
360                };
361
362            approx::assert_relative_eq!(
363                body_transform.rotation,
364                comparison_child_rotation,
365                epsilon = 1.0e-5
366            );
367            approx::assert_relative_eq!(body_transform.scale, child_transform.scale,);
368        }
369    }
370}