Skip to main content

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_velocitiesparticlePosition, particlePositions, particleVelocity, particleVelocitiesr3SoftBody_ParticlePosition, r3SoftBody_ParticlePositions, r3SoftBody_ParticleVelocity, r3SoftBody_ParticleVelocitiesparticle_position, particle_positions, particle_velocity, particle_velocities) and modified (set_particle_position, set_particle_velocitysetParticlePosition, setParticleVelocityr3SoftBody_SetParticlePosition, r3SoftBody_SetParticleVelocityset_particle_position, set_particle_velocity) at any time, one by one or all at once. The elements built from them (edges, cells, and boundaryedges, cells, and boundaryr3SoftBody_Edges, r3SoftBody_Cells, and r3SoftBody_Boundaryedges, cells, and boundary) can be read as well, e.g., in order to render the body with your own mesh. These arrays are copied into a buffer of your own, which capacity is given as the last argument, and a NULL buffer with a zero capacity only returns the length of the array. The elements are given as flat arrays of particle indices: 2 per edge, 3 (2D) or 4 (3D) per cell, and 2 (2D) or 3 (3D) per boundary element. The arrays of every particle and of every element are NumPy arrays with one row per particle or per element, e.g., of shape (N, 3) for the positions and (B, 3) for the boundary triangles. All the positions (or velocities) are modified at once by assigning such an array to particle_positions (or particle_velocities). A snapshot of all the properties of a particle (its mass, its rest position, whether it is pinned, etc.) is given by particle. 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 (set_particle_pinnedthe SoftBodyPinnedParticles componentsetParticlePinnedr3SoftBody_SetParticlePinnedset_particle_pinned). 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 (set_particle_kinematic_targetthe SoftBodyKinematicTargets componentsetParticleKinematicTargetr3SoftBody_SetParticleKinematicTargetset_particle_kinematic_target) 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:

let soft_body = &mut world.soft_bodies[sheet_handle];
// Read the particles.
let position = soft_body.particle_position(0);
let velocity = soft_body.particle_velocity(0);
let positions: Vec<Vector> = soft_body.particle_positions().collect();
assert_eq!(positions.len(), soft_body.num_particles());
// Move a particle.
soft_body.set_particle_position(1, position + Vector::new(0.0, 0.1));
soft_body.set_particle_velocity(1, velocity);
// Pin (or release) a particle; a pinned particle can be driven like a kinematic body.
soft_body.set_particle_pinned(2, true);
soft_body.set_particle_kinematic_target(2, Vector::new(-3.5, 3.5));
// 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());
// Read the particles.
let position = sheet.particlePosition(0);
let velocity = sheet.particleVelocity(0);
let positions: Float32Array = sheet.particlePositions(); // Two floats per particle.
console.log("The sheet has", sheet.numParticles(), "particles;", positions.length / 2);
// Move a particle.
sheet.setParticlePosition(1, { x: position.x, y: position.y + 0.1 });
sheet.setParticleVelocity(1, velocity);
// Pin (or release) a particle; a pinned particle can be driven like a kinematic body.
sheet.setParticlePinned(2, true);
sheet.setParticleKinematicTarget(2, { x: -3.5, y: 3.5 });
// The elements: edges (two indices each), cells (three) and the boundary segments (two).
let edges: Uint32Array = sheet.edges();
let cells: Uint32Array = sheet.cells();
let boundary: Uint32Array = sheet.boundary();
console.log(edges.length / 2, "edges,", cells.length / 3, "cells,", boundary.length / 2, "segments");

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:

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(())
}
// Read the particles.
R2Vector position = r2SoftBody_ParticlePosition(sheet_handle, 0);
R2Vector velocity = r2SoftBody_ParticleVelocity(sheet_handle, 0);
size_t num_particles = r2SoftBody_NumParticles(sheet_handle);
R2Vector *positions = malloc(num_particles * sizeof(R2Vector));
r2SoftBody_ParticlePositions(sheet_handle, positions, num_particles);
// Move a particle.
r2SoftBody_SetParticlePosition(sheet_handle, 1, r2VectorAdd(position, r2Vector(0.0, 0.1)));
r2SoftBody_SetParticleVelocity(sheet_handle, 1, velocity);
// Pin (or release) a particle; a pinned particle can be driven like a kinematic body.
r2SoftBody_SetParticlePinned(sheet_handle, 2, 1);
r2SoftBody_SetParticleKinematicTarget(sheet_handle, 2, r2Vector(-3.5, 3.5));
// The elements: edges, cells and the boundary segments, as flat arrays of particle indices
// (2, 3, and 2 indices per element). A NULL buffer with a zero capacity gives their length.
size_t num_edges = r2SoftBody_Edges(sheet_handle, NULL, 0) / 2;
size_t num_cells = r2SoftBody_Cells(sheet_handle, NULL, 0) / 3;
size_t boundary_len = r2SoftBody_Boundary(sheet_handle, NULL, 0);
uint32_t *boundary = malloc(boundary_len * sizeof(uint32_t));
r2SoftBody_Boundary(sheet_handle, boundary, boundary_len);
assert(num_edges > 0 && num_cells > 0 && boundary_len > 0);
free(positions);
free(boundary);
soft_body = world.soft_bodies[cloth_handle]
# Read the particles.
position = soft_body.particle_position(0)
velocity = soft_body.particle_velocity(0)
# All the positions (or velocities) at once, as an (N, 3) NumPy array.
positions = soft_body.particle_positions
assert positions.shape == (soft_body.num_particles, 3)
# Move a particle.
soft_body.set_particle_position(1, position + rp.Vec3(0.0, 0.1, 0.0))
soft_body.set_particle_velocity(1, velocity)
# Pin (or release) a particle; a pinned particle can be driven like a kinematic body.
soft_body.set_particle_pinned(2, True)
soft_body.set_particle_kinematic_target(2, (-1.0, 2.5, -0.8))
# The elements, as NumPy arrays of particle indices: edges, cells and the boundary triangles.
edges = soft_body.edges # Shape (E, 2).
cells = soft_body.cells # Shape (C, 4).
boundary = soft_body.boundary # Shape (B, 3).
assert len(edges) > 0 and len(cells) == 0 and len(boundary) > 0
warning

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 (set_cluster_pinnedthe SoftBodyClusterPinned componentsetClusterPinnedr3SoftBody_SetClusterPinnedset_cluster_pinned) pins all of its particles, and its kinematic target (set_cluster_kinematic_targetthe SoftBodyClusterKinematicTarget component, a world-space Transform which also pins the cluster when it is insertedsetClusterKinematicTargetr3SoftBody_SetClusterKinematicTargetset_cluster_kinematic_target) 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:

// Pin every particle of the cluster, then move it along a path: the cluster behaves like a
// kinematic rigid part dragging the rest of the body.
let jelly = &mut world.soft_bodies[jelly_handle];
jelly.set_cluster_pinned(cluster, true);
jelly.set_cluster_kinematic_target(cluster, Pose::from_translation(Vector::new(3.0, 2.5)));
// Release it: the cluster is simulated again.
jelly.set_cluster_pinned(cluster, false);
// Pin every particle of the cluster, then move it along a path: the cluster behaves like a
// kinematic rigid part dragging the rest of the body.
jelly.setClusterPinned(cluster, true);
jelly.setClusterKinematicTarget(cluster, { x: 3.0, y: 2.5 }, 0.0);
// Release it: the cluster is simulated again.
jelly.setClusterPinned(cluster, false);
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)>();
}
// Pin every particle of the cluster, then move it along a path: the cluster behaves like a
// kinematic rigid part dragging the rest of the body.
r2SoftBody_SetClusterPinned(jelly_handle, cluster, 1);
r2SoftBody_SetClusterKinematicTarget(jelly_handle, cluster, r2TranslationPose(r2Vector(3.0, 2.5)));
// Release it: the cluster is simulated again.
r2SoftBody_SetClusterPinned(jelly_handle, cluster, 0);
# Pin every particle of the cluster, then move it along a path: the cluster behaves like a
# kinematic rigid part dragging the rest of the body.
jelly = world.soft_bodies[jelly_handle]
jelly.set_cluster_pinned(cluster, True)
jelly.set_cluster_kinematic_target(cluster, rp.Isometry3(translation=(3.0, 2.0, 0.0)))
# Release it: the cluster is simulated again.
jelly.set_cluster_pinned(cluster, False)