✕
Specimen Report
· TypeScript
clothos
phiat/clothos
WebGL cloth-simulation prototype — a gravity-draped sheet that fills the viewport and reacts to cursor/touch with smoothstep falloff and spring-overshoot inertia.
Stars ★ 1
Forks ⑂ 0
Language TypeScript
Size 2,303 kB
Last Push 12h ago
Forged 9d ago
# clothos
[](https://clothos.norfnorf.com)
[](https://www.typescriptlang.org/)
[](https://threejs.org/)
[](https://vitejs.dev/)
[](LICENSE)
A WebGL cloth-simulation prototype: a gravity-draped sheet that fills the
viewport and reacts to cursor/touch with smoothstep falloff and spring-overshoot
inertia.

## Run
```bash
npm install
just dev # vite dev server
just build # tsc --noEmit + production bundle
just deploy # build + rsync dist/ to the host in .env
```
Run `just` alone for the full recipe list (also includes `ssh`, `logs`,
`open`, `gif`, `mp4`, `screenshot`).
## Layout
```
src/
main.ts scene, lights, render loop, resize, touch detection
cloth.ts Verlet cloth: particles, Jakobsen constraints, normals
interaction.ts pointer → cloth force with damped-spring overshoot
panelShift.ts glass-panel hue/sat driven by cursor dwell
nav.ts tap-to-toggle dropdowns
styles.css overlay, dropdowns, glass panel
```
- **Cloth** — `cols × rows` Verlet particles connected by structural / shear /
bend constraints, solved with 5 Jakobsen relaxation passes. Perimeter pinned;
pre-warmed ~60 steps so the sheet opens already draped. `applyPointerForce`
walks only the grid bbox under the influence radius.
- **Interaction** — projects the pointer onto z = 0 and deforms vertices with
a smoothstep-falloff radius. A damped-spring "center" tracks the pointer with
injected velocity, so an abrupt stop overshoots and springs back.
- **PanelShift** — accumulates dwell while the cursor sits over the glass and
writes `--glass-hue` / `--glass-sat`. Slow ramp up, fast snap back.
Touch / coarse-pointer devices get a high-contrast variant (stronger lights,
lower roughness, bigger push) — see the `*_TOUCH` constants in `main.ts`.
## Knobs
`Cloth` (`cloth.ts` module-level): `GRAVITY`, `DAMPING`, `ITERATIONS`, `SLACK`,
`PREWARM_STEPS`.
`Interaction` (per-instance fields):
- `radiusFrac` — influence radius as a fraction of cloth width.
- `pushStrength` — z-push (inward dent).
- `dragStrength` — lateral pull scaled by center velocity.
- `springStiffness` / `springDamping` — return-to-cursor spring; lower damping
vs. stiffness = bigger overshoot.
- `trackingGain` — how fast the center matches pointer velocity.
- `velocitySmoothing` — one-pole filter on raw pointer velocity.
`PanelShift` (per-instance fields): `buildupSec`, `decaySec`,
`baseHue` + `hueRange`, `baseSat` + `satRange`.
`main.ts`: `CLOTH_COLS`, `CLOTH_ROWS`, `SIZE_MARGIN`, plus paired
`*_DESKTOP` / `*_TOUCH` constants for lights, material, and interaction.
## Deploy
`just deploy` runs `npm run build` then rsyncs `dist/` to the host configured
in `.env` (see `.env.example`). Host is served by nginx with a Let's Encrypt
cert; auto-renewal is handled by `certbot.timer`.
## Media
The preview gif was generated with `just gif clothos.gif` — a two-pass ffmpeg
palette method that shrank a 14MB source down to ~2MB. Also available:
`just mp4 <input>` (h264) and `just screenshot` (headless chromium).
## License
[MIT](LICENSE) © phiat