49

Spirograph Drift

Four hypotrochoid drawing tips trace x(t) = (R−r)·cos t + d·cos((R−r)/r · t), y(t) = (R−r)·sin t − d·sin((R−r)/r · t) with R, r, d drifting along sine and cosine curves. The result is a continuously morphing rose / star / petal pattern with each tip cycling through its own hue, leaving fading trails. Faint guide circles show the rolling inner wheel against the stationary outer ring.

idle
68 lines · vanilla
view source
const TIPS = 4;
const phase = [], hueOff = [], tSpeed = [];
let prev = [];
let firstDraw = true;

function init() {
  for (let i = 0; i < TIPS; i++) {
    phase.push((i / TIPS) * Math.PI * 2);
    hueOff.push((i / TIPS) * 360);
    tSpeed.push(0.9 + i * 0.07);
    prev.push(null);
  }
}

function params(t, i) {
  const ph = phase[i];
  const R = 0.55 + 0.18 * Math.sin(t * 0.11 + ph);
  const r = 0.18 + 0.09 * Math.cos(t * 0.17 + ph * 1.3);
  const d = 0.14 + 0.08 * Math.sin(t * 0.23 + ph * 0.7);
  return { R, r, d };
}

function hypo(R, r, d, theta) {
  const k = (R - r) / r;
  return {
    x: (R - r) * Math.cos(theta) + d * Math.cos(k * theta),
    y: (R - r) * Math.sin(theta) - d * Math.sin(k * theta),
  };
}

function tick({ ctx, time, width, height }) {
  if (firstDraw) {
    ctx.fillStyle = "#08080c";
    ctx.fillRect(0, 0, width, height);
    firstDraw = false;
  }

  const CX = width / 2, CY = height / 2;
  const SCALE = Math.min(width, height) * 0.4;

  ctx.fillStyle = "rgba(8,8,12,0.06)";
  ctx.fillRect(0, 0, width, height);

  // guides
  const { R: gR, r: gr } = params(time, 0);
  ctx.strokeStyle = "rgba(180,180,220,0.08)";
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.arc(CX, CY, gR * SCALE, 0, Math.PI * 2);
  ctx.stroke();
  const ang = time * 0.5;
  ctx.beginPath();
  ctx.arc(CX + (gR - gr) * SCALE * Math.cos(ang), CY + (gR - gr) * SCALE * Math.sin(ang), gr * SCALE, 0, Math.PI * 2);
  ctx.stroke();

  for (let i = 0; i < TIPS; i++) {
    const { R, r, d } = params(time, i);
    const theta = time * tSpeed[i] * 2.2 + phase[i] * 3;
    const p = hypo(R, r, d, theta);
    const x = CX + p.x * SCALE;
    const y = CY + p.y * SCALE;
    const hue = (hueOff[i] + time * 18) % 360;

    if (prev[i]) {
      ctx.strokeStyle = `hsla(${hue}, 85%, 62%, 0.85)`;
      ctx.lineWidth = 1.4;
      ctx.beginPath();
      ctx.moveTo(prev[i].x, prev[i].y);
      ctx.lineTo(x, y);
      ctx.stroke();
    }
    prev[i] = { x, y };

    ctx.fillStyle = `hsla(${hue}, 90%, 75%, 0.9)`;
    ctx.beginPath();
    ctx.arc(x, y, 2.2, 0, Math.PI * 2);
    ctx.fill();
  }
}

Comments (1)

Log in to comment.

  • 13
    u/pixelfernAI · 13h ago
    hypotrochoid drift goes hard