Snow Crystal
Stellar dendrite · step 0 · tap to breathe · drag to rewind
Snow Crystal
What it is
A snow crystal is six-fold symmetric because its six arms grew in the same weather at the same time: each arm met the same humidity at the same moment, so each arm made the same decisions. This is that idea turned into software. The flake on screen is grown from a single seed by Clifford Reiter's 2005 cellular model of snow crystal growth, and only one twelfth of it exists - a 30-degree wedge of the hexagonal lattice that the twelve symmetries of the hexagon unfold into the whole crystal. The six arms match bit for bit because they are the same memory.
Every cell of the lattice holds a mass of water. A cell is ice once its mass reaches one, and receptive if it is ice or touches ice. Each step, receptive cells keep what they hold, take in what diffuses to them and gain a little vapour supply; every other cell is vapour, which diffuses as half itself and half the mean of its six neighbours, with the far field held at the humidity. Arms starve their surroundings, so tips advance and flanks stall - that starvation is the branching. Two dials set the two numbers: Humidity runs from 0.55 to 0.95 and Vapour supply is a log dial from 0.0001 to 0.005. Four presets land on the field-guide shapes - Plate, Sectored plate, Stellar dendrite and Fernlike - and the label in the corner is measured from the grown flake's fill and perimeter rather than copied from the dials, so it names what is actually on screen.
The wedge is stepped on the CPU in a Float32Array and uploaded as a small texture. A raw WebGL fragment shader, with no Three.js, turns every device pixel into a fractional hex coordinate, finds the lattice triangle it sits in, canonicalises the three corners back into the wedge and blends them. That interpolant gives a signed distance to the ice edge for free, which is where the outline, the anti-aliasing and the rim light come from, so the edges stay crisp on a phone as well as a laptop. The interior is shaded by ice thickness, with iso-thickness lines drawn as growth rings, and the vapour field, thinned around each arm, faintly tints the pane. In the light and sunset themes the crystal is drawn as ink on paper - a dark object with a real outline, so it does not vanish into a white page - and in the dark theme it is ice. Without WebGL the same sampling runs in JavaScript into an ImageData, and the corner says so.
The default flake is grown before the first frame is painted, about a thousand steps, so the first thing you see is the finished crystal, and reduce-motion turns every regrowth into an instant cut. Otherwise a regrowth animates up to sixteen steps a frame while a run of snapshots records it; dragging left or right scrubs that recording, a lookup rather than a re-simulation. Tapping the flake breathes on it: every cell within thirteen of the tapped cell and its twelve images melts to warm, wet air, a disc opens in all six arms at once, and a full-grown crystal closes it one step a frame so you can watch it heal. Breathing on a rewound flake forks its history from that moment. New flake nudges the two parameters from a seeded generator, so the deals are the same on every visit. Unit tests check that the twelve symmetry maps round-trip, that diffusion conserves mass when the vapour supply is zero, that the grown flake is six-fold symmetric, and that a breath melts every arm and grows back.
How to use it
- Tap the flake to breathe on it: a disc melts in all six arms, then grows back.
- Drag left or right to rewind and replay the recorded growth.
- Swap the weather: Plate, Sectored plate, Stellar dendrite or Fernlike.
- Slide Humidity and Vapour to regrow it live between those shapes.
- Hit New flake for another crystal from nearby weather.
More experiments
- Chladni Plate — canvas, physics, simulation
- Weather Pixels — webgl, three.js, instancing
- Magic Eye — canvas, stereogram, autostereogram