보로노이 다이어그램

Voronoi diagram

평면 위의 씨앗점 여러 개를 "가장 가까운 점" 기준으로 영역을 나눈 모자이크. 기린 무늬, 세포, 비눗방울에서 자연히 나타나는 패턴입니다.

다른 이름: Thiessen polygonsVoronoi tessellation
···
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 palette = [cs.getPropertyValue('--accent').trim(), cs.getPropertyValue('--accent-2').trim(), cs.getPropertyValue('--accent-3').trim()].map(x => x || '#5b5bf7');
function shade(hex, k) {
  const n = parseInt(hex.replace('#', ''), 16);
  const r = Math.min(255, (n >> 16) * k), g = Math.min(255, ((n >> 8) & 255) * k), b = Math.min(255, (n & 255) * k);
  return 'rgb(' + (r | 0) + ',' + (g | 0) + ',' + (b | 0) + ')';
}
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();

const N = 14;
const seeds = Array.from({ length: N }, (_, i) => ({
  x: Math.random() * w, y: Math.random() * h,
  vx: (Math.random() - 0.5) * 14, vy: (Math.random() - 0.5) * 14,
  c: shade(palette[i % palette.length], 0.55 + 0.45 * Math.random()),
}));
const STEP = 9;
function draw() {
  for (let y = 0; y < h; y += STEP) {
    for (let x = 0; x < w; x += STEP) {
      let best = 0, bd = Infinity;
      for (let i = 0; i < seeds.length; i++) {
        const s = seeds[i], d = (s.x - x) * (s.x - x) + (s.y - y) * (s.y - y);
        if (d < bd) { bd = d; best = i; }
      }
      ctx.fillStyle = seeds[best].c;
      ctx.fillRect(x, y, STEP + 1, STEP + 1);
    }
  }
  ctx.fillStyle = 'rgba(13,13,18,0.85)';
  for (const s of seeds) { ctx.beginPath(); ctx.arc(s.x, s.y, 3, 0, 7); ctx.fill(); }
}
function step(dt) {
  for (const s of seeds) {
    s.x += s.vx * dt; s.y += s.vy * dt;
    if (s.x < 0 || s.x > w) s.vx *= -1;
    if (s.y < 0 || s.y > h) s.vy *= -1;
    s.x = Math.max(0, Math.min(w, s.x)); s.y = Math.max(0, Math.min(h, s.y));
  }
}
draw(); // 초기 상태부터 바로 보이게
(function loop() { step(0.03); draw(); requestAnimationFrame(loop); })();

씨앗점을 몇 개 흩뿌린 뒤, 화면의 모든 픽셀을 "어느 씨앗점이 가장 가까운가"로 칠하면 됩니다. 그러면 각 씨앗점 주변에 다각형 모양의 세력권이 생기고, 그 경계는 두 씨앗점을 잇는 선분의 수직이등분선이 됩니다. 러시아 수학자 게오르기 보로노이가 1908년 일반화했지만, 씨앗점 사이의 "세력권"이라는 개념 자체는 그보다 훨씬 오래됐습니다(1854년 존 스노우가 런던 콜레라 발병지를 급수 펌프별로 나눈 지도가 유명한 초기 사례).

이 데모는 매 프레임 화면을 낮은 해상도 격자로 나눠 각 격자점에서 가장 가까운 씨앗까지 거리를 전수 비교하는 방식(브루트포스)으로 계산합니다. 실무에서는 격자점이 많을 때 델라우네이 삼각분할의 쌍대 그래프로 훨씬 빠르게 구합니다.

지도 위 상권 분할, 유기적인 타일 패턴, 로고의 조각난 배경 등에 씁니다. 씨앗점을 천천히 움직이면 경계선이 스스로 재배열되는 모습 자체가 하나의 장식이 됩니다.

언제 쓰나

유기적인 분할 패턴, 지도형 인포그래픽, 세포 질감의 배경이 필요할 때. 점이 많으면 델라우네이 기반 계산으로 바꾸세요.