Resources Numerical accuracy Calculation policy

Real-time view

Draws the live 3D view, its walk mode, its 360° panoramas and the standalone viewer file, with bounce light from baked probes.

How the precision is kept

Everything here ends as pixels on the screen or in a picture file. Meshes, instance matrices and probe coefficients are 32-bit arrays for the graphics card; the collision scene of the walk mode stores its triangles in Float32Array. The set-up arithmetic around them is 64-bit. The one number a person reads from this subsystem, the count of scattered items, is recorded under its own policy.

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-probes.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests tools/rt/test.mjs

Probe light as spherical harmonics

32-bit, display only

Sums a small traced panorama into the four coefficients per color the view shades with.

c = Σ L × Y × dΩ, dΩ = (2π ÷ w)(π ÷ h) sin θ

The sums are 64-bit, from the tracer's accumulation buffer and ray directions held in a Float32Array, and the twelve results are written to a Float32Array for a GPU texture. Y00 and Y1 are the rounded constants 0.282095 and 0.488603.

  • rt/rt-probes.js
  • lines 68 to 86
  • BimCraft 0.8.1
export function projectL1(accum, dirs, w, h, out = new Float32Array(12), at = 0) {
  const cell = ((2 * Math.PI) / w) * (Math.PI / h);
  const c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  for (let i = 0; i < w * h; i++) {
    const n = accum[i * 4 + 3];
    if (!(n > 0)) continue;
    const dx = dirs[i * 3], dy = dirs[i * 3 + 1], dz = dirs[i * 3 + 2];
    // dΩ from the pixel's latitude: every panorama row is a band of equal height in θ (RENDER-SPEC § 1)
    const row = Math.floor(i / w), sinT = Math.sin((Math.PI * (row + 0.5)) / h);
    const wgt = (cell * sinT) / n;
    const a = [accum[i * 4], accum[i * 4 + 1], accum[i * 4 + 2]];
    for (let k = 0; k < 3; k++) {
      const L = (AP1_TO_REC709[k][0] * a[0] + AP1_TO_REC709[k][1] * a[1] + AP1_TO_REC709[k][2] * a[2]) * wgt;
      c[k] += L * Y0; c[3 + k] += L * Y1 * dy; c[6 + k] += L * Y1 * dz; c[9 + k] += L * Y1 * dx;
    }
  }
  for (let k = 0; k < 12; k++) out[at + k] = c[k];
  return out;
}
rt/rt-pano.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests tools/rt/test.mjs

Cube faces to a 360° picture

32-bit, display only

Builds the equirectangular panorama from six rendered faces.

direction from (longitude, latitude); face = largest d·a; color = bilinear mix of four pixels

Directions and blend weights are 64-bit; the output is 8-bit color in a Uint8ClampedArray. The divisor is the largest of six dot products of a unit direction, which is never near zero.

  • rt/rt-pano.js
  • lines 59 to 77
  • BimCraft 0.8.1
export function cubeToEquirect(faceImgs, fr, W, H) {
  const F = faces(fr), out = new Uint8ClampedArray(W * H * 4);
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
    const d = dirOf(x, y, W, H, fr);
    // the face this direction leaves through: the largest component along a face's look direction
    let best = 0, bi = 0;
    for (let i = 0; i < 6; i++) { const v = dot(d, F[i].a); if (v > best) { best = v; bi = i; } }
    const Fi = F[bi], img = faceImgs[bi], n = img.n;
    const u = dot(d, Fi.b) / best, v = dot(d, Fi.c) / best;
    const px = Math.min(n - 1, Math.max(0, ((u + 1) / 2) * n - 0.5)), py = Math.min(n - 1, Math.max(0, ((1 - v) / 2) * n - 0.5));
    const x0 = Math.floor(px), y0 = Math.floor(py), x1 = Math.min(n - 1, x0 + 1), y1 = Math.min(n - 1, y0 + 1), tx = px - x0, ty = py - y0;
    const o = (y * W + x) * 4;
    for (let k = 0; k < 4; k++) {
      const a = img.data[(y0 * n + x0) * 4 + k], b = img.data[(y0 * n + x1) * 4 + k], c = img.data[(y1 * n + x0) * 4 + k], e = img.data[(y1 * n + x1) * 4 + k];
      out[o + k] = (a * (1 - tx) + b * tx) * (1 - ty) + (c * (1 - tx) + e * tx) * ty;
    }
  }
  return out;
}
rt/rt-look.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests tools/rt/test.mjs

Display chain for one pixel

32-bit, display only

Turns a linear color into the color shown: white balance, exposure, highlight knee, contrast, view transform.

c ← max(0, WB c) × 2^stops; c ← 0.18 × (c ÷ 0.18)^(1 + contrast)

A 64-bit JavaScript copy of the per-pixel steps; the file's header calls it the test oracle for the shader chain. The view itself runs the same steps on the graphics card.

  • rt/rt-look.js
  • lines 99 to 108
  • BimCraft 0.8.1
/** The per-pixel chain in JS (no bloom, no vignette: those need neighbours / the pixel's place) → display-linear rgb. */
export function displayPixel709(rgb, d) {
  let c = mul3(d.wb, rgb).map((x) => Math.max(0, x) * d.exposure);
  if (d.highlights > 0) {
    const L = lum(c), hs = d.highlights * 2;
    if (L > 1) { const s = (1 + (L - 1) / (1 + (L - 1) * hs)) / L; c = c.map((x) => x * s); }
  }
  if (d.contrast) c = c.map((x) => 0.18 * Math.pow(x / 0.18, 1 + d.contrast));
  return viewTransform(d.view, c);
}
rt/rt-walk.js BimCraft 0.8.1 32-bit, display only 1 calculation Reviewed

Tests tools/rt/test.mjs

Standing and falling in walk mode

32-bit, display only

Keeps the walker's eye at eye height above the floor below, stepping up or falling as the floor changes.

v ← v − 9.81 Δt; height ← max(floor + eye height, height + v Δt)

Moves the camera only. The floor comes from a ray cast against collision triangles held in a Float32Array, and gravity is the rounded 9.81 m/s².

  • rt/rt-walk.js
  • lines 143 to 149
  • BimCraft 0.8.1
const want = fl + o.height, now = dot(pos, U);
if (now < want) { for (let k = 0; k < 3; k++) pos[k] += U[k] * (want - now); vy = 0; }   // a step up
else if (now > want + 1e-3) {
  vy -= 9.81 * dt;
  const next = Math.max(want, now + vy * dt);
  for (let k = 0; k < 3; k++) pos[k] += U[k] * (next - now);
  if (next === want) vy = 0;

In detail

Method
Irradiance probes projected on spherical harmonics bands 0 and 1 (constants 0.282095 and 0.488603, three.js's order); cube faces resampled to an equirectangular picture; a display chain of white balance, exposure, highlight knee, contrast and view transform; walking by ray casts against a bounding-volume hierarchy.
Number types
Float32Array for probe coefficients, instance matrices, vertex data and collision triangles; Uint8 pixels; 64-bit numbers in between.
Limits
The spherical-harmonic constants are written to six digits and gravity in the walk is 9.81; both affect only what is drawn and how the camera moves. Probe light levels are not a measured illuminance.

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