About this technique →
clifford/sketch.js
// Clifford Attractor
//
// x(n+1) = sin(a·y) + c·cos(a·x)
// y(n+1) = sin(b·x) + d·cos(b·y)

export const WARMUP = { framesBeforeReady: 240 };

export const PARAMS = {
  a:          { value: -1.4, min: -3, max: 3, step: 0.01, label: "a", folder: "Dynamics" },
  b:          { value:  1.6, min: -3, max: 3, step: 0.01, label: "b", folder: "Dynamics" },
  c:          { value:  1.0, min: -3, max: 3, step: 0.01, label: "c", folder: "Dynamics" },
  d:          { value:  0.7, min: -3, max: 3, step: 0.01, label: "d", folder: "Dynamics" },
  iterations: { value: 8000, min: 1000, max: 30000, step: 500, label: "Points/Frame", folder: "Performance" },
  hue:        { value: 200, min: 0, max: 360, step: 1, label: "Base Hue", folder: "Appearance" },
  hueSpread:  { value: 60, min: 0, max: 200, step: 1, label: "Hue Spread", folder: "Appearance" },
  scale:      { value: 200, min: 50, max: 500, step: 1, label: "Scale", folder: "Appearance" },
  fade:       { value: 0.0, min: 0, max: 0.2, step: 0.005, label: "Background Fade", folder: "Appearance" },
};

export const SHARE = {
  bookmarked: ["a", "b", "c", "d", "iterations", "hue", "hueSpread", "scale", "fade"],
};

let state;

export function sketchSetup(p, w, h) {
  p.colorMode(p.HSB, 360, 100, 100, 1);
  p.background(0);
  p.noStroke();
  state = {
    x: 0.1,
    y: 0.1,
    prevParams: null,
  };
  return state;
}

export function sketchDraw(p, w, h, params) {
  const { a, b, c, d } = params;

  // Detect parameter change — reset if dynamics changed
  const prev = state.prevParams;
  if (prev && (prev.a !== a || prev.b !== b || prev.c !== c || prev.d !== d)) {
    p.background(0);
    state.x = 0.1;
    state.y = 0.1;
  }
  state.prevParams = { a, b, c, d };

  // Optional fade effect
  if (params.fade > 0) {
    p.fill(0, 0, 0, params.fade);
    p.rect(0, 0, w, h);
  }

  const cx = w / 2;
  const cy = h / 2;
  const sc = params.scale;

  let x = state.x;
  let y = state.y;
  const n = params.iterations;

  for (let i = 0; i < n; i++) {
    const nx = Math.sin(a * y) + c * Math.cos(a * x);
    const ny = Math.sin(b * x) + d * Math.cos(b * y);
    x = nx;
    y = ny;

    const px = cx + x * sc;
    const py = cy + y * sc;

    // Color by angle from origin in attractor space — gives spatial color variation
    const angle = Math.atan2(y, x);
    const hue = ((params.hue + (angle / Math.PI) * params.hueSpread) % 360 + 360) % 360;

    p.fill(hue, 80, 95, 0.05);
    p.rect(px, py, 1, 1);
  }

  state.x = x;
  state.y = y;
}