Files
websperiments/standalone/goldberg-sphere/placer_ortho_3d.mjs
mikael-lovqvists-claude-agent 9600a2bc2a Add Goldberg Polyhedron Paint experiment
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>
2026-05-10 16:00:19 +00:00

66 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Orthographic projection of the relaxed 3D Goldberg sphere.
//
// Rotates the sphere so the current face's centroid points toward +z,
// then projects each vertex's (x, y) components directly to canvas.
// Faces on the far hemisphere (z < 0) are still included if in the
// neighbourhood — they'll just appear behind the centre.
//
// Requires goldberg.relax_sphere() to have been called first.
// Falls back to vertices_3d if relaxed_vertices_3d is not set.
//
// Returns Map<face_index, { vertices_2d, centroid_2d, depth }>
import { Vec3, centroid_2d } from './geometry.mjs';
export function place_ortho_3d(poly, neighborhood, root_face_index, cx, cy, face_size) {
// --- Determine projection centre from root face ---
const root_face = poly.faces[root_face_index];
const root_verts = root_face.relaxed_vertices_3d ?? root_face.vertices_3d;
// Normalised centroid of root face = the outward direction we point toward +z.
let sx = 0, sy = 0, sz = 0;
for (const v of root_verts) { sx += v.x; sy += v.y; sz += v.z; }
const cl = Math.sqrt(sx * sx + sy * sy + sz * sz);
const C = new Vec3(sx / cl, sy / cl, sz / cl);
// Build orthonormal tangent frame {u, v, w=C}.
// u = perpendicular to C in the horizontal plane; v = C × u.
const up = Math.abs(C.y) < 0.9 ? new Vec3(0, 1, 0) : new Vec3(1, 0, 0);
const u = C.cross(up).normalize(); // points "right" on screen
const v = C.cross(u).normalize(); // points "up" on screen (then flipped for canvas)
// Project a Vec3 onto the tangent frame, returns [canvas_x, canvas_y, z_depth].
const project = (P) => [
P.dot(u), // tangent x
P.dot(v), // tangent y (flipped to canvas below)
P.dot(C), // depth (1 = facing viewer, -1 = back)
];
// Determine scale from root face's projected circumradius.
const root_proj = root_verts.map(project);
const rcx = root_proj.reduce((s, p) => s + p[0], 0) / root_proj.length;
const rcy = root_proj.reduce((s, p) => s + p[1], 0) / root_proj.length;
const root_r = Math.max(...root_proj.map(([x, y]) =>
Math.sqrt((x - rcx) ** 2 + (y - rcy) ** 2)));
if (root_r < 1e-10) { return new Map(); }
const scale = face_size / root_r;
// --- Build placements ---
const placements = new Map();
for (const entry of neighborhood) {
const face = entry.face;
const verts = face.relaxed_vertices_3d ?? face.vertices_3d;
const pts = verts.map(P => {
const [tx, ty] = project(P);
return [cx + tx * scale, cy - ty * scale]; // flip y: +v → up on canvas
});
placements.set(face.index, {
vertices_2d: pts,
centroid_2d: centroid_2d(pts),
depth: entry.depth,
});
}
return placements;
}