// Curl-Noise Flow
//
// Compute the curl of a 2D Perlin noise field N(x,y):
// curl_x = ∂N/∂y ≈ (N(x, y+ε) - N(x, y-ε)) / 2ε
// curl_y = -∂N/∂x ≈ -(N(x+ε, y) - N(x-ε, y)) / 2ε
//
// The resulting vector field is divergence-free — particles circulate
// but never converge or diverge, producing fluid eddies and spirals.
export const WARMUP = { framesBeforeReady: 240 };
export const PARAMS = {
particles: { value: 2000, min: 100, max: 8000, step: 100, label: "Particle Count", folder: "Structure", rebuildOnChange: true },
noiseScale: { value: 0.003, min: 0.0005, max: 0.02, step: 0.0001, label: "Field Frequency", folder: "Behavior" },
speed: { value: 1.5, min: 0.1, max: 5, step: 0.05, label: "Speed", folder: "Behavior" },
trail: { value: 0.04, min: 0, max: 0.3, step: 0.005, label: "Background Fade", folder: "Appearance" },
hue: { value: 200, min: 0, max: 360, step: 1, label: "Base Hue", folder: "Appearance" },
hueSpread: { value: 80, min: 0, max: 200, step: 1, label: "Hue Spread", folder: "Appearance" },
};
export const SHARE = {
bookmarked: ["particles", "noiseScale", "speed", "trail", "hue", "hueSpread"],
};
const EPS = 1.0; // finite-difference epsilon in noise-space pixels
let state;
export function sketchSetup(p, w, h, params) {
p.colorMode(p.HSB, 360, 100, 100, 1);
p.background(0);
const particles = [];
for (let i = 0; i < params.particles; i++) {
particles.push({
x: p.random(w),
y: p.random(h),
});
}
state = { particles, w, h };
return state;
}
export function sketchDraw(p, w, h, params) {
// Fade previous frame — low alpha creates trail effect
p.noStroke();
p.fill(0, 0, 0, params.trail);
p.rect(0, 0, w, h);
const s = params.noiseScale;
const speed = params.speed;
p.strokeWeight(1.2);
for (const pt of state.particles) {
const px = pt.x;
const py = pt.y;
// Sample noise for curl approximation
const nx = px * s;
const ny = py * s;
// ∂N/∂y — vary y
const dny = (p.noise(nx, ny + EPS * s) - p.noise(nx, ny - EPS * s)) / (2 * EPS * s);
// ∂N/∂x — vary x
const dnx = (p.noise(nx + EPS * s, ny) - p.noise(nx - EPS * s, ny)) / (2 * EPS * s);
// Curl: (∂N/∂y, -∂N/∂x)
const vx = dny;
const vy = -dnx;
// Advance particle
const nx2 = px + vx * speed;
const ny2 = py + vy * speed;
// Color by angle of velocity vector for variety
const angle = Math.atan2(vy, vx); // -PI..PI
const hue = ((params.hue + (angle / Math.PI) * params.hueSpread) % 360 + 360) % 360;
p.stroke(hue, 75, 90, 0.6);
p.line(px, py, nx2, ny2);
// Wrap around canvas edges
pt.x = ((nx2 % w) + w) % w;
pt.y = ((ny2 % h) + h) % h;
}
}