// Pure 3D geometry utilities — no DOM, no topology, only math. export const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2; export class Vec3 { constructor(x, y, z) { this.x = x; this.y = y; this.z = z; } add(v) { return new Vec3(this.x + v.x, this.y + v.y, this.z + v.z); } sub(v) { return new Vec3(this.x - v.x, this.y - v.y, this.z - v.z); } scale(s) { return new Vec3(this.x * s, this.y * s, this.z * s); } dot(v) { return this.x * v.x + this.y * v.y + this.z * v.z; } cross(v) { return new Vec3( this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x, ); } length() { return Math.sqrt(this.dot(this)); } normalize() { return this.scale(1 / this.length()); } lerp(v, t) { return this.scale(1 - t).add(v.scale(t)); } to_array() { return [this.x, this.y, this.z]; } toString() { return `Vec3(${this.x.toFixed(4)}, ${this.y.toFixed(4)}, ${this.z.toFixed(4)})`; } } // Project a Vec3 onto the unit sphere. export function normalize_to_sphere(v) { return v.normalize(); } // Midpoint between two Vec3 (not yet normalized). export function midpoint(a, b) { return a.add(b).scale(0.5); } // Returns n [x, y] pairs for a regular n-gon centered at origin with given circumradius. // Vertices start at angle `start_angle` (default: top, -π/2). export function regular_polygon_2d(n, circumradius, start_angle = -Math.PI / 2) { const pts = []; for (let i = 0; i < n; i++) { const angle = start_angle + (2 * Math.PI * i) / n; pts.push([circumradius * Math.cos(angle), circumradius * Math.sin(angle)]); } return pts; } // Centroid of an array of [x, y] points. export function centroid_2d(pts) { let sx = 0, sy = 0; for (const [x, y] of pts) { sx += x; sy += y; } return [sx / pts.length, sy / pts.length]; } // Apply a 2D similarity transform (rotation + uniform scale + translation) // represented as { a, b, tx, ty } where the mapping is: // [x', y'] = [a*x - b*y + tx, b*x + a*y + ty] // (a + bi is the complex multiplier, tx+ty is the translation) export function apply_transform_2d(transform, pt) { const { a, b, tx, ty } = transform; return [ a * pt[0] - b * pt[1] + tx, b * pt[0] + a * pt[1] + ty, ]; } // Compute the similarity transform that maps (src0 -> dst0, src1 -> dst1). // Returns { a, b, tx, ty }. export function similarity_transform_2d(src0, src1, dst0, dst1) { // Treat points as complex numbers. // We want w = c * z + d where c = a+bi, d = tx+ty*i // c = (dst1 - dst0) / (src1 - src0) [complex division] // d = dst0 - c * src0 const [sx0, sy0] = src0; const [sx1, sy1] = src1; const [dx0, dy0] = dst0; const [dx1, dy1] = dst1; // src_vec = src1 - src0 const svx = sx1 - sx0, svy = sy1 - sy0; // dst_vec = dst1 - dst0 const dvx = dx1 - dx0, dvy = dy1 - dy0; // c = dst_vec / src_vec (complex division) const denom = svx * svx + svy * svy; const a = (dvx * svx + dvy * svy) / denom; const b = (dvy * svx - dvx * svy) / denom; // d = dst0 - c * src0 const tx = dx0 - (a * sx0 - b * sy0); const ty = dy0 - (b * sx0 + a * sy0); return { a, b, tx, ty }; }