Resources Numerical accuracy Calculation policy

Rendering: sun direction, lamps and painted faces

Prepares the 3D model for a rendered picture: the sun's direction, the light fixtures as lamps, and which library material each painted face shows.

How the precision is kept

These functions feed the renderer and the 3D view. They pass the view's own mesh buffers on unchanged and work out directions and lamp positions for drawing only. Nothing here is a stored measurement or a reported quantity.

Calculations under this policy

3 calculations in 2 source files. 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-bim.js BimCraft 0.8.1 32-bit, display only 2 calculations Reviewed

Tests tools/bimcraft-test/render-test.mjs

Sun direction for a rendering

32-bit, display only

The direction to the sun in the 3D view's axes.

model = east × dₓ + north × d_y, with d_z kept; view = (mₓ, m_z, −m_y), normalized

The sun library's direction is turned by the site's true north, changed from Z up to Y up and normalized with Math.hypot. The time zone is the longitude ÷ 15 rounded to a whole hour. Used to light the picture only.

  • render-bim.js
  • lines 63 to 74
  • BimCraft 0.8.1
export function bimSun(site, settings, place = null) {
  const base = bimSunBase(site);
  const s = sunPosition(settings && settings.sun ? { ...base, ...settings.sun } : base);
  if (!s.up) return null;
  // east / north / up → the model's IFC axes (TrueNorth turns north in the plan) → viewer (Y up)
  const n = site?.north || [0, 1], e = [n[1], -n[0]], d = s.dir;
  const m = [e[0] * d[0] + n[0] * d[1], e[1] * d[0] + n[1] * d[1], d[2]];
  let v = [m[0], m[2], -m[1]];
  if (place) v = [place[0] * v[0] + place[4] * v[1] + place[8] * v[2], place[1] * v[0] + place[5] * v[1] + place[9] * v[2], place[2] * v[0] + place[6] * v[1] + place[10] * v[2]];
  const l = Math.hypot(v[0], v[1], v[2]) || 1;
  return [v[0] / l, v[1] / l, v[2] / l];
}

Lamps from light fixtures

32-bit, display only

A lamp for each light fixture: where it is, how bright and how wide.

lumens = stored value, else watts × 80, else 800; position = middle of the fixture's box, just under it

Lumens are clamped to 1 … 200000 and rounded to a whole number, the beam angle to whole degrees between 5 and 170. The position comes from the view's bounding box of the fixture. Used to light the picture only.

  • render-bim.js
  • lines 248 to 272
  • BimCraft 0.8.1
/** models: [{mi, api, mid, keyBase}] */
export function bimLamps(viewer, models) {
  const out = [];
  for (const m of models) {
    const L = viewer.layers.get(m.mi);
    if (!L || !L.group.visible) continue;
    for (const [id, f] of fixturesOf(m.api, m.mid)) {
      const key = (m.keyBase || 0) + id;
      if (viewer.vis && viewer.vis.state(key) !== SOLID) continue;
      const bb = viewer.keyBounds([key]);
      if (!bb) continue;
      const o = f.own || {}, kind = KINDS.includes(String(o.Kind).toLowerCase()) ? String(o.Kind).toLowerCase() : 'point';
      const lumens = Math.round(num(o.Lumens, 1, 200000, f.watts ? f.watts * 80 : 800));
      const w = Math.max(0.05, bb.max[0] - bb.min[0]), d = Math.max(0.05, bb.max[2] - bb.min[2]);
      out.push({
        key, name: f.name, kind, lumens, on: !(o.On === false || String(o.On).toLowerCase() === 'false'),
        p: [(bb.min[0] + bb.max[0]) / 2, bb.min[1] - (kind === 'area' ? 0.005 : 0.03), (bb.min[2] + bb.max[2]) / 2], dir: [0, -1, 0],
        color: hexOk(o.Color) || '#ffe7c4', angle: Math.round(num(o.Angle, 5, 170, 60)),
        size: kind === 'area' ? [Math.min(w, 20), Math.min(d, 20)] : [0.06, 0.06],
      });
      if (out.length >= 200) return out;
    }
  }
  return out;
}
paint-bim.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests tools/bimcraft-test/render-test.mjs

Which side of an element a face is

32-bit, display only

Names the axis of the element's placement that a face normal runs along, to pick the material painted on that side.

axis = the placement column with the largest |n · column|

The placement's three columns are normalized with Math.hypot and compared by dot product; the largest wins, with ties going to the earlier axis. It decides which painted material a face shows, not a dimension.

  • paint-bim.js
  • lines 123 to 133
  • BimCraft 0.8.1
export function faceAxis(api, mid, id, nFile, axesCache = null) {
  let ax = axesCache ? axesCache.get(id) : undefined;
  if (ax === undefined) { ax = placementAxes(api, mid, id); axesCache?.set(id, ax); }
  const A = ax || [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
  let best = -1, at = null;
  for (let i = 0; i < 3; i++) {
    const d = nFile[0] * A[i][0] + nFile[1] * A[i][1] + nFile[2] * A[i][2];
    if (Math.abs(d) > best) { best = Math.abs(d); at = 'XYZ'[i] + (d >= 0 ? '+' : '-'); }
  }
  return at;   // the axis the face is nearest (any unit normal has a component ≥ 0.577)
}

In detail

Method
Sun position from render/sun.js turned by the site's true north; lamp position from the fixture's bounding box; a face's material from the placement axis its normal is nearest.
Number types
64-bit numbers for directions; the mesh position and normal buffers are the 3D view's own.
Limits
A fixture without a lumen value is given its wattage × 80, or 800. Lumens and beam angle are stored as whole numbers. Without a site the sun uses 40° N, 105° W.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach