// Minimal column-major 4x4 matrix helpers (WebGPU layout). export type Mat4 = Float32Array; export function perspective(fovY: number, aspect: number, near: number, far: number): Mat4 { const f = 1 / Math.tan(fovY / 2); const m = new Float32Array(16); m[0] = f / aspect; m[5] = f; m[10] = far / (near - far); m[11] = -1; m[14] = (near * far) / (near - far); return m; } export function lookAt(eye: number[], target: number[], up: number[]): Mat4 { const z = normalize([eye[0] - target[0], eye[1] - target[1], eye[2] - target[2]]); const x = normalize(cross(up, z)); const y = cross(z, x); const m = new Float32Array(16); m[0] = x[0]; m[1] = y[0]; m[2] = z[0]; m[4] = x[1]; m[5] = y[1]; m[6] = z[1]; m[8] = x[2]; m[9] = y[2]; m[10] = z[2]; m[12] = -dot(x, eye); m[13] = -dot(y, eye); m[14] = -dot(z, eye); m[15] = 1; return m; } export function multiply(a: Mat4, b: Mat4): Mat4 { const out = new Float32Array(16); for (let c = 0; c < 4; c++) { for (let r = 0; r < 4; r++) { let s = 0; for (let k = 0; k < 4; k++) s += a[k * 4 + r] * b[c * 4 + k]; out[c * 4 + r] = s; } } return out; } function cross(a: number[], b: number[]): number[] { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; } function dot(a: number[], b: number[]): number { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } function normalize(a: number[]): number[] { const l = Math.hypot(a[0], a[1], a[2]) || 1; return [a[0] / l, a[1] / l, a[2] / l]; }