Bench outlines, area and perimeter
64-bitThe closed outlines of the mined area at one level, each with its area and perimeter.
point = origin + index × block size; area = Σ (x_i·y_j − x_j·y_i) ÷ 2; perimeter = Σ edge lengths
Edges are found and joined on whole-number grid indexes, and collinear points are removed with a whole-number cross product, so those decisions are exact. Coordinates are index × size, not a running sum. The area is the shoelace sum on absolute coordinates.
export function benchLoops(model, mined, k) {
const { nx, ny, size, origin } = model, is = (i, j) => i >= 0 && j >= 0 && i < nx && j < ny && mined[model.index(i, j, k)] === 1;
const out = new Map(); // start vertex "a,b" -> [[a0,b0,a1,b1]]
const add = (a0, b0, a1, b1) => { const key = a0 + ',' + b0; if (!out.has(key)) out.set(key, []); out.get(key).push([a0, b0, a1, b1, false]); };
for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) {
if (!is(i, j)) continue;
if (!is(i, j - 1)) add(i, j, i + 1, j);
if (!is(i + 1, j)) add(i + 1, j, i + 1, j + 1);
if (!is(i, j + 1)) add(i + 1, j + 1, i, j + 1);
if (!is(i - 1, j)) add(i, j + 1, i, j);
}
const loops = [];
for (const list of out.values()) for (const e0 of list) {
if (e0[4]) continue;
const ring = []; let e = e0, guard = 0;
while (e && !e[4] && guard++ < 1e6) {
e[4] = true; ring.push([e[0], e[1]]);
const next = out.get(e[2] + ',' + e[3]) || [];
// at a vertex with two ways on (two blocks touching at a corner) turn left-most first, so the loops stay simple
const dx = e[2] - e[0], dy = e[3] - e[1];
const free = next.filter((n) => !n[4]);
free.sort((a, b) => turn(dx, dy, a[2] - a[0], a[3] - a[1]) - turn(dx, dy, b[2] - b[0], b[3] - b[1]));
e = free[0];
}
if (ring.length < 4) continue;
const pts = simplify(ring).map(([a, b]) => [origin[0] + a * size[0], origin[1] + b * size[1]]);
let area = 0, perimeter = 0;
for (let i = 0; i < pts.length; i++) { const p = pts[i], q = pts[(i + 1) % pts.length]; area += p[0] * q[1] - q[0] * p[1]; perimeter += Math.hypot(q[0] - p[0], q[1] - p[1]); }
loops.push({ pts, area: area / 2, perimeter });
}
return loops.sort((a, b) => (a.area > 0) === (b.area > 0) ? Math.abs(b.area) - Math.abs(a.area) : b.area - a.area);
}