shader_legends_2 · IQMandelbox2D
IQ Mandelbox (2D edition) — iterated box-fold / ball-fold / scale / translate
The Mandelbox is a 3D fractal defined by the iterate v := scale * box_fold(ball_fold(v)) + c, parametrised by a 'scale' fold factor and the (r_min, r_fixed) ball-fold thresholds. This formula implements the 2D version of the same iterated function system — folding the plane into the [-1, 1]^2 box at each step, blowing small radii back out, and translating by a slow precessing c. The output renders as a tonemapped density field coloured by an IQ cosine palette, producing the iconic sponge-like silhouettes of the Mandelbox atlas.
Original: https://iquilezles.org/articles/mandelbulb/
▶ 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
Per pixel: v = (x, y). For k in 1..iterations: v = box_fold(v, 1) where each component is mirrored into [-1, 1]; v = ball_fold(v, r_min, r_fixed) which multiplies by r_fixed^2/|v|^2 when |v| < r_min; v = scale * v; v = v + c(t). Track escape-or-bailout radius per iteration into m; tonemap m/(1+m); map through a + b*cos(2pi*(c*t + d)).
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 360 | 120–720 | Square output size in pixels. |
scale | float | -1.6 | -3–3 | Per-iteration scaling. Standard Mandelbox values lie in [-3, -1.5] for sponges; 0..3 for cauliflower shapes. |
r_min | float | 0.5 | 0.05–1.5 | Ball-fold inner radius — points closer than this are pushed out. |
r_fixed | float | 1 | 0.1–2 | Ball-fold outer radius — points below r_min are scaled by (r_fixed/|v|)^2. |
iterations | int | 28 | 4–60 | Number of fold-fold-scale-translate iterations per pixel. |
bailout | float | 4 | 1–16 | Squared radius above which we count a pixel as 'escaped'. |
c_drift | float | 0.05 | 0–0.6 | Magnitude of the slow c(t) translation orbit. |
palette_offset | float | 0.5 | 0–1 | Phase offset for the cosine palette. |
time_speed | float | 0.02 | -0.2–0.2 | Drift speed of c(t) per step. |
Reference
First 60 lines of reference.py. Full source:
library_external/shader_legends_2/IQMandelbox2D/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
def _box_fold(v: np.ndarray, s: float) -> np.ndarray:
"""Mirror v into [-s, s]^d. This is the IQ Mandelbox 'box fold'
step: any component greater than +s is reflected back, and any
component less than -s is reflected back, iteratively until
|v_i| <= s."""
sign = np.where(v > 0.0, 1.0, -1.0)
absv = np.abs(v)
# m = how many s-boxes have we passed in the + direction
m = np.floor((absv + s) / (2.0 * s))
# pos within the (-s, s) interval for that box
p = absv - 2.0 * m * s
# alternate: if m is even we're in an inward copy; odd — outward
p = np.where(np.mod(m, 2.0) < 0.5, p, 2.0 * s - p)
return sign * p * (s / max(s, 1e-6))
def _ball_fold(v: np.ndarray, r_min: float, r_fixed: float) -> np.ndarray:
"""IQ Mandelbox 'ball fold': keep |v| above r_min. If
|v|^2 < r_min^2, scale up by r_fixed^2 / |v|^2. Otherwise
pass through. This is the divisiveness of the Mandelbox —
points too close to the origin are pushed out, points outside
the fixed ball are squashed toward r_fixed."""
r2 = v[..., 0] ** 2 + v[..., 1] ** 2
factor = np.where(
r2 < r_min * r_min,
r_fixed * r_fixed / np.maximum(r2, 1e-6),
1.0,
)
out = np.empty_like(v)
out[..., 0] = v[..., 0] * factor
out[..., 1] = v[..., 1] * factor
return out
def _scale(v: np.ndarray, s: float) -> np.ndarray:
return v * s
def _translate(v: np.ndarray, c0: float, c1: float) -> np.ndarray:
out = np.empty_like(v)
out[..., 0] = v[..., 0] + c0
out[..., 1] = v[..., 1] + c1
return out
class IQMandelbox2D:
"""Mandelbox, 2D edition — Inigo Quilez (iquilezles.org, ~2010).
The full 3D Mandelbox (https://iquilezles.org/articles/mandelbulb/,
https://www.shadertoy.com/view/Mdf3zX) iterates
v := scale * box_fold(ball_fold(v)) + c
Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shader_legends_2/IQMandelbox2D/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from shader legends II
Hex Grid Tiling — axial-coord tessellation with fBM-filled cells
shader_legends_2 · HexGridTiling
A hexagonal grid is the canonical 6-fold-symmetric alternative to the square-grid hash tiling.
IQ Domain Warping — fbm composed with itself at 2/3 levels
shader_legends_2 · IQDomainWarping
Inigo Quilez's domain warping trick: feed a noise function *itself* as input coordinates, producing organic flowing patterns that no plain noise function can match.
IQ Voronoi (cellular noise) — F1 / F2 distance field
shader_legends_2 · IQVoronoi
Inigo Quilez's classical 2D Voronoi / cellular noise: for each integer cell of the plane a single feature point is placed at cell_centre + jitter*(hash-0.5).
Lorenz Attractor (3D ODE) — strange-attractor trajectory with depth shading
shader_legends_2 · LorenzAttractor3D
The Lorenz system (1963) is the canonical strange-attractor: x' = sigma(y - x); y' = x(rho - z) - y; z' = xy - beta*z.
Menger Sponge, 2D Carpet edition — recursive 3x3 / centre-removal
shader_legends_2 · MengerSponge2D
The Menger sponge (1926) is a 3D fractal formed by sub-dividing each cube into 3x3x3 and removing the centre plus the six face-centres.