Flat area per color
64-bitThe total area of level, upward faces of each color, largest first.
s = ∛|m₀·(m₅·m₁₀ − m₆·m₉)|; area = Σ face area × s²; shown = round(area × 100) ÷ 100
The expression under the cube root is the first of the three terms of the 3×3 determinant. For a placement with scale k turned by θ about the vertical axis it equals k³·cos² θ, so s = k·cos^(2⁄3) θ and the area is multiplied by cos^(4⁄3) θ; at a quarter turn the face counts as zero area. The returned areas are rounded to 0.01 m².
/**
* The colours of the flat, upward-facing faces on screen (floors, paving, flat roofs, the ground) with their area in m², largest
* first, as the looks table keys them (0.8 EG.61: rain makes these shine). Library materials and pictures carry their own look and are left out.
*/
export function mcFlatColors(model, { minArea = 1 } = {}) {
const n = new Map();
model.walkShown((def, m, path, paint) => {
if (path.length && /^Scene /.test(model.root.insts.get(path[0])?.tag || '')) return false; // the scene's own trees and people are not ground
for (const f of def.faces.values()) {
if (f.hidden) continue;
const nn = faceNormal(def, f);
const nz = m[2] * nn[0] + m[6] * nn[1] + m[10] * nn[2]; // the normal's z after the placement (rotation and uniform scale)
const len = Math.hypot(m[2] * nn[0] + m[6] * nn[1] + m[10] * nn[2], m[0] * nn[0] + m[4] * nn[1] + m[8] * nn[2], m[1] * nn[0] + m[5] * nn[1] + m[9] * nn[2]) || 1;
if (nz / len < 0.98) continue;
const k = keyOf(f, paint);
if (k.startsWith('mat:') || k.startsWith('img:')) continue;
const s = Math.cbrt(Math.abs(m[0] * (m[5] * m[10] - m[6] * m[9]))); // a uniform scale, enough for an area
const a = faceArea(def, f) * s * s;
n.set(k, (n.get(k) || 0) + a);
}
});
return [...n].filter(([, a]) => a >= minArea).sort((a, b) => b[1] - a[1]).map(([key, area]) => ({ key, area: Math.round(area * 100) / 100 }));
}