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