reas_processing · perlinStreamlines
Perlin Streamlines
A 2-D vector field is sampled from one or more octaves of Perlin gradient noise. Particles emit at uniformly random positions and advance along the field one step per tick. Their trails are accumulated on the canvas with a low-alpha blend so the result is the classic painted-noise flow-field visualization popularised by Casey Reas's Processing community. Particles die after particle_lifespan ticks and respawn.
Original: https://en.wikipedia.org/wiki/Perlin_noise
▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.
Math essence
For each particle (x, y) compute theta = 2*pi * perlin(x*scale, y*scale) (summed across octaves). Position update: (x', y') = (x, y) + cos(theta), sin(theta) * noise_speed. Per-pixel trail blend: canvas = (1-a)*canvas + a*ink on the segment endpoints.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 500 | 200–1200 | Square canvas side length. |
num_particles | int | 6000 | 100–20000 | Number of tracer particles. |
field_scale | float | 0.012 | 0.003–0.08 | Spatial frequency of the Perlin noise. |
noise_speed | float | 1.6 | 0.2–6 | Particle velocity (pixels per step). |
octaves | int | 2 | 1–4 | Number of Perlin octaves summed into the angle. |
particle_lifespan | int | 90 | 10–400 | Ticks before a particle respawns at a new random position. |
line_alpha | float | 0.06 | 0.01–0.5 | Trail opacity per stamp. |
bg_color | tuple | 15,18,30 | — | |
ink_color | tuple | 210,230,255 | — | |
seed | int | null | 0–999999 |
Reference
First 60 lines of reference.py. Full source:
library_external/reas_processing/perlinStreamlines/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
def _fade(t: np.ndarray) -> np.ndarray:
return t * t * t * (t * (t * 6 - 15) + 10)
def _perlin2(rng: np.random.Generator, grid_w: int, grid_h: int):
perm = np.arange(256, dtype=np.int32)
rng.shuffle(perm)
perm = np.concatenate([perm, perm])
angles = rng.uniform(0.0, 2.0 * np.pi, size=(grid_h + 1, grid_w + 1))
grads = np.stack([np.cos(angles), np.sin(angles)], axis=-1).astype(np.float32)
return perm, grads
def perlin(x: np.ndarray, y: np.ndarray, perm: np.ndarray, grads: np.ndarray) -> np.ndarray:
gw = grads.shape[1] - 1
gh = grads.shape[0] - 1
x = np.mod(x, gw)
y = np.mod(y, gh)
xi = x.astype(np.int32)
yi = y.astype(np.int32)
xf = x - xi
yf = y - yi
u = _fade(xf)
v = _fade(yf)
x1 = np.clip(xi + 1, 0, gw)
y1 = np.clip(yi + 1, 0, gh)
g00 = grads[yi, xi]
g10 = grads[yi, x1]
g01 = grads[y1, xi]
g11 = grads[y1, x1]
d00 = xf * g00[..., 0] + yf * g00[..., 1]
d10 = (xf - 1) * g10[..., 0] + yf * g10[..., 1]
d01 = xf * g01[..., 0] + (yf - 1) * g01[..., 1]
d11 = (xf - 1) * g11[..., 0] + (yf - 1) * g11[..., 1]
nx0 = d00 + u * (d10 - d00)
nx1 = d01 + u * (d11 - d01)
return nx0 + v * (nx1 - nx0)
class PerlinStreamlines:
"""Streamlines in a Perlin noise field.
A 2-D vector field is constructed from the gradient of one or two
octaves of Perlin noise. Particles emit from random positions and
follow the field, depositing semi-transparent trails. step(n)
advances the simulation and accumulates onto the canvas. The
rendering is the canonical 'flow field' popularised by Reas and the
Processing community.
"""
family: ClassVar[str] = "reas_processing"
sketch: ClassVar[str] = "perlinStreamlines"
Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/reas_processing/perlinStreamlines/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from reas · processing
2-D Totalistic Cellular Automaton (Conway + variants)
reas_processing · wolframCA2D
Each cell looks at its 8 Moore neighbors and applies a totalistic rule.
Gray-Scott Reaction-Diffusion
reas_processing · grayScottRD
Two scalar species U and V diffuse on a square grid with rates Du and Dv and react with feed F and kill k.
Lindenmayer L-system + Turtle Rendering
reas_processing · lindenmayer
An axiom string is rewritten by parallel production rules for a fixed number of iterations, then interpreted by a 2-D turtle: 'F' walks forward, '+/-' turn left/right, '['/']' push/pop state.
Particle-Spring Cloth (Verlet + Hooke)
reas_processing · particleSpring
A rectangular grid of free particles is connected by Hooke's-law springs at rest length 'spacing'.
Voronoi Diagram from Random Seeds
reas_processing · voronoi
Every pixel on the canvas is colored with the palette color of the seed point closest to it.
Wolfram Elementary 1-D Cellular Automaton
reas_processing · wolframCA1D
Each row of the canvas is one generation of a Wolfram elementary cellular automaton.