1#[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
33pub 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
47type 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
65pub 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
76fn 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
87fn 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
137fn 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
156fn 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
171fn 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
189fn 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 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
238fn 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
251pub 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 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 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
320pub 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 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
374pub 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
588pub 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
628type RemovedComponent<'w, 's, T> = (RemovedComponents<'w, 's, T>, Query<'w, 's, (), With<T>>);
631
632pub 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 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
694fn 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
719fn 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
738fn 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
745type NewCluster = Or<(Added<RapierRigidBodyHandle>, Added<RapierSoftBodyHandle>)>;
748
749pub 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
808fn 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
829pub 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 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 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
900fn 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 #[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
922pub 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 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#[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 builder_pinned: Option<Vec<u32>>,
1003}
1004
1005type 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
1015type 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 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 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 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
1162fn 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
1187fn inherited_kinematic_target(
1190 rigidbody_set: &RapierRigidBodySet,
1191 raw: &RapierSoftBodyTearEvent,
1192 split: &rapier::dynamics::SoftClusterSplit,
1193 source_target: &SoftBodyClusterKinematicTarget,
1194) -> SoftBodyClusterKinematicTarget {
1195 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
1212pub 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 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 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 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 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 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 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 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 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 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#[cfg(all(feature = "to-bevy-mesh", feature = "dim3"))]
1457type SoftBodyMeshComponent = bevy::mesh::Mesh3d;
1458#[cfg(all(feature = "to-bevy-mesh", feature = "dim2"))]
1460type SoftBodyMeshComponent = bevy::mesh::Mesh2d;
1461
1462#[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 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 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}