About this technique →
ifs-attractor/sketch.js
// IFS Attractor — Chaos Game
//
// Pick a random affine transform from the set (weighted by probability),
// apply it to the current point, and plot the result. After millions of
// iterations the attractor emerges from the accumulated points.
//
// Each transform: x' = a*x + b*y + e,  y' = c*x + d*y + f

export const PARAMS = {
  preset:     { value: "Barnsley Fern", options: ["Barnsley Fern", "Sierpinski Triangle", "Heighway Dragon", "Maple Leaf"], label: "IFS Preset", folder: "Structure", rebuildOnChange: true },
  iterations: { value: 100000, min: 5000, max: 500000, step: 5000, label: "Iterations",    folder: "Performance" },
  hue:        { value: 120,    min: 0,    max: 360,    step: 1,    label: "Base Hue",      folder: "Appearance" },
  alpha:      { value: 0.05,   min: 0.01, max: 0.5,   step: 0.01, label: "Point Alpha",   folder: "Appearance" },
  scale:      { value: 0.7,    min: 0.2,  max: 1.2,   step: 0.01, label: "Scale (% canvas)", folder: "Appearance" },
};

// Each transform: [prob, a, b, c, d, e, f]
const PRESETS = {
  "Barnsley Fern": [
    [0.01,  0,      0,     0,    0.16,  0,    0   ],
    [0.85,  0.85,   0.04, -0.04, 0.85,  0,    1.60],
    [0.07,  0.20,  -0.26,  0.23, 0.22,  0,    1.60],
    [0.07, -0.15,   0.28,  0.26, 0.24,  0,    0.44],
  ],
  "Sierpinski Triangle": [
    [0.333, 0.5, 0,   0,   0.5,  0,    0   ],
    [0.333, 0.5, 0,   0,   0.5,  0.5,  0   ],
    [0.334, 0.5, 0,   0,   0.5,  0.25, 0.5 ],
  ],
  "Heighway Dragon": [
    [0.5,  0.5,  0.5, -0.5,  0.5, 0, 0],
    [0.5,  0.5, -0.5,  0.5, -0.5, 1, 0],
  ],
  "Maple Leaf": [
    [0.25,  0.14,  0.01, 0.00,  0.51, -0.08,  -1.31],
    [0.25,  0.43,  0.52,-0.45,  0.50,  1.49,  -0.75],
    [0.25,  0.45, -0.49, 0.47,  0.47, -1.62,  -0.74],
    [0.25,  0.49,  0.00, 0.00,  0.51,  0.02,   1.62],
  ],
};

// Bounding box info per preset (xmin, xmax, ymin, ymax) for scaling
const BOUNDS = {
  "Barnsley Fern":        [-2.18, 2.66, 0,  10  ],
  "Sierpinski Triangle":  [ 0,    1,    0,   1  ],
  "Heighway Dragon":      [-0.6,  1.4, -0.4, 1.0],
  "Maple Leaf":           [-2.5,  2.5, -2.5, 2.5],
};

export function sketchSetup(p, w, h) {
  p.colorMode(p.HSB, 360, 100, 100, 1);
  p.background(0);
  p.noStroke();
  return {};
}

export function sketchDraw(p, w, h, params) {
  p.background(0);

  const transforms = PRESETS[params.preset] ?? PRESETS["Barnsley Fern"];
  const [xmin, xmax, ymin, ymax] = BOUNDS[params.preset] ?? [-2.5, 2.5, -2.5, 2.5];
  const iters = params.iterations | 0;

  // Build cumulative probability array
  const cumProb = [];
  let acc = 0;
  for (const t of transforms) {
    acc += t[0];
    cumProb.push(acc);
  }

  // Fitting scale: map attractor bbox to canvas * scale param
  const sceneW = xmax - xmin;
  const sceneH = ymax - ymin;
  const margin = 1 - params.scale;
  const fitScale = Math.min(
    (w * params.scale) / sceneW,
    (h * params.scale) / sceneH,
  );
  const offX = w / 2 - (xmin + sceneW / 2) * fitScale;
  const offY = h / 2 + (ymin + sceneH / 2) * fitScale;

  let x = 0, y = 0;

  // Warm-up iterations (not plotted)
  for (let i = 0; i < 20; i++) {
    const r = Math.random();
    let t = transforms[0];
    for (let j = 0; j < cumProb.length; j++) {
      if (r < cumProb[j]) { t = transforms[j]; break; }
    }
    const nx = t[1] * x + t[2] * y + t[5];
    const ny = t[3] * x + t[4] * y + t[6];
    x = nx; y = ny;
  }

  // Plot points
  for (let i = 0; i < iters; i++) {
    const rnd = Math.random();
    let t = transforms[0];
    let tIdx = 0;
    for (let j = 0; j < cumProb.length; j++) {
      if (rnd < cumProb[j]) { t = transforms[j]; tIdx = j; break; }
    }
    const nx = t[1] * x + t[2] * y + t[5];
    const ny = t[3] * x + t[4] * y + t[6];
    x = nx; y = ny;

    const sx = offX + x * fitScale;
    const sy = offY - y * fitScale;

    if (sx >= 0 && sx < w && sy >= 0 && sy < h) {
      const hShift = (tIdx / transforms.length) * 60;
      const h2 = ((params.hue + hShift) % 360 + 360) % 360;
      p.fill(h2, 70, 90, params.alpha);
      p.rect(sx, sy, 1, 1);
    }
  }
}