toxi_toxiclibs · AdditiveWaves3D
Additive Waves — 3D Terrain
A 2D height-field terrain driven by the sum of multiple wave generators (sine, FBM noise, sawtooth, constant offset) defined in Karsten Schmidt's toxiclibscore wave module. Each generator contributes a sinusoidal or procedural height value z(x, y, t); the final terrain height is the sum (or weighted blend) of all contributions. Rendered as a triangle-mesh surface with optional face-shading from the local normal and a simple horizon gradient sky. The original 2009 demo was Karsten's first showcase of `toxi.math.wave.WaveSampler` for procedural terrain; the algorithm generalizes any layered-function field and shows up across the toxiclibs / thi.ng ecosystem as the canonical 'additive waves' example.
Original: https://web.archive.org/web/2011/http://toxiclibs.org/2009/12/additive-waves/
▶ 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 wave generator i with parameters (amplitude a_i, frequency f_i, phase φ_i, speed s_i), compute w_i(x, y, t) = a_i * sin(2π * (f_i · p + s_i * t) + φ_i) where p is (x, y) for the position dimension. The total height h(x, y, t) = Σ w_i(x, y, t) + Σ (fbm_i(x*s, y*s, t*ts) * amp_i). Triangulated as a regular grid of (N×N) vertices, two triangles per cell. Per-vertex color is a vertical gradient (deep blue at low altitude, white at peaks) modulated by a slope term (|∂h/∂x| + |∂h/∂y|) so steep faces darken. Normals are computed by cross-product of neighbouring cell edges.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 96 | 32–256 | Grid resolution (vertices per side). |
extent | float | 6 | 1–20 | World-space side length of the terrain. |
wave_count | int | 4 | 1–12 | Number of additive wave generators. |
noise_amp | float | 0.4 | 0–2 | Amplitude of the layered Perlin / FBM contribution. |
noise_scale | float | 0.6 | 0.1–4 | Spatial frequency of the noise layer. |
time_speed | float | 0.3 | 0–4 | Animation speed multiplier for all wave generators. |
shade_mode | str | gradient | — | Render style: gradient-shaded surface, wireframe, or flat color. |
Reference
First 60 lines of reference.py. Full source:
library_external/toxi_toxiclibs/AdditiveWaves3D/reference.py
"""AdditiveWaves3D — Layered wave / FBM height-field terrain.
A pure-numpy port of the December 2009 toxiclibs demo 'Additive waves'
(https://web.archive.org/web/2011/http://toxiclibs.org/2009/12/additive-waves/).
The terrain height at (x, y, t) is the sum of:
* `wave_count` sinusoidal generators (each with its own amplitude,
frequency, phase, time-speed), and
* a single FBM (fractional Brownian motion) noise layer.
The result is a 2D heightfield, rendered as a triangle-mesh surface with
per-vertex shading from the local normal.
"""
from __future__ import annotations
import numpy as np
class AdditiveWaves3D:
"""3D heightfield driven by layered sine waves + FBM noise.
The wave generators are sampled on a fixed (N, N) grid in the X-Y
plane; their sum plus the FBM contribution becomes the Z component.
The mesh is rebuilt each step (cheap; the mesh is small) and
"""
def __init__(self, dim=96, extent=6.0, wave_count=4, noise_amp=0.4,
noise_scale=0.6, time_speed=0.3, seed=0):
self.dim = dim
self.extent = extent
self.wave_count = wave_count
self.noise_amp = noise_amp
self.noise_scale = noise_scale
self.time_speed = time_speed
self.rng = np.random.default_rng(seed)
# Each wave generator: amplitude, freq, phase, time-speed
# Frequencies are 2-vectors (spatial), and each generator also has
# a temporal speed that drives the sin's argument.
self.waves = []
for _ in range(wave_count):
self.waves.append({
'amp': self.rng.uniform(0.2, 0.8),
'freq': self.rng.uniform(0.4, 1.2, size=2) * self.rng.choice([-1, 1], size=2),
'phase': self.rng.uniform(0, 2 * np.pi),
'speed': self.rng.uniform(0.3, 1.0),
})
# Precompute the X/Y grid.
xs = np.linspace(-extent / 2, extent / 2, dim, dtype=np.float32)
ys = np.linspace(-extent / 2, extent / 2, dim, dtype=np.float32)
self.xx, self.yy = np.meshgrid(xs, ys, indexing='xy')
self.t = 0.0
def step(self, n=1):
"""Advance the simulation by n steps."""
for _ in range(n):
self.t += self.time_speed * 0.016
# Sum the wave contributions
z = np.zeros_like(self.xx, dtype=np.float32)
for w in self.waves:
f = w['freq'] Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/toxi_toxiclibs/AdditiveWaves3D/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from toxi_toxiclibs
Super-Ellipsoid Mesh
toxi_toxiclibs · SuperEllipsoid
A super-ellipsoid is a 3D primitive that smoothly interpolates between a sphere, a box, and a pinched pillow via two shape parameters e1 (longitude exponent) and e2 (latitude exponent).
Verlet Physics 3D — Mass-Spring Cloth
toxi_toxiclibs · VerletPhysics3D
A 3D mass-spring system integrated with Verlet position-based dynamics and Jakobsen-style constraint relaxation.
Winged-Edge Mesh
toxi_toxiclibs · WingedEdgeMesh
A half-edge / winged-edge data structure for triangle meshes, designed for sub-million-triangle scenes where simple flat vertex/index arrays become cumbersome.