Skip to main content

soft_body_clusters

Joints and rigid colliders both need a frame to be attached to, i.e., a translation and a rotation, which a soft-body doesn't have. This is what soft frames are for: a soft frame is a rigid-body of type R3_SOFT_FRAME (see r3RigidBody_IsSoftFrame) which pose is computed at each timestep from a set of particles, by shape-matching. Since it is an ordinary rigid-body, every API working with rigid-bodies works with it too: impulse joints of any kind (fixed, revolute, prismatic, generic, etc.) can be attached to it, as well as rigid colliders (sensors included), and its position can be read at any time. Therefore a soft-body is linked to another soft-body, to a rigid-body, or to a multibody exactly the same way two rigid-bodies are.

The root body​

Every soft-body is created with one soft frame covering all of its particles: its root body. It is the rigid-body given by r3SoftBody_RootBody. A joint attached to it acts on the soft-body as a whole, and so does a force or an impulse applied to it. It also stands for the soft-body in the islands, and it is the parent of the colliders the engine built for the body's surface (a deformable collider bound to another cluster has the proxy of that cluster as its parent instead). The soft-body a collider belongs to is given by r3Collider_SoftBody, which is how a collider reported by a scene query or by a collision event is traced back to the body it covers.

// The rigid-body the engine created for the whole soft-body, read back after its insertion.
R2RigidBodyHandle root = r2SoftBody_RootBody(jelly_handle);
assert(r2RigidBody_IsSoftFrame(root));

// A rigid collider attached to it follows the frame of the whole body: here a sensor
// detecting what comes close to the jelly.
R2ColliderDesc sensor = r2BallColliderDesc(1.6);
sensor.isSensor = 1;
r2InsertCollider(root, &sensor);

// A joint attached to it acts on the soft-body as a whole: this one hangs the jelly under a
// fixed anchor by a spring.
R2RigidBodyDesc anchor_desc = r2FixedRigidBodyDesc();
anchor_desc.position.translation = r2Vector(3.0, 5.0);
R2RigidBodyHandle anchor = r2InsertRigidBody(world, &anchor_desc);
R2JointDesc spring = r2SpringJointDesc(2.0, 60.0, 2.0);
r2InsertImpulseJoint(anchor, root, &spring);
warning

The pose of the root body is recomputed from the particles at each timestep, therefore moving it has no effect.

Removing it with r3RemoveRigidBody is rejected (with R3_INVALID_ARGUMENT): the soft-body is removed as a whole with r3RemoveSoftBody instead (see removal).

Clusters​

A single frame for the whole body is often not expressive enough: several joints attached to the root body all act on the body as a whole, and their effect isn't concentrated where they are attached. This is why a soft-body can also be given clusters (r3SoftBody_AddCluster), i.e., soft frames over any subset of its particles, each with its own pose computed by shape-matching over that subset only. Joints attached to different clusters then act on different parts of the body, each with its own orientation:

One soft frame per cluster

Similarly, rigid colliders attached to the proxies of different clusters move and rotate independently, which is what allows the definition of rigid parts on a deformable body: the handle of a deformable hammer, the bones of a soft character, or the plate a jelly is carried on.

Rigid colliders attached to different soft frames

A cluster is identified by its index in the soft-body, returned by r3SoftBody_AddCluster, and its proxy is given by r3SoftBody_ClusterProxy. The indices of the live clusters of a body are given by r3SoftBody_Clusters, and the particles of one of them by r3SoftBody_ClusterParticles:

// A cluster over the top particles of the jelly: a rigid proxy that joints and
// colliders can attach to.
size_t num_jelly_particles = r2SoftBody_NumParticles(jelly_handle);
uint32_t *top = malloc(num_jelly_particles * sizeof(uint32_t));
size_t num_top = 0;
for (uint32_t i = 0; i < num_jelly_particles; i++) {
if (r2SoftBody_ParticlePosition(jelly_handle, i).y > 2.0) {
top[num_top++] = i;
}
}
uint32_t cluster = r2SoftBody_AddCluster(jelly_handle, top, num_top);
free(top);
R2RigidBodyHandle proxy = r2SoftBody_ClusterProxy(jelly_handle, cluster);
// A rigid plate welded onto the cluster.
R2RigidBodyDesc plate_desc = r2DynamicRigidBodyDesc();
plate_desc.position.translation = r2Vector(3.0, 2.4);
R2RigidBodyHandle plate = r2InsertRigidBody(world, &plate_desc);
R2ColliderDesc plate_collider = r2CuboidColliderDesc(r2Vector(1.2, 0.05));
plate_collider.density = 0.4;
r2InsertCollider(plate, &plate_collider);
R2JointDesc weld = r2FixedJointDesc();
weld.localFrame1.translation = r2Vector(0.0, -0.1);
r2InsertImpulseJoint(plate, proxy, &weld);
// A cluster can be pinned, driven or tuned as a whole.
r2SoftBody_SetClusterStiffnessScale(jelly_handle, cluster, 2.0);
r2SoftBody_SetClusterShapeMatchingEnabled(jelly_handle, cluster, 1);

A cluster also defines a few settings for the elements it covers, which gives regional materials without needing separate bodies:

  • The stiffness scale (r3SoftBody_SetClusterStiffnessScale) multiplies the Young modulus of every cell entirely contained in the cluster (the cells straddling its boundary are left unchanged).
  • The edge softness (r3SoftBody_SetClusterEdgeSoftness) overrides the softness of every edge entirely contained in the cluster, e.g., a stiffer collar on a shirt.
  • The tear resistance (r3SoftBody_SetClusterTearResistance) multiplies the tear thresholds of every element entirely contained in the cluster, e.g., a tough region, or a perforation line.
  • Shape-matching (r3SoftBody_SetClusterShapeMatchingEnabled) pulls the particles of the cluster toward the frame of its proxy (or toward the target pose given by r3SoftBody_SetClusterShapeMatchingTarget), so that part of the body tends to keep the shape it was created with.
warning

The rotation of a cluster is deduced from its particles, which isn't possible for a cluster made of a single particle (or, in 3D, of collinear particles). Such a cluster has no angular response, therefore the angular parts of the joints attached to its proxy are disabled.