import { Vec3 } from "./Vec3" /** 4x4 matrix in column-major storage: index = col * 4 + row, matching OpenGL * conventions so the standard perspective/lookAt formulas apply directly. */ export type Mat4 = Float32Array export namespace Mat4 { /** Matrix product A * B (apply B first, then A). */ export function multiply(a: Mat4, b: Mat4): Mat4 { const out = new Float32Array(16) for (let col = 0; col < 4; col++) { for (let row = 0; row < 4; row++) { let sum = 0 for (let k = 0; k < 4; k++) { sum += a[k * 4 + row] * b[col * 4 + k] } out[col * 4 + row] = sum } } return out } /** Right-handed perspective projection (camera looks down -Z). Maps the view * frustum to clip space; the -1 in row 3 copies -z into w, so the later * divide by w is what produces foreshortening. */ export function perspective(fovY: number, aspect: number, near: number, far: number): Mat4 { const f = 1 / Math.tan(fovY / 2) const out = new Float32Array(16) out[0] = f / aspect out[5] = f out[10] = (far + near) / (near - far) out[11] = -1 out[14] = (2 * far * near) / (near - far) return out } /** View matrix looking from `eye` toward `center`, with `up` roughly up. * Builds an orthonormal camera basis (s = right, u = up, f = forward) and * packs it as the inverse camera transform. */ export function lookAt(eye: Vec3, center: Vec3, up: Vec3): Mat4 { const f = Vec3.normalize(Vec3.sub(center, eye)) const s = Vec3.normalize(Vec3.cross(f, up)) const u = Vec3.cross(s, f) const out = new Float32Array(16) out[0] = s.x out[1] = u.x out[2] = -f.x out[4] = s.y out[5] = u.y out[6] = -f.y out[8] = s.z out[9] = u.z out[10] = -f.z out[12] = -Vec3.dot(s, eye) out[13] = -Vec3.dot(u, eye) out[14] = Vec3.dot(f, eye) out[15] = 1 return out } /** Transform a point, returning homogeneous coords. `w` is kept (not divided * out) because the rasterizer needs it for near-clipping and the perspective * divide/depth; for a perspective matrix w equals the view-space distance. */ export function transform(m: Mat4, v: Vec3): { x: number; y: number; z: number; w: number } { return { x: m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12], y: m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13], z: m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14], w: m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15], } } }