Resources Numerical accuracy Calculation policy

Real-time view and render bench

Draws the real-time 3D view, bakes its bounce light, times the path tracers and writes the standalone viewer's scene.

How the precision is kept

These files produce pictures. Radiance is accumulated in 64-bit by the reference tracer and handed on as Float32Array; probe light is stored as half floats on the GPU; the viewer file carries 32-bit geometry buffers. None of these values is a dimension or a quantity of the model.

Calculations under this policy

4 calculations in 4 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.

rt/rt-post.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests None found by the review

Filmic tone curve

32-bit, display only

Maps scene brightness to screen brightness for the filmic view transform.

h(x) = (x (0.15 x + 0.05) + 0.004) ÷ (x (0.15 x + 0.5) + 0.06) − 0.02 ÷ 0.3; output = h(2 c) ÷ h(11.2)

The white point h(11.2) is computed once in 64-bit; the per-pixel curve runs in the GPU shader. The result is a pixel color.

  • rt/rt-post.js
  • lines 17 to 18
  • BimCraft 0.8.1
// Hable's curve (John Hable, GDC 2010) with pt-post.js's constants: A 0.15, B 0.50, C 0.10, D 0.20, E 0.02, F 0.30, white 11.2
const hableJs = (x) => ((x * (0.15 * x + 0.1 * 0.5) + 0.2 * 0.02) / (x * (0.15 * x + 0.5) + 0.2 * 0.3)) - 0.02 / 0.3;
rt/rt-gi.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests None found by the review

Baked probe light

32-bit, display only

Collects the bounce light at each probe and removes the sky's own part.

shᵢ,ₖ = bakedᵢ,ₖ − skyₖ (k = 0…11)

The twelve coefficients per probe are held in a Float32Array and later packed to half floats for the GPU textures. They only light the real-time view.

  • rt/rt-gi.js
  • lines 126 to 137
  • BimCraft 0.8.1
const sh = new Float32Array(grid.n * 12);
let done = 0;
const runs = [];
for (let a = 0; a < grid.n; a += per) {
  const k = Math.min(per, grid.n - a);
  runs.push(pool.run(job, 'bakeProbes', { headers: headers.slice(a * 128, (a + k) * 128), w, h, spp: preset.probeSpp, dirs }, { priority: 0, signal, units: k * w * h * preset.probeSpp })
    .then((out) => { sh.set(out, a * 12); done += k; onProgress?.(done / grid.n); }));
}
await Promise.all(runs);
const { sky, envK } = skyFor(plain, settings);
const sk = skyL1(sky, up, envK);
for (let i = 0; i < grid.n; i++) for (let k = 0; k < 12; k++) sh[i * 12 + k] -= sk[k];
render/bench-kernels.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests None found by the review

Mean radiance per pixel

32-bit, display only

Averages the accumulated samples of each pixel for the bench and the parity comparison.

mean = Σ samples ÷ n

Each color channel of the accumulator is divided by the pixel's fourth channel, or by 1 when that is zero, and the result is stored in a Float32Array.

  • render/bench-kernels.js
  • lines 21 to 25
  • BimCraft 0.8.1
const mean3 = (acc, w, h) => {
  const out = new Float32Array(w * h * 3);
  for (let i = 0; i < w * h; i++) { const n = acc[i * 4 + 3] || 1; out[i * 3] = acc[i * 4] / n; out[i * 3 + 1] = acc[i * 4 + 1] / n; out[i * 3 + 2] = acc[i * 4 + 2] / n; }
  return out;
};
rt/rt-export.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests None found by the review

Scene for the standalone viewer

32-bit, display only

Packs the drawn scene into the viewer's file.

Positions, normals and texture coordinates are converted with Float32Array.from before they are written as base64. Placement matrices, views and bounds are written as plain numbers. The file feeds the viewer's renderer.

  • rt/rt-export.js
  • lines 31 to 68
  • BimCraft 0.8.1
export async function exportScene({ rt, plain, settings = {}, views = [], title = '', words = {}, maxScatter = 250000 }) {
  const geos = new Map(), geometries = [];
  const items = [];
  for (const it of plain.items) {
    if (!it.geometry) continue;
    let gi = geos.get(it.geometry);
    if (gi === undefined) {
      const g = it.geometry, P = g.getAttribute('position'), N = g.getAttribute('normal'), U = g.getAttribute('uv');
      geometries.push({ position: toB64(Float32Array.from(P.array)), normal: N ? toB64(Float32Array.from(N.array)) : null, uv: U ? toB64(Float32Array.from(U.array)) : null, index: g.index ? toB64(Uint32Array.from(g.index.array)) : null });
      gi = geometries.length - 1;
      geos.set(it.geometry, gi);
    }
    items.push({ g: gi, m: it.matrix ? Array.from(it.matrix) : null, look: it.look || 'default', lines: !!it.lines });
  }
  const resolved = {};
  for (const k of new Set(['__ground', 'default', ...items.filter((x) => !x.lines).map((x) => x.look)])) resolved[k] = await resolveMaterial(k, settings, plain);
  let left = maxScatter;
  const scatter = [], used = new Set();
  for (const s of plain.scatter || []) {
    const n = Math.min(s.result.count, Math.max(0, left));
    if (!n) continue;
    left -= n;
    for (let i = 0; i < n; i++) used.add(s.slugs[s.result.item[i]]);
    scatter.push({ count: n, matrices: toB64(s.result.matrices.subarray(0, n * 16)), item: toB64(s.result.item.subarray(0, n)), slugs: s.slugs });
  }
  const scatterLibrary = (plain.scatterLibrary || []).filter((e) => used.has(e.slug)).map((e) => ({ slug: e.slug, parts: e.parts.map((p) => ({ color: p.color, alpha: p.alpha ?? 1, P: toB64(Float32Array.from(p.P)), I: toB64(Uint32Array.from(p.I)) })) }));
  const g = rt.giProbes && rt.giProbes.data;
  const cam = rt.camera;
  return {
    format: 'cadcraft-live-scene', version: VERSION, title, words,
    up: plain.up, bounds: plain.bounds, groundAt: plain.groundAt ?? 0, sun: plain.sun || null, clip: plain.clip || [], defaultColor: plain.defaultColor,
    settings: { sky: settings.sky, ground: settings.ground, edges: settings.edges, exposure: settings.exposure, exposureMode: settings.exposureMode, fstop: settings.fstop, shutter: settings.shutter, iso: settings.iso, whiteBalance: settings.whiteBalance, post: settings.post },
    geometries, items, resolved, lights: plain.lights || [], scatter, scatterLibrary,
    gi: g ? { min: g.min, max: g.max, res: g.res, sh: toB64(g.sh) } : null,
    views: views.map((v) => ({ name: v.name, world: Array.from(v.world), proj: Array.from(v.proj) })),
    start: { world: cam.matrixWorld.elements.slice(), proj: cam.projectionMatrix.elements.slice() },
  };
}

In detail

Method
Tone curve after John Hable (the file's comment cites GDC 2010) with fixed constants; probe light as twelve coefficients per probe with the sky's part subtracted; the mean of path-traced samples per pixel.
Number types
Float64Array accumulators in the reference tracer; Float32Array and half-float textures for everything sent to the GPU or written to the viewer file.

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