Simulate
Say "simulate this" and let the engine run it. simulate.grid declares a square grid of
numbers that changes a little every frame: you list the steps that advance it (op.wave
makes it ripple, op.splat presses the pointer into it) and name the outputs you want derived
from it (op.gradient, the slope at every cell). The engine allocates the state, keeps the
history a wave needs, tracks the pointer, clamps the frame clock, and stops dispatching once
the field has faded to nothing. There is no GPU code in the definition at all.
An output is a field an effect can read. Declare the simulation once at module scope, then
hand waves.output('displacement') to displaceBy, which bends the layer inside along the
slope with a chromatic split and edge handling. Today the engine implements exactly one recipe,
[op.wave, op.splat] with a derived op.gradient; a different configuration throws when the
shader is defined, never at render time.
When an effect needs its own GPU step (a reaction-diffusion rule, a fluid, a flock), the
lower-level families take over: sim.grids, sim.fluids, sim.feedback, sim.agents.
import {simulate, grid, op, …} from 'shaders/std'
Reach for it when
| When you need | Use |
|---|---|
| ripples that spread from the cursor and distort the layer inside | simulate.grid with op.wave, op.splat, op.gradient, read by displaceBy |
| the ripples should fade faster or ring longer | op.wave with a damping prop, 0 rings forever, 20 dies in frames |
| the brush should be bigger or press harder | op.splat with a radius prop and pointerSpeed |
| the simulation should sleep once still | the rest option on simulate.grid |
| a handle to the field for an effect | GridSim and its output method |
Example
import {defineShader, p, simulate, op, pointer, pointerSpeed, displaceBy, transformEdges} from 'shaders/std'
// A 128-cell wave field the pointer stirs. The engine keeps two frames of history for the wave,
// tracks the pointer (a jump into the canvas is not a stroke), and sleeps once the field has decayed.
const waves = simulate.grid({
resolution: 128,
history: 2,
step: [
op.wave({damping: p('decay')}),
op.splat({at: pointer({teleportGuard: 'on'}), amount: pointerSpeed({max: 2}), radius: p('radius')}),
],
derive: {displacement: op.gradient()},
rest: {settlesWhen: 'derived-from-damping'},
})
// Ripples: the layer inside is displaced along the wave's slope, with a chromatic split at the crests.
export const Ripples = defineShader({
name: 'Ripples',
props: {
intensity: {default: 10},
decay: {default: 10},
radius: {default: 0.5},
chromaticSplit: {default: 1},
edges: {default: 'stretch', transform: transformEdges, compileTime: true},
},
effect: displaceBy(waves.output('displacement'), {
strength: p('intensity'),
chromatic: p('chromaticSplit'),
edges: p('edges'),
}),
})
Types
| Type | What it is |
|---|---|
GradientOp | A derived output: the slope of the grid at every cell, as a 2D vector. |
GridDeriveOp | Any output derived from the grid after the steps have run. |
GridSimConfig | What a grid simulation declares: its size, how many past frames it keeps, its steps, and its named outputs. |
GridStepOp | Any step that advances the grid each frame. |
SimOutputRef | A handle to one named output of a simulation, the thing you hand to displaceBy. |
SplatOp | A step that presses a soft round brush into the grid wherever the pointer moves. |
WaveOp | A step that makes the grid ripple like a water surface, fading by damping. |
Words
- simulateDeclare a simulation the engine runs for you.
- simulate.gridA square grid of numbers advanced once per frame by
step, with named outputs inderive. - opThe steps and outputs a grid simulation is built from.
- op.waveA wave step: the grid ripples outward from any disturbance and fades by
damping(a prop, 0–20). - op.splatA brush step: a soft round push into the grid at the pointer, scaled by how fast it moves.
- op.gradientA derived output: the grid's slope at every cell as a 2D vector, ready for
displaceBy. - GridSimA declared grid simulation.