shiffman_pearson_textbook · perlinFlowField
Perlin Noise Flow Field
A 2-D scalar field of Perlin gradient noise is sampled on a coarse grid; each cell is assigned a heading theta = 2*pi * n. Particles emit at random positions, look up their cell's heading each tick, and step a small velocity along it. Their (prev -> current) segments are stamped onto the canvas with a low-alpha blend, producing the classic Shiffman 'flow-field' organic-ink visualization. Particles that leave the canvas respawn at fresh random positions.
Original: https://natureofcode.com/autonomous-agents/
▶ 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): theta = 2*pi * perlin(x*scale, y*scale, ...) [with octaves summed at half amplitude each]. Step: (x', y') = (x, y) + (cos theta, sin theta) * speed. Trail: canvas[p] = (1 - alpha) * canvas[p] + alpha * ink. Per-pixel Perlin: shuffled 64-permutation + random unit gradients + Ken-Perlin fade t^3(t(6t - 15) + 10).
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 360 | 200–1200 | Square canvas side length. |
num_particles | int | 800 | 50–8000 | Tracer particle count. |
cell_size | int | 20 | 4–80 | Grid cell size (visual reference; sampling is bilinear). |
noise_scale | float | 0.08 | 0.005–0.5 | Spatial frequency of the noise; lower = larger structures. |
octaves | int | 1 | 1–4 | Number of Perlin octaves summed into the heading. |
particle_speed | float | 1.6 | 0.2–6 | Pixels per tick. |
line_alpha | float | 0.1 | 0.01–0.5 | Trail opacity per stamp. |
bg_color | tuple | 245,240,230 | — | Background canvas color. |
ink_color | tuple | 35,50,90 | — | Trail ink color. |
seed | int | null | 0–999999 |
Reference
First 60 lines of reference.py. Full source:
library_external/shiffman_pearson_textbook/perlinFlowField/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 _make_perlin(rng: np.random.Generator, size: int):
perm = np.arange(size, dtype=np.int32)
rng.shuffle(perm)
perm = np.concatenate([perm, perm])
angles = rng.uniform(0.0, 2.0 * np.pi, size=(size + 1, size + 1))
grads = np.stack([np.cos(angles), np.sin(angles)], axis=-1).astype(np.float32)
return perm, grads
def perlin2(x: np.ndarray, y: np.ndarray, perm: np.ndarray, grads: np.ndarray) -> np.ndarray:
s = grads.shape[0] - 1
x = np.mod(x, s).astype(np.float32)
y = np.mod(y, s).astype(np.float32)
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, s)
y1 = np.clip(yi + 1, 0, s)
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 PerlinFlowField:
"""Perlin-noise flow field following Shiffman, Nature of Code Ch. 6 / Coding Train.
A scalar noise field n(x, y) is sampled on an integer grid of step size
``cell_size``; the per-cell heading is ``theta = 2*pi * n``. Particles emit
at random positions, sample the heading at their current cell by bilinear
interpolation, and step a small velocity. Trail segments are accumulated
onto the canvas with a low-alpha blend. When a particle leaves the canvas
it respawns at a fresh random position. The step(n) call advances the
simulation n ticks; render() returns the running canvas.
The flow-field is the canonical Nature of Code example: Perlin's gradient
noise (1985) drives a non-divergent, smoothly varying 2-D vector field
that produces the classic organic ink-trail look.
""" Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shiffman_pearson_textbook/perlinFlowField/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from shiffman · pearson
Genetic Algorithm (Evolve Toward Target String)
shiffman_pearson_textbook · geneticAlgorithmString
A textbook genetic algorithm that evolves a population of binary strings toward a target ASCII string (default 'HELLO WORLD!').
Lindenmayer L-System (Textbook Formulation)
shiffman_pearson_textbook · lindenmayerTextbook
Lindenmayer L-system string rewriting with a 2-D turtle interpreter, the canonical textbook formulation used in both Shiffman's Nature of Code Ch.
Mandelbrot Set (Escape-Time Fractal)
shiffman_pearson_textbook · mandelbrotSet
The Mandelbrot set rendered by escape-time iteration of z = z^2 + c on a window of the complex plane.
Random Walk / Drunkard's Walk
shiffman_pearson_textbook · randomWalkDrunkard
Many random walkers each start at the canvas center and take a stochastic 2-D step every tick.
Reynolds Boids (Separation / Alignment / Cohesion)
shiffman_pearson_textbook · boidsClassic
Reynolds' classic boids flocking model.
Stochastic Recursive Fractal Tree
shiffman_pearson_textbook · recursiveTree
Stack-based stochastic fractal tree from Shiffman's Nature of Code Ch.