// Recursive Polygons — nested twisted polygons in Oklch hue space
//
// Each level draws a filled polygon, then recurses with a scaled,
// rotated copy — building a depth tunnel of color-shifted geometry.
export const PARAMS = {
sides: { value: 5, min: 3, max: 12, step: 1, label: "Polygon Sides", folder: "Structure" },
depth: { value: 30, min: 5, max: 80, step: 1, label: "Recursion Depth", folder: "Structure" },
scale: { value: 0.92, min: 0.7, max: 0.99, step: 0.005, label: "Scale per Level", folder: "Structure" },
twist: { value: 6, min: -45, max: 45, step: 0.5, label: "Twist Per Level (°)", folder: "Structure" },
hue: { value: 200, min: 0, max: 360, step: 1, label: "Start Hue", folder: "Appearance" },
hueShift: { value: 8, min: -30, max: 30, step: 0.5, label: "Hue Shift Per Level", folder: "Appearance" },
lightness:{ value: 0.55, min: 0.1, max: 0.9, step: 0.01, label: "Lightness", folder: "Appearance" },
bg: { value: "#0a0a0a", type: "color", label: "Background", folder: "Appearance" },
};
export const SHARE = {
bookmarked: ["sides", "depth", "scale", "twist", "hue", "hueShift"],
};
// Parse hex color to [r,g,b] 0-255
function hexToRgb255(hex) {
const h = hex.replace('#', '');
return [
parseInt(h.substring(0, 2), 16),
parseInt(h.substring(2, 4), 16),
parseInt(h.substring(4, 6), 16),
];
}
// Oklch color conversions (inline — no tng dependency needed)
function srgbToLinear(c) {
return c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}
function linearToSrgb(c) {
return Math.max(0, Math.min(1, c < 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055));
}
function oklchToRgb255(L, C, hDeg) {
const h = (hDeg * Math.PI) / 180;
const a = C * Math.cos(h);
const b = C * Math.sin(h);
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
const lc = l_ ** 3, mc = m_ ** 3, sc = s_ ** 3;
const r = linearToSrgb(4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc);
const g = linearToSrgb(-1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc);
const bv = linearToSrgb(-0.0041960863 * lc - 0.7034186147 * mc + 1.707614701 * sc);
return [Math.round(r * 255), Math.round(g * 255), Math.round(bv * 255)];
}
// Compute polygon vertices given center, radius, angle offset (radians)
function polygonVerts(cx, cy, r, sides, angleOffset) {
const pts = [];
for (let i = 0; i < sides; i++) {
const a = angleOffset + (i / sides) * Math.PI * 2;
pts.push([cx + r * Math.cos(a), cy + r * Math.sin(a)]);
}
return pts;
}
// Draw a filled polygon using canvas 2D path
function fillPolygon(ctx, verts, r, g, b) {
ctx.beginPath();
ctx.moveTo(verts[0][0], verts[0][1]);
for (let i = 1; i < verts.length; i++) {
ctx.lineTo(verts[i][0], verts[i][1]);
}
ctx.closePath();
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.fill();
}
export function sketchSetup(ctx, w, h, tng) {
return {};
}
export function sketchDraw(ctx, w, h, params, tng) {
// Fill background
const bgRgb = hexToRgb255(params.bg);
ctx.fillStyle = `rgb(${bgRgb[0]},${bgRgb[1]},${bgRgb[2]})`;
ctx.fillRect(0, 0, w, h);
const cx = w / 2;
const cy = h / 2;
// Start radius: inset slightly from canvas edge
const startRadius = Math.min(w, h) * 0.47;
const twistRad = (params.twist * Math.PI) / 180;
// Orient first polygon pointing up
const startAngle = -Math.PI / 2;
// Chroma for Oklch — moderate saturation for vivid but not garish colors
const chroma = 0.18;
// Draw from outermost to innermost so each level covers what's beneath
for (let level = 0; level < params.depth; level++) {
const r = startRadius * Math.pow(params.scale, level);
if (r < 0.5) break;
const angle = startAngle + twistRad * level;
const hue = ((params.hue + params.hueShift * level) % 360 + 360) % 360;
const [rr, rg, rb] = oklchToRgb255(params.lightness, chroma, hue);
const verts = polygonVerts(cx, cy, r, params.sides, angle);
fillPolygon(ctx, verts, rr, rg, rb);
}
}