// 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 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; }