Generate 3D graphics with JavaScript

by Ben Gillbanks

HTML

<canvas></canvas>

JavaScript

// tabs for indents on purpose
/** Scene graph ------------------------------------------------------------- */
const beep8 = {};
beep8.g3 = (() => {
	const TAU = Math.PI * 2;

	const vec3 = (x=0, y=0, z=0) => ({ x, y, z });

	function mul3(a, b) {
		// 3x3 * 3x3
		return [
			a[0]*b[0] + a[1]*b[3] + a[2]*b[6],
			a[0]*b[1] + a[1]*b[4] + a[2]*b[7],
			a[0]*b[2] + a[1]*b[5] + a[2]*b[8],
			a[3]*b[0] + a[4]*b[3] + a[5]*b[6],
			a[3]*b[1] + a[4]*b[4] + a[5]*b[7],
			a[3]*b[2] + a[4]*b[5] + a[5]*b[8],
			a[6]*b[0] + a[7]*b[3] + a[8]*b[6],
			a[6]*b[1] + a[7]*b[4] + a[8]*b[7],
			a[6]*b[2] + a[7]*b[5] + a[8]*b[8],
		];
	}

	function rotXYZ(rx=0, ry=0, rz=0) {
		const cx = Math.cos(rx), sx = Math.sin(rx);
		const cy = Math.cos(ry), sy = Math.sin(ry);
		const cz = Math.cos(rz), sz = Math.sin(rz);
		const Rx = [1,0,0, 0,cx,-sx, 0,sx,cx];
		const Ry = [cy,0,sy, 0,1,0, -sy,0,cy];
		const Rz = [cz,-sz,0, sz,cz,0, 0,0,1];
		// R = Rz * Ry * Rx
		return mul3(mul3(Rz, Ry), Rx);
	}

	function applyMat(v, m) {
		return {
			x: v.x*m[0] + v.y*m[1] + v.z*m[2],
			y: v.x*m[3] + v.y*m[4] + v.z*m[5],
			z: v.x*m[6] + v.y*m[7] + v.z*m[8],
		};
	}

	function add(a,b){ return { x:a.x+b.x, y:a.y+b.y, z:a.z+b.z }; }
	function sub(a,b){ return { x:a.x-b.x, y:a.y-b.y, z:a.z-b.z }; }
	function cross(a,b){ return { x:a.y*b.z-a.z*b.y, y:a.z*b.x-a.x*b.z, z:a.x*b.y-a.y*b.x }; }

	function createNode(opts={}) {
		return {
			pos: vec3(),
			rot: rotXYZ(),
			children: [],
			faces: [], // { verts:[vec3], fill, layer }
			...opts
		};
	}

	function addChild(parent, child) {
		parent.children.push(child);
		return child;
	}

	function faceNormal(a,b,c) {
		const ab = sub(b,a), ac = sub(c,a);
		return cross(ab, ac);
	}

	function worldFaces(node, parentPos, parentRot, out=[]) {
		const rot = mul3(parentRot, node.rot);
		const pos = add(parentPos, applyMat(node.pos, parentRot));

		for (const f of node.faces) {
			const verts =...