Skip to main content

soft_body_deformable_colliders

Games don't need the simulated shape of a body to be as detailed as its visual shape: a coarse and well-shaped lattice is faster and more stable to simulate than one cell per visual triangle. This is why Rapier supports cage simulation and skinning. The detailed mesh is embedded in a coarse volumetric lattice, aka. its cage, which is the only part being simulated. The vertices of the mesh are then interpolated from the deformed cells holding them, aka. skinning:

A detailed mesh, its coarse cage, and the mesh following the deformed cage

Skinned soft-bodies​

The r3VolumetricSoftBodyDesc constructor computes the cage of a closed mesh automatically, and the same mesh becomes the skin of the body once it is also given to r3SoftBodyDesc_SetSkin. That automatic cage is built for performance rather than geometric fidelity: in 3D, it encloses the whole mesh with the tetrahedra of a lattice, without snapping them to the mesh:

A screwdriver mesh and its automatically generated cage (blue)

By default, the body still collides through the boundary of its cage, which is as coarse as its cells. Its skin can become its actual collision mesh instead with the skinCollision field of R3SoftBodyDesc.

Note that the mesh arrays are only borrowed by the description until its insertion. The vertices of the skin, as well as the ones of any other mesh of the body, are read back with r3SoftBody_MeshVertices, given the collider of the mesh (r3SoftBody_MeshColliders gives the colliders of the meshes of a body). A skin that doesn't collide has no collider: r3SoftBody_Meshes lists every mesh of the body with its identifier (an R3SoftMeshInfo which is_skinned and collision_enabled fields tell which mesh is which), and r3SoftBody_MeshVerticesById reads the vertices of a mesh from that identifier. Their triangles (segments in 2D) are read the same way, with r3SoftBody_MeshIndices and r3SoftBody_MeshIndicesById:

// A detailed outline held by a coarse cage of cells: only the cells are simulated, and the
// outline (the skin) follows their deformation.
R2Vector vertices[48];
R2Edge indices[48];
for (uint32_t i = 0; i < 48; i++) {
R2Real angle = (R2Real)i / 48 * 2.0 * R2_PI;
vertices[i] = r2Vector(0.5 * cos(angle), 0.5 * sin(angle));
indices[i] = (R2Edge){i, (i + 1) % 48};
}
R2VectorView outline_vertices = {vertices, 48};
R2SurfaceElementView outline_segments = {indices, 48};
// The cage: the outline filled with cells of about 0.25 in size.
R2SoftBodyDesc skinned =
r2VolumetricSoftBodyDesc(outline_vertices, outline_segments, r2NewVolumeMeshParameters(0.25));
// The skin: the outline itself, following the cells holding its vertices.
r2SoftBodyDesc_SetSkin(&skinned, outline_vertices, outline_segments);
// Collide through the skin instead of the boundary of the cage.
skinned.skinCollision = 1;
skinned.translation = r2Vector(0.0, 4.0);
R2SoftBodyHandle skinned_handle = r2InsertSoftBody(world, &skinned);

// The skin is the body's collision mesh: read its vertices back to render it.
R2ColliderHandle skin_collider;
r2SoftBody_MeshColliders(skinned_handle, &skin_collider, 1);
R2Vector skin_vertices[48];
size_t num_skin_vertices = r2SoftBody_MeshVertices(skinned_handle, skin_collider, skin_vertices, 48);
info

A skin doesn't need a computed cage: any mesh can be given as the skin of a body built with cells, with r3SoftBodyDesc_SetSkin. Each of its vertices is bound to the cell closest to it, in the pose the cells are built in.

Deformable colliders​

The colliders built from the surface or the skin of a soft-body are generated by the engine itself, but it is also possible to give a body a collider of your own which vertices follow its particles: a deformable collider (r3InsertDeformableCollider). This is a polyline in 2D, or a triangle mesh in 3D, flagged as deformable, and attached to the proxy of one of the clusters of the body (the root body, a cluster itself, can be used too). Its vertices are given in the frame of that proxy, and they can be read back at any time in order to render the mesh where the simulation moved it. A deformable collider can be a sensor as well, e.g., to detect what enters a deformable volume.

How the vertices follow the particles is given by the binding (R3SoftMeshBindingDesc):

  • R3_SOFT_BINDING_SKINNED: each vertex is embedded in the cell of the cluster holding it, i.e., the collider is a skin of the cage.
  • R3_SOFT_BINDING_DIRECT: the vertex i follows the particle given for it, which must belong to the cluster. Its alternative that binds every vertex to the closest particle within a given distance (R3_SOFT_BINDING_DIRECT_BY_POSITION) is useful when the mesh is the one the particles were built from.

The collider is described by an ordinary R3ColliderDesc which shape is a polyline (2D) or a triangle mesh (3D) flagged with R2_POLYLINE_DEFORMABLE or R3_TRIMESH_DEFORMABLE (see r2ShapeDesc_SetPolyline and r3ShapeDesc_SetTrimesh), and its binding by an R3SoftMeshBindingDesc initialized with r3DefaultSoftMeshBindingDesc:

  • kind selects the binding: R3_SOFT_BINDING_SKINNED (the default), R3_SOFT_BINDING_DIRECT, or R3_SOFT_BINDING_DIRECT_BY_POSITION.
  • particles is the particle followed by each vertex, for a direct binding.
  • epsilon is the distance within which each vertex is bound to the closest particle, for a binding by position.
  • selfContacts makes the mesh collide with itself.

The collider is created by r3InsertDeformableCollider, given the rigid-body handle of the root body (r3SoftBody_RootBody) or of a cluster proxy (r3SoftBody_ClusterProxy) it is attached to. Its other properties (friction, collision groups, events, sensor, etc.) apply as usual. The arrays of the collider and of the binding are only borrowed until the insertion. If the binding fails, the error handler is called and the returned handle is invalid. Then the current vertices of the collider are read with r3SoftBody_MeshVertices:

// A deformable polyline bound to the blob: each vertex follows one particle (direct),
// or is embedded in the cell holding it (skinned). The polyline is given in the frame
// of the proxy it is attached to.
R2RigidBodyHandle root = r2SoftBody_RootBody(blob);
R2Pose root_pose_inverse = r2PoseInverse(r2RigidBody_Position(root));
size_t num = r2SoftBody_NumParticles(blob); // 24 particles.
R2Vector vertices[24];
R2Edge indices[24];
uint32_t particles[24];
r2SoftBody_ParticlePositions(blob, vertices, 24);
for (uint32_t i = 0; i < num; i++) {
vertices[i] = r2PoseTransformPoint(root_pose_inverse, vertices[i]);
indices[i] = (R2Edge){i, (i + 1) % num};
// The vertex `i` follows the particle `i`.
particles[i] = i;
}
R2ColliderDesc outline = r2DefaultColliderDesc();
r2ShapeDesc_SetPolyline(&outline.shape, (R2VectorView){vertices, num}, (R2EdgeView){indices, num},
R2_POLYLINE_DEFORMABLE);
outline.isSensor = 1;
R2SoftMeshBindingDesc binding = r2DefaultSoftMeshBindingDesc();
binding.kind = R2_SOFT_BINDING_DIRECT;
binding.particles = (R2IndexView){particles, num};
R2ColliderHandle outline_handle = r2InsertDeformableCollider(&outline, &binding, root);
// The polyline follows the particles: read its current vertices back.
R2Vector outline_vertices[24];
size_t num_outline_vertices = r2SoftBody_MeshVertices(blob, outline_handle, outline_vertices, 24);
info

A deformable collider has no mass: its density is ignored, and it is the particles which hold the mass of the soft-body. Note that a collider given no contact skin explicitly gets the particle radius of the soft-body as its skin, so its thickness matches the thickness of the surface of the body.