// 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;
}