Skip to content
Sample of Subdivided Columns

nervous_system_hansmeyer · HansmeyerSubdivision

Subdivided Columns

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. After 8-16 iterations the smooth hexagonal (or octagonal, or square) silhouette has been replaced by a fibrous, coral-like fold structure whose topology is regular but whose surface ruffles like an ice-cream cone or a sea-fan. The piece is famous as an early showcase of algorithmic ornament — a single local rule, applied at scale, producing a global aesthetic that resists hand-authoring. This implementation works in 2D for tractable rendering (Hansmeyer's columns are extruded 3D), keeps every subdivision iteration as a coloured edge layer (cool edge_color for outer coarse folds, warm depth_color for inner fine detail), and uses semi-transparent stroke blending so dense overlaps naturally darken rather than overwrite — producing the painterly accretion that distinguishes a subdivided-column render from a CAD wireframe.

Original: https://www.hansmeyer.com/subdividedcolumns

▶ 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
nervous_system_hansmeyer
Source
nervous system × hansmeyer
Evolution
subdivision
Render
wireframe
Complexity
medium
Agents
118092
Year
2014
Author
Michael Hansmeyer / Michael Hansmeyer & Benjamin Dillenburger

Math essence

Polygon P_0 has n_sides vertices on a circle of radius R. At each subdivision k in [0, K), each edge (a, b) is replaced by (edges_per_subdivision + 2) vertices: a, the p interior midpoints perturbed by normal-direction noise, and b. Perturbation magnitude decays as decay^k so coarse structure stays coarse. After K iterations the polygon has n_sides * (edges_per_subdivision + 2)^K vertices. Each edge of each subdivision is rendered as a vectorized line with alpha-blended color: color(depth) = (1 - 0.6 * (depth/max_depth)^0.8) * edge_color + 0.6 * (depth/max_depth)^0.8 * depth_color. The blend is associative so densely-overlapped regions accumulate toward the warm depth palette.

Parameters

Name Type Default Range Description
dim int 600 200–1400 Square canvas side length
n_sides int 6 3–16 Initial polygon side count
subdivisions int 9 2–16 Number of subdivision iterations
edges_per_subdivision int 1 1–6 Number of interior perturbed vertices inserted per edge (>=1)
perturbation float 0.16 0–0.5 Base perturbation magnitude as a fraction of dim
perturbation_decay float 0.78 0.3–1 Per-step magnitude decay (1=no decay)
radius float 0.36 0.1–0.5 Initial polygon radius as fraction of dim
edge_color tuple 200,215,240 null–null Outer / coarse-fold RGB
depth_color tuple 255,170,120 null–null Inner / fine-detail RGB
bg_color tuple 6,8,14 null–null Background RGB
edge_thickness int 1 1–3 Stroke thickness in pixels
edge_alpha float 0.25 0.05–1 Per-stroke alpha (lower = softer accumulation)
start_angle float 0 -3.1416–3.1416 Initial polygon rotation (radians)
seed int null 0–999999 RNG seed

Reference

First 60 lines of reference.py. Full source: library_external/nervous_system_hansmeyer/HansmeyerSubdivision/reference.py

from __future__ import annotations

from typing import ClassVar

import imageio.v2 as imageio
import numpy as np


class HansmeyerSubdivision:
    family: ClassVar[str] = "nervous_system_hansmeyer"
    sketch: ClassVar[str] = "HansmeyerSubdivision"

    def __init__(
        self,
        dim: int = 600,
        n_sides: int = 6,
        subdivisions: int = 9,
        edges_per_subdivision: int = 1,
        perturbation: float = 0.16,
        perturbation_decay: float = 0.78,
        radius: float = 0.36,
        edge_color: tuple[int, int, int] = (200, 215, 240),
        depth_color: tuple[int, int, int] = (255, 170, 120),
        bg_color: tuple[int, int, int] = (6, 8, 14),
        edge_thickness: int = 1,
        edge_alpha: float = 0.25,
        start_angle: float = 0.0,
        seed: int | None = None,
    ) -> None:
        self.dim = dim
        self.n_sides = n_sides
        self.subdivisions = subdivisions
        self.edges_per_subdivision = edges_per_subdivision
        self.perturbation = perturbation
        self.perturbation_decay = perturbation_decay
        self.radius = radius
        self.edge_color = np.array(edge_color, dtype=np.uint8)
        self.depth_color = np.array(depth_color, dtype=np.uint8)
        self.bg_color = np.array(bg_color, dtype=np.uint8)
        self.edge_thickness = edge_thickness
        self.edge_alpha = edge_alpha
        self.start_angle = start_angle
        self._seed = seed
        self.rng = np.random.default_rng(seed)
        self.canvas = np.full((dim, dim, 3), self.bg_color, dtype=np.uint8)
        self._edges: list[tuple[tuple[float, float], tuple[float, float], int]] = []
        self._stage = 0
        self.time = 0
        self.reset()

    def reset(self, seed: int | None = None) -> None:
        if seed is not None:
            self._seed = seed
        self.rng = np.random.default_rng(self._seed)
        self.canvas = np.full((self.dim, self.dim, 3), self.bg_color, dtype=np.uint8)
        cx = self.dim / 2.0
        cy = self.dim / 2.0
        R = self.radius * self.dim
        angles = self.start_angle + 2.0 * np.pi * np.arange(self.n_sides) / self.n_sides
        polygon: list[tuple[float, float]] = [

Try running it locally

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

python library_external/nervous_system_hansmeyer/HansmeyerSubdivision/reference.py

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

More from nervous system × hansmeyer

See all nervous_system_hansmeyer artworks →