Interactive WebGL Goldberg polyhedron viewer and painter with PBR shading, adjustable environment lighting, paint tools (pen, brush, circle, fill, line, pick), undo/redo, colour palettes, and mesh relaxation. Added to the standalone experiments index. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
3.0 KiB
JavaScript
99 lines
3.0 KiB
JavaScript
// 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 };
|
|
}
|