Skip to content
Sample of IQ Mandelbox (2D edition) — iterated box-fold / ball-fold / scale / translate

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.

Family
shader_legends_2
Source
shader legends II
Evolution
fractal
Render
scalar_field
Complexity
high
Agents
0
Year
2010
Author
Inigo Quilez (after Knighty/McDull)

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

Sample of Hex Grid Tiling — axial-coord tessellation with fBM-filled cells

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.

View →
Sample of IQ Domain Warping — fbm composed with itself at 2/3 levels

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.

View →
Sample of IQ Voronoi (cellular noise) — F1 / F2 distance field

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).

View →
Sample of Lorenz Attractor (3D ODE) — strange-attractor trajectory with depth shading

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.

View →
Sample of Menger Sponge, 2D Carpet edition — recursive 3x3 / centre-removal

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.

View →

See all shader_legends_2 artworks →