| name | machin-game-demo-cloth |
| description | Build, run, and modify machin-game-demo-cloth — a 3D verlet "cloth" banner in machin (MFL). Use when working on this repo, or as the reference example of structural + shear + bend constraints and per-substep wind as an external acceleration in machin. Covers the math3d module, the raylib FFI block, the package-global `wind` vector, and the constraint-coloring debug visualization. |
machin-game-demo-cloth
A 3D verlet cloth banner — a 14×14 particle grid whose two top corners are pinned, with structural + shear + bend distance constraints and an arrow-key-driven wind force. Pure composition on machin-game-demo-physics.
Shared game-dev substrate (raylib FFI, FlyCam, math module, build/vendoring raylib) lives in the canonical machin-gamedev skill. This file covers the cloth recipe.
Build & run
./build.sh
./machin-game-demo-cloth
Needs machin v0.48.0+, a C compiler, raylib, and a display.
Architecture
The shared substrate
Inherited verbatim from machin-game-demo-physics (the base):
math3d — the Vec3 type and 11 ops (add/sub/scale/dot/cross/len/lensq/norm/lerp/dist).
- The raylib FFI
extern "raylib" block — DrawSphere, DrawLine3D, DrawGrid, the Camera3D/Vector3/Color cstructs, and the input/fps helpers.
- The fixed-timestep accumulator in
main.
- The Verlet integrator (
pos += (pos - old) * damping + a*dt²).
- The relaxation distance-constraint solver.
- The O(n²) sphere-sphere and the sphere-ground collision.
- The
FlyCam (yaw/pitch + WASD).
- The LCG pseudo-random (
rng_seed, rngf, rng_range).
The cloth deltas
1. wind is a package global
var wind = v3(0.0, 0.0, 0.0)
A single Vec3 lives across export calls (well, here: across main's frame iterations). phys_integrate reads it once per substep:
accel := v3_add(v3_scale(w.gravity, dt * dt), v3_scale(wind, dt * dt))
The arrow-key input in main lerps wind toward the user's direction vector at 5% per frame so gusts ramp rather than step. Shift ups the magnitude to ~50 u/s for a strong gust; the unshifted default is ~18 u/s.
2. build_grid gains stiffness layers
The base's grid only added 4-neighbor (axial) constraints — a net not a cloth. We add two passes:
- shear — diagonal
idx → (x+1)*nz + (z+1) and idx → (x+1)*nz + (z-1). The grid folding flat would stretch these diagonally — they snap back.
- bend — skip-2 axial:
idx → (x+2)*nz + z and idx → x*nz + (z+2). Out-of-plane folding changes their length; they resist.
Together with the structural layer and 5 relaxation iterations, the grid drapes.
3. Stiffer solver parameters
phys_new(0.0, -9.8, 0.0, 0.985, 6, 5)
vs the base's (0.0, -15.0, 0.0, 0.995, 6, 3). We trade a bit of bounce for stability: gentler gravity (lighter cloth), stronger damping (oscillations die), more iterations (stiffer cloth).
4. Two corners pinned (banner), not the full top row
The base's build_grid pins the entire top row, which makes a draped tablecloth. We pin only the two corners to get a banner that swings and twists.
Patterns worth copying
- Express external force as
accel * dt², not as a velocity kick. Wind/gravity are accelerations; the integrator handles velocity implicitly via pos - old — accumulate acceleration terms and Verlet does the rest.
- Use a global Vec3 for the controller. Persisting state across frames in MFL is just
var wind = v3(0,0,0); lerping toward the target each frame is a one-liner.
- Compose primitive constraints with the right radius. Structural (rest = spacing), shear (rest = √2·spacing), bend (rest = 2·spacing). Same solver, three rest-lengths.
- Color constraints by tension — a free debug visualization. One
constraint_color function turns scalar distance into a heat-map.
draw_ground() once in main so the floor stays a constant reference as the cloth moves.
Modifying
- Stiffer / softer cloth: bump the iterations (e.g. 8) and/or the substeps (8).
- More gust: crank
wind lerp speed and/or the magnitude multipliers.
- Different shape: pin a different subset of the grid in
build_grid (e.g. all four corners, or only the middle), or change the cloth plane orientation.
- Color theme: swap the RGB ramps in
speed_color and constraint_color for a high-contrast palette.
Future directions
- Tearing: replace
Constraint.rest_len with a max_len; if exceeded, mark the constraint inactive. Same shader, different verb.
- Self-collision on the cloth (would currently fold).
- Interaction: mouse-pick a cloth particle and drag it (would extend the rope demo's picking code).
- Pressure / fluid coupling: a cloth that deforms inward based on a wind-pressure field; first step toward soft-body garments.