Skip to main content

bevy_rapier2d/dynamics/
soft_body.rs

1//! Components describing soft bodies: deformable bodies made of particles linked by elastic
2//! constraints, simulated together with the rigid-bodies.
3//!
4//! # Entities and conventions
5//!
6//! A soft body is created by inserting a [`SoftBody`] component on an entity. The positions of
7//! the soft body's particles given by its [`SoftBodyBuilder`] are expressed in the local frame of
8//! that entity: they are transformed by the entity's [`GlobalTransform`] when the soft body is
9//! created. After its creation, the particles live in world space and the plugin drives the
10//! entity's [`Transform`]: it follows the pose of the soft body's whole-body proxy (the frame of
11//! its whole-body cluster: its origin is the centroid of the free particles, and its rotation the
12//! best fit of the particles onto their rest shape, identity at creation), its scale is kept. The
13//! colliders of children entities are attached to that proxy, so their entities stay where
14//! their colliders are. A mesh synchronized with [`SoftBodyMeshSync`] is expressed in that frame.
15//! The mass-weighted center of the particles is written to [`SoftBodyState::center_of_mass`].
16//!
17//! The soft body entity also stands for the Rapier objects created with the soft body:
18//! - its surface colliders (their `user_data` holds the entity bits), so collision events,
19//!   contact pairs and scene queries report the soft body entity as the collider entity;
20//! - its hidden proxy rigid-bodies, so contact pairs report it as the rigid-body entity;
21//! - its whole-body proxy rigid-body in particular (the proxy of its whole-body cluster, see
22//!   [`RapierRigidBodySet::soft_body_whole_proxy`]): the entity is mapped to it, so impulse joints
23//!   can attach the whole soft body like a rigid-body, with the soft body entity as their
24//!   `parent` or by adding them to the soft body entity.
25//!
26//! [`SoftBodyCluster`] entities stand for their own cluster proxy rigid-body, which impulse joints
27//! and colliders can be attached to like to any rigid-body. A cluster (or the whole-body cluster
28//! of a soft body entity) can be driven with the [`SoftBodyClusterPinned`],
29//! [`SoftBodyClusterKinematicTarget`], [`SoftBodyClusterShapeMatching`] and
30//! [`SoftBodyClusterMaterial`] components.
31//!
32//! When a soft body tears, the particle indices of its [`SoftBodyPinnedParticles`],
33//! [`SoftBodyKinematicTargets`], [`SoftBodyAttachments`], [`SoftBodyExternalForce`] and
34//! [`SoftBodyExternalImpulse`] components (and the pinned particles of its [`SoftBody`] builder,
35//! restored when [`SoftBodyPinnedParticles`] is removed) are remapped to the renumbered
36//! particles, the entries of the particles moved to a new piece being moved to the entity of that
37//! piece. When a tear splits a cluster, the entity spawned for the new cluster inherits the
38//! [`SoftBodyClusterPinned`], [`SoftBodyClusterKinematicTarget`] (shifted so that the particles
39//! keep their targets), [`SoftBodyClusterShapeMatching`] and [`SoftBodyClusterMaterial`]
40//! components of the split cluster's entity.
41//!
42//! Modifying the [`SoftBody`] component after the soft body was created has no effect. Use the
43//! other components of this module, or the helpers of [`RapierRigidBodySet`] and
44//! [`RapierContextMut`], to control it at runtime. Removing the [`SoftBody`] component or
45//! despawning its entity removes the soft body from the simulation.
46//!
47//! [`RapierRigidBodySet`]: crate::plugin::context::RapierRigidBodySet
48//! [`RapierRigidBodySet::soft_body_whole_proxy`]: crate::plugin::context::RapierRigidBodySet::soft_body_whole_proxy
49//! [`RapierContextMut`]: crate::plugin::context::systemparams::RapierContextMut
50
51use crate::math::{Real, Vect};
52use bevy::prelude::*;
53
54#[cfg(feature = "dim3")]
55pub use rapier::dynamics::SoftBodyDihedral;
56pub use rapier::dynamics::{
57    SoftBindingError, SoftBodiesSettings, SoftBody as RapierSoftBody, SoftBodyBuilder,
58    SoftBodyCell, SoftBodyCellModel, SoftBodyCluster as RapierSoftBodyCluster, SoftBodyEdge,
59    SoftBodyEdgeKind, SoftBodyHandle, SoftBodyMaterial as RapierSoftBodyMaterial, SoftBodyParticle,
60    SoftBodyParticleSettings, SoftBodyPiece, SoftBodyTearEvent as RapierSoftBodyTearEvent,
61    SoftClusterRemoval, SoftClusterSplit, SoftCollisionMesh, SoftEdgePlasticFlow, SoftJointMove,
62    SoftMeshBinding, SoftMeshBindingMode, SoftMeshId, SoftMeshRef, SoftParticleAttachment,
63    SoftPatchConstraints, SoftRecoverySettings, SoftVolumePiece,
64};
65#[cfg(feature = "fem")]
66pub use rapier::dynamics::{SoftBodySolver, SoftFemParameters};
67pub use rapier::geometry::{PairContacts, SoftContactImpulse, SoftPairContacts};
68use rapier::prelude::SpringCoefficients;
69
70#[cfg(doc)]
71use crate::prelude::{Collider, ImpulseJoint, RigidBody};
72
73/// Describes a soft body to create.
74///
75/// The particle positions of [`Self::builder`] are expressed in the local frame of the entity (see
76/// the [module documentation](self) for the conventions). Changes made to this component after
77/// the soft body was created are ignored.
78///
79/// The collider-related components of the entity ([`crate::geometry::Friction`],
80/// [`crate::geometry::Restitution`], [`crate::geometry::CollisionGroups`],
81/// [`crate::geometry::SolverGroups`], [`crate::geometry::ActiveEvents`],
82/// [`crate::geometry::ActiveHooks`], [`crate::geometry::ActiveCollisionTypes`],
83/// [`crate::geometry::ContactForceEventThreshold`], [`crate::geometry::ContactSkin`] and
84/// [`crate::geometry::Sensor`]) configure the soft body's surface colliders (see
85/// [`RapierRigidBodySet::soft_body_colliders`]), like for a [`Collider`]: they override the
86/// builder's collider template, their changes are applied, and removing them restores the
87/// template's values (the particle radius for the contact skin). The `user_data` of the builder
88/// and of its collider template are overwritten with the entity bits.
89///
90/// [`RapierRigidBodySet::soft_body_colliders`]: crate::plugin::context::RapierRigidBodySet::soft_body_colliders
91#[derive(Component, Clone, Debug)]
92pub struct SoftBody {
93    /// The Rapier builder of the soft body, in the local frame of the entity.
94    pub builder: SoftBodyBuilder,
95}
96
97impl From<SoftBodyBuilder> for SoftBody {
98    fn from(builder: SoftBodyBuilder) -> Self {
99        Self::new(builder)
100    }
101}
102
103impl SoftBody {
104    /// A soft body created from the given Rapier builder (positions in the entity's local frame).
105    pub fn new(builder: SoftBodyBuilder) -> Self {
106        Self { builder }
107    }
108
109    /// Applies `f` to the builder of this soft body.
110    ///
111    /// This is useful to chain the builder's methods, e.g.
112    /// `SoftBody::rope(a, b, 20).map(|b| b.particle_mass(0.1).pinned_particles([0]))`.
113    pub fn map(mut self, f: impl FnOnce(SoftBodyBuilder) -> SoftBodyBuilder) -> Self {
114        self.builder = f(self.builder);
115        self
116    }
117
118    /// A rope of `num_particles` particles from `start` to `end` (see [`SoftBodyBuilder::rope`]).
119    pub fn rope(start: Vect, end: Vect, num_particles: usize) -> Self {
120        Self::new(SoftBodyBuilder::rope(start, end, num_particles))
121    }
122
123    /// A triangle-mesh soft body without volumetric cells (see [`SoftBodyBuilder::trimesh`]).
124    ///
125    /// Returns `None` if the mesh is empty.
126    pub fn trimesh(vertices: Vec<Vect>, indices: Vec<[u32; 3]>) -> Option<Self> {
127        SoftBodyBuilder::trimesh(vertices, indices).map(Self::new)
128    }
129
130    /// A volumetric soft body filling a closed, outward-oriented mesh (triangles in 3D, segments
131    /// in 2D) with cells of size `cell_size` (see [`SoftBodyBuilder::volumetric`]).
132    ///
133    /// Returns `None` if the mesh is empty, open, or encloses nothing at that cell size.
134    #[cfg(feature = "dim3")]
135    pub fn volumetric(vertices: &[Vect], indices: &[[u32; 3]], cell_size: Real) -> Option<Self> {
136        SoftBodyBuilder::volumetric(vertices, indices, cell_size).map(Self::new)
137    }
138
139    /// A volumetric soft body filling a closed, outward-oriented mesh (triangles in 3D, segments
140    /// in 2D) with cells of size `cell_size` (see [`SoftBodyBuilder::volumetric`]).
141    ///
142    /// Returns `None` if the mesh is empty, open, or encloses nothing at that cell size.
143    #[cfg(feature = "dim2")]
144    pub fn volumetric(vertices: &[Vect], indices: &[[u32; 2]], cell_size: Real) -> Option<Self> {
145        SoftBodyBuilder::volumetric(vertices, indices, cell_size).map(Self::new)
146    }
147
148    /// Same as [`Self::volumetric`], keeping the given mesh as the soft body's skin (see
149    /// [`SoftBodyBuilder::volumetric_skinned`]).
150    #[cfg(feature = "dim3")]
151    pub fn volumetric_skinned(
152        vertices: &[Vect],
153        indices: &[[u32; 3]],
154        cell_size: Real,
155    ) -> Option<Self> {
156        SoftBodyBuilder::volumetric_skinned(vertices, indices, cell_size).map(Self::new)
157    }
158
159    /// Same as [`Self::volumetric`], keeping the given polyline as the soft body's skin (see
160    /// [`SoftBodyBuilder::volumetric_skinned`]).
161    #[cfg(feature = "dim2")]
162    pub fn volumetric_skinned(
163        vertices: &[Vect],
164        indices: &[[u32; 2]],
165        cell_size: Real,
166    ) -> Option<Self> {
167        SoftBodyBuilder::volumetric_skinned(vertices, indices, cell_size).map(Self::new)
168    }
169
170    /// A rectangular cloth of `nu × nv` particles at `origin + i * du + j * dv` (see
171    /// [`SoftBodyBuilder::cloth`]).
172    #[cfg(feature = "dim3")]
173    pub fn cloth(origin: Vect, du: Vect, dv: Vect, nu: usize, nv: usize) -> Self {
174        Self::new(SoftBodyBuilder::cloth(origin, du, dv, nu, nv))
175    }
176
177    /// A cloth tube around the segment from `origin` to `origin + axis` (see
178    /// [`SoftBodyBuilder::cloth_tube`]).
179    #[cfg(feature = "dim3")]
180    pub fn cloth_tube(
181        origin: Vect,
182        axis: Vect,
183        radius_start: Real,
184        radius_end: Real,
185        num_around: usize,
186        num_along: usize,
187    ) -> Self {
188        Self::new(SoftBodyBuilder::cloth_tube(
189            origin,
190            axis,
191            radius_start,
192            radius_end,
193            num_around,
194            num_along,
195        ))
196    }
197
198    /// A solid box centered at the entity's origin, tetrahedralized on a `nx × ny × nz` particle
199    /// grid (see [`SoftBodyBuilder::cuboid`]).
200    #[cfg(feature = "dim3")]
201    pub fn cuboid(half_extents: Vect, nx: usize, ny: usize, nz: usize) -> Self {
202        Self::new(SoftBodyBuilder::cuboid(
203            Vect::ZERO,
204            half_extents,
205            nx,
206            ny,
207            nz,
208        ))
209    }
210
211    /// A hollow sphere centered at the entity's origin, with volume preservation (see
212    /// [`SoftBodyBuilder::sphere`]).
213    #[cfg(feature = "dim3")]
214    pub fn sphere(radius: Real, subdivisions: usize) -> Self {
215        Self::new(SoftBodyBuilder::sphere(Vect::ZERO, radius, subdivisions))
216    }
217
218    /// A closed polygon with counter-clockwise `points`, with area preservation (see
219    /// [`SoftBodyBuilder::polygon`]).
220    #[cfg(feature = "dim2")]
221    pub fn polygon(points: Vec<Vect>) -> Self {
222        Self::new(SoftBodyBuilder::polygon(points))
223    }
224
225    /// A regular polygon centered at the entity's origin with `num_particles` boundary particles
226    /// (see [`SoftBodyBuilder::disk`]).
227    #[cfg(feature = "dim2")]
228    pub fn disk(radius: Real, num_particles: usize) -> Self {
229        Self::new(SoftBodyBuilder::disk(Vect::ZERO, radius, num_particles))
230    }
231
232    /// A solid rectangle centered at the entity's origin, triangulated on a `nx × ny` particle
233    /// grid (see [`SoftBodyBuilder::grid`]).
234    #[cfg(feature = "dim2")]
235    pub fn grid(half_extents: Vect, nx: usize, ny: usize) -> Self {
236        Self::new(SoftBodyBuilder::grid(Vect::ZERO, half_extents, nx, ny))
237    }
238
239    /// A soft body from a polyline (see [`SoftBodyBuilder::polyline`]).
240    ///
241    /// Returns `None` if the polyline is empty.
242    #[cfg(feature = "dim2")]
243    pub fn polyline(vertices: Vec<Vect>, indices: Option<Vec<[u32; 2]>>) -> Option<Self> {
244        SoftBodyBuilder::polyline(vertices, indices).map(Self::new)
245    }
246}
247
248/// The Rapier handle of a [`SoftBody`] that was inserted to the physics scene.
249#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Component)]
250pub struct RapierSoftBodyHandle(pub SoftBodyHandle);
251
252/// The material (stiffness, damping, plasticity and tearing) of a soft body.
253///
254/// It overrides the material of the [`SoftBody`] builder, and changes to it are applied to the
255/// soft body. When removed, the builder's material is restored.
256#[derive(Component, Copy, Clone, Debug, PartialEq, Default, Reflect)]
257#[reflect(Component, Default, PartialEq)]
258pub struct SoftBodyMaterial(
259    /// The Rapier material.
260    #[reflect(remote = crate::reflect::SoftBodyMaterialWrapper)]
261    pub RapierSoftBodyMaterial,
262);
263
264impl SoftBodyMaterial {
265    /// A material with the same softness for every spring family (see
266    /// [`RapierSoftBodyMaterial::uniform`]).
267    pub fn uniform(natural_frequency: Real, damping_ratio: Real) -> Self {
268        Self(RapierSoftBodyMaterial::uniform(
269            rapier::dynamics::SpringCoefficients::new(natural_frequency, damping_ratio),
270        ))
271    }
272}
273
274impl From<RapierSoftBodyMaterial> for SoftBodyMaterial {
275    fn from(material: RapierSoftBodyMaterial) -> Self {
276        Self(material)
277    }
278}
279
280impl std::ops::Deref for SoftBodyMaterial {
281    type Target = RapierSoftBodyMaterial;
282    fn deref(&self) -> &Self::Target {
283        &self.0
284    }
285}
286
287impl std::ops::DerefMut for SoftBodyMaterial {
288    fn deref_mut(&mut self) -> &mut Self::Target {
289        &mut self.0
290    }
291}
292
293/// Add this component to disable a soft body: it is left where it is, without colliders or
294/// constraints, until this component is removed.
295#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
296#[reflect(Component, Default)]
297pub struct SoftBodyDisabled;
298
299/// The particles of a soft body that are pinned (kinematic).
300///
301/// While this component is present, exactly the listed particles are pinned (plus the particles
302/// of the clusters with a [`SoftBodyClusterPinned`] component): they hold their position, or
303/// follow the targets of [`SoftBodyKinematicTargets`]. When removed, the particles pinned by the
304/// [`SoftBody`] builder are restored. Invalid indices are ignored.
305#[derive(Component, Clone, Debug, Default, PartialEq, Eq, Reflect)]
306#[reflect(Component, Default, PartialEq)]
307pub struct SoftBodyPinnedParticles(pub Vec<u32>);
308
309/// World-space kinematic targets of pinned particles, as `(particle index, target)` pairs.
310///
311/// Each time this component changes, every listed pinned particle is moved to its target over
312/// the next simulation step (with the matching velocity, so friction and contacts see the
313/// motion), then held there. Targets of free particles are ignored.
314#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
315#[reflect(Component, Default, PartialEq)]
316pub struct SoftBodyKinematicTargets(pub Vec<(u32, Vect)>);
317
318/// A particle of a soft body attached to a rigid-body entity (see [`SoftBodyAttachments`]).
319#[derive(Copy, Clone, Debug, PartialEq, Eq, Reflect)]
320pub struct SoftBodyAttachment {
321    /// The index of the attached particle.
322    pub particle: u32,
323    /// The entity of the [`RigidBody`] (or [`SoftBodyCluster`]) the particle is attached to.
324    pub body: Entity,
325}
326
327/// Particles of a soft body attached to rigid-bodies by two-way point-to-point constraints.
328///
329/// The anchor of each attachment is the particle's position, relative to the rigid-body, at the
330/// time the attachment is created. Each time this component changes, the attachments of the soft
331/// body are updated to match it: the particles whose attached bodies changed are detached and
332/// attached again (with new anchors), the other attachments keep their anchor. When it is
333/// removed, every particle is detached. Attachments to entities that don't have a rigid-body yet
334/// are retried on the next frames.
335#[derive(Component, Clone, Debug, Default, PartialEq, Eq, Reflect)]
336#[reflect(Component, Default, PartialEq)]
337pub struct SoftBodyAttachments(pub Vec<SoftBodyAttachment>);
338
339/// Persistent forces applied to the particles of a soft body.
340///
341/// The forces are applied at each simulation step until this component changes or is removed,
342/// like [`crate::dynamics::ExternalForce`]. Pinned particles ignore them.
343#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
344#[reflect(Component, Default, PartialEq)]
345pub struct SoftBodyExternalForce {
346    /// The force applied to every particle.
347    pub force: Vect,
348    /// Additional forces applied to individual particles, as `(particle index, force)` pairs.
349    pub particle_forces: Vec<(u32, Vect)>,
350}
351
352/// One-time impulses applied to the particles of a soft body.
353///
354/// The impulses are applied (and this component reset to zero) at the next simulation step,
355/// like [`crate::dynamics::ExternalImpulse`]. Pinned particles ignore them.
356#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
357#[reflect(Component, Default, PartialEq)]
358pub struct SoftBodyExternalImpulse {
359    /// A velocity change applied to every particle, i.e., an impulse per unit of mass: the whole
360    /// soft body is kicked at the same velocity.
361    pub velocity_change: Vect,
362    /// Impulses applied to individual particles, as `(particle index, impulse)` pairs: the
363    /// velocity of each particle changes by its impulse divided by its mass.
364    pub particle_impulses: Vec<(u32, Vect)>,
365}
366
367impl SoftBodyExternalImpulse {
368    /// Resets these impulses to zero.
369    pub fn reset(&mut self) {
370        self.velocity_change = Vect::ZERO;
371        self.particle_impulses.clear();
372    }
373}
374
375/// The multiplier applied to the rest area (2D) or volume (3D) of a soft body to obtain the
376/// target of its volume preservation (`> 1` inflates the soft body).
377///
378/// It overrides the volume factor of the [`SoftBody`] builder. When removed, the builder's volume
379/// factor is restored.
380#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
381#[reflect(Component, Default, PartialEq)]
382pub struct SoftBodyVolumeFactor(pub Real);
383
384impl Default for SoftBodyVolumeFactor {
385    fn default() -> Self {
386        Self(1.0)
387    }
388}
389
390/// Selects the solver simulating the elasticity of a soft body.
391///
392/// It overrides the solver of the [`SoftBody`] builder. When removed, the builder's solver is
393/// restored.
394#[cfg(feature = "fem")]
395#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Default)]
396pub struct SoftBodyElasticitySolver(pub SoftBodySolver);
397
398/// The state of a soft body, written back after each simulation step.
399///
400/// This component is inserted automatically on every soft body entity. Modifying it has no
401/// effect.
402#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Reflect)]
403#[reflect(Component, Default, PartialEq)]
404pub struct SoftBodyState {
405    /// The world-space, mass-weighted, center of the particles.
406    pub center_of_mass: Vect,
407    /// Whether the soft body is sleeping.
408    pub is_sleeping: bool,
409    /// Whether the soft body takes part in the simulation (see [`SoftBodyDisabled`]; Rapier also
410    /// disables soft bodies with non-finite state).
411    pub is_enabled: bool,
412    /// A counter bumped whenever the topology of the soft body changes (e.g. because of a tear).
413    pub topology_version: u32,
414    /// The number of particles of the soft body.
415    pub num_particles: usize,
416}
417
418/// A cluster of particles of a soft body, backed by a proxy rigid-body.
419///
420/// Insert this component on an entity (usually not the soft body entity itself) to create the
421/// cluster: the entity then gets a [`crate::dynamics::RapierRigidBodyHandle`] of the cluster's
422/// proxy rigid-body, so [`ImpulseJoint`]s and [`Collider`]s can be attached to it like to any
423/// rigid-body. The proxy follows the particles of the cluster: its pose is written back to the
424/// entity's [`Transform`], but changes of the [`Transform`] (or velocity, forces...) of the
425/// entity are not applied to it. Do not add a [`RigidBody`] component to this entity. A
426/// [`Transform`] (and a `Visibility` when a rendering feature is enabled) is inserted if missing,
427/// so rendered children can be added to the entity.
428///
429/// Removing this component or despawning its entity removes the cluster (particles covered only
430/// by this cluster are removed with it). Changes made to this component after the cluster was
431/// created are ignored, except for [`Self::soft_body`] which is updated by the plugin when a tear
432/// moves the cluster to another soft body.
433#[derive(Component, Clone, Debug, PartialEq, Eq, Reflect)]
434#[reflect(Component, PartialEq)]
435#[cfg_attr(
436    any(
437        feature = "debug-render-2d",
438        feature = "debug-render-3d",
439        feature = "picking-backend",
440        feature = "async-collider",
441        feature = "to-bevy-mesh"
442    ),
443    require(Transform, Visibility)
444)]
445#[cfg_attr(
446    not(any(
447        feature = "debug-render-2d",
448        feature = "debug-render-3d",
449        feature = "picking-backend",
450        feature = "async-collider",
451        feature = "to-bevy-mesh"
452    )),
453    require(Transform)
454)]
455pub struct SoftBodyCluster {
456    /// The entity of the soft body the cluster belongs to.
457    pub soft_body: Entity,
458    /// The indices of the particles of the cluster.
459    pub particles: Vec<u32>,
460}
461
462impl SoftBodyCluster {
463    /// A cluster of the given particles of `soft_body`.
464    pub fn new(soft_body: Entity, particles: impl IntoIterator<Item = u32>) -> Self {
465        Self {
466            soft_body,
467            particles: particles.into_iter().collect(),
468        }
469    }
470}
471
472/// Pins every particle of a cluster: the cluster becomes a fixed region dragging the rest of its
473/// soft body, and can be moved with a [`SoftBodyClusterKinematicTarget`].
474///
475/// Insert it on a [`SoftBodyCluster`] entity, or on a [`SoftBody`] entity to pin its whole-body
476/// cluster. The pinned particles add up to the ones of the soft body's
477/// [`SoftBodyPinnedParticles`]. When removed, the particles of the cluster get back the pinned
478/// state given by the [`SoftBodyPinnedParticles`] of the soft body (or its [`SoftBody`] builder)
479/// and by the other pinned clusters.
480#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
481#[reflect(Component, Default, PartialEq)]
482pub struct SoftBodyClusterPinned;
483
484/// The world-space pose a pinned cluster is moved to.
485///
486/// Each time this component changes, the pinned particles of the cluster are moved rigidly over
487/// the next simulation step (with matching velocities) so that the rest shape of the cluster,
488/// centered on its rest centroid, is placed at this pose; then they are held there. Only the
489/// translation and rotation of the [`Transform`] are used. Inserting it also inserts
490/// [`SoftBodyClusterPinned`], since only pinned particles follow the target.
491///
492/// Insert it on a [`SoftBodyCluster`] entity, or on a [`SoftBody`] entity for its whole-body
493/// cluster. Removing it leaves the particles where they are.
494#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Reflect)]
495#[reflect(Component, Default, PartialEq)]
496#[require(SoftBodyClusterPinned)]
497pub struct SoftBodyClusterKinematicTarget(pub Transform);
498
499/// Enables the shape matching of the particles of a cluster toward the cluster's frame, or
500/// toward a target pose.
501///
502/// Insert it on a [`SoftBodyCluster`] entity, or on a [`SoftBody`] entity for its whole-body
503/// cluster (whose shape matching can also be enabled by its [`SoftBody`] builder). Changes are
504/// applied to the cluster. When removed, the shape matching of the cluster is disabled (or reset
505/// to the builder's setting for a whole-body cluster) and its target is cleared.
506#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Reflect)]
507#[reflect(Component, Default, PartialEq)]
508pub struct SoftBodyClusterShapeMatching {
509    /// The world-space pose the particles are shape-matched toward (only its translation and
510    /// rotation are used), or `None` to shape-match them toward the cluster's own frame.
511    pub target: Option<Transform>,
512}
513
514/// Regional material overrides of the elements (cells and edges) fully contained in a cluster.
515///
516/// Insert it on a [`SoftBodyCluster`] entity, or on a [`SoftBody`] entity for its whole-body
517/// cluster. Changes are applied to the elements of the cluster. When removed, the default values
518/// are applied back, which also discards the per-element tear resistances given by the
519/// [`SoftBody`] builder for these elements.
520#[derive(Component, Copy, Clone, Debug, PartialEq)]
521pub struct SoftBodyClusterMaterial {
522    /// Multiplier of the stiffness (Young modulus) of the cells of the cluster (default: `1.0`).
523    pub stiffness_scale: Real,
524    /// Softness overriding the one of the material for the structural and bending edges of the
525    /// cluster (default: `None`, the material's softness).
526    pub edge_softness: Option<SpringCoefficients<Real>>,
527    /// Multiplier of the tear thresholds of the edges and cells of the cluster (default: `1.0`).
528    pub tear_resistance: Real,
529}
530
531impl Default for SoftBodyClusterMaterial {
532    fn default() -> Self {
533        Self {
534            stiffness_scale: 1.0,
535            edge_softness: None,
536            tear_resistance: 1.0,
537        }
538    }
539}
540
541/// Turns the [`Collider`] of this entity into a deformable collider bound to the particles of a
542/// soft body.
543///
544/// The collider shape must be a polyline (2D) or a triangle mesh (3D) with the deformable flag
545/// (`PolylineFlags::DEFORMABLE`, `TriMeshFlags::DEFORMABLE`). Its vertices, placed by the
546/// [`GlobalTransform`] of the entity when it is created, are bound to the particles of `target`
547/// according to `binding`. After its creation, the collider follows the particles: changes to the
548/// [`Transform`] and shape of the entity are ignored, but the other collider components (friction,
549/// collision groups, events...) apply as usual.
550///
551/// If the binding fails, an error is logged and a [`DeformableColliderError`] is inserted.
552#[derive(Component, Clone, Debug)]
553pub struct DeformableCollider {
554    /// The entity of the [`SoftBody`] (the collider is bound to its whole-body cluster) or of the
555    /// [`SoftBodyCluster`] the collider is bound to.
556    pub target: Entity,
557    /// How the collider's vertices are bound to the particles.
558    pub binding: SoftMeshBinding,
559}
560
561impl DeformableCollider {
562    /// A deformable collider bound to `target` according to `binding`.
563    pub fn new(target: Entity, binding: SoftMeshBinding) -> Self {
564        Self { target, binding }
565    }
566}
567
568/// Inserted on an entity whose [`DeformableCollider`] could not be created.
569///
570/// Remove it (after fixing the collider or its binding) to try again.
571#[derive(Component, Copy, Clone, Debug, PartialEq, Eq)]
572pub struct DeformableColliderError(pub SoftBindingError);
573
574/// Add this component to a [`SoftBody`] entity to render the soft body with a mesh kept in sync
575/// with its particles.
576///
577/// The plugin generates the mesh (see [`soft_body_mesh`] for the geometry: the skin or surface
578/// of the soft body in 3D, its cells in 2D; never the meshes of [`DeformableCollider`]s) and
579/// inserts it as a `Mesh3d` (3D) or `Mesh2d` (2D) component if the entity doesn't have one
580/// already (the existing mesh asset is overwritten otherwise). After each simulation step, the
581/// vertex positions (and normals in 3D) are updated from the particles, and the mesh is rebuilt
582/// when the topology of the soft body changes (e.g. after a tear). The vertices are expressed in
583/// the frame of the entity's [`Transform`] (see the [module documentation](self)). A material
584/// must be added by the user.
585#[cfg(feature = "to-bevy-mesh")]
586#[derive(Component, Clone, Debug, Default)]
587pub struct SoftBodyMeshSync {
588    pub(crate) built: Option<SoftBodyMeshSignature>,
589}
590
591/// What a synchronized soft-body mesh was built from: it is rebuilt when this changes.
592#[cfg(feature = "to-bevy-mesh")]
593#[derive(Copy, Clone, Debug, PartialEq, Eq)]
594pub(crate) struct SoftBodyMeshSignature {
595    pub handle: SoftBodyHandle,
596    pub topology_version: u32,
597    pub num_vertices: usize,
598    pub num_indices: usize,
599}
600
601/// The geometry used to render a soft body.
602#[cfg(feature = "to-bevy-mesh")]
603pub(crate) struct SoftBodyRenderGeometry {
604    /// World-space vertex positions.
605    pub vertices: Vec<Vect>,
606    /// The indices of the triangles (triangle list) or segments (line list).
607    pub indices: Vec<u32>,
608    /// Whether the indices describe triangles (`true`) or segments.
609    pub triangles: bool,
610}
611
612#[cfg(feature = "to-bevy-mesh")]
613impl SoftBodyRenderGeometry {
614    /// The geometry rendering `soft_body`, whose colliders are in `colliders` (see
615    /// [`soft_body_mesh`]).
616    pub fn new(soft_body: &RapierSoftBody, colliders: &rapier::geometry::ColliderSet) -> Self {
617        let vertices: Vec<Vect> = soft_body.particle_positions().collect();
618
619        #[cfg(feature = "dim3")]
620        {
621            // The meshes created with the soft body (the colliders of the meshes added later,
622            // e.g. by `DeformableCollider`s, stand for other entities).
623            let own = |mesh: &&SoftCollisionMesh| {
624                !mesh.collision_enabled()
625                    || colliders
626                        .get(mesh.collider())
627                        .is_some_and(|co| co.user_data == soft_body.user_data)
628            };
629            let surface = |skinned: bool| {
630                soft_body.meshes().filter(own).find(|mesh| {
631                    mesh.arity() == 3 && !mesh.indices().is_empty() && mesh.is_skinned() == skinned
632                })
633            };
634            if let Some(mesh) = surface(true).or_else(|| surface(false)) {
635                return Self {
636                    vertices: mesh.vertex_positions(soft_body).collect(),
637                    indices: mesh.indices().iter().flatten().copied().collect(),
638                    triangles: true,
639                };
640            }
641
642            if !soft_body.boundary().is_empty() {
643                return Self {
644                    vertices,
645                    indices: soft_body.boundary().iter().flatten().copied().collect(),
646                    triangles: true,
647                };
648            }
649        }
650        #[cfg(feature = "dim2")]
651        let _ = colliders;
652
653        #[cfg(feature = "dim2")]
654        {
655            if !soft_body.cells().is_empty() {
656                return Self {
657                    vertices,
658                    indices: soft_body
659                        .cells()
660                        .iter()
661                        .flat_map(|cell| cell.vertices)
662                        .collect(),
663                    triangles: true,
664                };
665            }
666
667            if !soft_body.boundary().is_empty() {
668                return Self {
669                    vertices,
670                    indices: soft_body.boundary().iter().flatten().copied().collect(),
671                    triangles: false,
672                };
673            }
674        }
675
676        Self {
677            vertices,
678            indices: soft_body
679                .edges()
680                .iter()
681                .filter(|edge| edge.kind == SoftBodyEdgeKind::Structural)
682                .flat_map(|edge| edge.vertices)
683                .collect(),
684            triangles: false,
685        }
686    }
687
688    /// The mesh with this geometry, with its vertices transformed by `transform`.
689    pub fn to_mesh(&self, transform: &bevy::math::Affine3A) -> bevy::mesh::Mesh {
690        use bevy::asset::RenderAssetUsages;
691        use bevy::mesh::{Indices, Mesh, PrimitiveTopology};
692
693        let topology = if self.triangles {
694            PrimitiveTopology::TriangleList
695        } else {
696            PrimitiveTopology::LineList
697        };
698        let mut mesh = Mesh::new(topology, RenderAssetUsages::default());
699        mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, self.positions(transform));
700        mesh.insert_indices(Indices::U32(self.indices.clone()));
701        if self.triangles && !self.indices.is_empty() {
702            mesh.compute_smooth_normals();
703        }
704        mesh
705    }
706
707    /// The vertex positions transformed by `transform`, as a Bevy mesh attribute.
708    pub fn positions(&self, transform: &bevy::math::Affine3A) -> Vec<[f32; 3]> {
709        self.vertices
710            .iter()
711            .map(|v| {
712                #[cfg(feature = "dim2")]
713                let v = v.extend(0.0);
714                #[cfg(feature = "dim3")]
715                let v = *v;
716                transform.transform_point3(v).to_array()
717            })
718            .collect()
719    }
720}
721
722/// A mesh rendering the given Rapier soft body, with world-space vertices.
723///
724/// `colliders` is the collider set of the soft body's context: it tells apart the meshes created
725/// with the soft body from the ones added later by [`DeformableCollider`]s, which are not
726/// rendered. The rendered geometry is chosen in this order:
727/// - In 3D: the soft body's skin (see [`SoftBodyBuilder::volumetric_skinned`]), else its surface
728///   collision mesh, else its boundary triangles; for a soft body without surface (e.g. a rope),
729///   a line list of its structural edges. The mesh has normals.
730/// - In 2D: a triangle mesh of its cells (even if its skin is the collision mesh: the skin is
731///   embedded in the cells and cannot be filled), else a line list of its boundary, else of its
732///   structural edges.
733///
734/// The mesh can be modified after being added to the assets (it keeps its main-world data).
735#[cfg(feature = "to-bevy-mesh")]
736pub fn soft_body_mesh(
737    soft_body: &RapierSoftBody,
738    colliders: &rapier::geometry::ColliderSet,
739) -> bevy::mesh::Mesh {
740    SoftBodyRenderGeometry::new(soft_body, colliders).to_mesh(&bevy::math::Affine3A::IDENTITY)
741}