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.jsBimCraft 0.8.1 32-bit in part5 calculationsReviewed
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.
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.
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.