Differential growth

디퍼렌셜 그로스

A closed loop of points that repel each other while staying tied to their neighbours, splitting long edges as it goes — the simulation behind coral- and brain-fold-like growth.

Also known as: Differential line growth
···
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 ACCENT2 = cs.getPropertyValue('--accent-2').trim() || '#f25c8a';
let w, h, nodes;
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);
  const cx = w / 2, cy = h / 2, R = Math.min(w, h) * 0.12;
  nodes = Array.from({ length: 24 }, (_, i) => { const a = (i / 24) * Math.PI * 2; return { x: cx + Math.cos(a) * R, y: cy + Math.sin(a) * R }; });
}
addEventListener('resize', resize);
resize();

const MAX_NODES = 240, REPEL_R = 13, MAX_EDGE = 11;
function step() {
  const n = nodes.length;
  const fx = new Float32Array(n), fy = new Float32Array(n);
  for (let i = 0; i < n; i++) {
    for (let j = i + 1; j < n; j++) {
      const dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;
      const d2 = dx * dx + dy * dy;
      if (d2 < REPEL_R * REPEL_R && d2 > 0.0001) {
        const d = Math.sqrt(d2), f = (REPEL_R - d) / d * 0.5;
        fx[i] -= dx * f; fy[i] -= dy * f; fx[j] += dx * f; fy[j] += dy * f;
      }
    }
  }
  for (let i = 0; i < n; i++) {
    const prev = nodes[(i - 1 + n) % n], next = nodes[(i + 1) % n];
    fx[i] += (prev.x + next.x - 2 * nodes[i].x) * 0.22;
    fy[i] += (prev.y + next.y - 2 * nodes[i].y) * 0.22;
  }
  for (let i = 0; i < n; i++) { nodes[i].x += fx[i]; nodes[i].y += fy[i]; }
  if (n < MAX_NODES) {
    const grown = [];
    for (let i = 0; i < n; i++) {
      grown.push(nodes[i]);
      const next = nodes[(i + 1) % n];
      if (Math.hypot(next.x - nodes[i].x, next.y - nodes[i].y) > MAX_EDGE) grown.push({ x: (nodes[i].x + next.x) / 2, y: (nodes[i].y + next.y) / 2 });
    }
    nodes = grown;
  }
}
function draw() {
  ctx.fillStyle = '#0d0d12'; ctx.fillRect(0, 0, w, h);
  ctx.strokeStyle = ACCENT2; ctx.lineWidth = 1.6; ctx.beginPath();
  ctx.moveTo(nodes[0].x, nodes[0].y);
  for (let i = 1; i <= nodes.length; i++) { const p = nodes[i % nodes.length]; ctx.lineTo(p.x, p.y); }
  ctx.stroke();
}
for (let i = 0; i < 130; i++) step(); // 미리 자라게 해서 처음부터 주름이 보이도록
draw();
(function loop() { step(); draw(); requestAnimationFrame(loop); })();

Start from a small ring of points forming a closed loop. Each step applies two forces at once: nearby points repel each other (so they don't bunch up), while points adjacent along the curve pull toward each other (so the line stays smooth and unbroken). One more rule is added on top: whenever the gap between two neighbouring points exceeds a threshold, a new point is inserted between them.

Repeat this "push apart while growing" long enough and the ring can no longer stay round — it starts folding into wrinkles. The curve's length (its points) keeps increasing while the space it occupies stays bounded, so packing more line into the same area forces it to crumple. That's why coral surfaces, brain folds and leaf edges get their shape through a related mechanism — creative coders reach for it specifically because it mimics that biological growth pattern.

Use it for organic poster outlines, coral- or brain-fold-style textures, and logo animations that should feel like they're growing on their own. Since the point count only ever increases, cap it or the simulation keeps slowing down.

When to use

Use it when an outline or texture needs to feel like it is organically growing. Balance the point cap and repulsion radius together to keep performance and density in check.