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