Moves every point of the registered scans by its scan's matrix and writes the result as a LAS 1.2 or an E57 file.
How the precision is kept
Each point is read from the cloud's position array, which holds 32-bit offsets from the scan's origin. The origin is added and the 4 × 4 registration matrix applied in 64-bit arithmetic. LAS then stores each coordinate as a 32-bit integer multiple of a scale between 0.0001 and 1; E57 stores 64-bit doubles. Either way the exported coordinates start from a 32-bit offset.
Open item. Carry each scan's coordinates in 64-bit from the source file to the writers, so exported LAS and E57 points do not start from 32-bit offsets.
Calculations under this policy
6 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-export.jsCadCraft 0.8.2 32-bit in part6 calculationsReviewed
Tests bin/test-register.mjs
Registered position of a point
32-bit in part
One point in the registered frame: its stored position plus the scan's origin, moved by the scan's matrix.
p = M × (pos + origin)
c.pos is the cloud's position array, a Float32Array of offsets from a whole-unit origin (pointcloud.js loadCloud). The origin is added and the matrix applied in 64-bit, but every coordinate written to a file starts from that 32-bit offset, which holds about 7 significant digits.
scan-export.js
lines 22 to 25
CadCraft 0.8.2
const pt = (c, M, i, out) => {
const x = c.pos[i * 3] + c.origin[0], y = c.pos[i * 3 + 1] + c.origin[1], z = c.pos[i * 3 + 2] + c.origin[2];
out[0] = M[0] * x + M[1] * y + M[2] * z + M[3]; out[1] = M[4] * x + M[5] * y + M[6] * z + M[7]; out[2] = M[8] * x + M[9] * y + M[10] * z + M[11];
};
Bounds of the registered cloud
32-bit in part
The minimum and maximum x, y, z over every moved point, and the point count.
Each point is moved by pt and compared in 64-bit; the values compared come from the 32-bit positions. With no points both corners are set to 0.
scan-export.js
lines 26 to 35
CadCraft 0.8.2
function bounds(items) {
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity], p = [0, 0, 0];
let n = 0;
for (const { cloud: c, M } of items) for (let i = 0; i < c.n; i++) {
pt(c, M, i, p); n++;
for (let a = 0; a < 3; a++) { if (p[a] < lo[a]) lo[a] = p[a]; if (p[a] > hi[a]) hi[a] = p[a]; }
}
if (!n) { lo.fill(0); hi.fill(0); }
return { lo, hi, n };
}
LAS scale and offset
64-bit
Chooses the scale and offset the LAS file stores its integer coordinates with.
scale = first of 0.0001, 0.001, 0.01, 0.1, 1 with extent ÷ scale < 2e9; offset = ⌊min ÷ 100⌋ × 100
The extent is the largest side of the bounds. The offset is each minimum rounded down to a whole hundred, so the stored integers stay small. One scale is used for all three axes.
Stores one point's x, y, z as integers in the LAS file.
X = round((p − offset) ÷ scale)
Each coordinate is rounded to the nearest multiple of the scale and written as a signed 32-bit integer, as the LAS format stores it. The header's minimum and maximum are written from the unrounded values, so they can differ from the stored points by up to half a scale step.
scan-export.js
lines 75 to 76
CadCraft 0.8.2
const o = used * rec;
for (let a = 0; a < 3; a++) dv.setInt32(o + a * 4, Math.round((p[a] - offset[a]) / scale), true);
E57 point record
32-bit in part
Stores one point's x, y, z in an E57 data packet and widens the scan's bounds.
The moved coordinates are written as 64-bit doubles with no scaling or rounding. Their source is still the 32-bit position array, so the doubles carry the precision of pt, not of the original scan file.
scan-export.js
lines 129 to 131
CadCraft 0.8.2
const i = i0 + k;
pt(c, M, i, p);
for (let a = 0; a < 3; a++) { dv.setFloat64(starts[a] + k * 8, p[a], true); if (p[a] < lo[a]) lo[a] = p[a]; if (p[a] > hi[a]) hi[a] = p[a]; }
Numbers written as text in the E57 header
64-bit
Writes a bound or a time stamp as text in the E57 XML section.
String(v) gives the shortest text that reads back to the same 64-bit number, so nothing is rounded here. Negative zero is written as 0.
p = M × (position + origin), row-major 4 × 4 with the last row left out. LAS: X = round((p − offset) ÷ scale), offset = the minimum rounded down to a whole hundred, scale = the finest of 0.0001, 0.001, 0.01, 0.1, 1 with extent ÷ scale below 2e9. E57 1.0: cartesian x, y, z as doubles in data packets, pages of 1024 bytes with a CRC-32C.
Number types
Float32Array positions (pointcloud.js loadCloud), 64-bit origin and matrix arithmetic, signed 32-bit integers in LAS, 64-bit doubles in E57.
Limits
A 32-bit offset holds about 7 significant digits of the distance from the scan's origin. LAS coordinates are rounded to the chosen scale. Units are the files' own; nothing is converted.
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.