About this technique →
sierpinski/sketch.js
// Sierpinski Tetrahedron
//
// Recursive tetrahedron subdivision: at each level, a tetrahedron is replaced
// by 4 smaller tetrahedra at its corners. All leaf tetrahedra rendered with
// InstancedMesh for GPU efficiency.

import * as THREE from 'three';

export const PARAMS = {
  depth:       { value: 4,   min: 1, max: 6, step: 1,    label: "Recursion Depth", folder: "Structure",  rebuildOnChange: true },
  twist:       { value: 0,   min: -45, max: 45, step: 0.5, label: "Twist (°)",     folder: "Structure",  rebuildOnChange: true },
  hue:         { value: 200, min: 0, max: 360, step: 1,   label: "Hue",            folder: "Appearance" },
  rotateSpeed: { value: 0.2, min: 0, max: 1,   step: 0.01, label: "Rotation Speed", folder: "Behavior"  },
};

// ── Tetrahedron corner offsets (unit size, centered) ─────────────────────────
// Vertices of a regular tetrahedron with edge = 2, centered at origin.
// These are the 4 corners; each sub-tet lives at a corner of the parent.
const SQRT2_3 = Math.sqrt(2 / 3);
const SQRT1_3 = Math.sqrt(1 / 3);
const SQRT1_2 = Math.sqrt(0.5);

// Vertices of a regular tetrahedron scaled to edge length 1
function tetVerts(center, size) {
  // Unit tetrahedron vertices (edge=1)
  // v0 at top, v1/v2/v3 at base
  const h = Math.sqrt(2 / 3);   // height of edge-1 tet
  const r = 1 / Math.sqrt(3);   // circumradius of equilateral triangle (edge=1)
  const yTop  =  h * (3 / 4);   // top vertex y
  const yBase = -h * (1 / 4);   // base vertices y
  const verts = [
    [0,       yTop,        0       ],
    [ r,      yBase,        0       ],
    [-r / 2,  yBase,        r * Math.sqrt(3) / 2],
    [-r / 2,  yBase,       -r * Math.sqrt(3) / 2],
  ];
  return verts.map(([x, y, z]) => [
    center[0] + x * size,
    center[1] + y * size,
    center[2] + z * size,
  ]);
}

// Recursively collect leaf tetrahedron (center, size, level) into an array
function collectLeaves(center, size, depth, twistRad, level, leaves) {
  if (depth === 0) {
    leaves.push({ center, size });
    return;
  }

  // Apply twist rotation around Y axis at this level
  const angle = twistRad * level;
  const cosA = Math.cos(angle);
  const sinA = Math.sin(angle);

  // Get 4 corner positions of this tet
  const verts = tetVerts(center, size);
  const childSize = size * 0.5;

  // Each corner hosts a child tet centered at the corner
  for (const v of verts) {
    // Rotate corner offset relative to center
    const dx = v[0] - center[0];
    const dz = v[2] - center[2];
    const rx = dx * cosA - dz * sinA;
    const rz = dx * sinA + dz * cosA;
    const childCenter = [center[0] + rx, v[1], center[2] + rz];
    collectLeaves(childCenter, childSize, depth - 1, twistRad, level + 1, leaves);
  }
}

export function sceneSetup(THREE, scene, camera, renderer, params, seed) {
  scene.background = new THREE.Color(0x07080a);
  camera.position.set(0, 0.2, 3.5);
  camera.lookAt(0, 0, 0);

  // Lighting
  const ambient = new THREE.AmbientLight(0xffffff, 0.5);
  scene.add(ambient);
  const dir = new THREE.DirectionalLight(0xffffff, 1.2);
  dir.position.set(3, 5, 3);
  scene.add(dir);
  const fill = new THREE.DirectionalLight(0xffffff, 0.3);
  fill.position.set(-3, -2, -3);
  scene.add(fill);

  const depth = Math.max(1, params.depth | 0);
  const twistRad = (params.twist * Math.PI) / 180;

  // Collect all leaf tetrahedra
  const leaves = [];
  collectLeaves([0, 0, 0], 1.5, depth, twistRad, 0, leaves);

  const maxCount = leaves.length;

  const geom = new THREE.TetrahedronGeometry(0.5, 0);
  const mat = new THREE.MeshStandardMaterial({
    color: new THREE.Color().setHSL(params.hue / 360, 0.65, 0.55),
    roughness: 0.5,
    metalness: 0.15,
  });

  const mesh = new THREE.InstancedMesh(geom, mat, maxCount);
  mesh.count = maxCount;

  const dummy = new THREE.Object3D();
  for (let i = 0; i < leaves.length; i++) {
    const { center, size } = leaves[i];
    dummy.position.set(center[0], center[1], center[2]);
    // Scale so that geometry with radius 0.5 matches the leaf size
    dummy.scale.setScalar(size * 2);
    dummy.rotation.set(0, 0, 0);
    dummy.updateMatrix();
    mesh.setMatrixAt(i, dummy.matrix);
  }
  mesh.instanceMatrix.needsUpdate = true;

  // Wrap in a pivot for rotation
  const pivot = new THREE.Group();
  pivot.add(mesh);
  scene.add(pivot);

  return { pivot, mesh, mat };
}

export function sceneAnimate(THREE, scene, camera, state, params, time, delta) {
  const { pivot, mat } = state;

  // Update color
  mat.color.setHSL(params.hue / 360, 0.65, 0.55);

  // Orbit
  pivot.rotation.y = time * params.rotateSpeed;
  pivot.rotation.x = Math.sin(time * params.rotateSpeed * 0.4) * 0.3;
}