Skip to content
Sample of Mandelbox fold-scale 2D projection

fractals_3d_webgl · MandelboxFoldScale

Mandelbox fold-scale 2D projection

A 2D orthographic projection of Tom Lowe's Mandelbox (2010) escape-time field. For each starting c = (x, y, z) the iteration applies (1) a box-fold (per-component mirror across the cube [-1, 1]^3), (2) a sphere-fold (r < 0.5 -> *4, 0.5 <= r < 1 -> /r^2), (3) a uniform scale, and (4) a translation by c. The 'scale' parameter (Lowe's famous scale-2, scale-3, scale -1.5 knobs) controls whether the resulting set is solid, hollow, or contains nested sub-fractals. The full 3D field is sampled on a (dim, dim) grid with a 2-sample Z slab, projected from a slow camera orbit. Orbit-trap minimum |z| drives a warm-to-cool ramp; escape iteration count supplies a bright rim.

Original: https://en.wikipedia.org/wiki/Mandelbox

▶ 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
fractals_3d_webgl
Source
fractals_3d_webgl
Evolution
fractal
Render
scalar_field
Complexity
high
Agents
1
Year
2010
Author
Tom Lowe (miqel.com / sites.google.com/site/mandelbox) / Inigo Quilez (articles/mandelbox)

Math essence

For each c, iterate z := boxFold(z); if |z|<0.5 then z := 4z else if |z|<1 then z := z/|z|^2; then z := scale*z + c. Escape when |z| > 2.5. boxFold mirrors each component z_i -> 2*clamp(z_i, -1, 1) - z_i, producing a [0, 1] -> [-1, 0] reflection. The (u, v) grid is rotated by pitch/yaw and averaged over 2 z-planes, then coloured by minimum |z| along the orbit and by the iteration count of escape.

Parameters

Name Type Default Range Description
dim int 256 128–512 Square canvas side length in pixels.
scale float -1.5 -3–3 Mandelbox scale factor; -1.5 is the iconic 'negative-1.5' Mandelbox.
fold float 1 0.5–2 Box-fold half-extent (1.0 = the canonical cube).
max_iter int 12 4–32 Escape-time iteration cap.
camera_pitch float 0.45 -1.2–1.2 Camera pitch (radians), animated by step().
camera_yaw float 0.35 -1.2–1.2 Camera yaw (radians), animated by step().
orbit_speed float 0.12 0–0.4 Per-step camera phase increment.
warm tuple 255,180,90 — Warm end of the orbit-trap ramp.
cool tuple 90,160,255 — Cool end of the orbit-trap ramp.
void_color tuple 10,12,22 — Background colour (escaped points).
rim_color tuple 255,245,220 — Highlight rim colour (boundary iter).
seed int 7 0–999 RNG seed.

Reference

First 60 lines of reference.py. Full source: library_external/fractals_3d_webgl/MandelboxFoldScale/reference.py

from __future__ import annotations

from typing import ClassVar

import numpy as np


def _mandelbox_iterate(
    cx: np.ndarray, cy: np.ndarray, cz: np.ndarray, max_iter: int, escape: float,
    scale: float, fold: float
) -> tuple[np.ndarray, np.ndarray]:
    """Tom Lowe's Mandelbox iteration (2010).

    For each starting point c = (cx, cy, cz), iterate
        z := boxFold(z)
        if |z| < 0.5:   z := z * 4
        elif |z| < 1:   z := z / |z|^2
        z := scale * z + c
    Escape when |z| > escape.

    The boxFold is the per-component fold used in IQ/MIQEL's reference
    implementations:  z_i := clamp(z_i, -1, 1) * 2 - z_i, which mirrors
    the cube [-1, 1]^3 onto itself.  We track the minimum |z| (orbit
    trap) along each trajectory, the same quantity that gives the
    dramatic colour bands in scale-2 and scale-3 Mandelbox renders.
    """
    x = cx.copy()
    y = cy.copy()
    z = cz.copy()
    iters = np.zeros(cx.shape, dtype=np.int32)
    orbit_min = np.full(cx.shape, np.inf, dtype=np.float32)
    escape2 = escape * escape
    for _ in range(max_iter):
        r2 = x * x + y * y + z * z
        mask = r2 < escape2
        if not np.any(mask):
            break
        iters[mask] += 1
        r_m = np.sqrt(r2[mask])
        orbit_min[mask] = np.minimum(orbit_min[mask], r_m.astype(np.float32))

        # Box-fold: mirror each component across the [-1, 1] range
        xs = np.clip(x[mask], -fold, fold)
        ys = np.clip(y[mask], -fold, fold)
        zs = np.clip(z[mask], -fold, fold)
        xs = xs * 2.0 - x[mask]
        ys = ys * 2.0 - y[mask]
        zs = zs * 2.0 - z[mask]

        # Sphere-fold
        r2_s = xs * xs + ys * ys + zs * zs
        r_s = np.sqrt(r2_s)
        small = r_s < 0.5
        med = (r_s >= 0.5) & (r_s < 1.0)
        if np.any(small):
            xs[small] *= 4.0
            ys[small] *= 4.0
            zs[small] *= 4.0
        if np.any(med):
            r2_m = r2_s[med]

Try running it locally

The reference implementation is self-contained Python with numpy. Install the package and run:

python library_external/fractals_3d_webgl/MandelboxFoldScale/reference.py

Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.

More from fractals_3d_webgl

See all fractals_3d_webgl artworks →