Lab · Playground
Flow fields
Thousands of particles drifting through a Perlin-noise vector field, leaving trails that pile up into silk.
A flow field gives every point on the canvas a direction. Here the direction comes from Perlin
noise: noise(x, y, t) is a smooth random value between 0 and 1, and multiplying it by a few turns
of a circle gives an angle that changes gradually from one point to its neighbours. Drop particles
in, let each one step along the angle under it, and draw a faint line for every step. Where the
field converges, the lines pile up into bright ridges; where it diverges, the canvas stays dark.
The third noise coordinate, t, creeps forward each frame, so the field slowly breathes and the
pattern never settles. Click the canvas to clear it and scatter the particles again.
// Things to change: these five numbers.
const COUNT = 1400; // particles
const SCALE = 0.004; // noise zoom: smaller = broader, lazier curves
const TURNS = 3; // how many full turns the noise maps to
const SPEED = 1.4; // pixels per step
const DRIFT = 0.0012; // how fast the field itself changes
let particles = [];
let t = 0;
function setup() {
createCanvas(min(windowWidth, 640), 320);
colorMode(HSB, 360, 100, 100, 100);
reset();
}
function reset() {
background(230, 40, 8);
noiseSeed(floor(random(1e6)));
particles = [];
for (let i = 0; i < COUNT; i++) particles.push(spawn());
}
function spawn() {
const p = createVector(random(width), random(height));
return { pos: p, prev: p.copy(), life: random(80, 300) };
}
function draw() {
for (const p of particles) {
const a = noise(p.pos.x * SCALE, p.pos.y * SCALE, t) * TWO_PI * TURNS;
p.prev.set(p.pos);
p.pos.add(cos(a) * SPEED, sin(a) * SPEED);
// Hue follows the direction of travel, so parallel streams share a colour.
const hue = (degrees(a) + 180) % 360;
stroke(hue, 60, 100, 6);
line(p.prev.x, p.prev.y, p.pos.x, p.pos.y);
p.life--;
if (p.life < 0 || p.pos.x < 0 || p.pos.x > width || p.pos.y < 0 || p.pos.y > height) {
Object.assign(p, spawn());
}
}
t += DRIFT;
}
function mousePressed() {
if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) reset();
}
Things to change
-
SCALE— try0.015for tight, nervous whorls or0.001for long sweeping rivers. -
TURNS—1gives a gentle drift in one general direction;8makes a tangle of eddies. -
COUNTand the alpha instroke(…, 6)— more particles and a lower alpha build up slowly into something like brushed metal. -
DRIFT = 0freezes the field, so the particles trace the same streamlines over and over. -
Replace the angle with something that is not noise:
atan2(p.pos.y - height / 2, p.pos.x - width / 2) + HALF_PIis a vortex around the centre.