Open or closed surface
32-bit, display onlyThe elements of a batch whose triangles do not close.
key = round(x × 1e4), round(y × 1e4), round(z × 1e4); open if any edge count is odd
Each coordinate is multiplied by 10,000 and rounded, so vertices in the same 0.1 mm cell are one point. The edge key p × nP + q is an integer and stays exact while it is below 2^53.
/** Element ids (triIds[t] for triangle t of `indices`) whose triangles do not make a closed surface. */
export function openElements(positions, indices, triIds) {
const nV = positions.length / 3;
// One id per distinct position, so split vertices meet again.
const canon = new Int32Array(nV);
const seen = new Map();
for (let v = 0; v < nV; v++) {
const k = Math.round(positions[v * 3] * Q) + ',' + Math.round(positions[v * 3 + 1] * Q) + ',' + Math.round(positions[v * 3 + 2] * Q);
let id = seen.get(k);
if (id === undefined) { id = seen.size; seen.set(k, id); }
canon[v] = id;
}
const nP = seen.size;
// Triangles by element.
const byEl = new Map();
const nT = indices.length / 3;
for (let t = 0; t < nT; t++) {
const e = triIds[t];
let l = byEl.get(e);
if (!l) { l = []; byEl.set(e, l); }
l.push(t);
}
const open = new Set();
const edges = new Map();
for (const [e, tris] of byEl) {
edges.clear();
for (const t of tris) {
const a = canon[indices[t * 3]], b = canon[indices[t * 3 + 1]], c = canon[indices[t * 3 + 2]];
for (const [p, q] of [[a, b], [b, c], [c, a]]) {
if (p === q) continue; // a degenerate edge adds nothing
const key = p < q ? p * nP + q : q * nP + p;
edges.set(key, (edges.get(key) || 0) + 1);
}
}
for (const n of edges.values()) if (n % 2) { open.add(e); break; }
}
return open;
}