// Mandelbrot Set
//
// For each pixel, map screen coordinates to complex plane, iterate z = z² + c
// starting at z = 0. Color by smooth iteration count using log-log escape.
export const PARAMS = {
centerX: { value: -0.5, min: -2, max: 1, step: 0.0001, label: "Center X", folder: "View" },
centerY: { value: 0, min: -1.5, max: 1.5, step: 0.0001, label: "Center Y", folder: "View" },
zoom: { value: 1.0, min: 0.1, max: 100, step: 0.001, label: "Zoom", folder: "View" },
iterations: { value: 200, min: 50, max: 1000, step: 10, label: "Max Iterations", folder: "Performance" },
step: { value: 2, options: ["1", "2", "3", "4"], label: "Pixel Step", folder: "Performance", rebuildOnChange: true },
hue: { value: 200, min: 0, max: 360, step: 1, label: "Base Hue", folder: "Appearance" },
hueSpread: { value: 180, min: 0, max: 360, step: 1, label: "Hue Spread", folder: "Appearance" },
};
// HSL to RGB (h: 0-360, s/l: 0-1) → [r,g,b] 0-255
function hslToRgb(h, s, l) {
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return [
Math.round((r + m) * 255),
Math.round((g + m) * 255),
Math.round((b + m) * 255),
];
}
export function sketchSetup(p, w, h) {
p.pixelDensity(1);
return {};
}
export function sketchDraw(p, w, h, params) {
const { centerX, centerY, zoom, iterations, hue, hueSpread } = params;
const step = parseInt(params.step, 10) || 2;
const maxIter = iterations | 0;
// Scale: at zoom=1 the full width spans about 3.5 units
const scale = 3.5 / (zoom * w);
p.pixelDensity(1);
p.loadPixels();
for (let py = 0; py < h; py += step) {
for (let px = 0; px < w; px += step) {
// Map pixel to complex coordinate
const cr = (px - w / 2) * scale + centerX;
const ci = (py - h / 2) * scale + centerY;
let zr = 0.0, zi = 0.0;
let n = 0;
while (n < maxIter) {
const zr2 = zr * zr;
const zi2 = zi * zi;
if (zr2 + zi2 > 4.0) break;
zi = 2 * zr * zi + ci;
zr = zr2 - zi2 + cr;
n++;
}
let r, g, b;
if (n === maxIter) {
r = 0; g = 0; b = 0;
} else {
// Smooth iteration count
const log2 = Math.log(2);
const absZ = Math.sqrt(zr * zr + zi * zi);
const smooth = n + 1 - Math.log(Math.log(absZ) / log2) / log2;
const t = (smooth / maxIter) * hueSpread;
const h2 = ((hue + t * 3) % 360 + 360) % 360;
const sat = 0.75;
const lit = 0.45 + 0.2 * Math.sin(smooth * 0.3);
[r, g, b] = hslToRgb(h2, sat, Math.min(0.85, Math.max(0.05, lit)));
}
// Fill pixel block
for (let dy = 0; dy < step && py + dy < h; dy++) {
for (let dx = 0; dx < step && px + dx < w; dx++) {
const idx = ((py + dy) * w + (px + dx)) * 4;
p.pixels[idx] = r;
p.pixels[idx + 1] = g;
p.pixels[idx + 2] = b;
p.pixels[idx + 3] = 255;
}
}
}
}
p.updatePixels();
}