Physics · interface

ClothOptions

Cloth, as position-based dynamics with compliance.

Explained in Your first game, Rigid bodies.

interface ClothOptions
import type { ClothOptions } from '@driftengine/physics';

In depth

A separate solver from the rigid bodies, deliberately, and it is §5's decision in a different place. Thousands of rotationless particles are the wrong data layout for a rigid constraint graph: every particle would carry an orientation it never uses and an inertia tensor that is always zero. Two solvers with two jobs, not two implementations of one decision.

Compliance rather than a stiffness constant, for the same reason §7 states contact stiffness as a frequency: α = compliance / h² makes a constraint's stiffness independent of the step length, so the iteration count is a quality dial rather than a behaviour change. A raw position-based solver's stiffness is a function of how many times you iterated, which means tuning it is tuning the solver rather than the cloth.

Two-way coupled since 2026-08-27, by an impulse exchange rather than by a merged island. The refusal this replaces was right about the price and wrong about the only way to pay it. Putting particles into the rigid solver's constraint graph would merge islands — a character in a cloak becomes one island with everything the cloak brushes, and merged islands are what §8a's parallelism is made of. So that is still refused. What ships instead is the exchange the two solvers can make without either learning about the other's structure: the collision pass already moves a particle out of a body, and a particle moved by d in a tick of h has had its momentum changed by m·d/h; the body receives the opposite of that at the contact point, through the same applyImpulse any consumer would call.

What that costs, said plainly. The exchange is explicit — the body responds on the next tick rather than in the same solve — so it is a spring with a one-tick delay in it, and a very heavy sheet landing on a very light body can oscillate. coupling is a scale from 0 to 1 and 0 is the default, so no existing consumer's world moves by a millimetre until it asks. What would reverse the remaining refusal: a consumer who needs the sheet and the body to converge together in one solve, at which point the price is the island structure and it is a track rather than a field.

Properties

NameTypeDescription
stretchComplianceoptionalnumberMetres of stretch per newton. Zero is inextensible; larger is stretchier.
bendComplianceoptionalnumberThe same, for the constraints that resist folding.
iterationsoptionalnumberSolver passes per tick. A quality dial: compliance keeps stiffness meaningful across it.
selfDistanceoptionalnumberHow close two unconnected particles may come before they push each other apart, in metres.
More

0 is off and is the default, because self-collision is the half of cloth whose tuning decides whether it ships: a sheet that resists itself at a distance larger than its own spacing cannot lie flat, and one that resists at a distance far smaller than a fold's thickness passes through itself anyway. A consumer sets this from the sheet they built, and the honest starting point is a little under the grid spacing — makeClothGrid at 5 cm wants about 3 to 4 cm.

Particles joined by a constraint are exempt, and that is not an optimisation. A stretch link's rest length is the spacing, so a self-collision distance anywhere near it would have every neighbour pair fighting its own link at every step — the sheet would inflate rather than drape, which is the failure that reads as a solver bug. ClothBody builds the adjacency once at construction for exactly this.

dampingoptionalnumberPer-second velocity retained, as a fraction.
thicknessoptionalnumberHow far a particle is held off a surface it meets.
couplingoptionalnumberHow much of the momentum a pushed particle gave up is handed back to the body, 0 to 1.
More

0 by default, which is one-way coupling and is what every consumer had before this existed. 1 is the whole exchange; anything between is a sheet that pushes less than its mass says it should, which is the dial a consumer reaches for when the delay in the exchange makes a light body jitter.

particleMassoptionalnumberWhat one particle weighs, in kilograms. Only the coupling reads it.
More

Separate from invMass, and the separation is deliberate. That array is a relative weight in the position solve — every free particle is 1 and a pinned one is 0 — and the compliance is tuned against it, so redefining it as an inverse mass in kilograms would move every sheet any consumer has ever tuned. The coupling needs kilograms and nothing else does, so it takes them here.

The default is fifty grams: a square metre of ordinary cloth is two to three hundred grams, and makeClothGrid at a five-centimetre spacing puts four hundred particles in that square metre. A consumer building a tarpaulin or a chainmail cape sets it.

gravityXoptionalnumber
gravityYoptionalnumber
gravityZoptionalnumber