noise
import noise from "noise";
let n: noise.Noise = noise.noise(1234);
let h: f32 = n.perlin2(x * 0.05, y * 0.05); // -1 to 1
let terrain: f32 = n.fbm2(x * 0.01, y * 0.01, noise.Fractal {});noise makes smooth random patterns for generated worlds: terrain heights,
clouds, marble, caves, cell patterns, and evenly spread positions for
trees and rocks. Everything comes from a seed: the same seed gives the same
values in the VM, natively and on every platform, so a world generated
from a seed is the same world everywhere.
It is pure and freestanding: no heap, no state, t0 code included. Values
are f32. A Noise is a 256-byte table you keep in a variable; copying it
is fine.
Making a noise source
noise
pub fn noise(seed: u64) -> NoiseA noise source for seed: a shuffled table of 0..255 that the functions
below hash grid points with. Different seeds give unrelated patterns. Any
seed works, 0 included.
let ground: noise.Noise = noise.noise(world_seed);
let caves: noise.Noise = noise.noise(world_seed + 1);Smooth noise
pub fn Noise.perlin2(*const self, x: f32, y: f32) -> f32
pub fn Noise.perlin3(*const self, x: f32, y: f32, z: f32) -> f32
pub fn Noise.simplex2(*const self, x: f32, y: f32) -> f32
pub fn Noise.simplex3(*const self, x: f32, y: f32, z: f32) -> f32
pub fn Noise.value2(*const self, x: f32, y: f32) -> f32
pub fn Noise.value3(*const self, x: f32, y: f32, z: f32) -> f32Each returns a value from −1 to 1 that changes smoothly with the
coordinates: nearby points get nearby values, points a few units apart
are unrelated. Features are about one unit across, so scale the
coordinates to pick the size: n.perlin2(x * 0.05, y * 0.05) has features
about 20 units wide. A third coordinate can be time, for an animated 2D
pattern.
| Function | Example | What it looks like |
|---|---|---|
perlin2, perlin3 | n.perlin2(0.5, 0.5) | Ken Perlin’s improved noise: soft blobs; exactly 0 at whole-number coordinates |
simplex2, simplex3 | n.simplex3(x, y, t) | Simplex noise: like Perlin, fewer grid-aligned features, cheaper in 3D |
value2, value3 | n.value2(x, y) | Random heights at grid points, smoothly blended: blockier |
The values stay within −1 to 1 and come close to both ends. Perlin noise, value noise and Worley noise repeat every 256 units along each axis; simplex noise does not repeat along the axes. Coordinates must be finite.
// a height map from 0 to 1
for (y in 0..256) {
for (x in 0..256) {
let v: f32 = n.simplex2((x as f32) * 0.03, (y as f32) * 0.03);
heights[y * 256 + x] = v * 0.5 + 0.5;
}
}Worley noise
pub fn Noise.worley2(*const self, x: f32, y: f32, kind: Distance) -> Worley
pub fn Noise.worley3(*const self, x: f32, y: f32, z: f32, kind: Distance) -> Worley
pub struct Worley { f1: f32; f2: f32; id: u8; }
pub enum Distance : u8 { Euclidean = 0; Manhattan = 1; Chebyshev = 2; }Cellular noise: every grid cell holds one random point, and worley2
measures from (x, y) to the nearest of them (f1) and the second nearest
(f2). id names the nearest point’s cell (0 to 255): it is the same
everywhere in a Voronoi cell, so it can pick a colour per cell. kind
chooses how distance is measured: Euclidean gives round cells,
Manhattan (|dx| + |dy|) diamond ones, Chebyshev (the larger of |dx|
and |dy|) square ones.
let w: noise.Worley = n.worley2(x * 0.1, y * 0.1, noise.Distance.Euclidean);
let cracks: f32 = w.f2 - w.f1; // near 0 on the borders between cells
let tile: u8 = w.id; // the same for a whole cellf1 is 0 at a cell’s point and grows away from it. With Euclidean,
f1 stays below 1.42 and f2 below 1.81 in 2D (1.74 and 2.07 in 3D);
Chebyshev keeps f1 below 1 and f2 below 1.5; Manhattan keeps f1
below 2 and f2 below 2.5 in 2D (3 and 3.5 in 3D).
Fractal sums
pub struct Fractal { basis: Basis = Basis.Perlin; octaves: i64 = 5; lacunarity: f32 = 2.0; gain: f32 = 0.5; }
pub enum Basis : u8 { Perlin = 0; Simplex = 1; Value = 2; }
pub fn Noise.fbm2(*const self, x: f32, y: f32, f: Fractal) -> f32
pub fn Noise.fbm3(*const self, x: f32, y: f32, z: f32, f: Fractal) -> f32
pub fn Noise.ridged2(*const self, x: f32, y: f32, f: Fractal) -> f32
pub fn Noise.ridged3(*const self, x: f32, y: f32, z: f32, f: Fractal) -> f32
pub fn Noise.turbulence2(*const self, x: f32, y: f32, f: Fractal) -> f32
pub fn Noise.turbulence3(*const self, x: f32, y: f32, z: f32, f: Fractal) -> f32Natural shapes have detail at every size. A fractal sum adds octaves
layers of noise (basis), each at lacunarity times the previous
frequency (finer) and gain times its strength (fainter). Fractal {}
is five layers of Perlin noise, each twice as fine and half as strong.
| Function | Example | Range | Use |
|---|---|---|---|
fbm2, fbm3 | n.fbm2(x, y, noise.Fractal {}) | −1 to 1 | terrain, clouds |
ridged2, ridged3 | n.ridged2(x, y, noise.Fractal { octaves: 6; }) | 0 to 1 | mountain ridges, veins |
turbulence2, turbulence3 | n.turbulence3(x, y, t, noise.Fractal { basis: noise.Basis.Simplex; }) | 0 to 1 | fire, smoke, marble |
ridged turns each layer’s zero crossings into sharp crests, turbulence
into creases. More octaves cost more time and add finer detail; one octave
is the plain noise. octaves below 1 and a negative gain are bugs in the
caller and stop the program with a trap.
let shape: noise.Fractal = noise.Fractal { basis: noise.Basis.Simplex; octaves: 6; gain: 0.45; };
let height: f32 = n.fbm2(x * 0.004, y * 0.004, shape) * 120.0; // metresPoisson-disk sampling
pub fn poisson_disk(seed: u64, width: f32, height: f32, radius: f32, tries: i64,
out: []math3d.Vec2, scratch: []i32) -> i64
pub fn poisson_scratch_len(width: f32, height: f32, radius: f32, max_points: i64) -> i64Random points spread evenly over the rectangle from (0, 0) to (width, height): no two are closer than radius, and no large gaps are left (every
spot is within 2 * radius of a point). Placing trees, rocks or spawn
points this way avoids the clumps uniform random points make.
poisson_disk writes the points to out and returns how many it wrote; it
stops when the area is full or out is. tries is how hard it tries to
fit another point next to each one: 30 is the usual choice.
It allocates nothing. scratch is working memory of at least
poisson_scratch_len(width, height, radius, out.len) integers; the points
are math3d vectors. The same seed and arguments give the same
points.
import math3d from "math3d";
var trees: [500]math3d.Vec2;
var scratch: [2000]i32; // poisson_scratch_len(100.0, 60.0, 4.0, 500) is 1292
let count: i64 = noise.poisson_disk(7, 100.0, 60.0, 4.0, 30, trees[0..500], scratch[0..2000]);
for (i in 0..count) { plant(trees[i]); }A width, height or radius that is not positive, tries below 1, or
scratch too short stops the program with a trap.
Testing
tape test stdlib/noise.tape checks every function against values computed
independently in Python, the ranges, smoothness and repetition over
thousands of points, and the Poisson spacing and coverage, in the VM and
natively.
Last modified: