Faces from imported polygons
64-bitBuilds faces from the polygons of an imported file, sharing vertices between them.
key = round(x × 1e5), round(y × 1e5), round(z × 1e5)
Points with the same key (a 1e-5 m grid) share one vertex, which keeps the first point's 64-bit coordinates; a later point in that cell is moved to it, by up to about 0.017 mm across the cell's diagonal. A polygon whose corners leave its Newell plane by more than max(EPS, size × 1e-6) becomes a fan of triangles.
/**
* A definition from imported polygons. Each entry is either an array of points (one outer loop)
* or {loops: [outer, ...holes], color}. Holes are turned to run against the outer loop; a polygon
* that is not flat (beyond 1 µm per meter of size) comes in as a fan of triangles. Edges between
* coplanar faces are soft (a triangulated flat area still reads as one surface). `segs` ([p, q]
* pairs) become plain edges in the same definition, welded to the faces' vertices.
*/
export function meshDef(model, polys, name, kind = 'group', segs = []) {
const d = model.newDef(kind, name);
const grid = new Map();
const vid = (p) => {
const k = p.map((x) => Math.round(x * 1e5)).join(',');
let id = grid.get(k);
if (id === undefined) { id = vAdd(d, p); grid.set(k, id); }
return id;
};
const ring = (pts) => {
const loop = [];
for (const p of pts) { const v = vid(p); if (loop[loop.length - 1] !== v) loop.push(v); }
while (loop.length > 1 && loop[0] === loop[loop.length - 1]) loop.pop();
if (loop.length < 3 || new Set(loop).size !== loop.length) return null;
return polyArea3(loop.map((v) => d.verts.get(v))) <= EPS2 ? null : loop;
};
for (const poly of polys) {
const obj = !Array.isArray(poly);
const rings = obj ? (Array.isArray(poly.loops) ? poly.loops : []) : [poly];
const color = obj && typeof poly.color === 'string' ? poly.color : undefined;
const outer = rings.length ? ring(rings[0]) : null;
if (!outer) continue;
const P = outer.map((v) => d.verts.get(v));
const n = newell(P);
let size = 0;
for (const p of P) size = Math.max(size, dist(p, P[0]));
const tol = Math.max(EPS, size * 1e-6);
if (P.some((p) => Math.abs(dot(n, sub(p, P[0]))) > tol)) {
// not flat: a fan of triangles (each one flat by definition)
for (let i = 1; i + 1 < outer.length; i++) {
const t = [outer[0], outer[i], outer[i + 1]];
if (polyArea3(t.map((v) => d.verts.get(v))) > EPS2) fAdd(d, [t], color);
}
continue;
}
const holes = [];
for (const r of rings.slice(1)) {
const h = ring(r);
if (!h || h.some((v) => outer.includes(v))) continue;
holes.push(dot(newell(h.map((v) => d.verts.get(v))), n) > 0 ? h.reverse() : h);
}
fAdd(d, [outer, ...holes], color);
}
for (const [p, q] of segs) { const a = vid(p), b = vid(q); if (a !== b) eAdd(d, a, b); }
const ef = edgeFaces(d);
for (const [eid, e] of d.edges) {
const fs = ef.get(key(e.a, e.b)) || [];
if (fs.length === 2 && dot(faceNormal(d, d.faces.get(fs[0])), faceNormal(d, d.faces.get(fs[1]))) > 1 - 1e-6) e.soft = true;
}
return d;
}