forked from mikael-lovqvist/websperiments
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>
69 lines
2.3 KiB
JavaScript
69 lines
2.3 KiB
JavaScript
// Placement via unfolding: each neighbor face is placed adjacent to its BFS
|
|
// parent by a similarity transform, then shrunk toward its centroid by
|
|
// DEPTH_SHRINK^depth so depth rings visually separate.
|
|
//
|
|
// Returns Map<face_index, { vertices_2d, centroid_2d, depth }>
|
|
|
|
import {
|
|
regular_polygon_2d,
|
|
centroid_2d,
|
|
similarity_transform_2d,
|
|
apply_transform_2d,
|
|
} from './geometry.mjs';
|
|
|
|
const DEPTH_SHRINK = 0.82;
|
|
|
|
export function place_unfold(poly, neighborhood, root_face_index, cx, cy, face_size) {
|
|
const placements = new Map();
|
|
|
|
for (const entry of neighborhood) {
|
|
const fi = entry.face.index;
|
|
const face = entry.face;
|
|
const n = face.size;
|
|
|
|
if (entry.parent_index === null) {
|
|
// Root face: regular polygon centred on canvas.
|
|
const pts = regular_polygon_2d(n, face_size).map(([x, y]) => [x + cx, y + cy]);
|
|
placements.set(fi, { vertices_2d: pts, centroid_2d: [cx, cy], depth: 0 });
|
|
} else {
|
|
const parent_pl = placements.get(entry.parent_index);
|
|
if (!parent_pl) { continue; }
|
|
|
|
const parent_face = poly.faces[entry.parent_index];
|
|
const pei = entry.parent_edge_index;
|
|
const pa = parent_pl.vertices_2d[pei];
|
|
const pb = parent_pl.vertices_2d[(pei + 1) % parent_face.size];
|
|
|
|
// Find which edge of this face leads back to the parent.
|
|
let nei = -1;
|
|
for (let j = 0; j < face.edge_neighbors.length; j++) {
|
|
if (face.edge_neighbors[j] === entry.parent_index) { nei = j; break; }
|
|
}
|
|
if (nei === -1) {
|
|
console.warn(`place_unfold: face ${fi} has no reverse edge to parent ${entry.parent_index}`);
|
|
continue;
|
|
}
|
|
|
|
// Winding is opposite: parent (a→b) ≡ neighbor (b→a).
|
|
const local_pts = regular_polygon_2d(n, 1);
|
|
const local_a = local_pts[nei];
|
|
const local_b = local_pts[(nei + 1) % n];
|
|
const transform = similarity_transform_2d(local_a, local_b, pb, pa);
|
|
|
|
let vertices_2d = local_pts.map(pt => apply_transform_2d(transform, pt));
|
|
const c = centroid_2d(vertices_2d);
|
|
|
|
// Shrink toward centroid after transform (before would be cancelled by scaling).
|
|
const shrink = Math.pow(DEPTH_SHRINK, entry.depth);
|
|
vertices_2d = vertices_2d.map(([x, y]) => [
|
|
c[0] + (x - c[0]) * shrink,
|
|
c[1] + (y - c[1]) * shrink,
|
|
]);
|
|
|
|
placements.set(fi, { vertices_2d, centroid_2d: c, depth: entry.depth });
|
|
}
|
|
}
|
|
|
|
return placements;
|
|
}
|