Delaunay triangulation
64-bitTriangles over a set of points in plan.
D = 2 (aₓ(b_y − c_y) + bₓ(c_y − a_y) + cₓ(a_y − b_y)); center = (Σ |p|² × Δ) ÷ D; inside when |p − center|² < r² (1 − 10⁻¹²)
The circumcenter uses the squares of the absolute coordinates, so for a site far from the origin the products are large and their differences lose digits. The decision is a floating-point comparison with a relative margin of 1e-12, not an exact predicate. A finished triangle whose cross product is below 1e-14 (absolute) is dropped.
export function delaunay(pts) {
const n = pts.length;
if (n < 3) return [];
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (const [x, y] of pts) { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); }
const d = Math.max(x1 - x0, y1 - y0) || 1, cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
const P = pts.concat([[cx - 20 * d, cy - d], [cx, cy + 20 * d], [cx + 20 * d, cy - d]]);
const circ = (a, b, c) => {
const [ax, ay] = P[a], [bx, by] = P[b], [qx, qy] = P[c];
const D = 2 * (ax * (by - qy) + bx * (qy - ay) + qx * (ay - by));
if (Math.abs(D) < 1e-18) return { x: 0, y: 0, r2: Infinity };
const a2 = ax * ax + ay * ay, b2 = bx * bx + by * by, c2 = qx * qx + qy * qy;
const x = (a2 * (by - qy) + b2 * (qy - ay) + c2 * (ay - by)) / D, y = (a2 * (qx - bx) + b2 * (ax - qx) + c2 * (bx - ax)) / D;
return { x, y, r2: (ax - x) ** 2 + (ay - y) ** 2 };
};
let tris = [{ v: [n, n + 1, n + 2], c: circ(n, n + 1, n + 2) }];
for (let i = 0; i < n; i++) {
const [px, py] = P[i];
const bad = [], keep = [];
for (const tr of tris) ((px - tr.c.x) ** 2 + (py - tr.c.y) ** 2 < tr.c.r2 * (1 - 1e-12) ? bad : keep).push(tr);
// the hole's rim: edges of the bad triangles that only one of them has
const count = new Map();
for (const tr of bad) for (let k = 0; k < 3; k++) { const a = tr.v[k], b = tr.v[(k + 1) % 3], key = a < b ? a + ',' + b : b + ',' + a; count.set(key, (count.get(key) || 0) + 1); }
tris = keep;
for (const tr of bad) for (let k = 0; k < 3; k++) {
const a = tr.v[k], b = tr.v[(k + 1) % 3];
if (count.get(a < b ? a + ',' + b : b + ',' + a) === 1) tris.push({ v: [a, b, i], c: circ(a, b, i) });
}
}
const out = [];
for (const { v } of tris) {
if (v.some((k) => k >= n)) continue;
const [a, b, c] = v, [ax, ay] = P[a], [bx, by] = P[b], [qx, qy] = P[c];
const cr = (bx - ax) * (qy - ay) - (by - ay) * (qx - ax);
if (Math.abs(cr) < 1e-14) continue;
out.push(cr > 0 ? [a, b, c] : [a, c, b]);
}
return out;
}