nervous_system_hansmeyer · DillenburgerVoxelField
Voxel Field Cross-Section
Benjamin Dillenburger's voxel-printing research (Digital Building Technologies, ETH Zurich, ~2016) replaced the smooth isosurface extraction of conventional architectural geometry with a literal 3D voxel grid: every cell is either solid or empty, and the boundary of the field is what gets fabricated. This gives architects a building block whose resolution matches the printer's voxel size and whose interior porosity is naturally expressible as a 3D scalar field — usually Perlin/simplex FBM with a tuned threshold. The output 'looks like' a stone cross-section but is, in fact, a flat 2D slice through a 3D field. We render a single slice (or a blend of N adjacent slices) as a top-down cross-section: filled voxels become colored squares with darker per-cell edges, the overall field is masked to a circular boundary (the architectural mass), and distance-from-center fades brightness so the structure reads as illuminated from above.
Original: https://benjamin-dillenburger.com/voxel-printing/
▶ 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
A 3D grid of size N^3 is filled with FBM noise: f(x,y,z) = sum_k persistence^k * noise(lacunarity^k * (x,y,z)), where noise is a value-noise gradient lookup (Perlin's improved-noise interpolant: 6t^5 - 15t^4 + 10t^3 fade on trilinear lattice values). Cells are kept iff f > threshold. A 2D cross-section is taken along axis ∈ {x,y,z} at fractional position t; optionally N adjacent slices are OR-blended to simulate slice thickness. Each filled voxel paints a square of size dim/N pixels, with a thin darker edge, then distance-from-center fade brightens center and darkens periphery.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 600 | 200–1400 | Square canvas side length |
grid_size | int | 96 | 16–192 | Voxels per axis (N^3 total cells) |
noise_scale | float | 2.6 | 0.5–8 | Lattice coordinate range for noise sampling |
threshold | float | 0.05 | -0.6–0.6 | FBM cutoff — higher = sparser structure |
octaves | int | 4 | 1–8 | FBM octave count |
persistence | float | 0.55 | 0.1–0.95 | FBM amplitude decay per octave |
lacunarity | float | 2 | 1.2–4 | FBM frequency multiplier per octave |
cross_section_axis | str | z | null–null | Slice axis (x|y|z) |
cross_section_t | float | 0.5 | 0–1 | Slice position as fraction along axis |
radius | float | 0.46 | 0.15–0.5 | Architectural mass radius as fraction of dim |
n_slices_blend | int | 1 | 1–7 | Number of adjacent slices to OR-blend |
voxel_color | tuple | 220,215,230 | null–null | Voxel interior RGB |
bg_color | tuple | 8,10,16 | null–null | Background RGB |
accent_color | tuple | 255,170,110 | null–null | Periphery fade accent RGB |
seed | int | null | 0–999999 | RNG seed (drives Perlin permutation) |
Reference
First 60 lines of reference.py. Full source:
library_external/nervous_system_hansmeyer/DillenburgerVoxelField/reference.py
from __future__ import annotations
from typing import ClassVar
import imageio.v2 as imageio
import numpy as np
def _hash3(seed: int) -> np.ndarray:
s = np.arange(256 * 256 * 256, dtype=np.uint32)
s = (s ^ (s >> 16)) * np.uint32(0x85ebca6b)
s = (s ^ (s >> 13)) * np.uint32(0xc2b2ae35)
s = s ^ np.uint32(seed & 0xFFFFFFFF) ^ (s >> 16)
return (s & np.uint32(0xFF)).astype(np.float32) / 255.0
def _fade(t: np.ndarray) -> np.ndarray:
return t * t * t * (t * (t * 6 - 15) + 10)
def _gradient_lookup_table(seed: int) -> np.ndarray:
rng = np.random.default_rng(seed)
grads = rng.normal(0.0, 1.0, size=(16, 3))
norms = np.linalg.norm(grads, axis=1, keepdims=True)
norms = np.where(norms < 1e-9, 1.0, norms)
return grads / norms
class _Perlin3D:
def __init__(self, seed: int = 0) -> None:
self.grads = _gradient_lookup_table(seed)
self.perm = np.arange(256, dtype=np.int32)
rng = np.random.default_rng(seed * 7919 + 1)
rng.shuffle(self.perm)
self.perm = np.concatenate([self.perm, self.perm])
def noise(self, x: np.ndarray, y: np.ndarray, z: np.ndarray) -> np.ndarray:
xi = np.floor(x).astype(np.int64) & 255
yi = np.floor(y).astype(np.int64) & 255
zi = np.floor(z).astype(np.int64) & 255
xf = x - np.floor(x)
yf = y - np.floor(y)
zf = z - np.floor(z)
u = _fade(xf)
v = _fade(yf)
w = _fade(zf)
aaa = self.perm[self.perm[self.perm[xi] + yi] + zi] & 15
aba = self.perm[self.perm[self.perm[xi] + yi + 1] + zi] & 15
aab = self.perm[self.perm[self.perm[xi] + yi] + zi + 1] & 15
abb = self.perm[self.perm[self.perm[xi] + yi + 1] + zi + 1] & 15
baa = self.perm[self.perm[self.perm[xi + 1] + yi] + zi] & 15
bba = self.perm[self.perm[self.perm[xi + 1] + yi + 1] + zi] & 15
bab = self.perm[self.perm[self.perm[xi + 1] + yi] + zi + 1] & 15
bbb = self.perm[self.perm[self.perm[xi + 1] + yi + 1] + zi + 1] & 15
def g(idx: np.ndarray, dx: np.ndarray, dy: np.ndarray, dz: np.ndarray) -> np.ndarray:
return np.sum(self.grads[idx] * np.stack([dx, dy, dz], axis=-1), axis=-1)
g000 = g(aaa, xf, yf, zf)
g100 = g(baa, xf - 1, yf, zf) Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/nervous_system_hansmeyer/DillenburgerVoxelField/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from nervous system × hansmeyer
Floraform Growth
nervous_system_hansmeyer · FloraformGrowth
A 2D port of Nervous System's Floraform generative jewelry system.
Hyphae Network
nervous_system_hansmeyer · HyphaeNetwork
A 2D port of Nervous System's Hyphae generative jewelry.
Radiolarian Lattice
nervous_system_hansmeyer · RadiolarianLattice
Nervous System's Radiolaria series translates Ernst Haeckel's 19th-century radiolarian drawings into 3D-printed sculpture.
Subdivided Columns
nervous_system_hansmeyer · HansmeyerSubdivision
Michael Hansmeyer's 2014 'Subdivided Columns' is a computational-design exercise in baroque complexity from algorithmic simplicity: start with a regular polygon, subdivide each edge, perturb each new vertex by a small random offset, repeat.