// 2D Supershape — Gielis superformula rendered as a stroked parametric polyline
//
// r(φ) = ( |cos(m·φ/4)/a|^n2 + |sin(m·φ/4)/b|^n3 )^(-1/n1)
//
// Six parameters control shape; m=6,n1=0.3 → flower; m=12,n1=10 → polygon.
export const PARAMS = {
m: { value: 6.0, min: 0, max: 16, step: 0.1, label: "Symmetry (m)", folder: "Shape" },
n1: { value: 0.3, min: 0.05, max: 5, step: 0.01, label: "n1 (envelope)", folder: "Shape" },
n2: { value: 0.3, min: 0.05, max: 5, step: 0.01, label: "n2", folder: "Shape" },
n3: { value: 0.3, min: 0.05, max: 5, step: 0.01, label: "n3", folder: "Shape" },
a: { value: 1.0, min: 0.1, max: 3, step: 0.01, label: "a", folder: "Shape" },
b: { value: 1.0, min: 0.1, max: 3, step: 0.01, label: "b", folder: "Shape" },
resolution:{ value: 800, min: 100, max: 2000, step: 50, label: "Vertices", folder: "Performance" },
scale: { value: 0.4, min: 0.1, max: 0.9, step: 0.01, label: "Scale (% of canvas)", folder: "Appearance" },
hueA: { value: 200, min: 0, max: 360, step: 1, label: "Stroke Hue Start", folder: "Appearance" },
hueB: { value: 320, min: 0, max: 360, step: 1, label: "Stroke Hue End", folder: "Appearance" },
bg: { value: "#0a0a0a", type: "color", label: "Background", folder: "Appearance" },
strokeW: { value: 1.5, min: 0.5, max: 6, step: 0.1, label: "Stroke Width", folder: "Appearance" },
};
export const SHARE = {
bookmarked: ["m", "n1", "n2", "n3", "hueA", "hueB"],
};
// --- Oklch helpers (inline, no deps) ---
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)];
}
// Parse "#rrggbb" → [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),
];
}
// --- Superformula ---
function superformula(phi, m, n1, n2, n3, a, b) {
const t1 = Math.abs(Math.cos(m * phi / 4) / a);
const t2 = Math.abs(Math.sin(m * phi / 4) / b);
const base = Math.pow(t1, n2) + Math.pow(t2, n3);
// Guard against base === 0 (e.g. m=0)
if (base === 0) return 1;
return Math.pow(base, -1 / n1);
}
// --- Sketch API ---
export function sketchSetup(ctx, w, h, tng) {
return {};
}
export function sketchDraw(ctx, w, h, params, tng) {
// 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;
const maxR = Math.min(w, h) * params.scale;
const steps = params.resolution | 0;
const { m, n1, n2, n3, a, b } = params;
// Pre-compute all vertices
const xs = new Float64Array(steps + 1);
const ys = new Float64Array(steps + 1);
for (let i = 0; i <= steps; i++) {
const phi = (i / steps) * Math.PI * 2;
const r = superformula(phi, m, n1, n2, n3, a, b) * maxR;
xs[i] = cx + r * Math.cos(phi);
ys[i] = cy + r * Math.sin(phi);
}
// Draw segment-by-segment with hue gradient in Oklch
ctx.lineWidth = params.strokeW;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
const chroma = 0.18;
const lightness = 0.65;
for (let i = 0; i < steps; i++) {
const t = i / (steps - 1);
// Interpolate hue linearly in degrees (wrapping handled by modulo in oklch)
const hue = params.hueA + (params.hueB - params.hueA) * t;
const [r, g, bv] = oklchToRgb255(lightness, chroma, ((hue % 360) + 360) % 360);
ctx.beginPath();
ctx.strokeStyle = `rgb(${r},${g},${bv})`;
ctx.moveTo(xs[i], ys[i]);
ctx.lineTo(xs[i + 1], ys[i + 1]);
ctx.stroke();
}
}