Delaunay triangulation

델라우네이 삼각분할

A way of connecting points into triangles that avoids slivers. Mathematically the dual of the Voronoi diagram.

Also known as: Delaunay mesh
···
js
const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const cs = getComputedStyle(document.documentElement);
const ACCENT = cs.getPropertyValue('--accent').trim() || '#5b5bf7';
const FG = cs.getPropertyValue('--fg').trim() || '#f1f0ec';
let w, h;
function resize() {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  w = innerWidth; h = innerHeight;
  c.width = w * dpr; c.height = h * dpr;
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
addEventListener('resize', resize);
resize();

function circumcircle(a, b, cpt) {
  const d = 2 * (a[0] * (b[1] - cpt[1]) + b[0] * (cpt[1] - a[1]) + cpt[0] * (a[1] - b[1]));
  if (Math.abs(d) < 1e-6) return null;
  const A2 = a[0] * a[0] + a[1] * a[1], B2 = b[0] * b[0] + b[1] * b[1], C2 = cpt[0] * cpt[0] + cpt[1] * cpt[1];
  const ux = (A2 * (b[1] - cpt[1]) + B2 * (cpt[1] - a[1]) + C2 * (a[1] - b[1])) / d;
  const uy = (A2 * (cpt[0] - b[0]) + B2 * (a[0] - cpt[0]) + C2 * (b[0] - a[0])) / d;
  return { x: ux, y: uy, r: Math.hypot(ux - a[0], uy - a[1]) };
}
function triangulate(points) {
  const M = 6000;
  const pts = points.concat([[-M, -M], [M * 2, -M], [-M, M * 2]]);
  const s0 = points.length, s1 = points.length + 1, s2 = points.length + 2;
  let tris = [[s0, s1, s2]];
  for (let i = 0; i < points.length; i++) {
    const p = points[i], bad = [];
    for (const t of tris) { const cc = circumcircle(pts[t[0]], pts[t[1]], pts[t[2]]); if (cc && Math.hypot(p[0] - cc.x, p[1] - cc.y) < cc.r) bad.push(t); }
    const edgeCount = new Map();
    for (const t of bad) for (const e of [[t[0], t[1]], [t[1], t[2]], [t[2], t[0]]]) {
      const key = [...e].sort((a, b) => a - b).join(',');
      edgeCount.set(key, (edgeCount.get(key) || 0) + 1);
    }
    tris = tris.filter((t) => !bad.includes(t));
    for (const t of bad) for (const e of [[t[0], t[1]], [t[1], t[2]], [t[2], t[0]]]) {
      const key = [...e].sort((a, b) => a - b).join(',');
      if (edgeCount.get(key) === 1) tris.push([e[0], e[1], i]);
    }
  }
  return { pts, tris: tris.filter((t) => t.every((idx) => idx < points.length)) };
}

const N = 26;
const nodes = Array.from({ length: N }, () => ({ x: Math.random() * w, y: Math.random() * h, vx: (Math.random() - 0.5) * 10, vy: (Math.random() - 0.5) * 10 }));
function draw() {
  ctx.clearRect(0, 0, w, h);
  const points = nodes.map((n) => [n.x, n.y]);
  const { pts, tris } = triangulate(points);
  ctx.strokeStyle = ACCENT; ctx.globalAlpha = 0.5; ctx.lineWidth = 1;
  for (const t of tris) {
    ctx.beginPath();
    ctx.moveTo(pts[t[0]][0], pts[t[0]][1]); ctx.lineTo(pts[t[1]][0], pts[t[1]][1]); ctx.lineTo(pts[t[2]][0], pts[t[2]][1]);
    ctx.closePath(); ctx.stroke();
  }
  ctx.globalAlpha = 1; ctx.fillStyle = FG;
  for (const n of nodes) { ctx.beginPath(); ctx.arc(n.x, n.y, 2.6, 0, 7); ctx.fill(); }
}
function step(dt) {
  for (const n of nodes) {
    n.x += n.vx * dt; n.y += n.vy * dt;
    if (n.x < 10 || n.x > w - 10) n.vx *= -1;
    if (n.y < 10 || n.y > h - 10) n.vy *= -1;
  }
}
draw();
(function loop() { step(0.03); draw(); requestAnimationFrame(loop); })();

There are countless ways to triangulate the same set of points, but Delaunay triangulation picks the one where no point sits inside any triangle's circumcircle. That single rule makes the result unique and minimises long, sliver-thin triangles. Russian mathematician Boris Delaunay formalised it in 1934.

This demo uses the Bowyer–Watson algorithm: add points one at a time, delete every existing triangle whose circumcircle contains the new point, and re-fill the resulting hole by connecting the new point to its boundary. Re-triangulating every frame while the points drift lets you watch the mesh renegotiate itself live.

It is the basis of terrain polygon meshes, low-poly art and natural-looking wireframe backgrounds. The Voronoi diagram of the same points falls straight out of it — connect the circumcenters of neighbouring triangles — which is why the two are always discussed as a pair.

When to use

Use it for low-poly backgrounds, terrain meshes, drawing a network over a scatter plot. Move to a faster library (d3-delaunay etc.) past a few thousand points.