Skip to main content

bevy_rapier3d/plugin/systems/
remove.rs

1use crate::dynamics::ImpulseJoint;
2use crate::dynamics::ImpulseJointDisabled;
3use crate::dynamics::MultibodyJoint;
4use crate::dynamics::RapierImpulseJointHandle;
5use crate::dynamics::RapierMultibodyJointHandle;
6use crate::dynamics::RapierRigidBodyHandle;
7use crate::dynamics::RigidBody;
8use crate::dynamics::SoftBody;
9use crate::dynamics::SoftBodyCluster;
10use crate::geometry::Collider;
11use crate::geometry::ColliderDisabled;
12use crate::geometry::RapierColliderHandle;
13use crate::plugin::context::{
14    RapierContextColliders, RapierContextJoints, RapierContextSimulation, RapierRigidBodySet,
15};
16#[cfg(feature = "dim3")]
17use crate::prelude::GyroscopicForces;
18use crate::prelude::{
19    ActiveCollisionTypes, ActiveEvents, ActiveHooks, AdditionalMassProperties,
20    AdditionalPgsIterations, AdditionalSolverIterations, AllowFastRotation, Ccd,
21    ColliderMassProperties, CollisionGroups, ContactForceEventThreshold, ContactSkin, Damping,
22    Dominance, ExternalForce, Friction, GravityScale, LockedAxes, MassModifiedEvent,
23    MassProperties, ReadColliderMassProperties, Restitution, RigidBodyDisabled, Sensor, Sleeping,
24    SoftCcd, SolverGroups,
25};
26use bevy::ecs::query::IterQueryData;
27use bevy::prelude::*;
28
29/// System responsible for removing from Rapier the rigid-bodies/colliders/joints which had
30/// their related `bevy_rapier` components removed by the user (through component removal or
31/// despawn).
32pub fn sync_removals(
33    mut commands: Commands,
34    mut context_writer: Query<(
35        &mut RapierContextSimulation,
36        &mut RapierContextColliders,
37        &mut RapierContextJoints,
38        &mut RapierRigidBodySet,
39    )>,
40    // Sometimes a Remove immediately followed by Add happens. These `q_has_*` queries prevent that immediate Add
41    // from being removed by this system by verifying it's still removed.
42    (
43        q_has_rigidbody_handle,
44        q_has_collider_handle,
45        q_has_multibody_joint_handle,
46        q_has_impulse_joint_handle,
47    ): (
48        Query<(), With<RapierRigidBodyHandle>>,
49        Query<(), With<RapierColliderHandle>>,
50        Query<(), With<RapierMultibodyJointHandle>>,
51        Query<(), With<RapierImpulseJointHandle>>,
52    ),
53    mut removed_bodies: RemovedComponents<RapierRigidBodyHandle>,
54    mut removed_colliders: RemovedComponents<RapierColliderHandle>,
55    mut removed_impulse_joints: RemovedComponents<RapierImpulseJointHandle>,
56    mut removed_multibody_joints: RemovedComponents<RapierMultibodyJointHandle>,
57    orphan_bodies: Query<
58        Entity,
59        (
60            With<RapierRigidBodyHandle>,
61            Without<RigidBody>,
62            Without<SoftBodyCluster>,
63        ),
64    >,
65    orphan_colliders: Query<Entity, (With<RapierColliderHandle>, Without<Collider>)>,
66    orphan_impulse_joints: Query<Entity, (With<RapierImpulseJointHandle>, Without<ImpulseJoint>)>,
67    orphan_multibody_joints: Query<
68        Entity,
69        (With<RapierMultibodyJointHandle>, Without<MultibodyJoint>),
70    >,
71
72    mut removed_sensors: RemovedComponents<Sensor>,
73    mut removed_rigid_body_disabled: RemovedComponents<RigidBodyDisabled>,
74    mut removed_colliders_disabled: RemovedComponents<ColliderDisabled>,
75    (mut removed_impulse_joints_disabled, mut impulse_joints): (
76        RemovedComponents<ImpulseJointDisabled>,
77        Query<&mut ImpulseJoint, Without<ImpulseJointDisabled>>,
78    ),
79
80    mut mass_modified: MessageWriter<MassModifiedEvent>,
81) {
82    /*
83     * Rigid-bodies removal detection.
84     */
85    for entity in removed_bodies
86        .read()
87        .filter(|e| !q_has_rigidbody_handle.contains(*e))
88    {
89        let Some(((mut context, mut context_colliders, mut joints, mut rigidbody_set), handle)) =
90            find_context(&mut context_writer, |res| res.3.entity2body.remove(&entity))
91        else {
92            continue;
93        };
94        let context = &mut *context;
95        let joints = &mut *joints;
96        let rigidbody_set = &mut *rigidbody_set;
97
98        let _ = rigidbody_set.last_body_transform_set.remove(&handle);
99        rigidbody_set.bodies.remove(
100            handle,
101            &mut context.islands,
102            &mut context_colliders.colliders,
103            &mut joints.impulse_joints,
104            &mut joints.multibody_joints,
105            &mut rigidbody_set.soft_bodies,
106            false,
107        );
108    }
109
110    for entity in orphan_bodies.iter() {
111        if let Some(((mut context, mut context_colliders, mut joints, mut rigidbody_set), handle)) =
112            find_context(&mut context_writer, |res| res.3.entity2body.remove(&entity))
113        {
114            let context = &mut *context;
115            let joints = &mut *joints;
116            let rigidbody_set = &mut *rigidbody_set;
117            let _ = rigidbody_set.last_body_transform_set.remove(&handle);
118            rigidbody_set.bodies.remove(
119                handle,
120                &mut context.islands,
121                &mut context_colliders.colliders,
122                &mut joints.impulse_joints,
123                &mut joints.multibody_joints,
124                &mut rigidbody_set.soft_bodies,
125                false,
126            );
127        }
128        commands.entity(entity).remove::<RapierRigidBodyHandle>();
129    }
130
131    /*
132     * Collider removal detection.
133     */
134    for entity in removed_colliders
135        .read()
136        .filter(|e| !q_has_collider_handle.contains(*e))
137    {
138        let Some(((mut context, mut context_colliders, _, mut rigidbody_set), handle)) =
139            find_context(&mut context_writer, |res| {
140                res.1.entity2collider.remove(&entity)
141            })
142        else {
143            continue;
144        };
145        let context = &mut *context;
146        let rigidbody_set = &mut *rigidbody_set;
147        if let Some(parent) = context_colliders.collider_parent(rigidbody_set, entity) {
148            mass_modified.write(parent.into());
149        }
150
151        context_colliders.colliders.remove(
152            handle,
153            &mut context.islands,
154            &mut rigidbody_set.bodies,
155            &mut rigidbody_set.soft_bodies,
156            true,
157        );
158        context.deleted_colliders.insert(handle, entity);
159    }
160
161    for entity in orphan_colliders.iter() {
162        if let Some(((mut context, mut context_colliders, _, mut rigidbody_set), handle)) =
163            find_context(&mut context_writer, |res| {
164                res.1.entity2collider.remove(&entity)
165            })
166        {
167            let context = &mut *context;
168            let context_colliders = &mut *context_colliders;
169            let rigidbody_set = &mut *rigidbody_set;
170            if let Some(parent) = context_colliders.collider_parent(rigidbody_set, entity) {
171                mass_modified.write(parent.into());
172            }
173
174            context_colliders.colliders.remove(
175                handle,
176                &mut context.islands,
177                &mut rigidbody_set.bodies,
178                &mut rigidbody_set.soft_bodies,
179                true,
180            );
181            context.deleted_colliders.insert(handle, entity);
182        }
183        commands.entity(entity).remove::<RapierColliderHandle>();
184    }
185
186    /*
187     * Impulse joint removal detection.
188     */
189    for entity in removed_impulse_joints
190        .read()
191        .filter(|e| !q_has_impulse_joint_handle.contains(*e))
192    {
193        let Some(((_, _, mut joints, _), handle)) = find_context(&mut context_writer, |res| {
194            res.2.entity2impulse_joint.remove(&entity)
195        }) else {
196            continue;
197        };
198        joints.impulse_joints.remove(handle, true);
199    }
200
201    for entity in orphan_impulse_joints.iter() {
202        if let Some(((_, _, mut joints, _), handle)) = find_context(&mut context_writer, |res| {
203            res.2.entity2impulse_joint.remove(&entity)
204        }) {
205            joints.impulse_joints.remove(handle, true);
206        }
207        commands.entity(entity).remove::<RapierImpulseJointHandle>();
208    }
209
210    /*
211     * Multibody joint removal detection.
212     */
213    for entity in removed_multibody_joints
214        .read()
215        .filter(|e| !q_has_multibody_joint_handle.contains(*e))
216    {
217        let Some(((_, _, mut joints, _), handle)) = find_context(&mut context_writer, |res| {
218            res.2.entity2multibody_joint.remove(&entity)
219        }) else {
220            continue;
221        };
222        joints.multibody_joints.remove(handle, true);
223    }
224
225    for entity in orphan_multibody_joints.iter() {
226        if let Some(((_, _, mut joints, _), handle)) = find_context(&mut context_writer, |res| {
227            res.2.entity2multibody_joint.remove(&entity)
228        }) {
229            joints.multibody_joints.remove(handle, true);
230        }
231        commands
232            .entity(entity)
233            .remove::<RapierMultibodyJointHandle>();
234    }
235
236    /*
237     * Marker components removal detection.
238     */
239    for entity in removed_sensors.read() {
240        for (_, mut colliders, _, rigidbody_set) in context_writer.iter_mut() {
241            // A soft body entity stands for all its surface colliders.
242            let handles = rigidbody_set
243                .soft_body_colliders(&colliders.colliders, entity)
244                .or_else(|| colliders.entity2collider.get(&entity).map(|h| vec![*h]));
245            if let Some(handles) = handles {
246                for handle in handles {
247                    if let Some(co) = colliders.colliders.get_mut(handle) {
248                        co.set_sensor(false);
249                    }
250                }
251                break;
252            }
253        }
254    }
255
256    for entity in removed_colliders_disabled.read() {
257        if let Some((mut context, handle)) = find_context(&mut context_writer, |context| {
258            context.1.entity2collider.get(&entity).copied()
259        }) {
260            if let Some(co) = context.1.colliders.get_mut(handle) {
261                co.set_enabled(true);
262            }
263        }
264    }
265
266    // Flag the joint as changed so `apply_joint_user_changes` enables it again.
267    for entity in removed_impulse_joints_disabled.read() {
268        if let Ok(mut joint) = impulse_joints.get_mut(entity) {
269            joint.set_changed();
270        }
271    }
272
273    for entity in removed_rigid_body_disabled.read() {
274        if let Some(((_, _, _, mut rigidbody_set), handle)) =
275            find_context(&mut context_writer, |res| {
276                res.3.entity2body.get(&entity).copied()
277            })
278        {
279            if let Some(rb) = rigidbody_set.bodies.get_mut(handle) {
280                rb.set_enabled(true);
281            }
282        }
283    }
284}
285
286/// Removal detection for a single component: the entities it was removed from, paired with a
287/// query checking if it was re-inserted since.
288type RemovedComponent<'w, 's, T> = (RemovedComponents<'w, 's, T>, Query<'w, 's, (), With<T>>);
289
290/// System responsible for resetting the Rapier rigid-body properties to their default values
291/// when the corresponding `bevy_rapier` component is removed by the user.
292pub fn reset_removed_rigid_body_components(
293    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
294    (mut removed_forces, mut removed_damping, mut removed_gravity_scale, mut removed_dominance): (
295        RemovedComponent<ExternalForce>,
296        RemovedComponent<Damping>,
297        RemovedComponent<GravityScale>,
298        RemovedComponent<Dominance>,
299    ),
300    (mut removed_ccd, mut removed_soft_ccd, mut removed_fast_rotation): (
301        RemovedComponent<Ccd>,
302        RemovedComponent<SoftCcd>,
303        RemovedComponent<AllowFastRotation>,
304    ),
305    (mut removed_locked_axes, mut removed_mass_props, mut removed_sleeping): (
306        RemovedComponent<LockedAxes>,
307        RemovedComponent<AdditionalMassProperties>,
308        RemovedComponent<Sleeping>,
309    ),
310    (mut removed_solver_iters, mut removed_pgs_iters): (
311        RemovedComponent<AdditionalSolverIterations>,
312        RemovedComponent<AdditionalPgsIterations>,
313    ),
314    #[cfg(feature = "dim3")] mut removed_gyroscopic_forces: RemovedComponent<GyroscopicForces>,
315    mut mass_modified: MessageWriter<MassModifiedEvent>,
316) {
317    let sets = &mut rigid_body_sets;
318    reset_removed(&mut removed_forces, sets, |_, rb| {
319        rb.reset_forces(true);
320        rb.reset_torques(true);
321    });
322    reset_removed(&mut removed_damping, sets, |_, rb| {
323        let default = Damping::default();
324        rb.set_linear_damping(default.linear_damping);
325        rb.set_angular_damping(default.angular_damping);
326    });
327    reset_removed(&mut removed_gravity_scale, sets, |_, rb| {
328        rb.set_gravity_scale(GravityScale::default().0, true);
329    });
330    reset_removed(&mut removed_dominance, sets, |_, rb| {
331        rb.set_dominance_group(Dominance::default().groups);
332    });
333    reset_removed(&mut removed_ccd, sets, |_, rb| {
334        rb.enable_ccd(Ccd::default().enabled);
335    });
336    reset_removed(&mut removed_soft_ccd, sets, |_, rb| {
337        rb.set_soft_ccd_prediction(SoftCcd::default().prediction);
338    });
339    reset_removed(&mut removed_fast_rotation, sets, |_, rb| {
340        rb.set_allow_fast_rotation(false);
341    });
342    reset_removed(&mut removed_locked_axes, sets, |_, rb| {
343        rb.set_locked_axes(LockedAxes::default().into(), true);
344    });
345    reset_removed(&mut removed_mass_props, sets, |entity, rb| {
346        rb.set_additional_mass_properties(MassProperties::default().into_rapier(), true);
347        mass_modified.write(entity.into());
348    });
349    reset_removed(&mut removed_sleeping, sets, |_, rb| {
350        let default = Sleeping::default();
351        let activation = rb.activation_mut();
352        activation.normalized_linear_threshold = default.normalized_linear_threshold;
353        activation.angular_threshold = default.angular_threshold;
354        activation.time_until_sleep = default.time_until_sleep;
355    });
356    reset_removed(&mut removed_solver_iters, sets, |_, rb| {
357        rb.set_additional_solver_iterations(AdditionalSolverIterations::default().0);
358    });
359    reset_removed(&mut removed_pgs_iters, sets, |_, rb| {
360        rb.set_additional_pgs_iterations(AdditionalPgsIterations::default().0);
361    });
362    #[cfg(feature = "dim3")]
363    reset_removed(&mut removed_gyroscopic_forces, sets, |_, rb| {
364        rb.enable_gyroscopic_forces(GyroscopicForces::default().enabled);
365    });
366}
367
368/// Calls `reset` on the Rapier rigid-body of each entity `T` was removed from, unless `T` was
369/// inserted back since.
370fn reset_removed<T: Component>(
371    (removed, still_present): &mut RemovedComponent<T>,
372    rigid_body_sets: &mut Query<&mut RapierRigidBodySet>,
373    mut reset: impl FnMut(Entity, &mut rapier::dynamics::RigidBody),
374) {
375    for entity in removed.read() {
376        if still_present.contains(entity) {
377            continue;
378        }
379
380        for mut rigid_body_set in rigid_body_sets.iter_mut() {
381            if let Some(handle) = rigid_body_set.entity2body.get(&entity).copied() {
382                if let Some(rb) = rigid_body_set.bodies.get_mut(handle) {
383                    reset(entity, rb);
384                }
385                break;
386            }
387        }
388    }
389}
390
391/// System responsible for resetting the Rapier collider properties to their default values when
392/// the corresponding `bevy_rapier` component is removed by the user.
393///
394/// The default values are the ones a collider gets when inserted without that component.
395pub fn reset_removed_collider_components(
396    mut contexts: Query<(&mut RapierContextColliders, &RapierRigidBodySet)>,
397    soft_bodies: Query<&SoftBody>,
398    (mut removed_friction, mut removed_restitution, mut removed_contact_skin): (
399        RemovedComponent<Friction>,
400        RemovedComponent<Restitution>,
401        RemovedComponent<ContactSkin>,
402    ),
403    (mut removed_collision_groups, mut removed_solver_groups): (
404        RemovedComponent<CollisionGroups>,
405        RemovedComponent<SolverGroups>,
406    ),
407    (mut removed_events, mut removed_hooks, mut removed_collision_types): (
408        RemovedComponent<ActiveEvents>,
409        RemovedComponent<ActiveHooks>,
410        RemovedComponent<ActiveCollisionTypes>,
411    ),
412    (mut removed_force_threshold, mut removed_mass_props): (
413        RemovedComponent<ContactForceEventThreshold>,
414        RemovedComponent<ColliderMassProperties>,
415    ),
416    mut read_mass_props: Query<&mut ReadColliderMassProperties>,
417    mut mass_modified: MessageWriter<MassModifiedEvent>,
418) {
419    let ctxts = &mut contexts;
420    let sbs = &soft_bodies;
421    reset_removed_collider(&mut removed_friction, ctxts, sbs, |_, co, _, defaults| {
422        co.set_friction(defaults.friction);
423        co.set_friction_combine_rule(defaults.friction_combine_rule);
424    });
425    reset_removed_collider(
426        &mut removed_restitution,
427        ctxts,
428        sbs,
429        |_, co, _, defaults| {
430            co.set_restitution(defaults.restitution);
431            co.set_restitution_combine_rule(defaults.restitution_combine_rule);
432        },
433    );
434    reset_removed_collider(
435        &mut removed_contact_skin,
436        ctxts,
437        sbs,
438        |entity, co, rigid_body_set, defaults| {
439            // The surface colliders of a soft body are as thick as its particles.
440            let skin = rigid_body_set
441                .soft_body(entity)
442                .map_or(defaults.contact_skin, |sb| sb.particle_radius());
443            co.set_contact_skin(skin);
444        },
445    );
446    reset_removed_collider(
447        &mut removed_collision_groups,
448        ctxts,
449        sbs,
450        |_, co, _, defaults| {
451            co.set_collision_groups(defaults.collision_groups);
452        },
453    );
454    reset_removed_collider(
455        &mut removed_solver_groups,
456        ctxts,
457        sbs,
458        |_, co, _, defaults| {
459            co.set_solver_groups(defaults.solver_groups);
460        },
461    );
462    reset_removed_collider(&mut removed_events, ctxts, sbs, |_, co, _, defaults| {
463        co.set_active_events(defaults.active_events);
464    });
465    reset_removed_collider(&mut removed_hooks, ctxts, sbs, |_, co, _, defaults| {
466        co.set_active_hooks(defaults.active_hooks);
467    });
468    reset_removed_collider(
469        &mut removed_collision_types,
470        ctxts,
471        sbs,
472        |_, co, _, defaults| {
473            co.set_active_collision_types(defaults.active_collision_types);
474        },
475    );
476    reset_removed_collider(
477        &mut removed_force_threshold,
478        ctxts,
479        sbs,
480        |_, co, _, defaults| {
481            co.set_contact_force_event_threshold(defaults.contact_force_event_threshold);
482        },
483    );
484    reset_removed_collider(
485        &mut removed_mass_props,
486        ctxts,
487        sbs,
488        |entity, co, rigid_body_set, _| {
489            // The surface colliders of a soft body have no mass.
490            if rigid_body_set.soft_body(entity).is_some() {
491                return;
492            }
493            co.set_density(rapier::geometry::ColliderBuilder::default_density());
494
495            if let Ok(mut read_mass_props) = read_mass_props.get_mut(entity) {
496                read_mass_props.set_if_neq(ReadColliderMassProperties::from_rapier(co));
497            }
498
499            if let Some(body_entity) = co
500                .parent()
501                .and_then(|h| rigid_body_set.rigid_body_entity(h))
502            {
503                mass_modified.write(body_entity.into());
504            }
505        },
506    );
507}
508
509/// Calls `reset` on the Rapier collider of each entity `T` was removed from, unless `T` was
510/// inserted back since.
511///
512/// For a soft body entity, `reset` is called on each of its surface colliders (see
513/// [`RapierRigidBodySet::soft_body_colliders`]), with the collider template of its builder as
514/// defaults.
515fn reset_removed_collider<T: Component>(
516    (removed, still_present): &mut RemovedComponent<T>,
517    contexts: &mut Query<(&mut RapierContextColliders, &RapierRigidBodySet)>,
518    soft_bodies: &Query<&SoftBody>,
519    mut reset: impl FnMut(
520        Entity,
521        &mut rapier::geometry::Collider,
522        &RapierRigidBodySet,
523        &rapier::geometry::ColliderBuilder,
524    ),
525) {
526    let defaults = rapier::geometry::ColliderBuilder::default();
527    for entity in removed.read() {
528        if still_present.contains(entity) {
529            continue;
530        }
531
532        for (mut context_colliders, rigid_body_set) in contexts.iter_mut() {
533            if let Some(handles) =
534                rigid_body_set.soft_body_colliders(&context_colliders.colliders, entity)
535            {
536                let template = soft_bodies
537                    .get(entity)
538                    .ok()
539                    .and_then(|sb| sb.builder.collider_template.as_ref())
540                    .unwrap_or(&defaults);
541                for handle in handles {
542                    if let Some(co) = context_colliders.colliders.get_mut(handle) {
543                        reset(entity, co, rigid_body_set, template);
544                    }
545                }
546                break;
547            }
548            if let Some(handle) = context_colliders.entity2collider.get(&entity).copied() {
549                if let Some(co) = context_colliders.colliders.get_mut(handle) {
550                    reset(entity, co, rigid_body_set, &defaults);
551                }
552                break;
553            }
554        }
555    }
556}
557
558fn find_context<'a, TReturn, TQueryParams: IterQueryData>(
559    context_writer: &'a mut Query<TQueryParams>,
560    item_finder: impl Fn(&mut TQueryParams::Item<'_, '_>) -> Option<TReturn>,
561) -> Option<(TQueryParams::Item<'a, 'a>, TReturn)> {
562    let ret: Option<(TQueryParams::Item<'_, '_>, TReturn)> = context_writer
563        .iter_mut()
564        .find_map(|mut context| item_finder(&mut context).map(|handle| (context, handle)));
565    ret
566}