bevy_rapier3d/plugin/systems/
multiple_rapier_contexts.rs1use crate::dynamics::{
4 RapierImpulseJointHandle, RapierMultibodyJointHandle, RapierRigidBodyHandle,
5 RapierSoftBodyHandle,
6};
7use crate::geometry::RapierColliderHandle;
8use crate::plugin::context::RapierRigidBodySet;
9use crate::plugin::context::{
10 DefaultRapierContext, RapierContextColliders, RapierContextEntityLink, RapierContextJoints,
11};
12use bevy::ecs::system::SystemParam;
13use bevy::prelude::*;
14
15#[derive(SystemParam)]
21pub struct RapierContextLinkResolver<'w, 's> {
22 default_context: Query<'w, 's, Entity, With<DefaultRapierContext>>,
23 child_of: Query<'w, 's, &'static ChildOf>,
24 links: Query<'w, 's, &'static RapierContextEntityLink>,
25}
26
27impl RapierContextLinkResolver<'_, '_> {
28 pub fn resolve(
33 &self,
34 entity: Entity,
35 link: Option<&RapierContextEntityLink>,
36 commands: &mut Commands,
37 ) -> Option<Entity> {
38 if let Some(link) = link {
39 return Some(link.0);
40 }
41
42 let context = self
43 .child_of
44 .iter_ancestors(entity)
45 .find_map(|ancestor| self.links.get(ancestor).ok().map(|link| link.0))
46 .or_else(|| self.default_context.single().ok())?;
47 commands
48 .entity(entity)
49 .insert(RapierContextEntityLink(context));
50 Some(context)
51 }
52}
53
54pub fn on_add_entity_with_parent(
59 q_add_entity_without_parent: Query<
60 (Entity, &ChildOf),
61 (
62 With<RapierContextEntityLink>,
63 Or<(Changed<RapierContextEntityLink>, Changed<ChildOf>)>,
64 ),
65 >,
66 q_child_of: Query<&ChildOf>,
67 q_physics_world: Query<&RapierContextEntityLink>,
68 mut commands: Commands,
69) {
70 for (ent, child_of) in &q_add_entity_without_parent {
71 let mut parent = Some(child_of.parent());
72 while let Some(parent_entity) = parent {
73 if let Ok(pw) = q_physics_world.get(parent_entity) {
74 if q_physics_world.get(ent).map(|x| x != pw).unwrap_or(true) {
76 remove_old_physics(ent, &mut commands);
77 commands.entity(ent).insert(*pw);
78 }
79 break;
80 }
81 parent = q_child_of.get(parent_entity).ok().map(|x| x.parent());
82 }
83 }
84}
85
86fn remove_old_physics(entity: Entity, commands: &mut Commands) {
88 commands
89 .entity(entity)
90 .remove::<RapierColliderHandle>()
91 .remove::<RapierRigidBodyHandle>()
92 .remove::<RapierMultibodyJointHandle>()
93 .remove::<RapierImpulseJointHandle>()
94 .remove::<RapierSoftBodyHandle>();
95}
96
97pub fn on_change_context(
102 q_changed_contexts: Query<
103 (Entity, Ref<RapierContextEntityLink>),
104 Changed<RapierContextEntityLink>,
105 >,
106 q_children: Query<&Children>,
107 q_physics_context: Query<&RapierContextEntityLink>,
108 q_context: Query<(
109 &RapierContextColliders,
110 &RapierContextJoints,
111 &RapierRigidBodySet,
112 )>,
113 mut commands: Commands,
114) {
115 for (entity, new_physics_context) in &q_changed_contexts {
116 let context = q_context.get(new_physics_context.0);
117 if !context
119 .map(|(colliders, joints, rigidbody_set)| {
120 colliders.entity2collider.contains_key(&entity)
122 || rigidbody_set.entity2body.contains_key(&entity)
123 || rigidbody_set.entity2soft_body.contains_key(&entity)
124 || joints.entity2impulse_joint.contains_key(&entity)
125 || joints.entity2multibody_joint.contains_key(&entity)
126 })
127 .unwrap_or(false)
128 {
129 remove_old_physics(entity, &mut commands);
130 bubble_down_context_change(
131 &mut commands,
132 entity,
133 &q_children,
134 *new_physics_context,
135 &q_physics_context,
136 );
137 }
138 }
139}
140
141fn bubble_down_context_change(
142 commands: &mut Commands,
143 entity: Entity,
144 q_children: &Query<&Children>,
145 new_physics_context: RapierContextEntityLink,
146 q_physics_context: &Query<&RapierContextEntityLink>,
147) {
148 let Ok(children) = q_children.get(entity) else {
149 return;
150 };
151
152 children.iter().for_each(|child| {
153 if q_physics_context
154 .get(child)
155 .map(|x| *x == new_physics_context)
156 .unwrap_or(false)
157 {
158 return;
159 }
160
161 remove_old_physics(child, commands);
162 commands.entity(child).insert(new_physics_context);
163
164 bubble_down_context_change(
165 commands,
166 child,
167 q_children,
168 new_physics_context,
169 q_physics_context,
170 );
171 });
172}
173
174#[cfg(test)]
175mod test {
176 use crate::plugin::{
177 context::{
178 RapierContextColliders, RapierContextEntityLink, RapierContextSimulation,
179 RapierRigidBodySet,
180 },
181 NoUserData, PhysicsSet, RapierPhysicsPlugin,
182 };
183 use crate::prelude::{ActiveEvents, Collider, ContactForceEventThreshold, RigidBody, Sensor};
184 use bevy::prelude::*;
185 use bevy::time::{TimePlugin, TimeUpdateStrategy};
186 use rapier::math::Real;
187
188 #[test]
189 pub fn multi_context_hierarchy_update() {
190 let mut app = App::new();
191 app.add_plugins((
192 TransformPlugin,
193 TimePlugin,
194 RapierPhysicsPlugin::<NoUserData>::default(),
195 ))
196 .add_systems(
197 PostUpdate,
198 setup_physics
199 .run_if(run_once)
200 .before(PhysicsSet::SyncBackend),
201 );
202 app.insert_resource(TimeUpdateStrategy::ManualDuration(
204 std::time::Duration::from_secs_f32(1f32 / 60f32),
205 ));
206 app.finish();
207 app.update();
208 let world = app.world_mut();
210 let mut query = world.query_filtered::<Entity, With<Marker<'R'>>>();
211 for entity in query.iter(world) {
212 world
213 .get::<RapierContextEntityLink>(entity)
214 .unwrap_or_else(|| panic!("no link to rapier context entity from {entity}."));
215 }
216 let new_rapier_context = world.spawn((RapierContextSimulation::default(),)).id();
218 app.update();
221 let world = app.world_mut();
222 let mut query = world.query_filtered::<&mut RapierContextEntityLink, With<Marker<'P'>>>();
223 let mut link_parent = query.single_mut(world).unwrap();
224 link_parent.0 = new_rapier_context;
225 app.update();
226 let world = app.world_mut();
227 let mut query = world.query_filtered::<&RapierContextEntityLink, With<Marker<'C'>>>();
228 let link_child = query.single_mut(world).unwrap();
229 assert_eq!(link_child.0, new_rapier_context);
230 return;
231
232 #[derive(Component)]
233 pub struct Marker<const MARKER: char>;
234
235 #[cfg(feature = "dim3")]
236 fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
237 Collider::cuboid(hx, hy, hz)
238 }
239 #[cfg(feature = "dim2")]
240 fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
241 Collider::cuboid(hx, hy)
242 }
243 pub fn setup_physics(mut commands: Commands) {
244 commands.spawn((
245 Transform::from_xyz(0.0, -1.2, 0.0),
246 cuboid(4.0, 1.0, 1.0),
247 Marker::<'R'>,
248 ));
249
250 commands.spawn((
251 Transform::from_xyz(0.0, 5.0, 0.0),
252 cuboid(4.0, 1.5, 1.0),
253 Sensor,
254 Marker::<'R'>,
255 ));
256
257 commands
258 .spawn((
259 Transform::from_xyz(0.0, 13.0, 0.0),
260 RigidBody::Dynamic,
261 cuboid(0.5, 0.5, 0.5),
262 ActiveEvents::COLLISION_EVENTS,
263 ContactForceEventThreshold(30.0),
264 Marker::<'P'>,
265 Marker::<'R'>,
266 ))
267 .with_children(|child_builder| {
268 child_builder.spawn((
269 Transform::from_xyz(0.0, -1.2, 0.0),
270 cuboid(4.0, 1.0, 1.0),
271 Marker::<'C'>,
272 Marker::<'R'>,
273 ));
274 });
275 }
276 }
277
278 #[test]
279 pub fn children_inherit_the_parent_context_at_initialization() {
280 let mut app = App::new();
281 app.add_plugins((
282 TransformPlugin,
283 TimePlugin,
284 RapierPhysicsPlugin::<NoUserData>::default(),
285 ));
286 app.insert_resource(TimeUpdateStrategy::ManualDuration(
287 std::time::Duration::from_secs_f32(1f32 / 60f32),
288 ));
289 app.finish();
290 app.update();
291
292 let other_context = app
293 .world_mut()
294 .spawn(RapierContextSimulation::default())
295 .id();
296 app.update();
297
298 let mut child = Entity::PLACEHOLDER;
299 let mut grandchild = Entity::PLACEHOLDER;
300 let parent = app
301 .world_mut()
302 .spawn((
303 Transform::default(),
304 RigidBody::Dynamic,
305 RapierContextEntityLink(other_context),
306 ))
307 .with_children(|builder| {
308 child = builder
309 .spawn((Transform::default(), Collider::ball(0.5)))
310 .with_children(|builder| {
311 grandchild = builder
312 .spawn((Transform::default(), Collider::ball(0.5)))
313 .id();
314 })
315 .id();
316 })
317 .id();
318 app.update();
319
320 let world = app.world();
321 for entity in [child, grandchild] {
322 assert_eq!(
323 world.get::<RapierContextEntityLink>(entity).unwrap().0,
324 other_context
325 );
326 }
327 let colliders = world.get::<RapierContextColliders>(other_context).unwrap();
328 assert!(colliders.entity2collider().contains_key(&child));
329 assert!(colliders.entity2collider().contains_key(&grandchild));
330 assert_eq!(
331 colliders.collider_parent(
332 world.get::<RapierRigidBodySet>(other_context).unwrap(),
333 child
334 ),
335 Some(parent)
336 );
337 }
338}