Skip to content
Sample of Additive Waves — 3D Terrain

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.

Family
toxi_toxiclibs
Source
toxi_toxiclibs
Evolution
wave_field
Render
wireframe_or_shaded_mesh_3d
Complexity
low
Agents
0
Year
2009
Author
Karsten Schmidt (toxi / postspectacular.com)

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

See all toxi_toxiclibs artworks →