soft_body_particles
The state of a soft-body is the state of its particles, which are identified by their index in the body. Their positions
and their velocities can be read (particle_position, particle_positions, particle_velocity,
particle_velocities) and modified (set_particle_position,
set_particle_velocity) at any time, one by one or all at
once. The elements built from them (edges, cells, and boundary) can be read as well, e.g., in order to render the body with your own mesh. These
are methods of the Rapier soft-body of the entity, given by the soft_body and soft_body_mut methods of the
physics context (see soft-bodies and entities). Note that the positions
are expressed in world-space, and not in the frame of the entity.
A particle can also be pinned (the SoftBodyPinnedParticles
component). A pinned particle is kinematic: it is no longer affected by the forces
nor by the contacts, and it will simply hold its position, or follow the kinematic target
(the SoftBodyKinematicTargets
component) or the velocity it is given. This is, e.g., how a piece of cloth
is hung on a wall, or how a rope is dragged by the player. Releasing the particle gives it back its nominal mass and
lets it keep its current velocity:
The SoftBodyPinnedParticles component lists exactly the particles that are pinned: it replaces the
particles pinned by the builder, which are restored when the component is removed. The SoftBodyKinematicTargets
component gives world-space targets to some of the pinned particles: each time it changes, the listed particles are
moved to their target over the next step, then held there:
- Example 2D
- Example 3D
fn control_particles(
mut commands: Commands,
mut context: WriteRapierContext,
sheet: Single<Entity, With<Sheet>>,
) -> Result {
let mut context = context.single_mut()?;
let Some(soft_body) = context.soft_body_mut(*sheet) else {
return Ok(());
};
// Read the particles (in world-space).
let position = soft_body.particle_position(0);
let velocity = soft_body.particle_velocity(0);
let positions: Vec<Vec2> = soft_body.particle_positions().collect();
assert_eq!(positions.len(), soft_body.num_particles());
// Move a particle.
soft_body.set_particle_position(1, position + Vec2::new(0.0, 0.1));
soft_body.set_particle_velocity(1, velocity);
// The elements: edges, cells and the boundary segments.
let num_edges = soft_body.edges().len();
let num_cells = soft_body.cells().len();
let boundary: &[[u32; 2]] = soft_body.boundary();
assert!(num_edges > 0 && num_cells > 0 && !boundary.is_empty());
// Pin particles (exactly the listed ones), and drive the particle 2 kinematically.
commands.entity(*sheet).insert((
SoftBodyPinnedParticles(vec![0, 19, 2]),
SoftBodyKinematicTargets(vec![(2, Vec2::new(-3.5, 3.5))]),
));
Ok(())
}
fn control_particles(
mut commands: Commands,
mut context: WriteRapierContext,
cloth: Single<Entity, With<Cloth>>,
) -> Result {
let mut context = context.single_mut()?;
let Some(soft_body) = context.soft_body_mut(*cloth) else {
return Ok(());
};
// Read the particles (in world-space).
let position = soft_body.particle_position(0);
let velocity = soft_body.particle_velocity(0);
let positions: Vec<Vec3> = soft_body.particle_positions().collect();
assert_eq!(positions.len(), soft_body.num_particles());
// Move a particle.
soft_body.set_particle_position(1, position + Vec3::new(0.0, 0.1, 0.0));
soft_body.set_particle_velocity(1, velocity);
// The elements: edges, cells and the boundary triangles.
let num_edges = soft_body.edges().len();
let num_cells = soft_body.cells().len();
let boundary: &[[u32; 3]] = soft_body.boundary();
assert!(num_edges > 0 && num_cells == 0 && !boundary.is_empty());
// Pin particles (exactly the listed ones), and drive the particle 2 kinematically.
commands.entity(*cloth).insert((
SoftBodyPinnedParticles(vec![0, 19, 380, 399, 2]),
SoftBodyKinematicTargets(vec![(2, Vec3::new(-1.0, 2.5, -0.8))]),
));
Ok(())
}
Setting the position of a particle explicitly teleports it: no contact is taken into account along the way, so a particle can be moved inside of another object this way. Whenever the motion must be seen by the contacts and by the friction (to drag a piece of cloth, for example), it is recommended to pin the particle and to give it a kinematic target instead.
Controlling a region kinematically
A whole region of the body is controlled at once through a cluster covering it. Pinning
the cluster (the SoftBodyClusterPinned
component) pins all of its particles, and its kinematic target
(the SoftBodyClusterKinematicTarget component, a world-space
Transform which also pins the cluster when it is inserted) moves them
rigidly: each pinned particle is sent where the rest shape of the cluster places it at the target pose, with the
matching velocity. The rest of the body is then simulated as usual, and drags behind the controlled region, e.g., the
hand of a soft character carrying something:
- Example 2D
- Example 3D
fn drive_cluster(mut commands: Commands, cluster: Single<Entity, With<PlateCluster>>) {
// Pin every particle of the cluster (the target inserts `SoftBodyClusterPinned`), and move
// it to a world-space pose: the cluster behaves like a kinematic rigid part dragging the
// rest of the body.
commands
.entity(*cluster)
.insert(SoftBodyClusterKinematicTarget(Transform::from_xyz(
3.0, 2.5, 0.0,
)));
}
fn release_cluster(mut commands: Commands, cluster: Single<Entity, With<PlateCluster>>) {
// Release it: the cluster is simulated again.
commands
.entity(*cluster)
.remove::<(SoftBodyClusterKinematicTarget, SoftBodyClusterPinned)>();
}
fn drive_cluster(mut commands: Commands, cluster: Single<Entity, With<PlateCluster>>) {
// Pin every particle of the cluster (the target inserts `SoftBodyClusterPinned`), and move
// it to a world-space pose: the cluster behaves like a kinematic rigid part dragging the
// rest of the body.
commands
.entity(*cluster)
.insert(SoftBodyClusterKinematicTarget(Transform::from_xyz(
3.0, 2.0, 0.0,
)));
}
fn release_cluster(mut commands: Commands, cluster: Single<Entity, With<PlateCluster>>) {
// Release it: the cluster is simulated again.
commands
.entity(*cluster)
.remove::<(SoftBodyClusterKinematicTarget, SoftBodyClusterPinned)>();
}