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 =...