soft_body_sets
Like the rigid-bodies and the colliders, the soft-bodies of a simulation are stored inside of a set: the SoftBodySet.
The examples of this page use the PhysicsWorld, a façade owning all the sets of one simulation, but the sets can also
be used directly. Note that the insertion of a soft-body (SoftBodySet::insert) needs the rigid-body set and the
collider set as well, because a soft-body owns the rigid-body standing for it and the colliders of its surface:
- Example 2D
- Example 3D
// The sets can also be used directly, without the `PhysicsWorld` façade.
let mut soft_body_set = SoftBodySet::new();
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
let rope = SoftBodyBuilder::rope(Vector::new(0.0, 3.0), Vector::new(2.0, 3.0), 20);
let rope_handle = soft_body_set.insert(rope, &mut rigid_body_set, &mut collider_set);
let soft_body = &soft_body_set[rope_handle];
assert_eq!(soft_body.num_particles(), 20);
// The sets can also be used directly, without the `PhysicsWorld` façade.
let mut soft_body_set = SoftBodySet::new();
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
let rope = SoftBodyBuilder::rope(Vector::new(0.0, 3.0, 0.0), Vector::new(2.0, 3.0, 0.0), 20);
let rope_handle = soft_body_set.insert(rope, &mut rigid_body_set, &mut collider_set);
let soft_body = &soft_body_set[rope_handle];
assert_eq!(soft_body.num_particles(), 20);
A soft-body is created by inserting a SoftBody component on an entity. This component only wraps the Rapier
SoftBodyBuilder of the body, and it is read only once: the plugin creates the Rapier soft-body during the next physics
update, then inserts its RapierSoftBodyHandle on the entity, as well as a SoftBodyState component updated after each
step with the (mass-weighted) center of mass of the body, whether it is sleeping, its number of particles, etc.
Therefore modifying the SoftBody component afterwards has no effect: the body is controlled at runtime by the other
soft-body components (SoftBodyMaterial, SoftBodyPinnedParticles, etc.) described in the next sections. Adding the
SoftBodyDisabled component disables the soft-body until it is removed, and despawning the entity (or removing its
SoftBody component) removes the soft-body from the simulation.
The positions of the particles given to the builder are expressed in the local frame of the entity: they are
transformed by its GlobalTransform when the soft-body is created. After that, the particles live in world-space, and
the plugin drives the Transform of the entity: it follows the pose of the root body
of the soft-body, i.e., its translation follows the centroid of the free particles, and its rotation is the one best
fitting the particles onto their rest shape (the identity when the soft-body is created), whereas its scale is kept.
The colliders of the children of the soft-body entity are attached to the root body, so these children follow the body
as a whole. Whatever must follow a specific part of the body is better attached to a
cluster entity.
The soft-body entity also stands for the rigid-body and the colliders the engine creates for it:
- It is mapped to the root body of the soft-body, so it can be used like any rigid-body entity by the impulse joints.
- The colliders of its surface carry the bits of the entity in their user-data, so the collision events, the contact pairs, and the scene queries report the soft-body entity itself as the collider entity.
- Its collider components (
Friction,Restitution,CollisionGroups,SolverGroups,ActiveEvents,ActiveHooks,ActiveCollisionTypes,ContactForceEventThreshold,ContactSkin, andSensor) configure the colliders of its surface exactly like for aCollider: they are applied when the soft-body is created and whenever they change, and removing one of them restores the value of the collider template of the builder. These colliders are given byRapierRigidBodySet::soft_body_colliders.
Finally, the Rapier soft-bodies are stored in the soft_bodies set of the RapierRigidBodySet component of the physics
context, next to the rigid-bodies. The Rapier soft-body type is re-exported as RapierSoftBody (and its material as
RapierSoftBodyMaterial) to avoid any confusion with the components. They are accessed with the ReadRapierContext and
WriteRapierContext system parameters, which provide shortcuts for the most common operations: soft_body gives the
Rapier soft-body of an entity, soft_body_entity gives the entity of a soft-body handle, soft_body_particle_positions
and soft_body_center_of_mass read its state, and soft_body_mut gives mutable access to the soft-body of an entity:
- Example 2D
- Example 3D
fn read_soft_bodies(context: ReadRapierContext, sheet: Single<Entity, With<Sheet>>) -> Result {
let context = context.single()?;
// The Rapier soft-bodies live in the `RapierRigidBodySet` of the context, together with the
// map from their entity to their handle (also given by their `RapierSoftBodyHandle`).
let Some(handle) = context.rigidbody_set.entity2soft_body().get(&*sheet) else {
return Ok(()); // Not created yet.
};
let soft_body: &RapierSoftBody = &context.rigidbody_set.soft_bodies[*handle];
// Shortcuts are provided for the most common operations.
assert_eq!(context.soft_body_entity(*handle), Some(*sheet));
assert_eq!(
context.soft_body_particle_positions(*sheet).unwrap().len(),
soft_body.num_particles()
);
let _center = context.soft_body_center_of_mass(*sheet);
// The colliders of its surface, configured by its collider components.
let surface_colliders = context
.rigidbody_set
.soft_body_colliders(&context.colliders.colliders, *sheet)
.unwrap_or_default();
for handle in surface_colliders {
let _friction = context.colliders.colliders[handle].friction();
}
Ok(())
}
fn read_soft_bodies(context: ReadRapierContext, cloth: Single<Entity, With<Cloth>>) -> Result {
let context = context.single()?;
// The Rapier soft-bodies live in the `RapierRigidBodySet` of the context, together with the
// map from their entity to their handle (also given by their `RapierSoftBodyHandle`).
let Some(handle) = context.rigidbody_set.entity2soft_body().get(&*cloth) else {
return Ok(()); // Not created yet.
};
let soft_body: &RapierSoftBody = &context.rigidbody_set.soft_bodies[*handle];
// Shortcuts are provided for the most common operations.
assert_eq!(context.soft_body_entity(*handle), Some(*cloth));
assert_eq!(
context.soft_body_particle_positions(*cloth).unwrap().len(),
soft_body.num_particles()
);
let _center = context.soft_body_center_of_mass(*cloth);
// The colliders of its surface, configured by its collider components.
let surface_colliders = context
.rigidbody_set
.soft_body_colliders(&context.colliders.colliders, *cloth)
.unwrap_or_default();
for handle in surface_colliders {
let _friction = context.colliders.colliders[handle].friction();
}
Ok(())
}
The properties covered by a component should be modified through that component rather than through soft_body_mut.
The components are applied again whenever they change, so, e.g., a material set with RapierSoftBody::set_material is
replaced by the SoftBodyMaterial of the entity the next time this component is modified.
The SoftBodyMeshSync component (from the to-bevy-mesh feature, enabled by default) renders the soft-body with a mesh
kept in sync with its particles. The plugin generates the mesh and inserts it as a Mesh3d (3D) or Mesh2d (2D)
component, updates its vertices after each step, and rebuilds it whenever the topology of the body changes, e.g., after
a tear. Its vertices are expressed in the frame of the Transform of the entity, and the
material must be added by you, as shown in the creation example. In 3D, the
mesh is the skin of the body if it has one, else its surface (or its boundary triangles), or a line list of its edges
for a body without any surface, e.g., a rope. In 2D, it is made of its cells, or of its boundary segments (or of its
edges). The meshes of the deformable colliders bound to the body are never
part of it.
The soft-bodies are owned by the world, next to the rigid-bodies and the colliders, and are identified by their
R3SoftBodyHandle. There is no separate set to manage: r3InsertSoftBody creates the soft-body as well as the
rigid-body standing for it and the colliders of its surface, and r3FreeWorld frees all of them. The number of
soft-bodies of the world is given by r3SoftBodyCount, and their handles by r3SoftBodyHandles. A handle becomes
invalid once its soft-body is removed, which is checked by r3SoftBody_Contains. Conversely, the soft-body a
rigid-body stands for (its root body, or the proxy of one of its
clusters) is given by r3RigidBody_SoftBody, which returns an invalid handle for any
other rigid-body:
- Example 2D
- Example 3D
// The world owns every soft-body: their number and their handles can be read at any time.
R2SoftBodyDesc rope_desc = r2RopeSoftBodyDesc(r2Vector(0.0, 3.0), r2Vector(2.0, 3.0), 20);
R2SoftBodyHandle rope_handle = r2InsertSoftBody(world, &rope_desc);
size_t num_soft_bodies = r2SoftBodyCount(world);
R2SoftBodyHandle *soft_bodies = malloc(num_soft_bodies * sizeof(R2SoftBodyHandle));
r2SoftBodyHandles(world, soft_bodies, num_soft_bodies);
for (size_t i = 0; i < num_soft_bodies; i++) {
printf("Soft-body %u has %zu particles.\n", soft_bodies[i].index,
r2SoftBody_NumParticles(soft_bodies[i]));
}
free(soft_bodies);
// Whether a handle still refers to a soft-body of the world.
assert(r2SoftBody_Contains(rope_handle));
// The soft-body a rigid-body stands for (its root body, or the proxy of one of its clusters).
R2SoftBodyHandle owner = r2RigidBody_SoftBody(r2SoftBody_RootBody(rope_handle));
assert(owner.index == rope_handle.index && owner.generation == rope_handle.generation);
// The world owns every soft-body: their number and their handles can be read at any time.
R3SoftBodyDesc rope_desc = r3RopeSoftBodyDesc(r3Vector(0.0, 3.0, 0.0), r3Vector(2.0, 3.0, 0.0), 20);
R3SoftBodyHandle rope_handle = r3InsertSoftBody(world, &rope_desc);
size_t num_soft_bodies = r3SoftBodyCount(world);
R3SoftBodyHandle *soft_bodies = malloc(num_soft_bodies * sizeof(R3SoftBodyHandle));
r3SoftBodyHandles(world, soft_bodies, num_soft_bodies);
for (size_t i = 0; i < num_soft_bodies; i++) {
printf("Soft-body %u has %zu particles.\n", soft_bodies[i].index,
r3SoftBody_NumParticles(soft_bodies[i]));
}
free(soft_bodies);
// Whether a handle still refers to a soft-body of the world.
assert(r3SoftBody_Contains(rope_handle));
// The soft-body a rigid-body stands for (its root body, or the proxy of one of its clusters).
R3SoftBodyHandle owner = r3RigidBody_SoftBody(r3SoftBody_RootBody(rope_handle));
assert(owner.index == rope_handle.index && owner.generation == rope_handle.generation);
The state of the body as a whole can be read at any time as well: its number of particles (r3SoftBody_NumParticles),
its total mass (r3SoftBody_Mass), its mass-weighted center of mass (r3SoftBody_CenterOfMass), the area (2D) or the
volume (3D) it currently encloses and its rest value (r3SoftBody_Volume, r3SoftBody_RestVolume), and whether it is
sleeping (r3SoftBody_IsSleeping, see also r3SoftBody_WakeUp). Finally, a soft-body can be disabled until it is
enabled again with r3SoftBody_SetEnabled.
Like the rigid-bodies and the colliders, the soft-bodies of a simulation are stored inside of a set: the SoftBodySet,
given by PhysicsWorld.soft_bodies. The examples of this page use the PhysicsWorld, which owns all the sets of one
simulation, but the sets can also be used directly (a PhysicsPipeline then simulates the soft-bodies of the set given
as its soft_bodies argument). Note that the insertion of a soft-body (SoftBodySet.insert) needs the rigid-body set
and the collider set as well, because a soft-body owns the rigid-body standing for it and the colliders of its surface:
# The sets can also be used directly, without the `PhysicsWorld` façade.
soft_body_set = rp.SoftBodySet()
rigid_body_set = rp.RigidBodySet()
collider_set = rp.ColliderSet()
rope = rp.SoftBody.rope((0.0, 3.0, 0.0), (2.0, 3.0, 0.0), 20)
rope_handle = soft_body_set.insert(rope, rigid_body_set, collider_set)
soft_body = soft_body_set[rope_handle]
assert soft_body.num_particles == 20
# The state of the body as a whole.
print("Mass:", soft_body.mass, "center of mass:", soft_body.center_of_mass)
print("Sleeping:", soft_body.is_sleeping)
# Every soft body of the set.
for handle, soft_body in soft_body_set:
print(handle, soft_body.num_particles)
The set supports len(), in, indexing by a SoftBodyHandle, and iteration over (handle, soft_body) pairs. The
SoftBody objects it gives are live views: reading or modifying one of them reads or modifies the soft-body stored in
the set, and using one of them after its soft-body was removed raises an InvalidHandle error. On the other hand, the objects describing its particles, elements, and
clusters (e.g., the SoftBodyParticle given by particle) are snapshots, which don't follow the simulation.
The state of the body as a whole can be read at any time as well: its number of particles (num_particles), its total
mass (mass), its mass-weighted center of mass (center_of_mass), the volume it currently encloses and its rest value
(volume, rest_volume), and whether it is sleeping (is_sleeping, see also wake_up). A soft-body can also be
disabled until it is enabled again with set_enabled. Conversely, the soft-body a rigid-body stands for (its
root body, or the proxy of one of its clusters) is
given by the soft_body property of that RigidBody, which is None for any other rigid-body.