bevy_rapier2d/plugin/systems/
mod.rs1mod 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
31pub 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 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 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}