soft_body_creation_and_insertion
A soft-body is described by a SoftBodyDesc, which constructors build the lattice of the most common shapes (their
JavaScript names are camel-cased, e.g., clothTube):
| Constructor | Dimension | Lattice |
|---|---|---|
rope | 2D, 3D | Structural and bending edges between the particles of a line. |
cloth, cloth_anisotropic, cloth_tube | 3D | Structural, shear and bending edges, with a triangle surface. |
grid | 2D | Triangle cells filling a rectangle. |
cuboid | 3D | Tetrahedral cells filling a box. |
polygon, disk | 2D | A closed boundary preserving its area. |
sphere | 3D | A closed surface preserving its volume, with dihedral bending constraints. |
trimesh, polyline | 2D, 3D (trimesh), 2D (polyline) | The vertices and edges of a mesh, held by shape matching. |
volumetric | 2D, 3D | Cells filling a closed mesh. |
The volumetric constructor is the one to use for an arbitrary solid: it fills a closed mesh (segments in 2D, triangles
in 3D, oriented outward) with cells of about the requested size. The interior of the mesh is triangulated by Delaunay
refinement in 2D, whereas in 3D every cell of a lattice the mesh reaches is kept whole: the result contains the mesh
instead of following it exactly, and its boundary is as blocky as its cells.
- Example 2D
- Example 3D
// Fill a closed, counter-clockwise polyline (here a rectangle) with triangle cells of about
// 0.2 in size.
let boxVertices = new Float32Array([-0.5, -0.25, 0.5, -0.25, 0.5, 0.25, -0.5, 0.25]);
let boxIndices = new Uint32Array([0, 1, 1, 2, 2, 3, 3, 0]);
let blockDesc = RAPIER.SoftBodyDesc.volumetric(boxVertices, boxIndices, 0.2);
let block = world.createSoftBody(blockDesc.setTranslation({ x: -3.0, y: 1.0 }));
// Fill a closed, outward-oriented triangle mesh (here a box) with tetrahedral cells of
// about 0.2 in size.
let boxVertices = new Float32Array([
-0.5, -0.25, -0.25, 0.5, -0.25, -0.25, 0.5, 0.25, -0.25, -0.5, 0.25, -0.25,
-0.5, -0.25, 0.25, 0.5, -0.25, 0.25, 0.5, 0.25, 0.25, -0.5, 0.25, 0.25,
]);
let boxIndices = new Uint32Array([
0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 0, 1, 5, 0, 5, 4,
3, 7, 6, 3, 6, 2, 0, 4, 7, 0, 7, 3, 1, 2, 6, 1, 6, 5,
]);
let blockDesc = RAPIER.SoftBodyDesc.volumetric(boxVertices, boxIndices, 0.2);
let block = world.createSoftBody(blockDesc.setTranslation({ x: -3.0, y: 1.0, z: 0.0 }));
The builder allows the definition of everything else that is specific to one soft-body: the particles held in place (the
pinned particles, setPinnedParticles), the softness of its constraints
(setSoftness), the mass of its particles (one mass for every particle with
setParticleMass, a total mass for the whole body with
setMass, or one mass per particle with
setMasses), their radius
(setParticleRadius), the collider its surface is made of, and whether that
surface is allowed to collide with itself (setSelfContacts). The particles
can also be given a linear damping (setLinearDamping), a gravity scale
(setGravityScale), a dominance group
(setDominanceGroup), and be allowed to sleep or
not (setCanSleep), exactly like a rigid-body. Inserting the
soft-body into the world (World.createSoftBody) will
automatically create the rigid-body standing for it (its root body), as well as the
colliders covering its surface:
- Example 2D
- Example 3D
// The world that will contain our soft bodies.
let world = new RAPIER.World({ x: 0.0, y: -9.81 });
world.createCollider(RAPIER.ColliderDesc.cuboid(10.0, 0.1));
// Description of a rope of 20 particles between two points.
let example1 = RAPIER.SoftBodyDesc.rope({ x: 0.0, y: 3.0 }, { x: 2.0, y: 3.0 }, 20);
// Description of a grid of `nx` by `ny` particles filled with triangle cells.
let example2 = RAPIER.SoftBodyDesc.grid({ x: 3.0, y: 1.0 }, { x: 1.0, y: 1.0 }, 6, 6);
// Description of a disk: a ring of particles holding its area (a pressurized blob).
let example3 = RAPIER.SoftBodyDesc.disk({ x: 0.0, y: 3.0 }, 0.8, 24);
// Description of a closed polygon of particles holding its area.
let example4 = RAPIER.SoftBodyDesc.polygon([5.0, 4.0, 7.0, 4.0, 7.0, 6.0, 5.0, 6.0]);
// Description over raw particle positions; the elements are added by the setters.
let example5 = new RAPIER.SoftBodyDesc([0.0, 1.0, 1.0, 1.0]).setEdges([0, 1]);
let n = 20;
let sheetDesc = RAPIER.SoftBodyDesc.grid({ x: -3.0, y: 3.0 }, { x: 1.0, y: 1.0 }, n, n)
// Particles held in place.
.setPinnedParticles([0, n - 1])
// A uniform softness (natural frequency in Hz, damping ratio) for every constraint.
.setSoftness(30.0, 1.0)
// The mass of each particle.
// Default: 1.0
.setParticleMass(0.05)
// The thickness of the particles, for collisions.
// Default: 0.01
.setParticleRadius(0.05)
// The template of the body's colliders: its shape is replaced by the deformable surface.
.setSurfaceCollider(RAPIER.ColliderDesc.ball(0.05).setFriction(0.8))
// Whether the body may fall asleep.
// Default: true
.setCanSleep(true);
// Create the soft body: this creates its hidden root rigid body and its colliders.
let sheet = world.createSoftBody(sheetDesc);
// The integer handle of the soft body can be read from the `handle` field.
let sheetHandle = sheet.handle;
// The world that will contain our soft bodies.
let world = new RAPIER.World({ x: 0.0, y: -9.81, z: 0.0 });
world.createCollider(RAPIER.ColliderDesc.cuboid(10.0, 0.1, 10.0));
// Description of a rope of 20 particles between two points.
let example1 = RAPIER.SoftBodyDesc.rope({ x: 0.0, y: 3.0, z: 0.0 }, { x: 2.0, y: 3.0, z: 0.0 }, 20);
// Description of a cloth: `nu` by `nv` particles, particle `(i, j)` at `origin + i * du + j * dv`.
let example2 = RAPIER.SoftBodyDesc.cloth(
{ x: -1.0, y: 2.0, z: -1.0 }, { x: 0.1, y: 0.0, z: 0.0 }, { x: 0.0, y: 0.0, z: 0.1 }, 20, 20,
);
// Description of a box of `nx * ny * nz` particles filled with tetrahedral cells.
let example3 = RAPIER.SoftBodyDesc.cuboid({ x: 3.0, y: 1.0, z: 0.0 }, { x: 0.5, y: 0.5, z: 0.5 }, 4, 4, 4);
// Description of a hollow sphere holding its volume (a balloon).
let example4 = RAPIER.SoftBodyDesc.sphere({ x: 0.0, y: 3.0, z: 3.0 }, 0.8, 2);
// Description over raw particle positions; the elements are added by the setters.
let example5 = new RAPIER.SoftBodyDesc([0.0, 1.0, 0.0, 1.0, 1.0, 0.0]).setEdges([0, 1]);
let n = 20;
let clothDesc = RAPIER.SoftBodyDesc.cloth(
{ x: -1.0, y: 2.0, z: -1.0 }, { x: 0.1, y: 0.0, z: 0.0 }, { x: 0.0, y: 0.0, z: 0.1 }, n, n,
)
// Particles held in place.
.setPinnedParticles([0, n - 1, n * (n - 1), n * n - 1])
// A uniform softness (natural frequency in Hz, damping ratio) for every constraint.
.setSoftness(30.0, 1.0)
// The mass of each particle.
// Default: 1.0
.setParticleMass(0.05)
// The thickness of the particles, for collisions.
// Default: 0.01
.setParticleRadius(0.02)
// The template of the body's colliders: its shape is replaced by the deformable surface.
.setSurfaceCollider(RAPIER.ColliderDesc.ball(0.05).setFriction(0.8))
// Whether the surface collides with itself.
// Default: false
.setSelfContacts(true)
// Whether the body may fall asleep.
// Default: true
.setCanSleep(true);
// Create the soft body: this creates its hidden root rigid body and its colliders.
let cloth = world.createSoftBody(clothDesc);
// The integer handle of the soft body can be read from the `handle` field.
let clothHandle = cloth.handle;
The collider given to SoftBodyDesc.setSurfaceCollider is
only a template: its shape is replaced by the deformable surface of the soft-body, and its density is ignored, whereas
all its other properties are kept. Therefore this is where the friction, the collision
groups, or the active events of
the soft-body must be set. A body that should only collide through colliders of your own can be built without any
default one (setNoSurfaceCollider).
Two builders can be merged into a single soft-body with append, and their pieces
sewn together with additional edges (addEdges), which rest length is the distance
their particles have when they are added. This is, e.g., how the sleeves of a shirt are attached to its body.