// SPDX-License-Identifier: AGPL-3.0-only // meshStep — minimal 3D vector math (immutable tuple style). export type Vec3 = readonly [number, number, number]; export const v = (x: number, y: number, z: number): Vec3 => [x, y, z]; export const add = (a: Vec3, b: Vec3): Vec3 => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; export const sub = (a: Vec3, b: Vec3): Vec3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; export const scale = (a: Vec3, s: number): Vec3 => [a[0] * s, a[1] * s, a[2] * s]; export const mul = (a: Vec3, b: Vec3): Vec3 => [a[0] * b[0], a[1] * b[1], a[2] * b[2]]; export const dot = (a: Vec3, b: Vec3): number => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; export const cross = (a: Vec3, b: Vec3): Vec3 => [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], ]; export const len = (a: Vec3): number => Math.hypot(a[0], a[1], a[2]); export const len2 = (a: Vec3): number => a[0] * a[0] + a[1] * a[1] + a[2] * a[2]; export const dist = (a: Vec3, b: Vec3): number => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]); export const lerp = (a: Vec3, b: Vec3, t: number): Vec3 => [ a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t, ]; export const normalize = (a: Vec3): Vec3 => { const l = len(a); return l > 0 ? [a[0] / l, a[1] / l, a[2] / l] : [0, 0, 0]; }; /** Angle between two vectors in radians (numerically stable). */ export const angleBetween = (a: Vec3, b: Vec3): number => { const an = normalize(a); const bn = normalize(b); const c = Math.max(-1, Math.min(1, dot(an, bn))); return Math.acos(c); };