soft_body_creation_and_insertion
A soft-body is described by a R3SoftBodyDesc, which constructors build the lattice of the most common shapes:
| Constructor | Dimension | Lattice |
|---|---|---|
r3DefaultSoftBodyDesc, then r3SoftBodyDesc_SetParticles | 2D, 3D | No element at all: only the given particles. |
r3RopeSoftBodyDesc | 2D, 3D | Structural and bending edges between the particles of a line. |
r3ClothSoftBodyDesc, r3ClothAnisotropicSoftBodyDesc, r3ClothTubeSoftBodyDesc | 3D | Structural, shear and bending edges, with a triangle surface. |
r2GridSoftBodyDesc | 2D | Triangle cells filling a rectangle. |
r3CuboidSoftBodyDesc | 3D | Tetrahedral cells filling a box. |
r2PolygonSoftBodyDesc, r2DiskSoftBodyDesc | 2D | A closed boundary preserving its area. |
r3SphereSoftBodyDesc | 3D | A closed surface preserving its volume, with dihedral bending constraints. |
r3SoftBodyDesc_SetSurfaceMesh | 2D (segments), 3D (triangles) | The vertices and edges of a mesh, held by shape matching. |
r2SoftBodyDesc_SetTrimesh | 2D | The vertices and edges of a triangle mesh, held by shape matching. |
r3VolumetricSoftBodyDesc | 2D, 3D | Cells filling a closed mesh. |
The constructors return a description initialized with the default values of every other field, which can then be
modified before its insertion. The ones existing in a single dimension are only given with their prefix in that
dimension, e.g., there is no r3GridSoftBodyDesc.
The r3VolumetricSoftBodyDesc 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. The meshing parameters are given to the constructor as an R3VolumeMeshParameters, initialized by r3NewVolumeMeshParameters from the size of the cells: in 2D, min_angle is the minimum angle of the triangles, whereas in 3D, cover_smoothing and cover_subdivisions control how much the cover is smoothed and subdivided around the boundary, i.e., how closely it follows the mesh, and enclosure whether the surface alone is covered (1), leaving the interior empty. Note that the mesh is only filled by the insertion, which reports an error if the mesh isn't closed or encloses nothing at that cell size.
- Example 2D
- Example 3D
// Fill a closed, counter-clockwise polyline with triangle cells of about 0.2 in size.
const R2Vector vertices[] = {
r2Vector(-0.5, -0.25),
r2Vector(0.5, -0.25),
r2Vector(0.5, 0.25),
r2Vector(-0.5, 0.25),
};
const R2Edge indices[] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}};
R2SoftBodyDesc block = r2VolumetricSoftBodyDesc((R2VectorView){vertices, 4}, (R2EdgeView){indices, 4},
r2NewVolumeMeshParameters(0.2));
block.translation = r2Vector(-3.0, 1.0);
// The polyline is only read during the insertion, which fails if it isn't closed.
R2SoftBodyHandle block_handle = r2InsertSoftBody(world, &block);
// Fill a closed, outward-oriented triangle mesh with tetrahedral cells of about 0.2 in size.
// The triangle mesh of a cuboid (the subdivision counts only matter for curved shapes).
R3SharedShape *cuboid = r3CuboidSharedShape(r3Vector(0.5, 0.25, 0.25));
R3TriMeshData *mesh = r3SharedShape_ToTrimesh(cuboid, 0, 0);
size_t num_vertices = r3TriMeshData_Vertices(mesh, NULL, 0);
size_t num_indices = r3TriMeshData_Indices(mesh, NULL, 0);
R3Vector *vertices = malloc(num_vertices * sizeof(R3Vector));
R3Triangle *triangles = malloc(num_indices * sizeof(uint32_t));
r3TriMeshData_Vertices(mesh, vertices, num_vertices);
r3TriMeshData_Indices(mesh, (uint32_t *)triangles, num_indices);
R3SoftBodyDesc block = r3VolumetricSoftBodyDesc((R3VectorView){vertices, num_vertices},
(R3TriangleView){triangles, num_indices / 3},
r3NewVolumeMeshParameters(0.2));
block.translation = r3Vector(-3.0, 1.0, 0.0);
// The mesh is only read during the insertion, which fails if it isn't closed.
R3SoftBodyHandle block_handle = r3InsertSoftBody(world, &block);
free(vertices);
free(triangles);
r3FreeTriMeshData(mesh);
r3FreeSharedShape(cuboid);
The description allows the definition of everything else that is specific to one soft-body: the particles held in place (the
pinned particles, pinned), the softness of its constraints
(material, e.g., the same softness for every constraint with r3UniformSoftBodyMaterial), the mass of its particles (one mass for every particle with
particleMass, a total mass for the whole body with
totalMass, or one mass per particle with
masses), their radius
(particleRadius), the collider its surface is made of, and whether that
surface is allowed to collide with itself (selfContacts). The particles
can also be given a linear damping (linearDamping), a gravity scale
(gravityScale), a dominance group
(dominanceGroup), and be allowed to sleep or
not (canSleep), exactly like a rigid-body. Inserting the
soft-body into the world (r3InsertSoftBody) will
automatically create the rigid-body standing for it (its root body), as well as the
colliders covering its surface:
- Example 2D
- Example 3D
// A world with a ground.
R2World *world = r2NewWorld();
R2ColliderDesc ground = r2CuboidColliderDesc(r2Vector(10.0, 0.1));
r2InsertColliderWithoutParent(world, &ground);
// Description of a rope of 20 particles between two points.
R2SoftBodyDesc rope = r2RopeSoftBodyDesc(r2Vector(0.0, 3.0), r2Vector(2.0, 3.0), 20);
// Description of a grid of `nx` by `ny` particles filled with triangle cells.
R2SoftBodyDesc grid = r2GridSoftBodyDesc(r2Vector(3.0, 1.0), r2Vector(1.0, 1.0), 6, 6);
// Description of a disk: a ring of particles holding its area (a pressurized blob).
R2SoftBodyDesc disk = r2DiskSoftBodyDesc(r2Vector(0.0, 3.0), 0.8, 24);
// Description of a closed polygon of particles holding its area.
const R2Vector polygon_points[] = {
r2Vector(5.0, 4.0),
r2Vector(7.0, 4.0),
r2Vector(7.0, 6.0),
r2Vector(5.0, 6.0),
};
R2SoftBodyDesc polygon = r2PolygonSoftBodyDesc((R2VectorView){polygon_points, 4});
const uint32_t n = 20;
R2SoftBodyDesc sheet = r2GridSoftBodyDesc(r2Vector(-3.0, 3.0), r2Vector(1.0, 1.0), n, n);
// Particles held in place.
const uint32_t pinned[] = {0, n - 1};
sheet.pinned = (R2IndexView){pinned, 2};
// A uniform softness (natural frequency in Hz, damping ratio) for every constraint.
sheet.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30.0, 1.0});
// The mass of each particle.
// Default: 1.0
sheet.particleMass = 0.05;
// The thickness of the particles, for collisions.
// Default: disabled, i.e., the radius computed by the constructor.
sheet.particleRadius = (R2OptionalReal){1, 0.05};
// The template of the body's colliders: its shape is replaced by the deformable surface.
sheet.collider = r2BallColliderDesc(0.05);
sheet.collider.friction = 0.8;
// Whether the body may fall asleep.
// Default: 1
sheet.canSleep = 1;
// Insert the soft-body: this creates its hidden root rigid-body and its colliders.
R2SoftBodyHandle sheet_handle = r2InsertSoftBody(world, &sheet);
// A world with a ground.
R3World *world = r3NewWorld();
R3ColliderDesc ground = r3CuboidColliderDesc(r3Vector(10.0, 0.1, 10.0));
r3InsertColliderWithoutParent(world, &ground);
// Description of a rope of 20 particles between two points.
R3SoftBodyDesc rope = r3RopeSoftBodyDesc(r3Vector(0.0, 3.0, 0.0), r3Vector(2.0, 3.0, 0.0), 20);
// Description of a cloth: `nu` by `nv` particles, particle `(i, j)` at `origin + i * du + j * dv`.
const uint32_t n = 20;
R3SoftBodyDesc cloth =
r3ClothSoftBodyDesc(r3Vector(-1.0, 2.0, -1.0), r3Vector(0.1, 0.0, 0.0), r3Vector(0.0, 0.0, 0.1), n, n);
// Description of a box of `nx * ny * nz` particles filled with tetrahedral cells.
R3SoftBodyDesc box = r3CuboidSoftBodyDesc(r3Vector(3.0, 1.0, 0.0), r3Vector(0.5, 0.5, 0.5), 4, 4, 4);
// Description of a hollow sphere holding its volume (a balloon).
R3SoftBodyDesc balloon = r3SphereSoftBodyDesc(r3Vector(0.0, 3.0, 3.0), 0.8, 2);
// Any field of a description can be modified before its insertion.
// Particles held in place.
const uint32_t pinned[] = {0, n - 1, n * (n - 1), n * n - 1};
cloth.pinned = (R3IndexView){pinned, 4};
// A uniform softness (natural frequency in Hz, damping ratio) for every constraint.
cloth.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30.0, 1.0});
// The mass of each particle.
// Default: 1.0
cloth.particleMass = 0.05;
// The thickness of the particles, for collisions.
// Default: disabled, i.e., the radius computed by the constructor.
cloth.particleRadius = (R3OptionalReal){1, 0.02};
// The template of the body's colliders: its shape is replaced by the deformable surface.
cloth.collider = r3BallColliderDesc(0.05);
cloth.collider.friction = 0.8;
// Whether the surface collides with itself.
// Default: 0
cloth.selfContacts = 1;
// Whether the body may fall asleep.
// Default: 1
cloth.canSleep = 1;
// Insert the soft-body: this creates its hidden root rigid-body and its colliders.
R3SoftBodyHandle cloth_handle = r3InsertSoftBody(world, &cloth);
The collider given to the collider field of the description 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 (collisionEnabled set to 0).
Two descriptions can be merged into a single soft-body with r3SoftBodyDesc_SetAppended (each appended description keeps its own particles, masses, and elements, whereas every other setting comes from the description it is appended to), and their pieces
sewn together with additional edges (r3SoftBodyDesc_SetAddedEdges), 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.