Smooth shading normals
32-bit, display onlyThe normal at each corner of faces joined by soft edges.
n = normalize(Σ n_f × area_f) over the joined faces that meet at the corner
Unit face normals are weighted by the faces' outer-loop areas and summed in 64-bit, then normalized. They are used for shading and are written as the normals of exported files.
/**
* Smooth shading (0.4 M18): faces joined by soft edges are one smooth surface. → Map `${fid}|${v}` → the unit normal
* (definition coordinates) at that corner: the area-weighted average over the faces of its surface that meet at v.
* Faces with no soft edge are left out (they shade flat).
*/
export function smoothNormals(def) {
const out = new Map();
if (![...def.edges.values()].some((e) => e.soft)) return out;
const ef = edgeFaces(def), up = new Map();
const find = (x) => { while (up.get(x) !== x) { up.set(x, up.get(up.get(x))); x = up.get(x); } return x; };
for (const fid of def.faces.keys()) up.set(fid, fid);
const joined = new Set();
for (const e of def.edges.values()) {
if (!e.soft) continue;
const fs = ef.get(key(e.a, e.b)) || [];
for (let i = 1; i < fs.length; i++) { up.set(find(fs[i]), find(fs[0])); joined.add(fs[0]); joined.add(fs[i]); }
}
const acc = new Map();
for (const fid of joined) {
const f = def.faces.get(fid), n = faceNormal(def, f), a = polyArea3(f.loops[0].map((v) => def.verts.get(v))) || 1e-12, r = find(fid);
for (const loop of f.loops) for (const v of loop) { const k = r + '|' + v; acc.set(k, add(acc.get(k) || [0, 0, 0], mul(n, a))); }
}
for (const fid of joined) {
const f = def.faces.get(fid), r = find(fid), fn = faceNormal(def, f);
for (const loop of f.loops) for (const v of loop) {
const s = acc.get(r + '|' + v), L = len(s);
out.set(fid + '|' + v, L > 1e-12 ? mul(s, 1 / L) : fn);
}
}
return out;
}