Resources Numerical accuracy Calculation policy

Scan compared with the model

Compares a point cloud with each model element's box and reports whether the element is built, partly built or missing, how far the scanned surface is from it, and the percent complete.

How the precision is kept

The scan's points are transformed into the model's space in 64-bit and then stored in a Float32Array; every distance, the mean offset and the RMS are computed from those 32-bit coordinates. The result is a tolerance-bound comparison of boxes with points: the file's own header says it is not a survey and not a quantity or payment basis.

Open item. Keep the transformed scan points in a Float64Array so the reported mean offset and RMS are computed from 64-bit coordinates.

Calculations under this policy

5 calculations 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.

scan-progress.js BimCraft 0.8.1 32-bit in part 5 calculations Reviewed

Tests tools/bimcraft-test/scan-progress-test.mjs

Scan points into the model's space

32-bit in part

Moves every scan point through the cloud's placement matrix into the model's space.

p′ = M × (x, y, z, 1), stored in a Float32Array

The matrix product is computed in 64-bit and the result is written into a Float32Array. All later distances read the coordinates back from that 32-bit array.

  • scan-progress.js
  • lines 44 to 49
  • BimCraft 0.8.1
const P = new Float32Array(n * 3);
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
for (let i = 0; i < n; i++) {
  let x = pos[i * 3], y = pos[i * 3 + 1], z = pos[i * 3 + 2];
  if (M) { const X = M[0] * x + M[4] * y + M[8] * z + M[12], Y = M[1] * x + M[5] * y + M[9] * z + M[13], Z = M[2] * x + M[6] * y + M[10] * z + M[14]; x = X; y = Y; z = Z; }
  P[i * 3] = x; P[i * 3 + 1] = y; P[i * 3 + 2] = z;

Signed distance of a point from a box

32-bit in part

The distance from a scan point to an element's box surface, positive outside and negative inside, and the running sums for the mean and RMS.

outside: d = √Σ gapₖ²; inside: d = −min distance to a face

The point's coordinates are read from the 32-bit array; the box corners are 64-bit. The sums of d and d² are added in the order the grid visits the points.

  • scan-progress.js
  • lines 91 to 97
  • BimCraft 0.8.1
let out2 = 0, inner = Infinity;
for (let k = 0; k < 3; k++) { const d = p[k] < min[k] ? min[k] - p[k] : p[k] > max[k] ? p[k] - max[k] : 0; out2 += d * d; if (p[k] >= min[k] && p[k] <= max[k]) inner = Math.min(inner, p[k] - min[k], max[k] - p[k]); }
const d = out2 > 0 ? Math.sqrt(out2) : -inner;
if (d > reach) return;
any++;
if (Math.abs(d) > band) return;
cnt++; sum += d; sum2 += d * d; seenPts[i] = 1;

Coverage, state and deviation of an element

32-bit in part

An element's covered share, its state, and the mean offset and RMS of the scan from its surface.

coverage = max over large faces of covered cells ÷ cells; mean = Σd ÷ n; rms = √(Σd² ÷ n)

The mean and RMS come from distances of 32-bit points. Coverage is rounded to 3 decimals in the row. Faces smaller than a quarter of the largest face are left out of the coverage.

  • scan-progress.js
  • lines 108 to 115
  • BimCraft 0.8.1
let coverage = 0;
const bigFace = Math.max(...faces.map((f) => f.area));
for (const f of faces) if (f.area >= 0.25 * bigFace) coverage = Math.max(coverage, f.got / (f.nu * f.nv));   // thin end faces are not what a scanner judges an element by
const state = coverage >= builtMin ? 'built' : coverage >= partlyMin ? 'partly' : any ? 'missing' : 'unseen';
const mean = cnt ? sum / cnt : null, rms = cnt ? Math.sqrt(sum2 / cnt) : null;
const dev = (state === 'built' || state === 'partly') && cnt ? (Math.abs(mean) <= tol ? 'ok' : Math.abs(mean) <= 3 * tol ? 'near' : 'off') : null;
usedPts += cnt;
rows.push({ key, state, coverage: Math.round(coverage * 1000) / 1000, mean: mean === null ? null : mean, rms, dev, points: cnt, volume });

Percent complete

64-bit

The share of judged elements that are built, by count and by box volume.

pct = round(1000 × built ÷ judged) ÷ 10; by volume: round(1000 × V_built ÷ V_judged) ÷ 10

Both percentages are rounded to one decimal. The volume is the element's box volume, the product of its three sizes, not the volume of its body.

  • scan-progress.js
  • lines 124 to 125
  • BimCraft 0.8.1
const judged = T.built + T.partly + T.missing;
if (judged) { T.pct = Math.round(1000 * T.built / judged) / 10; T.pctVolume = vAll > 0 ? Math.round(1000 * vB / vAll) / 10 : null; }

Progress report as CSV

32-bit in part

Writes one row per element with its state, coverage, mean offset, RMS, point count and volume.

coverage: 3 decimals; mean, rms, volume: 4 decimals

The offsets are rounded to 4 decimals of the caller's unit for the file. The values being rounded are the ones computed from the 32-bit points.

  • scan-progress.js
  • lines 152 to 160
  • BimCraft 0.8.1
/** A CSV of the rows (key → name through names). Cells starting with = + - @ get a leading apostrophe. */
export function progressCsv(res, names = new Map(), unit = 'm') {
  const safe = (v) => { const s = String(v ?? ''); return /^[=+\-@\t\r]/.test(s) ? "'" + s : s; };
  const q = (v) => '"' + safe(v).replace(/"/g, '""') + '"';
  const f = (v, d) => (v === null || v === undefined ? '' : String(Math.round(v * 10 ** d) / 10 ** d));
  const lines = [['element', 'state', 'coverage', 'mean offset (' + unit + ')', 'rms (' + unit + ')', 'deviation', 'points', 'volume (' + unit + '3)', 'schedule', 'flag'].map(q).join(',')];
  for (const r of res.rows) lines.push([names.get(r.key) ?? r.key, r.state, f(r.coverage, 3), f(r.mean, 4), f(r.rms, 4), r.dev ?? '', r.points, f(r.volume, 4), r.plan ?? '', r.flag ?? ''].map(q).join(','));
  return lines.join('\r\n') + '\r\n';
}

In detail

Method
Each box face is cut into cells of about 0.2 m (at most 24 × 24); a cell is covered when a point lies within the tolerance of the face plane. Coverage is the best face's covered share. Deviation is the mean and RMS of the signed distance of the points within the band of the box surface.
Number types
Points: Float32Array. Box corners, sums and results: 64-bit numbers. Cell indexes: Int32Array.
Tolerances
tol 0.05, cell 0.2, band 5 × tol, reach 1.5, in the caller's units (the viewer's are meters). Built at coverage ≥ 0.4, partly at ≥ 0.1. Deviation ok within tol, near within 3 × tol.
Limits
Offsets larger than the band are not seen. An element whose box is much larger than its body can read as partly built or missing.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach