Keeps one point per grid cell when a large point cloud is imported, and estimates how many points an import will keep.
How the precision is kept
Points are kept in Float64Array. In the lowest and nearest modes a kept point is one of the input points, unchanged. In average mode x, y and z are summed in arrival order and divided by the count. Cell indexes are floored from 64-bit quotients and combined into one whole-number key.
Calculations under this policy
4 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.
Decides whether an incoming point is kept, replaces the cell's point, or is dropped.
i = ⌊(x − oₓ) ÷ cell⌋; key = i × 2³² + (j + 2³¹); nearest: d² = (fₓ − i − ½)² + (f_y − j − ½)²
The key is exact because |i| < 2²¹ keeps i × 2³² + j below 2⁵³. Lowest and nearest replace the stored point by a whole input point. Average adds x, y and z to running sums in the order the points arrive.
thin.js
lines 40 to 66
CivilCraft 0.8.1
add(x, y, z, cls) {
this.seen++;
if (this.classes && !this.classes.has(cls)) { this.filtered++; return false; }
const r = this.region;
if (r && (x < r.minX || x > r.maxX || y < r.minY || y > r.maxY)) { this.outside++; return false; }
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return false;
const fx = (x - this.ox) / this.cell, fy = (y - this.oy) / this.cell;
const ix = Math.floor(fx), iy = Math.floor(fy);
if (Math.abs(ix) >= 2097152 || Math.abs(iy) >= 2147483647) { this.outside++; return false; } // keys stay exact (origin near the data)
const key = ix * 4294967296 + (iy + 2147483648);
let k = this.map.get(key);
if (k === undefined) {
if (this.count >= this.max) { this.capped = true; this.dropped++; return false; }
k = this.count++;
if (k >= this.cap) this.grow();
this.map.set(key, k);
this.X[k] = x; this.Y[k] = y; this.Z[k] = z;
this.W[k] = this.mode === 'average' ? 1 : this.mode === 'nearest' ? (fx - ix - 0.5) ** 2 + (fy - iy - 0.5) ** 2 : 0;
return true;
}
if (this.mode === 'lowest') { if (z < this.Z[k]) { this.X[k] = x; this.Y[k] = y; this.Z[k] = z; } }
else if (this.mode === 'nearest') {
const d = (fx - ix - 0.5) ** 2 + (fy - iy - 0.5) ** 2;
if (d < this.W[k]) { this.X[k] = x; this.Y[k] = y; this.Z[k] = z; this.W[k] = d; }
} else { this.X[k] += x; this.Y[k] += y; this.Z[k] += z; this.W[k] += 1; }
return true;
}
The thinned points
64-bit
Returns one point per occupied cell.
average mode: x̄ = Σx ÷ n, ȳ = Σy ÷ n, z̄ = Σz ÷ n
For lowest and nearest the stored coordinates are returned as they came in. For average each sum is divided by the count once.
Tells the import dialog about how many points a thinning will keep.
kept ≈ Σ min(points × f, cell area × f ÷ spacing² + 1), f = part of the coarse cell inside the area
A 64-bit sum over the coarse cells of counts (128 × 128 by default), rounded to a whole number at the end. It is an estimate for the dialog, not a count of the import.
thin.js
lines 97 to 120
CivilCraft 0.8.1
/**
* spacing: thinning cell, in the file's units (convert first); classes: Set | null; box: the area in the file's
* coordinates (null = all). Returns { points (to keep, about), inArea (points of those classes in the area) }.
*/
estimate({ spacing, classes = null, box = null }) {
const n = this.n, per = new Float64Array(n * n);
for (const [c, a] of this.byClass) if (!classes || classes.has(c)) for (let k = 0; k < a.length; k++) per[k] += a[k];
let keep = 0, inArea = 0;
const cellArea = this.w * this.h;
for (let j = 0; j < n; j++) for (let i = 0; i < n; i++) {
const v = per[j * n + i];
if (!v) continue;
let f = 1;
if (box) {
const x0 = this.box.minX + i * this.w, y0 = this.box.minY + j * this.h;
const ox = Math.max(0, Math.min(box.maxX, x0 + this.w) - Math.max(box.minX, x0)), oy = Math.max(0, Math.min(box.maxY, y0 + this.h) - Math.max(box.minY, y0));
f = (ox * oy) / cellArea;
if (!f) continue;
}
inArea += v * f;
keep += Math.min(v * f, (cellArea * f) / (spacing * spacing) + 1);
}
return { points: Math.round(keep), inArea: Math.round(inArea) };
}
In detail
Method
Square cells of the given spacing anchored at an origin; one stored point per occupied cell, chosen by mode: lowest, nearest the cell center, or the average.
Number types
Float64Array for x, y, z and the per-cell weight; whole-number keys in a Map; Uint32Array counts in the coverage estimate.
Limits
Average mode sums raw coordinates, so with large coordinates and many points in a cell the mean loses low digits. A cell index beyond ±2,097,152 in x is refused so the key stays an exact whole number. The estimate assumes points are spread evenly inside each coarse cell.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.