Verlet Cloth
Cloth physics through position-based dynamics
Cloth simulation doesn't store velocity. Instead, it tracks position history. Each particle remembers where it was, and that memory encodes all the motion it needs. Constraints — the invisible threads holding the cloth together — pull particles back toward each other through iterative relaxation. The result: fabric that bends, stretches, and drapes like the real thing, all from a few simple rules applied thousands of times per frame.
Verlet Integration
The key insight: velocity is implicit. Traditional physics stores both position and velocity. Verlet integration stores only the current position and the previous position. The difference between them encodes motion. When the cloth moves, the gap between where it was and where it is becomes its velocity.
update() {
if (this.pinned) return;
const velX = (this.x - this.prevX) * damping;
const velY = (this.y - this.prevY) * damping;
this.prevX = this.x;
this.prevY = this.y;
this.x += velX;
this.y += velY + gravity;
}
The particle doesn't know how fast it's going. It only knows where it is and where it was. That difference, scaled by damping and added to the current position, produces the next position. Gravity adds downward acceleration. The beauty: no velocity state to corrupt, no accumulated floating-point drift. Position history carries everything.
Distance Constraints
Cloth holds its shape because particles want to stay near their neighbors. Each constraint records the rest length — the ideal distance between two particles — then pulls them back together when they drift apart. The constraint doesn't push or accelerate. It directly corrects position.
relax() {
const dx = this.p2.x - this.p1.x;
const dy = this.p2.y - this.p1.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) return;
const diff = (dist - this.restLength) / dist;
const offsetX = dx * diff * 0.5;
const offsetY = dy * diff * 0.5;
if (!this.p1.pinned) {
this.p1.x += offsetX;
this.p1.y += offsetY;
}
if (!this.p2.pinned) {
this.p2.x -= offsetX;
this.p2.y -= offsetY;
}
}
The constraint measures current distance, compares it to rest length, and moves both particles toward each other to close the gap. Half the correction to each particle preserves symmetry. Pinned particles refuse correction — they're anchors the cloth hangs from. The formula is pure geometry: find the error, distribute it, done.
Iterative Relaxation
One pass isn't enough. Moving a particle to satisfy one constraint violates others. The solution: run the constraints many times per frame, each iteration bringing the system closer to equilibrium. More iterations mean stiffer cloth. Fewer iterations mean stretchier cloth. The control you see labeled "Stiffness" is literally the iteration count.
for (let i = 0; i < stiffness; i++) {
for (const c of constraints) {
c.relax();
}
for (const p of particles) {
p.constrain();
}
}
The order matters less than the count. Each relaxation pass reduces error. After enough iterations, the cloth settles into a configuration where all constraints are approximately satisfied. This isn't physically accurate, but it's visually convincing — and fast enough to run in real time. Game physics favors believable over correct.
Grid Structure
The cloth isn't a solid sheet. It's a mesh. Each particle connects to at most four neighbors: left, right, up, down. The grid spacing defines the resolution. More particles mean finer cloth but more computation. The initial configuration spreads particles evenly, then lets physics deform them.
const COLS = 25;
const ROWS = 18;
const SPACING = 22;
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const idx = row * COLS + col;
if (col < COLS - 1) {
constraints.push(new Constraint(particles[idx], particles[idx + 1]));
}
if (row < ROWS - 1) {
constraints.push(new Constraint(particles[idx], particles[idx + COLS]));
}
}
}
Horizontal constraints link columns. Vertical constraints link rows. Diagonal constraints would add shear resistance, preventing the cloth from collapsing into a diamond shape, but they're computationally heavier. The choice of which constraints to include changes the material behavior. Fewer constraints, stretchier cloth.
Pinned Particles
The cloth needs anchors. Three particles along the top row are pinned: left corner, right corner, and center. These particles never move. Every constraint touching them transmits force through the mesh, but the pinned particles absorb it into immobility. Without pins, gravity would pull the entire cloth off-screen.
const pinned = row === 0 && (col === 0 || col === COLS - 1 || col === Math.floor(COLS / 2));
particles.push(new Particle(x, y, pinned));
Pins are drawn differently — filled circles instead of dots. Dragging a pinned particle does nothing, because the constraint relaxation checks the pinned flag before applying corrections. The cloth hangs from its anchors, and gravity does the rest.
What This Reveals
Cloth simulation shows that you don't need complex physics to produce complex behavior. A grid of points, a web of distances, an iterative solver — and fabric emerges. The technique extends beyond cloth. Same code structure handles rope, chains, soft bodies, and ragdoll physics. The insight: position-based dynamics sidesteps the difficulty of computing forces and accelerations. You simply measure what's wrong and move things toward what's right.
Verlet integration also reveals a deeper principle: state compression. Traditional methods store position and velocity separately. Verlet stores position twice, and lets the difference encode velocity. This compression eliminates a class of bugs where velocity accumulates error. The position history is never wrong, because it's always the actual position. The simulation becomes self-correcting by construction.