Skip to main content

bevy_rapier3d/plugin/systems/
soft_body.rs

1//! Systems synchronizing the soft-body components with Rapier.
2
3#[cfg(feature = "fem")]
4use crate::dynamics::SoftBodyElasticitySolver;
5use crate::dynamics::{
6    RapierRigidBodyHandle, RapierSoftBody, RapierSoftBodyHandle, RapierSoftBodyTearEvent, SoftBody,
7    SoftBodyAttachment, SoftBodyAttachments, SoftBodyBuilder, SoftBodyCluster,
8    SoftBodyClusterKinematicTarget, SoftBodyClusterMaterial, SoftBodyClusterPinned,
9    SoftBodyClusterShapeMatching, SoftBodyDisabled, SoftBodyExternalForce, SoftBodyExternalImpulse,
10    SoftBodyKinematicTargets, SoftBodyMaterial, SoftBodyPinnedParticles, SoftBodyState,
11    SoftBodyVolumeFactor,
12};
13use crate::geometry::{
14    ActiveCollisionTypes, ActiveEvents, ActiveHooks, CollisionGroups, ContactForceEventThreshold,
15    ContactSkin, Friction, RapierColliderHandle, Restitution, Sensor, SolverGroups,
16};
17use crate::pipeline::{
18    SoftBodyClusterSplit, SoftBodyJointMove, SoftBodyTearEvent, SoftBodyTearPiece,
19};
20use crate::plugin::context::{
21    RapierContextColliders, RapierContextEntityLink, RapierContextJoints, RapierContextSimulation,
22    RapierRigidBodySet,
23};
24use crate::utils;
25use bevy::ecs::query::QueryItem;
26use bevy::prelude::*;
27
28use super::RapierContextLinkResolver;
29use rapier::dynamics::{RigidBodyHandle, RigidBodySet, SoftBodyHandle};
30use rapier::geometry::Collider as RapierCollider;
31use std::collections::HashMap;
32
33/// The collider components of a soft body entity, applied to its surface colliders.
34pub type SoftBodyColliderComponents<'a> = (
35    Option<&'a Friction>,
36    Option<&'a Restitution>,
37    Option<&'a CollisionGroups>,
38    Option<&'a SolverGroups>,
39    Option<&'a ActiveEvents>,
40    Option<&'a ActiveHooks>,
41    Option<&'a ActiveCollisionTypes>,
42    Option<&'a ContactForceEventThreshold>,
43    Option<&'a ContactSkin>,
44    Has<Sensor>,
45);
46
47/// The filter matching the soft bodies whose [`SoftBodyColliderComponents`] must be applied.
48type SoftBodyColliderChanges = (
49    With<RapierSoftBodyHandle>,
50    Or<(
51        Changed<Friction>,
52        Changed<Restitution>,
53        Changed<CollisionGroups>,
54        Changed<SolverGroups>,
55        Changed<ActiveEvents>,
56        Changed<ActiveHooks>,
57        Changed<ActiveCollisionTypes>,
58        Changed<ContactForceEventThreshold>,
59        Changed<ContactSkin>,
60        Changed<Sensor>,
61        Added<RapierSoftBodyHandle>,
62    )>,
63);
64
65/// The components of a soft body entity read when the soft body is created.
66pub type SoftBodyComponents<'a> = (
67    (Entity, Option<&'a RapierContextEntityLink>),
68    &'a SoftBody,
69    Option<&'a GlobalTransform>,
70    Option<&'a SoftBodyMaterial>,
71    Option<&'a SoftBodyVolumeFactor>,
72    Option<&'a SoftBodyPinnedParticles>,
73    Option<&'a SoftBodyDisabled>,
74);
75
76/// The state written back into [`SoftBodyState`].
77fn soft_body_state(sb: &RapierSoftBody) -> SoftBodyState {
78    SoftBodyState {
79        center_of_mass: sb.center_of_mass(),
80        is_sleeping: sb.is_sleeping(),
81        is_enabled: sb.is_enabled(),
82        topology_version: sb.topology_version(),
83        num_particles: sb.num_particles(),
84    }
85}
86
87/// Applies the collider components of a soft body entity to one of its surface colliders.
88fn apply_collider_components(
89    co: &mut RapierCollider,
90    (
91        friction,
92        restitution,
93        collision_groups,
94        solver_groups,
95        active_events,
96        active_hooks,
97        active_collision_types,
98        force_threshold,
99        contact_skin,
100        sensor,
101    ): QueryItem<SoftBodyColliderComponents>,
102) {
103    if let Some(friction) = friction {
104        co.set_friction(friction.coefficient);
105        co.set_friction_combine_rule(friction.combine_rule.into());
106    }
107    if let Some(restitution) = restitution {
108        co.set_restitution(restitution.coefficient);
109        co.set_restitution_combine_rule(restitution.combine_rule.into());
110    }
111    if let Some(groups) = collision_groups {
112        co.set_collision_groups((*groups).into());
113    }
114    if let Some(groups) = solver_groups {
115        co.set_solver_groups((*groups).into());
116    }
117    if let Some(events) = active_events {
118        co.set_active_events((*events).into());
119    }
120    if let Some(hooks) = active_hooks {
121        co.set_active_hooks((*hooks).into());
122    }
123    if let Some(types) = active_collision_types {
124        co.set_active_collision_types((*types).into());
125    }
126    if let Some(threshold) = force_threshold {
127        co.set_contact_force_event_threshold(threshold.0);
128    }
129    if let Some(skin) = contact_skin {
130        co.set_contact_skin(skin.0);
131    }
132    if sensor {
133        co.set_sensor(true);
134    }
135}
136
137/// Transforms the world-space data of a soft-body builder by `transform`.
138fn transform_builder(builder: &mut SoftBodyBuilder, transform: &GlobalTransform) {
139    let affine = transform.affine();
140    let apply = |p: &mut crate::math::Vect| {
141        #[cfg(feature = "dim2")]
142        {
143            *p = affine.transform_point3(p.extend(0.0)).truncate();
144        }
145        #[cfg(feature = "dim3")]
146        {
147            *p = affine.transform_point3(*p);
148        }
149    };
150    builder.positions.iter_mut().for_each(apply);
151    if let Some((vertices, _)) = &mut builder.skin {
152        vertices.iter_mut().for_each(apply);
153    }
154}
155
156/// Pins exactly the particles listed in `pinned` (invalid indices are ignored).
157fn set_pinned_particles(sb: &mut RapierSoftBody, pinned: &[u32]) {
158    let mut target = vec![false; sb.num_particles()];
159    for &i in pinned {
160        if let Some(pin) = target.get_mut(i as usize) {
161            *pin = true;
162        }
163    }
164    for (i, pin) in target.into_iter().enumerate() {
165        if sb.particles()[i].is_pinned() != pin {
166            sb.set_particle_pinned(i, pin);
167        }
168    }
169}
170
171/// The particles of the clusters of `sb` with a [`SoftBodyClusterPinned`] component.
172fn pinned_cluster_particles(
173    sb: &RapierSoftBody,
174    entity2body: &HashMap<Entity, RigidBodyHandle>,
175    pinned_clusters: &Query<Entity, With<SoftBodyClusterPinned>>,
176) -> Vec<u32> {
177    let mut particles = vec![];
178    for entity in pinned_clusters {
179        let Some(proxy) = entity2body.get(&entity) else {
180            continue;
181        };
182        if let Some((_, cluster)) = sb.live_clusters().find(|(_, c)| c.proxy() == *proxy) {
183            particles.extend_from_slice(cluster.particles());
184        }
185    }
186    particles
187}
188
189/// Makes the particle attachments of `sb` match `attachments`.
190///
191/// Only the particles whose set of attached bodies changed are detached and attached again, so
192/// the other attachments keep their anchor. Returns `false` if some bodies don't exist yet.
193fn update_attachments(
194    sb: &mut RapierSoftBody,
195    attachments: &[SoftBodyAttachment],
196    bodies: &RigidBodySet,
197    entity2body: &HashMap<Entity, RigidBodyHandle>,
198) -> bool {
199    let mut complete = true;
200    let mut desired: HashMap<u32, Vec<RigidBodyHandle>> = HashMap::new();
201    for attachment in attachments {
202        let Some(body) = entity2body.get(&attachment.body) else {
203            // The rigid-body may not be created yet: try again next frame.
204            complete = false;
205            continue;
206        };
207        if (attachment.particle as usize) < sb.num_particles() {
208            desired.entry(attachment.particle).or_default().push(*body);
209        }
210    }
211    let mut current: HashMap<u32, Vec<RigidBodyHandle>> = HashMap::new();
212    for attachment in sb.particle_attachments() {
213        current
214            .entry(attachment.particle)
215            .or_default()
216            .push(attachment.body);
217    }
218    let sorted = |mut bodies: Vec<RigidBodyHandle>| {
219        bodies.sort_by_key(|h| h.into_raw_parts());
220        bodies
221    };
222    let mut particles: Vec<u32> = desired.keys().chain(current.keys()).copied().collect();
223    particles.sort_unstable();
224    particles.dedup();
225    for particle in particles {
226        let wanted = sorted(desired.remove(&particle).unwrap_or_default());
227        let existing = sorted(current.remove(&particle).unwrap_or_default());
228        if wanted != existing {
229            sb.detach_particle(particle as usize);
230            for body in wanted {
231                sb.attach_particle(particle as usize, body, bodies);
232            }
233        }
234    }
235    complete
236}
237
238/// Detaches every particle attached to a rigid-body.
239fn detach_all_particles(sb: &mut RapierSoftBody) {
240    let mut attached: Vec<u32> = sb
241        .particle_attachments()
242        .iter()
243        .map(|a| a.particle)
244        .collect();
245    attached.dedup();
246    for particle in attached {
247        sb.detach_particle(particle as usize);
248    }
249}
250
251/// System responsible for creating the Rapier soft bodies of new [`SoftBody`] components.
252pub fn init_soft_bodies(
253    mut commands: Commands,
254    context_links: RapierContextLinkResolver,
255    mut context_access: Query<(&mut RapierRigidBodySet, &mut RapierContextColliders)>,
256    soft_bodies: Query<SoftBodyComponents, Without<RapierSoftBodyHandle>>,
257) {
258    for (
259        (entity, entity_context_link),
260        soft_body,
261        transform,
262        material,
263        volume_factor,
264        pinned,
265        disabled,
266    ) in soft_bodies.iter()
267    {
268        // Use the RapierContextEntityLink, or insert the context of an ancestor or the default one.
269        let context_entity = context_links.resolve(entity, entity_context_link, &mut commands);
270        let Some(context_entity) = context_entity else {
271            continue;
272        };
273        let Ok((mut rigidbody_set, mut context_colliders)) = context_access.get_mut(context_entity)
274        else {
275            log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
276            continue;
277        };
278        let rigidbody_set = &mut *rigidbody_set;
279
280        let mut builder = soft_body.builder.clone();
281        if let Some(transform) = transform {
282            transform_builder(&mut builder, transform);
283        }
284        if let Some(material) = material {
285            builder.material = material.0;
286        }
287        if let Some(volume_factor) = volume_factor {
288            builder.volume_factor = volume_factor.0;
289        }
290        if let Some(pinned) = pinned {
291            builder.pinned = pinned.0.clone();
292        }
293        let user_data = entity.to_bits() as u128;
294        builder.user_data = user_data;
295        // The collider components are applied by `apply_soft_body_user_changes`.
296        builder.collider_template = builder
297            .collider_template
298            .take()
299            .map(|template| template.user_data(user_data));
300
301        let handle = rigidbody_set.soft_bodies.insert(
302            builder,
303            &mut rigidbody_set.bodies,
304            &mut context_colliders.colliders,
305        );
306        let sb = &mut rigidbody_set.soft_bodies[handle];
307        if disabled.is_some() {
308            sb.set_enabled(false);
309        }
310        let state = soft_body_state(sb);
311        rigidbody_set.map_soft_body_collision_mesh(&mut context_colliders, handle, entity);
312        rigidbody_set.entity2soft_body.insert(entity, handle);
313        rigidbody_set.map_soft_body_whole_proxy(entity, handle);
314        commands
315            .entity(entity)
316            .insert((RapierSoftBodyHandle(handle), state));
317    }
318}
319
320/// System responsible for creating the proxy rigid-bodies of new [`SoftBodyCluster`] components.
321pub fn init_soft_body_clusters(
322    mut commands: Commands,
323    mut context_access: Query<(&mut RapierRigidBodySet, &mut RapierContextColliders)>,
324    links: Query<&RapierContextEntityLink>,
325    clusters: Query<(Entity, &SoftBodyCluster), Without<RapierRigidBodyHandle>>,
326) {
327    for (entity, cluster) in clusters.iter() {
328        if entity == cluster.soft_body {
329            log::error!(
330                "The `SoftBodyCluster` of {entity} must be on another entity than its soft body."
331            );
332            commands.entity(entity).remove::<SoftBodyCluster>();
333            continue;
334        }
335        // The soft body may not be created yet: try again next frame.
336        let Ok(link) = links.get(cluster.soft_body) else {
337            continue;
338        };
339        let Ok((mut rigidbody_set, mut context_colliders)) = context_access.get_mut(link.0) else {
340            continue;
341        };
342        let rigidbody_set = &mut *rigidbody_set;
343        let Some(handle) = rigidbody_set
344            .entity2soft_body
345            .get(&cluster.soft_body)
346            .copied()
347        else {
348            continue;
349        };
350
351        let Some(index) = rigidbody_set.soft_bodies.add_cluster(
352            handle,
353            &cluster.particles,
354            &mut rigidbody_set.bodies,
355            &mut context_colliders.colliders,
356        ) else {
357            log::error!("The `SoftBodyCluster` of {entity} doesn't contain any valid particle.");
358            commands.entity(entity).remove::<SoftBodyCluster>();
359            continue;
360        };
361        let Some(proxy) = rigidbody_set.soft_bodies[handle].cluster_proxy(index) else {
362            continue;
363        };
364        if let Some(rb) = rigidbody_set.bodies.get_mut(proxy) {
365            rb.user_data = entity.to_bits() as u128;
366        }
367        rigidbody_set.entity2body.insert(entity, proxy);
368        commands
369            .entity(entity)
370            .insert((RapierRigidBodyHandle(proxy), *link));
371    }
372}
373
374/// System responsible for applying changes the user made to a soft-body-related component.
375pub fn apply_soft_body_user_changes(
376    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
377    (changed_materials, changed_volume_factors, changed_disabled): (
378        Query<
379            (
380                &RapierSoftBodyHandle,
381                &RapierContextEntityLink,
382                &SoftBodyMaterial,
383            ),
384            Or<(Changed<SoftBodyMaterial>, Added<RapierSoftBodyHandle>)>,
385        >,
386        Query<
387            (
388                &RapierSoftBodyHandle,
389                &RapierContextEntityLink,
390                &SoftBodyVolumeFactor,
391            ),
392            Or<(Changed<SoftBodyVolumeFactor>, Added<RapierSoftBodyHandle>)>,
393        >,
394        Query<
395            (&RapierSoftBodyHandle, &RapierContextEntityLink),
396            (
397                With<SoftBodyDisabled>,
398                Or<(Changed<SoftBodyDisabled>, Added<RapierSoftBodyHandle>)>,
399            ),
400        >,
401    ),
402    (changed_pinned, changed_targets): (
403        Query<
404            (
405                &RapierSoftBodyHandle,
406                &RapierContextEntityLink,
407                &SoftBodyPinnedParticles,
408            ),
409            Or<(
410                Changed<SoftBodyPinnedParticles>,
411                Added<RapierSoftBodyHandle>,
412            )>,
413        >,
414        Query<
415            (
416                &RapierSoftBodyHandle,
417                &RapierContextEntityLink,
418                &SoftBodyKinematicTargets,
419            ),
420            Or<(
421                Changed<SoftBodyKinematicTargets>,
422                Added<RapierSoftBodyHandle>,
423            )>,
424        >,
425    ),
426    mut changed_attachments: Query<
427        (
428            &RapierSoftBodyHandle,
429            &RapierContextEntityLink,
430            Mut<SoftBodyAttachments>,
431        ),
432        Or<(Changed<SoftBodyAttachments>, Added<RapierSoftBodyHandle>)>,
433    >,
434    (changed_forces, mut changed_impulses): (
435        Query<
436            (
437                &RapierSoftBodyHandle,
438                &RapierContextEntityLink,
439                &SoftBodyExternalForce,
440            ),
441            Or<(Changed<SoftBodyExternalForce>, Added<RapierSoftBodyHandle>)>,
442        >,
443        Query<
444            (
445                &RapierSoftBodyHandle,
446                &RapierContextEntityLink,
447                Mut<SoftBodyExternalImpulse>,
448            ),
449            Or<(
450                Changed<SoftBodyExternalImpulse>,
451                Added<RapierSoftBodyHandle>,
452            )>,
453        >,
454    ),
455    #[cfg(feature = "fem")] changed_solvers: Query<
456        (
457            &RapierSoftBodyHandle,
458            &RapierContextEntityLink,
459            &SoftBodyElasticitySolver,
460        ),
461        Or<(
462            Changed<SoftBodyElasticitySolver>,
463            Added<RapierSoftBodyHandle>,
464        )>,
465    >,
466    pinned_clusters: Query<Entity, With<SoftBodyClusterPinned>>,
467    (mut context_colliders, changed_colliders): (
468        Query<&mut RapierContextColliders>,
469        Query<
470            (Entity, &RapierContextEntityLink, SoftBodyColliderComponents),
471            SoftBodyColliderChanges,
472        >,
473    ),
474) {
475    for (entity, link, components) in changed_colliders.iter() {
476        let (Ok(rigidbody_set), Ok(mut colliders)) = (
477            rigid_body_sets.get(link.0),
478            context_colliders.get_mut(link.0),
479        ) else {
480            continue;
481        };
482        for handle in rigidbody_set
483            .soft_body_colliders(&colliders.colliders, entity)
484            .unwrap_or_default()
485        {
486            if let Some(co) = colliders.colliders.get_mut(handle) {
487                apply_collider_components(co, components);
488            }
489        }
490    }
491
492    let mut with_soft_body = |link: &RapierContextEntityLink,
493                              handle: &RapierSoftBodyHandle,
494                              f: &mut dyn FnMut(
495        &mut RapierSoftBody,
496        &RigidBodySet,
497        &HashMap<Entity, RigidBodyHandle>,
498    )| {
499        let Ok(mut rigidbody_set) = rigid_body_sets.get_mut(link.0) else {
500            return false;
501        };
502        let rigidbody_set = &mut *rigidbody_set;
503        let Some(sb) = rigidbody_set.soft_bodies.get_mut(handle.0) else {
504            return false;
505        };
506        f(sb, &rigidbody_set.bodies, &rigidbody_set.entity2body);
507        true
508    };
509
510    for (handle, link, material) in changed_materials.iter() {
511        with_soft_body(link, handle, &mut |sb, _, _| sb.set_material(material.0));
512    }
513
514    for (handle, link, factor) in changed_volume_factors.iter() {
515        with_soft_body(link, handle, &mut |sb, _, _| sb.set_volume_factor(factor.0));
516    }
517
518    for (handle, link) in changed_disabled.iter() {
519        with_soft_body(link, handle, &mut |sb, _, _| sb.set_enabled(false));
520    }
521
522    #[cfg(feature = "fem")]
523    for (handle, link, solver) in changed_solvers.iter() {
524        with_soft_body(link, handle, &mut |sb, _, _| {
525            if sb.solver() != solver.0 {
526                sb.set_solver(solver.0);
527            }
528        });
529    }
530
531    for (handle, link, pinned) in changed_pinned.iter() {
532        with_soft_body(link, handle, &mut |sb, _, entity2body| {
533            let mut all_pinned = pinned.0.clone();
534            all_pinned.extend(pinned_cluster_particles(sb, entity2body, &pinned_clusters));
535            set_pinned_particles(sb, &all_pinned)
536        });
537    }
538
539    for (handle, link, targets) in changed_targets.iter() {
540        with_soft_body(link, handle, &mut |sb, _, _| {
541            for (i, target) in &targets.0 {
542                if (*i as usize) < sb.num_particles() {
543                    sb.set_particle_kinematic_target(*i as usize, *target);
544                }
545            }
546        });
547    }
548
549    for (handle, link, mut attachments) in changed_attachments.iter_mut() {
550        let mut retry = false;
551        with_soft_body(link, handle, &mut |sb, bodies, entity2body| {
552            retry = !update_attachments(sb, &attachments.0, bodies, entity2body);
553        });
554        if retry {
555            attachments.set_changed();
556        }
557    }
558
559    for (handle, link, forces) in changed_forces.iter() {
560        with_soft_body(link, handle, &mut |sb, _, _| {
561            sb.reset_forces(false);
562            sb.add_force(forces.force, true);
563            for (i, force) in &forces.particle_forces {
564                if (*i as usize) < sb.num_particles() {
565                    sb.add_particle_force(*i as usize, *force, true);
566                }
567            }
568        });
569    }
570
571    for (handle, link, mut impulses) in changed_impulses.iter_mut() {
572        let applied = with_soft_body(link, handle, &mut |sb, _, _| {
573            if impulses.velocity_change != crate::math::Vect::ZERO {
574                sb.apply_impulse(impulses.velocity_change, true);
575            }
576            for (i, impulse) in &impulses.particle_impulses {
577                if (*i as usize) < sb.num_particles() {
578                    sb.apply_particle_impulse(*i as usize, *impulse, true);
579                }
580            }
581        });
582        if applied {
583            impulses.bypass_change_detection().reset();
584        }
585    }
586}
587
588/// System responsible for removing from Rapier the soft bodies whose [`SoftBody`] component was
589/// removed by the user (through component removal or despawn).
590pub fn sync_soft_body_removals(
591    mut commands: Commands,
592    mut contexts: Query<(
593        &mut RapierContextSimulation,
594        &mut RapierContextColliders,
595        &mut RapierContextJoints,
596        &mut RapierRigidBodySet,
597    )>,
598    has_handle: Query<(), With<RapierSoftBodyHandle>>,
599    mut removed_handles: RemovedComponents<RapierSoftBodyHandle>,
600    orphans: Query<Entity, (With<RapierSoftBodyHandle>, Without<SoftBody>)>,
601) {
602    let mut remove = |entity: Entity| {
603        for (mut simulation, mut colliders, mut joints, mut rigidbody_set) in contexts.iter_mut() {
604            if let Some(handle) = rigidbody_set.entity2soft_body.get(&entity).copied() {
605                simulation.remove_soft_body(
606                    &mut colliders,
607                    &mut joints,
608                    &mut rigidbody_set,
609                    handle,
610                );
611                break;
612            }
613        }
614    };
615
616    for entity in removed_handles.read() {
617        if !has_handle.contains(entity) {
618            remove(entity);
619        }
620    }
621
622    for entity in orphans.iter() {
623        remove(entity);
624        commands.entity(entity).remove::<RapierSoftBodyHandle>();
625    }
626}
627
628/// Removal detection for a single component: the entities it was removed from, paired with a
629/// query checking if it was re-inserted since.
630type RemovedComponent<'w, 's, T> = (RemovedComponents<'w, 's, T>, Query<'w, 's, (), With<T>>);
631
632/// System responsible for resetting the Rapier soft-body properties to the values of their
633/// [`SoftBody`] builder when the corresponding component is removed by the user.
634pub fn reset_removed_soft_body_components(
635    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
636    soft_bodies: Query<&SoftBody>,
637    (mut removed_materials, mut removed_volume_factors, mut removed_disabled): (
638        RemovedComponent<SoftBodyMaterial>,
639        RemovedComponent<SoftBodyVolumeFactor>,
640        RemovedComponent<SoftBodyDisabled>,
641    ),
642    (mut removed_pinned, mut removed_attachments, mut removed_forces): (
643        RemovedComponent<SoftBodyPinnedParticles>,
644        RemovedComponent<SoftBodyAttachments>,
645        RemovedComponent<SoftBodyExternalForce>,
646    ),
647    #[cfg(feature = "fem")] mut removed_solvers: RemovedComponent<SoftBodyElasticitySolver>,
648    pinned_clusters: Query<Entity, With<SoftBodyClusterPinned>>,
649) {
650    let sets = &mut rigid_body_sets;
651    let builders = &soft_bodies;
652    reset_removed(&mut removed_materials, sets, builders, |sb, builder| {
653        sb.set_material(builder.map(|b| b.material).unwrap_or_default());
654    });
655    reset_removed(
656        &mut removed_volume_factors,
657        sets,
658        builders,
659        |sb, builder| {
660            sb.set_volume_factor(builder.map(|b| b.volume_factor).unwrap_or(1.0));
661        },
662    );
663    reset_removed(&mut removed_disabled, sets, builders, |sb, _| {
664        sb.set_enabled(true);
665    });
666    let unpinned = reset_removed(&mut removed_pinned, sets, builders, |sb, builder| {
667        set_pinned_particles(sb, builder.map(|b| &b.pinned[..]).unwrap_or_default());
668    });
669    // Pin the pinned clusters again.
670    if unpinned {
671        for mut set in sets.iter_mut() {
672            let set = &mut *set;
673            for entity in &pinned_clusters {
674                if let Some((handle, index)) = set.soft_body_cluster_index(entity) {
675                    if let Some(sb) = set.soft_bodies.get_mut(handle) {
676                        sb.set_cluster_pinned(index, true);
677                    }
678                }
679            }
680        }
681    }
682    reset_removed(&mut removed_attachments, sets, builders, |sb, _| {
683        detach_all_particles(sb);
684    });
685    reset_removed(&mut removed_forces, sets, builders, |sb, _| {
686        sb.reset_forces(true);
687    });
688    #[cfg(feature = "fem")]
689    reset_removed(&mut removed_solvers, sets, builders, |sb, builder| {
690        sb.set_solver(builder.map(|b| b.solver).unwrap_or_default());
691    });
692}
693
694/// Calls `reset` on the Rapier soft body of each entity `T` was removed from, unless `T` was
695/// inserted back since. Returns `true` if `reset` was called.
696fn reset_removed<T: Component>(
697    (removed, still_present): &mut RemovedComponent<T>,
698    rigid_body_sets: &mut Query<&mut RapierRigidBodySet>,
699    soft_bodies: &Query<&SoftBody>,
700    mut reset: impl FnMut(&mut RapierSoftBody, Option<&SoftBodyBuilder>),
701) -> bool {
702    let mut any = false;
703    for entity in removed.read() {
704        if still_present.contains(entity) {
705            continue;
706        }
707
708        for mut rigid_body_set in rigid_body_sets.iter_mut() {
709            if let Some(sb) = rigid_body_set.soft_body_mut(entity) {
710                reset(sb, soft_bodies.get(entity).ok().map(|sb| &sb.builder));
711                any = true;
712                break;
713            }
714        }
715    }
716    any
717}
718
719/// Calls `f` with the soft body and the index of the cluster standing for `entity` (see
720/// [`RapierRigidBodySet::soft_body_cluster_index`]).
721fn with_cluster(
722    rigid_body_sets: &mut Query<&mut RapierRigidBodySet>,
723    link: &RapierContextEntityLink,
724    entity: Entity,
725    f: impl FnOnce(&mut RapierSoftBody, u32),
726) {
727    let Ok(mut set) = rigid_body_sets.get_mut(link.0) else {
728        return;
729    };
730    let Some((handle, index)) = set.soft_body_cluster_index(entity) else {
731        return;
732    };
733    if let Some(sb) = set.soft_bodies.get_mut(handle) {
734        f(sb, index);
735    }
736}
737
738/// Applies a [`SoftBodyClusterMaterial`] to the `index`-th cluster of `sb`.
739fn apply_cluster_material(sb: &mut RapierSoftBody, index: u32, material: &SoftBodyClusterMaterial) {
740    sb.set_cluster_stiffness_scale(index, material.stiffness_scale);
741    sb.set_cluster_edge_softness(index, material.edge_softness);
742    sb.set_cluster_tear_resistance(index, material.tear_resistance);
743}
744
745/// The entities whose cluster was just created: the cluster entities with a new proxy, and the
746/// soft body entities (for their whole-body cluster) with a new soft body.
747type NewCluster = Or<(Added<RapierRigidBodyHandle>, Added<RapierSoftBodyHandle>)>;
748
749/// System responsible for applying the changes the user made to the cluster components
750/// ([`SoftBodyClusterPinned`], [`SoftBodyClusterKinematicTarget`],
751/// [`SoftBodyClusterShapeMatching`] and [`SoftBodyClusterMaterial`]).
752pub fn apply_soft_body_cluster_changes(
753    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
754    pinned: Query<
755        (Entity, &RapierContextEntityLink),
756        (
757            With<SoftBodyClusterPinned>,
758            Or<(Changed<SoftBodyClusterPinned>, NewCluster)>,
759        ),
760    >,
761    targets: Query<
762        (
763            Entity,
764            &RapierContextEntityLink,
765            &SoftBodyClusterKinematicTarget,
766        ),
767        Or<(Changed<SoftBodyClusterKinematicTarget>, NewCluster)>,
768    >,
769    shape_matching: Query<
770        (
771            Entity,
772            &RapierContextEntityLink,
773            &SoftBodyClusterShapeMatching,
774        ),
775        Or<(Changed<SoftBodyClusterShapeMatching>, NewCluster)>,
776    >,
777    materials: Query<
778        (Entity, &RapierContextEntityLink, &SoftBodyClusterMaterial),
779        Or<(Changed<SoftBodyClusterMaterial>, NewCluster)>,
780    >,
781) {
782    let sets = &mut rigid_body_sets;
783    for (entity, link) in &pinned {
784        with_cluster(sets, link, entity, |sb, i| sb.set_cluster_pinned(i, true));
785    }
786    for (entity, link, target) in &targets {
787        let pose = utils::transform_to_iso(&target.0);
788        with_cluster(sets, link, entity, |sb, i| {
789            sb.set_cluster_kinematic_target(i, pose)
790        });
791    }
792    for (entity, link, shape_matching) in &shape_matching {
793        let target = shape_matching.target.as_ref().map(utils::transform_to_iso);
794        with_cluster(sets, link, entity, |sb, i| {
795            sb.enable_cluster_shape_matching(i, true);
796            if let Some(cluster) = sb.cluster_mut(i) {
797                cluster.set_shape_matching_target(target);
798            }
799        });
800    }
801    for (entity, link, material) in &materials {
802        with_cluster(sets, link, entity, |sb, i| {
803            apply_cluster_material(sb, i, material)
804        });
805    }
806}
807
808/// The context index, soft body and cluster index standing for each entity `T` was removed from,
809/// unless `T` was inserted back since.
810fn removed_clusters<T: Component>(
811    (removed, still_present): &mut RemovedComponent<T>,
812    rigid_body_sets: &Query<&mut RapierRigidBodySet>,
813) -> Vec<(usize, SoftBodyHandle, u32)> {
814    let mut result = vec![];
815    for entity in removed.read() {
816        if still_present.contains(entity) {
817            continue;
818        }
819        for (set_index, set) in rigid_body_sets.iter().enumerate() {
820            if let Some((handle, index)) = set.soft_body_cluster_index(entity) {
821                result.push((set_index, handle, index));
822                break;
823            }
824        }
825    }
826    result
827}
828
829/// System responsible for resetting the clusters whose [`SoftBodyClusterPinned`],
830/// [`SoftBodyClusterShapeMatching`] or [`SoftBodyClusterMaterial`] component was removed.
831///
832/// This must run before the removal of the clusters of despawned entities.
833pub fn reset_removed_soft_body_cluster_components(
834    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
835    soft_bodies: Query<(&SoftBody, Option<&SoftBodyPinnedParticles>)>,
836    pinned_clusters: Query<Entity, With<SoftBodyClusterPinned>>,
837    (mut removed_pinned, mut removed_shape_matching, mut removed_materials): (
838        RemovedComponent<SoftBodyClusterPinned>,
839        RemovedComponent<SoftBodyClusterShapeMatching>,
840        RemovedComponent<SoftBodyClusterMaterial>,
841    ),
842) {
843    let unpinned = removed_clusters(&mut removed_pinned, &rigid_body_sets);
844    let unmatched = removed_clusters(&mut removed_shape_matching, &rigid_body_sets);
845    let unmaterialized = removed_clusters(&mut removed_materials, &rigid_body_sets);
846
847    for (set_index, mut set) in rigid_body_sets.iter_mut().enumerate() {
848        let set = &mut *set;
849        for (_, handle, index) in unpinned.iter().filter(|(s, _, _)| *s == set_index) {
850            let Some(sb) = set.soft_bodies.get(*handle) else {
851                continue;
852            };
853            // The particles pinned without this cluster: by the soft body, or by other clusters.
854            let components = set
855                .soft_body_entity(*handle)
856                .and_then(|entity| soft_bodies.get(entity).ok());
857            let mut still_pinned = match components {
858                Some((_, Some(pinned))) => pinned.0.clone(),
859                Some((soft_body, None)) => soft_body.builder.pinned.clone(),
860                None => vec![],
861            };
862            still_pinned.extend(pinned_cluster_particles(
863                sb,
864                &set.entity2body,
865                &pinned_clusters,
866            ));
867            let particles = sb.cluster(*index).map(|c| c.particles().to_vec());
868            let Some(sb) = set.soft_bodies.get_mut(*handle) else {
869                continue;
870            };
871            for particle in particles.unwrap_or_default() {
872                if (particle as usize) < sb.num_particles() {
873                    sb.set_particle_pinned(particle as usize, still_pinned.contains(&particle));
874                }
875            }
876        }
877        for (_, handle, index) in unmatched.iter().filter(|(s, _, _)| *s == set_index) {
878            let builder_setting = set
879                .soft_body_entity(*handle)
880                .and_then(|entity| soft_bodies.get(entity).ok())
881                .is_some_and(|(soft_body, _)| soft_body.builder.shape_matching);
882            let Some(sb) = set.soft_bodies.get_mut(*handle) else {
883                continue;
884            };
885            // Only the whole-body cluster is shape-matched by the builder.
886            let enabled = *index == 0 && builder_setting;
887            sb.enable_cluster_shape_matching(*index, enabled);
888            if let Some(cluster) = sb.cluster_mut(*index) {
889                cluster.set_shape_matching_target(None);
890            }
891        }
892        for (_, handle, index) in unmaterialized.iter().filter(|(s, _, _)| *s == set_index) {
893            if let Some(sb) = set.soft_bodies.get_mut(*handle) {
894                apply_cluster_material(sb, *index, &SoftBodyClusterMaterial::default());
895            }
896        }
897    }
898}
899
900/// Writes `world` into `transform`, relative to the parent's global transform if any.
901fn set_world_transform(
902    transform: &mut Transform,
903    mut world: Transform,
904    parent: Option<&GlobalTransform>,
905) {
906    world.scale = transform.scale;
907    if let Some(parent) = parent {
908        world = GlobalTransform::from(world).reparented_to(parent);
909        world.scale = transform.scale;
910    }
911    // In 2D, preserve the `z` component set by the user.
912    #[cfg(feature = "dim2")]
913    {
914        world.translation.z = transform.translation.z;
915    }
916    if transform.translation != world.translation || transform.rotation != world.rotation {
917        transform.translation = world.translation;
918        transform.rotation = world.rotation;
919    }
920}
921
922/// System responsible for writing the state of the soft bodies and cluster proxies back into
923/// [`SoftBodyState`] and [`Transform`] components after a simulation step.
924pub fn writeback_soft_bodies(
925    rigid_body_sets: Query<&RapierRigidBodySet>,
926    global_transforms: Query<&GlobalTransform>,
927    mut soft_bodies: Query<(
928        Entity,
929        &RapierSoftBodyHandle,
930        &RapierContextEntityLink,
931        Option<&mut SoftBodyState>,
932        Option<&mut Transform>,
933        Option<&ChildOf>,
934    )>,
935    mut clusters: Query<
936        (
937            &RapierRigidBodyHandle,
938            &RapierContextEntityLink,
939            &mut Transform,
940            Option<&ChildOf>,
941        ),
942        (With<SoftBodyCluster>, Without<RapierSoftBodyHandle>),
943    >,
944) {
945    for (entity, handle, link, state, transform, child_of) in soft_bodies.iter_mut() {
946        let Some(set) = rigid_body_sets.get(link.0).ok() else {
947            continue;
948        };
949        let Some(sb) = set.soft_bodies.get(handle.0) else {
950            continue;
951        };
952        let new_state = soft_body_state(sb);
953        if let Some(mut state) = state {
954            state.set_if_neq(new_state);
955        }
956        if let Some(mut transform) = transform {
957            // Follow the whole-body proxy, so the colliders attached to it by children entities
958            // stay where their entity is.
959            let pose = set
960                .entity2body
961                .get(&entity)
962                .and_then(|proxy| set.bodies.get(*proxy))
963                .map(|rb| utils::iso_to_transform(rb.position()))
964                .unwrap_or_else(|| {
965                    #[cfg(feature = "dim2")]
966                    let translation = new_state.center_of_mass.extend(0.0);
967                    #[cfg(feature = "dim3")]
968                    let translation = new_state.center_of_mass;
969                    Transform::from_translation(translation)
970                });
971            let parent = child_of.and_then(|c| global_transforms.get(c.parent()).ok());
972            set_world_transform(&mut transform, pose, parent);
973        }
974    }
975
976    for (handle, link, mut transform, child_of) in clusters.iter_mut() {
977        let Some(rb) = rigid_body_sets
978            .get(link.0)
979            .ok()
980            .and_then(|set| set.bodies.get(handle.0))
981        else {
982            continue;
983        };
984        let parent = child_of.and_then(|c| global_transforms.get(c.parent()).ok());
985        set_world_transform(
986            &mut transform,
987            utils::iso_to_transform(rb.position()),
988            parent,
989        );
990    }
991}
992
993/// The components of a soft body entity referring to its particles by index.
994#[derive(Clone, Default)]
995struct IndexedComponents {
996    pinned: Option<SoftBodyPinnedParticles>,
997    targets: Option<SoftBodyKinematicTargets>,
998    attachments: Option<SoftBodyAttachments>,
999    force: Option<SoftBodyExternalForce>,
1000    impulse: Option<SoftBodyExternalImpulse>,
1001    /// The particles pinned by the [`SoftBody`] builder.
1002    builder_pinned: Option<Vec<u32>>,
1003}
1004
1005/// The query reading the [`IndexedComponents`] of an entity.
1006type IndexedComponentsQuery<'a> = (
1007    Option<&'a SoftBodyPinnedParticles>,
1008    Option<&'a SoftBodyKinematicTargets>,
1009    Option<&'a SoftBodyAttachments>,
1010    Option<&'a SoftBodyExternalForce>,
1011    Option<&'a SoftBodyExternalImpulse>,
1012    Option<&'a SoftBody>,
1013);
1014
1015/// The cluster components of a cluster entity, inherited by the clusters split off its cluster.
1016type ClusterComponentsQuery<'a> = (
1017    Has<SoftBodyClusterPinned>,
1018    Option<&'a SoftBodyClusterKinematicTarget>,
1019    Option<&'a SoftBodyClusterShapeMatching>,
1020    Option<&'a SoftBodyClusterMaterial>,
1021);
1022
1023impl IndexedComponents {
1024    fn new(
1025        (pinned, targets, attachments, force, impulse, soft_body): QueryItem<
1026            IndexedComponentsQuery,
1027        >,
1028    ) -> Self {
1029        Self {
1030            pinned: pinned.cloned(),
1031            targets: targets.cloned(),
1032            attachments: attachments.cloned(),
1033            force: force.cloned(),
1034            impulse: impulse.cloned(),
1035            builder_pinned: soft_body
1036                .map(|sb| sb.builder.pinned.clone())
1037                .filter(|pinned| !pinned.is_empty()),
1038        }
1039    }
1040
1041    fn is_empty(&self) -> bool {
1042        self.pinned.is_none()
1043            && self.targets.is_none()
1044            && self.attachments.is_none()
1045            && self.force.is_none()
1046            && self.impulse.is_none()
1047            && self.builder_pinned.is_none()
1048    }
1049
1050    /// Splits these components between the pieces of a tear, following the particles they refer
1051    /// to (the entries of the particles that no longer exist are dropped).
1052    fn split(&self, event: &RapierSoftBodyTearEvent, pieces: &[SoftBodyHandle]) -> Vec<Self> {
1053        let destination = |particle: u32| {
1054            let (handle, index) = event.particle_destination(particle)?;
1055            Some((pieces.iter().position(|h| *h == handle)?, index))
1056        };
1057        let split_list = |list: &[u32]| {
1058            let mut result = vec![vec![]; pieces.len()];
1059            for particle in list {
1060                if let Some((k, index)) = destination(*particle) {
1061                    result[k].push(index);
1062                }
1063            }
1064            result
1065        };
1066        let mut result = vec![Self::default(); pieces.len()];
1067        if let Some(pinned) = &self.pinned {
1068            for (r, pinned) in result.iter_mut().zip(split_list(&pinned.0)) {
1069                r.pinned = Some(SoftBodyPinnedParticles(pinned));
1070            }
1071        }
1072        if let Some(pinned) = &self.builder_pinned {
1073            for (r, pinned) in result.iter_mut().zip(split_list(pinned)) {
1074                r.builder_pinned = Some(pinned);
1075            }
1076        }
1077        if let Some(targets) = &self.targets {
1078            result.iter_mut().for_each(|r| r.targets = Some(default()));
1079            for (particle, target) in &targets.0 {
1080                if let Some((k, index)) = destination(*particle) {
1081                    result[k].targets.as_mut().unwrap().0.push((index, *target));
1082                }
1083            }
1084        }
1085        if let Some(attachments) = &self.attachments {
1086            result
1087                .iter_mut()
1088                .for_each(|r| r.attachments = Some(default()));
1089            for attachment in &attachments.0 {
1090                if let Some((k, index)) = destination(attachment.particle) {
1091                    result[k]
1092                        .attachments
1093                        .as_mut()
1094                        .unwrap()
1095                        .0
1096                        .push(SoftBodyAttachment {
1097                            particle: index,
1098                            body: attachment.body,
1099                        });
1100                }
1101            }
1102        }
1103        if let Some(force) = &self.force {
1104            result.iter_mut().for_each(|r| {
1105                r.force = Some(SoftBodyExternalForce {
1106                    force: force.force,
1107                    particle_forces: vec![],
1108                })
1109            });
1110            for (particle, particle_force) in &force.particle_forces {
1111                if let Some((k, index)) = destination(*particle) {
1112                    let forces = &mut result[k].force.as_mut().unwrap().particle_forces;
1113                    forces.push((index, *particle_force));
1114                }
1115            }
1116        }
1117        if let Some(impulse) = &self.impulse {
1118            result.iter_mut().for_each(|r| {
1119                r.impulse = Some(SoftBodyExternalImpulse {
1120                    velocity_change: impulse.velocity_change,
1121                    particle_impulses: vec![],
1122                })
1123            });
1124            for (particle, particle_impulse) in &impulse.particle_impulses {
1125                if let Some((k, index)) = destination(*particle) {
1126                    let impulses = &mut result[k].impulse.as_mut().unwrap().particle_impulses;
1127                    impulses.push((index, *particle_impulse));
1128                }
1129            }
1130        }
1131        result
1132    }
1133
1134    /// Inserts these components on `entity`.
1135    fn insert(self, entity_commands: &mut EntityCommands) {
1136        if let Some(pinned) = self.pinned {
1137            entity_commands.try_insert(pinned);
1138        }
1139        if let Some(targets) = self.targets {
1140            entity_commands.try_insert(targets);
1141        }
1142        if let Some(attachments) = self.attachments {
1143            entity_commands.try_insert(attachments);
1144        }
1145        if let Some(force) = self.force {
1146            entity_commands.try_insert(force);
1147        }
1148        if let Some(impulse) = self.impulse {
1149            entity_commands.try_insert(impulse);
1150        }
1151        if let Some(pinned) = self.builder_pinned {
1152            // The builder is kept for the removal of `SoftBodyPinnedParticles`: keep it in sync.
1153            entity_commands.queue_silenced(move |mut entity: EntityWorldMut| {
1154                if let Some(mut soft_body) = entity.get_mut::<SoftBody>() {
1155                    soft_body.bypass_change_detection().builder.pinned = pinned;
1156                }
1157            });
1158        }
1159    }
1160}
1161
1162/// The mass-weighted rest centroid of the particles of the given clusters, as
1163/// `(soft body, cluster index)` pairs.
1164fn rest_centroid(
1165    rigidbody_set: &RapierRigidBodySet,
1166    clusters: impl IntoIterator<Item = (SoftBodyHandle, u32)>,
1167) -> crate::math::Vect {
1168    let mut com = crate::math::Vect::ZERO;
1169    let mut mass = 0.0;
1170    for (handle, index) in clusters {
1171        let Some(sb) = rigidbody_set.soft_bodies.get(handle) else {
1172            continue;
1173        };
1174        for &v in sb.cluster(index).map(|c| c.particles()).unwrap_or_default() {
1175            let particle = &sb.particles()[v as usize];
1176            com += particle.rest_position() * particle.mass();
1177            mass += particle.mass();
1178        }
1179    }
1180    if mass > 0.0 {
1181        com / mass
1182    } else {
1183        com
1184    }
1185}
1186
1187/// The kinematic target of a cluster split off the source cluster of `split`, placing its
1188/// particles where `source_target` placed them in the source cluster.
1189fn inherited_kinematic_target(
1190    rigidbody_set: &RapierRigidBodySet,
1191    raw: &RapierSoftBodyTearEvent,
1192    split: &rapier::dynamics::SoftClusterSplit,
1193    source_target: &SoftBodyClusterKinematicTarget,
1194) -> SoftBodyClusterKinematicTarget {
1195    // A target places the rest shape centered on the cluster's rest centroid, which moved.
1196    let source_centroid = rest_centroid(
1197        rigidbody_set,
1198        raw.clusters
1199            .iter()
1200            .filter(|s| s.source_cluster == split.source_cluster)
1201            .map(|s| (s.soft_body, s.cluster)),
1202    );
1203    let centroid = rest_centroid(rigidbody_set, [(split.soft_body, split.cluster)]);
1204    let delta = centroid - source_centroid;
1205    #[cfg(feature = "dim2")]
1206    let delta = delta.extend(0.0);
1207    let mut target = *source_target;
1208    target.0.translation += target.0.rotation * delta;
1209    target
1210}
1211
1212/// System responsible for spawning the entities of the soft bodies split off by tears, and for
1213/// sending the corresponding [`SoftBodyTearEvent`] messages.
1214pub fn handle_soft_body_tears(
1215    mut commands: Commands,
1216    mut contexts: Query<(
1217        Entity,
1218        &mut RapierContextSimulation,
1219        &mut RapierContextColliders,
1220        &RapierContextJoints,
1221        &mut RapierRigidBodySet,
1222    )>,
1223    mut clusters: Query<&mut SoftBodyCluster>,
1224    cluster_components: Query<ClusterComponentsQuery>,
1225    indexed_components: Query<IndexedComponentsQuery>,
1226    mut tear_events: MessageWriter<SoftBodyTearEvent>,
1227) {
1228    // The index-based components of the torn soft bodies and of their pieces, remapped through
1229    // every tear of this frame.
1230    let mut remapped: HashMap<Entity, IndexedComponents> = HashMap::new();
1231
1232    for (context_entity, mut simulation, mut colliders, joints, mut rigidbody_set) in
1233        contexts.iter_mut()
1234    {
1235        if simulation.pending_soft_body_tears.is_empty() {
1236            continue;
1237        }
1238        let colliders = &mut *colliders;
1239        let rigidbody_set = &mut *rigidbody_set;
1240        for pending in std::mem::take(&mut simulation.pending_soft_body_tears) {
1241            let raw = pending.raw;
1242            // Soft bodies inserted without entity are left alone.
1243            let Some(torn_entity) = rigidbody_set.soft_body_entity(raw.soft_body) else {
1244                continue;
1245            };
1246
1247            let mut pieces = Vec::with_capacity(raw.pieces.len());
1248            let mut piece_entities = Vec::with_capacity(raw.pieces.len());
1249            for (k, piece) in raw.pieces.iter().enumerate() {
1250                // The entity already spawned for this piece by a manual tear, if any.
1251                let reserved = k
1252                    .checked_sub(1)
1253                    .and_then(|i| pending.piece_entities.get(i))
1254                    .copied();
1255                let piece_entity = if k == 0 {
1256                    torn_entity
1257                } else {
1258                    let Some(sb) = rigidbody_set.soft_bodies.get(piece.soft_body) else {
1259                        if let Some(reserved) = reserved {
1260                            commands.entity(reserved).despawn();
1261                        }
1262                        continue;
1263                    };
1264                    let state = soft_body_state(sb);
1265                    let mut torn_commands = commands.entity(torn_entity);
1266                    let deny = |builder: &mut bevy::ecs::entity::EntityClonerBuilder<
1267                        bevy::ecs::entity::OptOut,
1268                    >| {
1269                        builder.deny::<(
1270                            RapierSoftBodyHandle,
1271                            SoftBodyState,
1272                            SoftBodyPinnedParticles,
1273                            SoftBodyKinematicTargets,
1274                            SoftBodyAttachments,
1275                            SoftBodyExternalForce,
1276                            SoftBodyExternalImpulse,
1277                            Children,
1278                            RapierRigidBodyHandle,
1279                            RapierColliderHandle,
1280                        )>();
1281                        #[cfg(all(feature = "to-bevy-mesh", feature = "dim3"))]
1282                        builder.deny::<bevy::mesh::Mesh3d>();
1283                        #[cfg(all(feature = "to-bevy-mesh", feature = "dim2"))]
1284                        builder.deny::<bevy::mesh::Mesh2d>();
1285                    };
1286                    let piece_entity = match reserved {
1287                        Some(reserved) => {
1288                            torn_commands.clone_with_opt_out(reserved, deny);
1289                            reserved
1290                        }
1291                        None => torn_commands.clone_and_spawn_with_opt_out(deny).id(),
1292                    };
1293                    commands.entity(piece_entity).insert((
1294                        RapierSoftBodyHandle(piece.soft_body),
1295                        state,
1296                        RapierContextEntityLink(context_entity),
1297                    ));
1298                    rigidbody_set.map_soft_body_piece(colliders, piece.soft_body, piece_entity);
1299                    piece_entity
1300                };
1301                rigidbody_set.map_soft_body_collision_mesh(
1302                    colliders,
1303                    piece.soft_body,
1304                    piece_entity,
1305                );
1306                rigidbody_set.map_soft_body_whole_proxy(piece_entity, piece.soft_body);
1307
1308                // The cluster entities moved to this piece now belong to its soft body.
1309                if let Some(sb) = rigidbody_set.soft_bodies.get(piece.soft_body) {
1310                    for [_, cluster] in &piece.clusters {
1311                        let Some(proxy_entity) = sb
1312                            .cluster_proxy(*cluster)
1313                            .and_then(|proxy| rigidbody_set.rigid_body_entity(proxy))
1314                        else {
1315                            continue;
1316                        };
1317                        if let Ok(mut cluster) = clusters.get_mut(proxy_entity) {
1318                            if cluster.soft_body != piece_entity {
1319                                cluster.soft_body = piece_entity;
1320                            }
1321                        }
1322                    }
1323                }
1324
1325                piece_entities.push((piece.soft_body, piece_entity));
1326                pieces.push(SoftBodyTearPiece {
1327                    soft_body: piece_entity,
1328                    particles: piece.particles.clone(),
1329                });
1330            }
1331            if raw.pieces.is_empty() {
1332                rigidbody_set.map_soft_body_collision_mesh(colliders, raw.soft_body, torn_entity);
1333                rigidbody_set.map_soft_body_whole_proxy(torn_entity, raw.soft_body);
1334            } else {
1335                // The particles were renumbered: remap the components referring to them.
1336                let source = remapped.remove(&torn_entity).unwrap_or_else(|| {
1337                    indexed_components
1338                        .get(torn_entity)
1339                        .map(IndexedComponents::new)
1340                        .unwrap_or_default()
1341                });
1342                if !source.is_empty() {
1343                    let handles: Vec<_> = piece_entities.iter().map(|(h, _)| *h).collect();
1344                    let split = source.split(&raw, &handles);
1345                    for ((_, entity), components) in piece_entities.iter().zip(split) {
1346                        remapped.insert(*entity, components);
1347                    }
1348                }
1349            }
1350
1351            let mut cluster_splits = Vec::with_capacity(raw.clusters.len());
1352            for split in &raw.clusters {
1353                let Some(soft_body) = rigidbody_set.soft_body_entity(split.soft_body) else {
1354                    continue;
1355                };
1356                let mut proxy = rigidbody_set.rigid_body_entity(split.proxy);
1357                if !split.keeps_proxy {
1358                    // The entity of the split cluster: a cluster entity or a soft body entity.
1359                    let source = raw
1360                        .clusters
1361                        .iter()
1362                        .find(|s| s.source_cluster == split.source_cluster && s.keeps_proxy)
1363                        .and_then(|s| rigidbody_set.rigid_body_entity(s.proxy));
1364                    let (pinned, target, shape_matching, material) = source
1365                        .and_then(|e| cluster_components.get(e).ok())
1366                        .unwrap_or_default();
1367                    let target = target.map(|target| {
1368                        inherited_kinematic_target(rigidbody_set, &raw, split, target)
1369                    });
1370
1371                    if source.is_some_and(|e| clusters.contains(e)) {
1372                        // Give a fresh proxy its own entity if the split cluster had one.
1373                        let particles = rigidbody_set
1374                            .soft_bodies
1375                            .get(split.soft_body)
1376                            .and_then(|sb| sb.cluster(split.cluster))
1377                            .map(|c| c.particles().to_vec())
1378                            .unwrap_or_default();
1379                        let pose = rigidbody_set
1380                            .bodies
1381                            .get(split.proxy)
1382                            .map(|rb| utils::iso_to_transform(rb.position()))
1383                            .unwrap_or_default();
1384                        let mut cluster_commands = commands.spawn((
1385                            SoftBodyCluster {
1386                                soft_body,
1387                                particles,
1388                            },
1389                            RapierRigidBodyHandle(split.proxy),
1390                            RapierContextEntityLink(context_entity),
1391                            pose,
1392                        ));
1393                        // The piece inherits the components driving the split cluster.
1394                        if pinned {
1395                            cluster_commands.insert(SoftBodyClusterPinned);
1396                        }
1397                        if let Some(target) = target {
1398                            cluster_commands.insert(target);
1399                        }
1400                        if let Some(shape_matching) = shape_matching {
1401                            cluster_commands.insert(*shape_matching);
1402                        }
1403                        if let Some(material) = material {
1404                            cluster_commands.insert(*material);
1405                        }
1406                        let cluster_entity = cluster_commands.id();
1407                        if let Some(rb) = rigidbody_set.bodies.get_mut(split.proxy) {
1408                            rb.user_data = cluster_entity.to_bits() as u128;
1409                        }
1410                        rigidbody_set
1411                            .entity2body
1412                            .insert(cluster_entity, split.proxy);
1413                        proxy = Some(cluster_entity);
1414                    } else if let (Some(target), Some(piece)) = (target, proxy) {
1415                        // A piece's whole-body cluster: its entity was cloned with the target of
1416                        // the torn soft body, which must be shifted too.
1417                        commands.entity(piece).try_insert(target);
1418                    }
1419                }
1420                cluster_splits.push(SoftBodyClusterSplit {
1421                    source_cluster: split.source_cluster,
1422                    soft_body,
1423                    cluster: split.cluster,
1424                    proxy,
1425                    keeps_proxy: split.keeps_proxy,
1426                });
1427            }
1428
1429            let moved_joints = raw
1430                .moved_joints
1431                .iter()
1432                .map(|moved| SoftBodyJointMove {
1433                    joint: joints.impulse_joint_entity(moved.joint),
1434                    from: rigidbody_set.rigid_body_entity(moved.from),
1435                    to: rigidbody_set.rigid_body_entity(moved.to),
1436                })
1437                .collect();
1438
1439            tear_events.write(SoftBodyTearEvent {
1440                context: context_entity,
1441                soft_body: torn_entity,
1442                pieces,
1443                cluster_splits,
1444                moved_joints,
1445                raw,
1446            });
1447        }
1448    }
1449
1450    for (entity, components) in remapped {
1451        components.insert(&mut commands.entity(entity));
1452    }
1453}
1454
1455/// The mesh component a soft-body mesh is rendered with.
1456#[cfg(all(feature = "to-bevy-mesh", feature = "dim3"))]
1457type SoftBodyMeshComponent = bevy::mesh::Mesh3d;
1458/// The mesh component a soft-body mesh is rendered with.
1459#[cfg(all(feature = "to-bevy-mesh", feature = "dim2"))]
1460type SoftBodyMeshComponent = bevy::mesh::Mesh2d;
1461
1462/// System responsible for updating the meshes of the soft bodies with a
1463/// [`SoftBodyMeshSync`](crate::dynamics::SoftBodyMeshSync) component.
1464#[cfg(feature = "to-bevy-mesh")]
1465pub fn sync_soft_body_meshes(
1466    mut commands: Commands,
1467    meshes: Option<ResMut<Assets<bevy::mesh::Mesh>>>,
1468    rigid_body_sets: Query<(&RapierRigidBodySet, &RapierContextColliders)>,
1469    global_transforms: Query<&GlobalTransform>,
1470    mut soft_bodies: Query<(
1471        Entity,
1472        &RapierSoftBodyHandle,
1473        &RapierContextEntityLink,
1474        &mut crate::dynamics::SoftBodyMeshSync,
1475        Option<&Transform>,
1476        Option<&ChildOf>,
1477        Option<&SoftBodyMeshComponent>,
1478    )>,
1479) {
1480    use crate::dynamics::{SoftBodyMeshSignature, SoftBodyRenderGeometry};
1481    use bevy::mesh::{Indices, Mesh};
1482
1483    let Some(mut meshes) = meshes else {
1484        return;
1485    };
1486
1487    for (entity, handle, link, mut sync, transform, child_of, mesh_component) in
1488        soft_bodies.iter_mut()
1489    {
1490        let Ok((set, colliders)) = rigid_body_sets.get(link.0) else {
1491            continue;
1492        };
1493        let Some(sb) = set.soft_bodies.get(handle.0) else {
1494            continue;
1495        };
1496        let geometry = SoftBodyRenderGeometry::new(sb, &colliders.colliders);
1497        // The global transform the entity will have after this frame's transform propagation.
1498        let global = match (transform, child_of) {
1499            (Some(transform), Some(child_of)) => global_transforms
1500                .get(child_of.parent())
1501                .map(|parent| parent.mul_transform(*transform))
1502                .unwrap_or_else(|_| GlobalTransform::from(*transform)),
1503            (Some(transform), None) => GlobalTransform::from(*transform),
1504            (None, _) => GlobalTransform::IDENTITY,
1505        };
1506        let world_to_local = global.affine().inverse();
1507        let signature = SoftBodyMeshSignature {
1508            handle: handle.0,
1509            topology_version: sb.topology_version(),
1510            num_vertices: geometry.vertices.len(),
1511            num_indices: geometry.indices.len(),
1512        };
1513
1514        if sync.built == Some(signature) {
1515            if let Some(mut mesh) = mesh_component.and_then(|m| meshes.get_mut(&m.0)) {
1516                mesh.insert_attribute(
1517                    Mesh::ATTRIBUTE_POSITION,
1518                    geometry.positions(&world_to_local),
1519                );
1520                if geometry.triangles
1521                    && !geometry.indices.is_empty()
1522                    && matches!(mesh.indices(), Some(Indices::U32(_)))
1523                {
1524                    mesh.compute_smooth_normals();
1525                }
1526                continue;
1527            }
1528        }
1529
1530        // First sync or topology change: (re)build the whole mesh.
1531        let mesh = geometry.to_mesh(&world_to_local);
1532        match mesh_component {
1533            Some(component) if meshes.contains(&component.0) => {
1534                let _ = meshes.insert(&component.0, mesh);
1535            }
1536            _ => {
1537                let mesh_handle = meshes.add(mesh);
1538                commands
1539                    .entity(entity)
1540                    .insert(SoftBodyMeshComponent::from(mesh_handle));
1541            }
1542        }
1543        sync.built = Some(signature);
1544    }
1545}