# Slime — soft-body jelly (Three.js)

A single-page Three.js scene: a glossy, translucent slime blob sitting on a studio floor. You can poke,
squish and drag it; it wobbles and settles. There is no build step. `index.html` is the whole app, and
three.js r170 loads from a pinned CDN (`cdn.jsdelivr.net/npm/three@0.170.0`) through an import map.

## Running

Open `index.html` in a modern browser, either directly from disk or from any static server (for example
`python3 -m http.server`, then visit http://localhost:8000). You need an internet connection for the CDN.

## Controls

| Action | Mouse | Touch |
| --- | --- | --- |
| Poke / squish | Click or press and hold on the slime | Tap or press and hold on the slime |
| Pull / stretch / shove | Drag after pressing on the slime | Drag a finger that started on the slime (several fingers work) |
| Orbit camera | Drag on empty space | One-finger drag on empty space |
| Zoom | Scroll wheel | Pinch on empty space |

UI panel (bottom):

- **Reset** (key `R`): puts the blob back, resting in the middle of the floor.
- **Drop** (key `Space` / `D`): lifts the blob to a height with a slight random tilt and spin, then lets it fall and splat.
- **Colour swatches**: lime, bubblegum, aqua, tangerine and grape.
- **Wobble slider**: goes from firm (left) to very wobbly (right).

The small counter in the top-right shows the frame rate and the current render resolution scale.

## How it works

**Soft body.** The blob is simulated with position-based dynamics on a 642-particle icosphere, using fixed
240 Hz substeps:

- Shape matching (rotation plus some volume-preserving linear deformation) provides the jelly spring.
- Deviations from the matched shape are diffused over the mesh, so dents stay smooth and local.
- A global volume constraint makes the blob bulge wherever it is pressed in.
- The floor has contact with friction.
- Damping works on two parts. Non-rigid (wobble) motion is always damped. Rigid motion is damped only while the blob touches the floor, so it hops a little and then settles.
- The pointer drives soft "grab" constraints, plus an impulse when you first press.

**Rendering.** The visible mesh is one subdivision finer than the simulation (2562 vertices). Each extra vertex
follows the fitted shape plus the averaged deviation of its two parent particles, so the surface stays round.

**Material.** The slime uses `MeshPhysicalMaterial` for clearcoat, specular highlights and environment
reflections, with two injected shader parts:

- **Translucency without a transmission pass:** each pixel refracts the view ray, finds where it hits the
  floor, shades the floor there (including its shadow and tile grid), and applies Beer–Lambert absorption.
  Thin rims transmit more light and the core looks deeper and more saturated.
- **Subsurface-like glow:** back-lit translucency, wrapped diffuse lighting and a soft rim.

**Contact shadow.** There are no shadow maps. Every frame the solver computes footprint statistics: body spread,
the footprint projected along the key light, and the contact patch. The floor shader turns these into:

- a broad ambient-occlusion blur,
- a colour-tinted key-light shadow whose edge softens with height,
- a small bright "caustic" spot where the jelly focuses light,
- a dark crease around the contact patch.

**Mobile performance.** The frame needs no extra render passes (no shadow maps, transmission target or
post-processing). Physics costs roughly 1 ms per frame on desktop. Pixel ratio starts at 1.5 on touch
devices, and the app lowers or raises the resolution to hold about 60 fps.

URL option: `?dpr=1` forces a fixed starting pixel ratio. Adaptive resolution still adjusts it afterwards.
