# Jelly Blob — Three.js Soft Body

A single-page Three.js scene: a glossy, translucent slime/jelly blob resting on a floor.
It's a real soft body — poke it, squish it, drop it — it wobbles, jiggles, and settles,
and casts a soft contact shadow. Built to run at 60 fps on a mid-range phone.

Everything is in one self-contained `index.html` (no build step, no bundler).
three.js is loaded from a pinned CDN (`three@0.160.1`, jsdelivr with an unpkg fallback).

## Run it

- Open `index.html` directly in a browser (works from `file://`), **or**
- serve the folder and open it:

  ```sh
  python3 -m http.server 8000
  # http://localhost:8000
  ```

Requires WebGL2 and an internet connection for the CDN (first load only; it's cacheable).

## Controls

| Action | Desktop | Touch |
| --- | --- | --- |
| Poke / squish the blob | click or press-and-hold on the blob; drag across it to drag a dent around | tap, press, or drag on the blob |
| Orbit camera | drag on empty space | drag on empty space |
| Zoom | scroll wheel | pinch with two fingers (on empty space) |
| Drop blob from height | **Drop from height** button, or `D` | **Drop from height** button |
| Reset (blob + camera stays) | **Reset** button, or `R` | **Reset** button |

The small panel also shows a live FPS readout.

## How it works

**Soft body** (`SoftBody` in the inline script, ~no three.js dependency):
a welded icosphere (2562 verts / 5120 faces on desktop, 642 / 1280 on phones) where
every vertex is a mass with:

- a *goal spring* toward `body anchor + rest offset` (shape memory),
- *edge springs* with damping along the surface connectivity (this is what makes
  jiggle ripple and propagate),
- *internal pressure* proportional to the volume deficit (squash bulge + bounce),
- gravity, air drag, and a floor contact (projection + friction + restitution).

The blob hangs from a ghost **body anchor** particle that carries the total mass.
The summed goal-spring reactions on it simplify to `kGoal · n · (centroid − anchor)`,
and a one-sided support spring (engaged only below rest height, like a table) holds
it up — so the blob rests like a real jelly (round body, small flat contact patch),
still free-falls for drops, and squashes/rebounds on impact through the same spring.
Integration is semi-implicit Euler at a fixed 120 Hz with up to 5 substeps per frame.

**Rendering**: `MeshPhysicalMaterial` (low roughness + clearcoat = glossy jelly) with
a small `onBeforeCompile` injection that fakes subsurface scattering in linear space:
rim scatter on thin edges, back transmission when the key light shines through the
silhouette, and depth absorption toward a deep green in thick regions. Lighting comes
from a procedurally generated PMREM studio environment (no extra downloads) plus a
key and rim light. ACES tone mapping.

**Contact shadow**: a radial-gradient plane driven by the blob's live footprint and
height above the floor — always soft, tracks the squash, and costs no shadow maps.

**Performance**: the sim runs on flat typed arrays with zero per-frame allocations,
refreshes vertex normals in the same pass as the volume integral, and **sleeps
entirely** once the blob has been still for ~0.2 s (position-delta based, so contact
jitter never blocks sleep). Any poke/drop wakes it instantly. Phones automatically
use the lighter mesh and a capped pixel ratio.

**Tuning**: all sim constants are in `SOFTBODY_DEFAULTS` near the top of the inline
script (stiffnesses, damping, pressure, restitution, friction), and the poke feel in
`TAP_IMPULSE` / `HOLD_ACCEL` / `POKE_R` further down.

## Files

- `index.html` — the whole demo (HTML, CSS, physics, rendering, UI)
- `README.md` — this file

three.js is MIT-licensed, © its authors; loaded from
`https://cdn.jsdelivr.net/npm/three@0.160.1/build/three.module.js`.
