Helix Noise · GPU smoke · obstacle · curl of a baked potential
GPUThe velocity is never baked directly. Instead field.withBoundary(sdf) wraps the field
against an obstacle, bounded.bakePotential3D(64) stores the vector potential A (rgb) and the
signed distance (alpha) in one RGBA16F volume, and the advection shader reconstructs the velocity on the fly
as u = ∇×A — a curl of the trilinear samples. Because it is a curl, the interpolated velocity is
discretely divergence-free (a directly-baked velocity leaks divergence and makes smoke pool or vanish); the
same alpha channel carves the solid out of the smoke and shades its surface in the raymarcher. Dye advects entirely
in 3-D textures — no CPU per-frame work. Drag to orbit, scroll to zoom, toggle draw to inject smoke.
Trilinearly interpolating a baked velocity destroys its divergence-free property — the reconstructed
field gains O(voxel²) divergence, which shows up as smoke piling into some cells and thinning out of others.
Baking the vector potential and taking a finite-difference curl in the shader keeps the velocity
discretely divergence-free to machine precision, because the discrete curl of an interpolant has exactly zero
discrete divergence. The bakePotential3D() call hands you rgb = A and alpha = the obstacle's signed
distance, so one texture drives both the flow and the solid.
64³ is demo resolution and the field is frozen per seed (smoke moves through it; the obstacle is respected
kinematically — free-slip, no wake shedding or pressure feedback, because this is an authoring field,
not a solver). Semi-Lagrangian advection adds numerical diffusion — the smoke's softness. Needs WebGL2 +
EXT_color_buffer_float; the canvas says so if either is missing.