Resources Numerical accuracy Calculation policy

Areas of flat upward faces by color

Adds up the area of the level, upward-facing faces on screen for each color, in square meters, for the wet-surface look.

How the precision is kept

Each face's own area is multiplied by the square of a scale taken from its placement matrix, summed per color in 64-bit, and rounded to 0.01 m² in the result. The scale is the cube root of one term of the matrix's 3×3 determinant, not of the whole determinant.

Open item. Take the scale from the full 3×3 determinant of the placement matrix, so a placement turned about the vertical axis keeps its area.

Calculations under this policy

1 calculation in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

render-mc.js ModelCraft 0.8.1 64-bit 1 calculation Reviewed

Tests bin/test-render.mjs

Flat area per color

64-bit

The 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².

  • render-mc.js
  • lines 190 to 212
  • ModelCraft 0.8.1
/**
 * 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 }));
}

In detail

Method
Face area from the model kernel times s², with s = ∛|m₀·(m₅·m₁₀ − m₆·m₉)|.
Number types
64-bit numbers; areas rounded to two decimals in the returned list.
Tolerances
A face counts as level when the z part of its placed unit normal is at least 0.98 (within about 11.5° of level). Colors with less than minArea (1 m² by default) are left out.
Limits
The scale is right only for placements that are not turned about the vertical axis: a turn by θ multiplies the area by cos θ to the power 4⁄3.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach