bevy_rapier2d/plugin/systems/
multiple_rapier_contexts.rs1use crate::dynamics::{
4 RapierImpulseJointHandle, RapierMultibodyJointHandle, RapierRigidBodyHandle,
5};
6use crate::geometry::RapierColliderHandle;
7use crate::plugin::context::RapierRigidBodySet;
8use crate::plugin::context::{
9 RapierContextColliders, RapierContextEntityLink, RapierContextJoints,
10};
11use bevy::prelude::*;
12
13pub fn on_add_entity_with_parent(
18 q_add_entity_without_parent: Query<
19 (Entity, &ChildOf),
20 (
21 With<RapierContextEntityLink>,
22 Or<(Changed<RapierContextEntityLink>, Changed<ChildOf>)>,
23 ),
24 >,
25 q_child_of: Query<&ChildOf>,
26 q_physics_world: Query<&RapierContextEntityLink>,
27 mut commands: Commands,
28) {
29 for (ent, child_of) in &q_add_entity_without_parent {
30 let mut parent = Some(child_of.parent());
31 while let Some(parent_entity) = parent {
32 if let Ok(pw) = q_physics_world.get(parent_entity) {
33 if q_physics_world.get(ent).map(|x| x != pw).unwrap_or(true) {
35 remove_old_physics(ent, &mut commands);
36 commands.entity(ent).insert(*pw);
37 }
38 break;
39 }
40 parent = q_child_of.get(parent_entity).ok().map(|x| x.parent());
41 }
42 }
43}
44
45fn remove_old_physics(entity: Entity, commands: &mut Commands) {
47 commands
48 .entity(entity)
49 .remove::<RapierColliderHandle>()
50 .remove::<RapierRigidBodyHandle>()
51 .remove::<RapierMultibodyJointHandle>()
52 .remove::<RapierImpulseJointHandle>();
53}
54
55pub fn on_change_context(
60 q_changed_contexts: Query<
61 (Entity, Ref<RapierContextEntityLink>),
62 Changed<RapierContextEntityLink>,
63 >,
64 q_children: Query<&Children>,
65 q_physics_context: Query<&RapierContextEntityLink>,
66 q_context: Query<(
67 &RapierContextColliders,
68 &RapierContextJoints,
69 &RapierRigidBodySet,
70 )>,
71 mut commands: Commands,
72) {
73 for (entity, new_physics_context) in &q_changed_contexts {
74 let context = q_context.get(new_physics_context.0);
75 if !context
77 .map(|(colliders, joints, rigidbody_set)| {
78 colliders.entity2collider.contains_key(&entity)
80 || rigidbody_set.entity2body.contains_key(&entity)
81 || joints.entity2impulse_joint.contains_key(&entity)
82 || joints.entity2multibody_joint.contains_key(&entity)
83 })
84 .unwrap_or(false)
85 {
86 remove_old_physics(entity, &mut commands);
87 bubble_down_context_change(
88 &mut commands,
89 entity,
90 &q_children,
91 *new_physics_context,
92 &q_physics_context,
93 );
94 }
95 }
96}
97
98fn bubble_down_context_change(
99 commands: &mut Commands,
100 entity: Entity,
101 q_children: &Query<&Children>,
102 new_physics_context: RapierContextEntityLink,
103 q_physics_context: &Query<&RapierContextEntityLink>,
104) {
105 let Ok(children) = q_children.get(entity) else {
106 return;
107 };
108
109 children.iter().for_each(|child| {
110 if q_physics_context
111 .get(child)
112 .map(|x| *x == new_physics_context)
113 .unwrap_or(false)
114 {
115 return;
116 }
117
118 remove_old_physics(child, commands);
119 commands.entity(child).insert(new_physics_context);
120
121 bubble_down_context_change(
122 commands,
123 child,
124 q_children,
125 new_physics_context,
126 q_physics_context,
127 );
128 });
129}
130
131#[cfg(test)]
132mod test {
133 use crate::plugin::{
134 context::{RapierContextEntityLink, RapierContextSimulation},
135 NoUserData, PhysicsSet, RapierPhysicsPlugin,
136 };
137 use crate::prelude::{ActiveEvents, Collider, ContactForceEventThreshold, RigidBody, Sensor};
138 use bevy::prelude::*;
139 use bevy::time::{TimePlugin, TimeUpdateStrategy};
140 use rapier::math::Real;
141
142 #[test]
143 pub fn multi_context_hierarchy_update() {
144 let mut app = App::new();
145 app.add_plugins((
146 TransformPlugin,
147 TimePlugin,
148 RapierPhysicsPlugin::<NoUserData>::default(),
149 ))
150 .add_systems(
151 PostUpdate,
152 setup_physics
153 .run_if(run_once)
154 .before(PhysicsSet::SyncBackend),
155 );
156 app.insert_resource(TimeUpdateStrategy::ManualDuration(
158 std::time::Duration::from_secs_f32(1f32 / 60f32),
159 ));
160 app.finish();
161 app.update();
162 let world = app.world_mut();
164 let mut query = world.query_filtered::<Entity, With<Marker<'R'>>>();
165 for entity in query.iter(world) {
166 world
167 .get::<RapierContextEntityLink>(entity)
168 .unwrap_or_else(|| panic!("no link to rapier context entity from {entity}."));
169 }
170 let new_rapier_context = world.spawn((RapierContextSimulation::default(),)).id();
172 app.update();
175 let world = app.world_mut();
176 let mut query = world.query_filtered::<&mut RapierContextEntityLink, With<Marker<'P'>>>();
177 let mut link_parent = query.single_mut(world).unwrap();
178 link_parent.0 = new_rapier_context;
179 app.update();
180 let world = app.world_mut();
181 let mut query = world.query_filtered::<&RapierContextEntityLink, With<Marker<'C'>>>();
182 let link_child = query.single_mut(world).unwrap();
183 assert_eq!(link_child.0, new_rapier_context);
184 return;
185
186 #[derive(Component)]
187 pub struct Marker<const MARKER: char>;
188
189 #[cfg(feature = "dim3")]
190 fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
191 Collider::cuboid(hx, hy, hz)
192 }
193 #[cfg(feature = "dim2")]
194 fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
195 Collider::cuboid(hx, hy)
196 }
197 pub fn setup_physics(mut commands: Commands) {
198 commands.spawn((
199 Transform::from_xyz(0.0, -1.2, 0.0),
200 cuboid(4.0, 1.0, 1.0),
201 Marker::<'R'>,
202 ));
203
204 commands.spawn((
205 Transform::from_xyz(0.0, 5.0, 0.0),
206 cuboid(4.0, 1.5, 1.0),
207 Sensor,
208 Marker::<'R'>,
209 ));
210
211 commands
212 .spawn((
213 Transform::from_xyz(0.0, 13.0, 0.0),
214 RigidBody::Dynamic,
215 cuboid(0.5, 0.5, 0.5),
216 ActiveEvents::COLLISION_EVENTS,
217 ContactForceEventThreshold(30.0),
218 Marker::<'P'>,
219 Marker::<'R'>,
220 ))
221 .with_children(|child_builder| {
222 child_builder.spawn((
223 Transform::from_xyz(0.0, -1.2, 0.0),
224 cuboid(4.0, 1.0, 1.0),
225 Marker::<'C'>,
226 Marker::<'R'>,
227 ));
228 });
229 }
230 }
231}