Sync all skills and memories 2026-04-14 07:27

This commit is contained in:
2026-04-14 07:27:20 +09:00
parent 516bb44fe6
commit 1eba2bca95
386 changed files with 167655 additions and 0 deletions

View File

@@ -0,0 +1,439 @@
# Animation
## Frame-Based Animation
### The Draw Loop
```javascript
function draw() {
// Called ~60 times/sec by default
// frameCount — integer, starts at 1
// deltaTime — ms since last frame (use for framerate-independent motion)
// millis() — ms since sketch start
}
```
### Time-Based vs Frame-Based
```javascript
// Frame-based (speed varies with framerate)
x += speed;
// Time-based (consistent speed regardless of framerate)
x += speed * (deltaTime / 16.67); // normalized to 60fps
```
### Normalized Time
```javascript
// Progress from 0 to 1 over N seconds
let duration = 5000; // 5 seconds in ms
let t = constrain(millis() / duration, 0, 1);
// Looping progress (0 → 1 → 0 → 1...)
let period = 3000; // 3 second loop
let t = (millis() % period) / period;
// Ping-pong (0 → 1 → 0 → 1...)
let raw = (millis() % (period * 2)) / period;
let t = raw <= 1 ? raw : 2 - raw;
```
## Easing Functions
### Built-in Lerp
```javascript
// Linear interpolation — smooth but mechanical
let x = lerp(startX, endX, t);
// Map for non-0-1 ranges
let y = map(t, 0, 1, startY, endY);
```
### Common Easing Curves
```javascript
// Ease in (slow start)
function easeInQuad(t) { return t * t; }
function easeInCubic(t) { return t * t * t; }
function easeInExpo(t) { return t === 0 ? 0 : pow(2, 10 * (t - 1)); }
// Ease out (slow end)
function easeOutQuad(t) { return 1 - (1 - t) * (1 - t); }
function easeOutCubic(t) { return 1 - pow(1 - t, 3); }
function easeOutExpo(t) { return t === 1 ? 1 : 1 - pow(2, -10 * t); }
// Ease in-out (slow both ends)
function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2;
}
function easeInOutQuint(t) {
return t < 0.5 ? 16 * t * t * t * t * t : 1 - pow(-2 * t + 2, 5) / 2;
}
// Elastic (spring overshoot)
function easeOutElastic(t) {
if (t === 0 || t === 1) return t;
return pow(2, -10 * t) * sin((t * 10 - 0.75) * (2 * PI / 3)) + 1;
}
// Bounce
function easeOutBounce(t) {
if (t < 1/2.75) return 7.5625 * t * t;
else if (t < 2/2.75) { t -= 1.5/2.75; return 7.5625 * t * t + 0.75; }
else if (t < 2.5/2.75) { t -= 2.25/2.75; return 7.5625 * t * t + 0.9375; }
else { t -= 2.625/2.75; return 7.5625 * t * t + 0.984375; }
}
// Smooth step (Hermite interpolation — great default)
function smoothstep(t) { return t * t * (3 - 2 * t); }
// Smoother step (Ken Perlin)
function smootherstep(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
```
### Applying Easing
```javascript
// Animate from startVal to endVal over duration ms
function easedValue(startVal, endVal, startTime, duration, easeFn) {
let t = constrain((millis() - startTime) / duration, 0, 1);
return lerp(startVal, endVal, easeFn(t));
}
// Usage
let x = easedValue(100, 700, animStartTime, 2000, easeOutCubic);
```
## Spring Physics
More natural than easing — responds to force, overshoots, settles.
```javascript
class Spring {
constructor(value, target, stiffness = 0.1, damping = 0.7) {
this.value = value;
this.target = target;
this.velocity = 0;
this.stiffness = stiffness;
this.damping = damping;
}
update() {
let force = (this.target - this.value) * this.stiffness;
this.velocity += force;
this.velocity *= this.damping;
this.value += this.velocity;
return this.value;
}
setTarget(t) { this.target = t; }
isSettled(threshold = 0.01) {
return abs(this.velocity) < threshold && abs(this.value - this.target) < threshold;
}
}
// Usage
let springX = new Spring(0, 0, 0.08, 0.85);
function draw() {
springX.setTarget(mouseX);
let x = springX.update();
ellipse(x, height/2, 50);
}
```
### 2D Spring
```javascript
class Spring2D {
constructor(x, y) {
this.pos = createVector(x, y);
this.target = createVector(x, y);
this.vel = createVector(0, 0);
this.stiffness = 0.08;
this.damping = 0.85;
}
update() {
let force = p5.Vector.sub(this.target, this.pos).mult(this.stiffness);
this.vel.add(force).mult(this.damping);
this.pos.add(this.vel);
return this.pos;
}
}
```
## State Machines
For complex multi-phase animations.
```javascript
const STATES = { IDLE: 0, ENTER: 1, ACTIVE: 2, EXIT: 3 };
let state = STATES.IDLE;
let stateStart = 0;
function setState(newState) {
state = newState;
stateStart = millis();
}
function stateTime() {
return millis() - stateStart;
}
function draw() {
switch (state) {
case STATES.IDLE:
// waiting...
break;
case STATES.ENTER:
let t = constrain(stateTime() / 1000, 0, 1);
let alpha = easeOutCubic(t) * 255;
// fade in...
if (t >= 1) setState(STATES.ACTIVE);
break;
case STATES.ACTIVE:
// main animation...
break;
case STATES.EXIT:
let t2 = constrain(stateTime() / 500, 0, 1);
// fade out...
if (t2 >= 1) setState(STATES.IDLE);
break;
}
}
```
## Timeline Sequencing
For timed multi-scene animations (motion graphics, title sequences).
```javascript
class Timeline {
constructor() {
this.events = [];
}
at(timeMs, duration, fn) {
this.events.push({ start: timeMs, end: timeMs + duration, fn });
return this;
}
update() {
let now = millis();
for (let e of this.events) {
if (now >= e.start && now < e.end) {
let t = (now - e.start) / (e.end - e.start);
e.fn(t);
}
}
}
}
// Usage
let timeline = new Timeline();
timeline
.at(0, 2000, (t) => {
// Scene 1: title fade in (0-2s)
let alpha = easeOutCubic(t) * 255;
fill(255, alpha);
textSize(48);
text("Hello", width/2, height/2);
})
.at(2000, 1000, (t) => {
// Scene 2: title fade out (2-3s)
let alpha = (1 - easeInCubic(t)) * 255;
fill(255, alpha);
textSize(48);
text("Hello", width/2, height/2);
})
.at(3000, 5000, (t) => {
// Scene 3: main content (3-8s)
renderMainContent(t);
});
function draw() {
background(0);
timeline.update();
}
```
## Noise-Driven Motion
More organic than deterministic animation.
```javascript
// Smooth wandering position
let x = map(noise(frameCount * 0.005, 0), 0, 1, 0, width);
let y = map(noise(0, frameCount * 0.005), 0, 1, 0, height);
// Noise-driven rotation
let angle = noise(frameCount * 0.01) * TWO_PI;
// Noise-driven scale (breathing effect)
let s = map(noise(frameCount * 0.02), 0, 1, 0.8, 1.2);
// Noise-driven color shift
let hue = map(noise(frameCount * 0.003), 0, 1, 0, 360);
```
## Transition Patterns
### Fade In/Out
```javascript
function fadeIn(t) { return constrain(t, 0, 1); }
function fadeOut(t) { return constrain(1 - t, 0, 1); }
```
### Slide
```javascript
function slideIn(t, direction = 'left') {
let et = easeOutCubic(t);
switch (direction) {
case 'left': return lerp(-width, 0, et);
case 'right': return lerp(width, 0, et);
case 'up': return lerp(-height, 0, et);
case 'down': return lerp(height, 0, et);
}
}
```
### Scale Reveal
```javascript
function scaleReveal(t) {
let et = easeOutElastic(constrain(t, 0, 1));
push();
translate(width/2, height/2);
scale(et);
translate(-width/2, -height/2);
// draw content...
pop();
}
```
### Staggered Entry
```javascript
// N elements appear one after another
let staggerDelay = 100; // ms between each
for (let i = 0; i < elements.length; i++) {
let itemStart = baseTime + i * staggerDelay;
let t = constrain((millis() - itemStart) / 500, 0, 1);
let alpha = easeOutCubic(t) * 255;
let yOffset = lerp(30, 0, easeOutCubic(t));
// draw element with alpha and yOffset
}
```
## Recording Deterministic Animations
For frame-perfect export, use frame count instead of millis():
```javascript
const TOTAL_FRAMES = 300; // 10 seconds at 30fps
const FPS = 30;
function draw() {
let t = frameCount / TOTAL_FRAMES; // 0 to 1 over full duration
if (t > 1) { noLoop(); return; }
// Use t for all animation timing — deterministic
renderFrame(t);
// Export
if (CONFIG.recording) {
saveCanvas('frame-' + nf(frameCount, 4), 'png');
}
}
```
## Scene Fade Envelopes (Video)
Every scene in a multi-scene video needs fade-in and fade-out. Hard cuts between visually different generative scenes are jarring.
```javascript
const SCENE_FRAMES = 150; // 5 seconds at 30fps
const FADE = 15; // half-second fade
function draw() {
let lf = frameCount - 1; // 0-indexed local frame
let t = lf / SCENE_FRAMES; // 0..1 normalized progress
// Fade envelope: ramp up at start, ramp down at end
let fade = 1;
if (lf < FADE) fade = lf / FADE;
if (lf > SCENE_FRAMES - FADE) fade = (SCENE_FRAMES - lf) / FADE;
fade = fade * fade * (3 - 2 * fade); // smoothstep for organic feel
// Apply fade to all visual output
// Option 1: multiply alpha values by fade
fill(r, g, b, alpha * fade);
// Option 2: tint entire composited image
tint(255, fade * 255);
image(sceneBuffer, 0, 0);
noTint();
// Option 3: multiply pixel brightness (for pixel-level scenes)
pixels[i] = r * fade;
}
```
## Animating Static Algorithms
Some generative algorithms produce a single static result (attractors, circle packing, Voronoi). In video, static content reads as frozen/broken. Techniques to add motion:
### Progressive Reveal
Expand a mask from center outward to reveal the precomputed result:
```javascript
let revealRadius = easeOutCubic(min(t * 1.5, 1)) * (width * 0.8);
// In the render loop, skip pixels beyond revealRadius from center
let dx = x - width/2, dy = y - height/2;
if (sqrt(dx*dx + dy*dy) > revealRadius) continue;
// Soft edge:
let edgeFade = constrain((revealRadius - dist) / 40, 0, 1);
```
### Parameter Sweep
Slowly change a parameter to show the algorithm evolving:
```javascript
// Attractor with drifting parameters
let a = -1.7 + sin(t * 0.5) * 0.2; // oscillate around base value
let b = 1.3 + cos(t * 0.3) * 0.15;
```
### Slow Camera Motion
Apply subtle zoom or rotation to the final image:
```javascript
push();
translate(width/2, height/2);
scale(1 + t * 0.05); // slow 5% zoom over scene duration
rotate(t * 0.1); // gentle rotation
translate(-width/2, -height/2);
image(precomputedResult, 0, 0);
pop();
```
### Overlay Dynamic Elements
Add particles, grain, or subtle noise on top of static content:
```javascript
// Static background
image(staticResult, 0, 0);
// Dynamic overlay
for (let p of ambientParticles) {
p.update();
p.display(); // slow-moving specks add life
}
```

View File

@@ -0,0 +1,352 @@
# Color Systems
## Color Modes
### HSB (Recommended for Generative Art)
```javascript
colorMode(HSB, 360, 100, 100, 100);
// Hue: 0-360 (color wheel position)
// Saturation: 0-100 (gray to vivid)
// Brightness: 0-100 (black to full)
// Alpha: 0-100
fill(200, 80, 90); // blue, vivid, bright
fill(200, 80, 90, 50); // 50% transparent
```
HSB advantages:
- Rotate hue: `(baseHue + offset) % 360`
- Desaturate: reduce S
- Darken: reduce B
- Monochrome variations: fix H, vary S and B
- Complementary: `(hue + 180) % 360`
- Analogous: `hue +/- 30`
### HSL
```javascript
colorMode(HSL, 360, 100, 100, 100);
// Lightness 50 = pure color, 0 = black, 100 = white
// More intuitive for tints (L > 50) and shades (L < 50)
```
### RGB
```javascript
colorMode(RGB, 255, 255, 255, 255); // default
// Direct channel control, less intuitive for procedural palettes
```
## Color Objects
```javascript
let c = color(200, 80, 90); // create color object
fill(c);
// Extract components
let h = hue(c);
let s = saturation(c);
let b = brightness(c);
let r = red(c);
let g = green(c);
let bl = blue(c);
let a = alpha(c);
// Hex colors work everywhere
fill('#e8d5b7');
fill('#e8d5b7cc'); // with alpha
// Modify via setters
c.setAlpha(128);
c.setRed(200);
```
## Color Interpolation
### lerpColor
```javascript
let c1 = color(0, 80, 100); // red
let c2 = color(200, 80, 100); // blue
let mixed = lerpColor(c1, c2, 0.5); // midpoint blend
// Works in current colorMode
```
### paletteLerp (p5.js 1.11+)
Interpolate through multiple colors at once.
```javascript
let colors = [
color('#2E0854'),
color('#850E35'),
color('#EE6C4D'),
color('#F5E663')
];
let c = paletteLerp(colors, t); // t = 0..1, interpolates through all
```
### Manual Multi-Stop Gradient
```javascript
function multiLerp(colors, t) {
t = constrain(t, 0, 1);
let segment = t * (colors.length - 1);
let idx = floor(segment);
let frac = segment - idx;
idx = min(idx, colors.length - 2);
return lerpColor(colors[idx], colors[idx + 1], frac);
}
```
## Gradient Rendering
### Linear Gradient
```javascript
function linearGradient(x1, y1, x2, y2, c1, c2) {
let steps = dist(x1, y1, x2, y2);
for (let i = 0; i <= steps; i++) {
let t = i / steps;
let c = lerpColor(c1, c2, t);
stroke(c);
let x = lerp(x1, x2, t);
let y = lerp(y1, y2, t);
// Draw perpendicular line at each point
let dx = -(y2 - y1) / steps * 1000;
let dy = (x2 - x1) / steps * 1000;
line(x - dx, y - dy, x + dx, y + dy);
}
}
```
### Radial Gradient
```javascript
function radialGradient(cx, cy, r, innerColor, outerColor) {
noStroke();
for (let i = r; i > 0; i--) {
let t = 1 - i / r;
fill(lerpColor(innerColor, outerColor, t));
ellipse(cx, cy, i * 2);
}
}
```
### Noise-Based Gradient
```javascript
function noiseGradient(colors, noiseScale, time) {
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let n = noise(x * noiseScale, y * noiseScale, time);
let c = multiLerp(colors, n);
let idx = 4 * (y * width + x);
pixels[idx] = red(c);
pixels[idx+1] = green(c);
pixels[idx+2] = blue(c);
pixels[idx+3] = 255;
}
}
updatePixels();
}
```
## Procedural Palette Generation
### Complementary
```javascript
function complementary(baseHue) {
return [baseHue, (baseHue + 180) % 360];
}
```
### Analogous
```javascript
function analogous(baseHue, spread = 30) {
return [
(baseHue - spread + 360) % 360,
baseHue,
(baseHue + spread) % 360
];
}
```
### Triadic
```javascript
function triadic(baseHue) {
return [baseHue, (baseHue + 120) % 360, (baseHue + 240) % 360];
}
```
### Split Complementary
```javascript
function splitComplementary(baseHue) {
return [baseHue, (baseHue + 150) % 360, (baseHue + 210) % 360];
}
```
### Tetradic (Rectangle)
```javascript
function tetradic(baseHue) {
return [baseHue, (baseHue + 60) % 360, (baseHue + 180) % 360, (baseHue + 240) % 360];
}
```
### Monochromatic Variations
```javascript
function monoVariations(hue, count = 5) {
let colors = [];
for (let i = 0; i < count; i++) {
let s = map(i, 0, count - 1, 20, 90);
let b = map(i, 0, count - 1, 95, 40);
colors.push(color(hue, s, b));
}
return colors;
}
```
## Curated Palette Library
### Warm Palettes
```javascript
const SUNSET = ['#2E0854', '#850E35', '#EE6C4D', '#F5E663'];
const EMBER = ['#1a0000', '#4a0000', '#8b2500', '#cd5c00', '#ffd700'];
const PEACH = ['#fff5eb', '#ffdab9', '#ff9a76', '#ff6b6b', '#c94c4c'];
const COPPER = ['#1c1108', '#3d2b1f', '#7b4b2a', '#b87333', '#daa06d'];
```
### Cool Palettes
```javascript
const OCEAN = ['#0a0e27', '#1a1b4b', '#2a4a7f', '#3d7cb8', '#87ceeb'];
const ARCTIC = ['#0d1b2a', '#1b263b', '#415a77', '#778da9', '#e0e1dd'];
const FOREST = ['#0b1a0b', '#1a3a1a', '#2d5a2d', '#4a8c4a', '#90c990'];
const DEEP_SEA = ['#000814', '#001d3d', '#003566', '#006d77', '#83c5be'];
```
### Neutral Palettes
```javascript
const GRAPHITE = ['#1a1a1a', '#333333', '#555555', '#888888', '#cccccc'];
const CREAM = ['#f4f0e8', '#e8dcc8', '#c9b99a', '#a89070', '#7a6450'];
const SLATE = ['#1e293b', '#334155', '#475569', '#64748b', '#94a3b8'];
```
### Vivid Palettes
```javascript
const NEON = ['#ff00ff', '#00ffff', '#ff0080', '#80ff00', '#0080ff'];
const RAINBOW = ['#ff0000', '#ff8000', '#ffff00', '#00ff00', '#0000ff', '#8000ff'];
const VAPOR = ['#ff71ce', '#01cdfe', '#05ffa1', '#b967ff', '#fffb96'];
const CYBER = ['#0f0f0f', '#00ff41', '#ff0090', '#00d4ff', '#ffd000'];
```
### Earth Tones
```javascript
const TERRA = ['#2c1810', '#5c3a2a', '#8b6b4a', '#c4a672', '#e8d5b7'];
const MOSS = ['#1a1f16', '#3d4a2e', '#6b7c4f', '#9aab7a', '#c8d4a9'];
const CLAY = ['#3b2f2f', '#6b4c4c', '#9e7676', '#c9a0a0', '#e8caca'];
```
## Blend Modes
```javascript
blendMode(BLEND); // default — alpha compositing
blendMode(ADD); // additive — bright glow effects
blendMode(MULTIPLY); // darkening — shadows, texture overlay
blendMode(SCREEN); // lightening — soft glow
blendMode(OVERLAY); // contrast boost — high/low emphasis
blendMode(DIFFERENCE); // color subtraction — psychedelic
blendMode(EXCLUSION); // softer difference
blendMode(REPLACE); // overwrite (no alpha blending)
blendMode(REMOVE); // subtract alpha
blendMode(LIGHTEST); // keep brighter pixel
blendMode(DARKEST); // keep darker pixel
blendMode(BURN); // darken + saturate
blendMode(DODGE); // lighten + saturate
blendMode(SOFT_LIGHT); // subtle overlay
blendMode(HARD_LIGHT); // strong overlay
// ALWAYS reset after use
blendMode(BLEND);
```
### Blend Mode Recipes
| Effect | Mode | Use case |
|--------|------|----------|
| Additive glow | `ADD` | Light beams, fire, particles |
| Shadow overlay | `MULTIPLY` | Texture, vignette |
| Soft light mix | `SCREEN` | Fog, mist, backlight |
| High contrast | `OVERLAY` | Dramatic compositing |
| Color negative | `DIFFERENCE` | Glitch, psychedelic |
| Layer compositing | `BLEND` | Standard alpha layering |
## Background Techniques
### Textured Background
```javascript
function texturedBackground(baseColor, noiseScale, noiseAmount) {
loadPixels();
let r = red(baseColor), g = green(baseColor), b = blue(baseColor);
for (let i = 0; i < pixels.length; i += 4) {
let x = (i / 4) % width;
let y = floor((i / 4) / width);
let n = (noise(x * noiseScale, y * noiseScale) - 0.5) * noiseAmount;
pixels[i] = constrain(r + n, 0, 255);
pixels[i+1] = constrain(g + n, 0, 255);
pixels[i+2] = constrain(b + n, 0, 255);
pixels[i+3] = 255;
}
updatePixels();
}
```
### Vignette
```javascript
function vignette(strength = 0.5, radius = 0.7) {
loadPixels();
let cx = width / 2, cy = height / 2;
let maxDist = dist(0, 0, cx, cy);
for (let i = 0; i < pixels.length; i += 4) {
let x = (i / 4) % width;
let y = floor((i / 4) / width);
let d = dist(x, y, cx, cy) / maxDist;
let factor = 1.0 - smoothstep(constrain((d - radius) / (1 - radius), 0, 1)) * strength;
pixels[i] *= factor;
pixels[i+1] *= factor;
pixels[i+2] *= factor;
}
updatePixels();
}
function smoothstep(t) { return t * t * (3 - 2 * t); }
```
### Film Grain
```javascript
function filmGrain(amount = 30) {
loadPixels();
for (let i = 0; i < pixels.length; i += 4) {
let grain = random(-amount, amount);
pixels[i] = constrain(pixels[i] + grain, 0, 255);
pixels[i+1] = constrain(pixels[i+1] + grain, 0, 255);
pixels[i+2] = constrain(pixels[i+2] + grain, 0, 255);
}
updatePixels();
}
```

View File

@@ -0,0 +1,410 @@
# Core API Reference
## Canvas Setup
### createCanvas()
```javascript
// 2D (default renderer)
createCanvas(1920, 1080);
// WebGL (3D, shaders)
createCanvas(1920, 1080, WEBGL);
// Responsive
createCanvas(windowWidth, windowHeight);
```
### Pixel Density
High-DPI displays render at 2x by default. This doubles memory usage and halves performance.
```javascript
// Force 1x for consistent export and performance
pixelDensity(1);
// Match display (default) — sharp on retina but expensive
pixelDensity(displayDensity());
// ALWAYS call before createCanvas()
function setup() {
pixelDensity(1); // first
createCanvas(1920, 1080); // second
}
```
For export, always `pixelDensity(1)` and use the exact target resolution. Never rely on device scaling for final output.
### Responsive Resize
```javascript
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Recreate offscreen buffers at new size
bgLayer = createGraphics(width, height);
// Reinitialize any size-dependent state
}
```
## Coordinate System
### P2D (Default)
- Origin: top-left (0, 0)
- X increases rightward
- Y increases downward
- Angles: radians by default, `angleMode(DEGREES)` to switch
### WEBGL
- Origin: center of canvas
- X increases rightward, Y increases **upward**, Z increases toward viewer
- To get P2D-like coordinates in WEBGL: `translate(-width/2, -height/2)`
## Draw Loop
```javascript
function preload() {
// Load assets before setup — fonts, images, JSON, CSV
// Blocks execution until all loads complete
font = loadFont('font.otf');
img = loadImage('texture.png');
data = loadJSON('data.json');
}
function setup() {
// Runs once. Create canvas, initialize state.
createCanvas(1920, 1080);
colorMode(HSB, 360, 100, 100, 100);
randomSeed(CONFIG.seed);
noiseSeed(CONFIG.seed);
}
function draw() {
// Runs every frame (default 60fps).
// Set frameRate(30) in setup() to change.
// Call noLoop() for static sketches (render once).
}
```
### Frame Control
```javascript
frameRate(30); // set target FPS
noLoop(); // stop draw loop (static pieces)
loop(); // restart draw loop
redraw(); // call draw() once (manual refresh)
frameCount // frames since start (integer)
deltaTime // milliseconds since last frame (float)
millis() // milliseconds since sketch started
```
## Transform Stack
Every transform is cumulative. Use `push()`/`pop()` to isolate.
```javascript
push();
translate(width / 2, height / 2);
rotate(angle);
scale(1.5);
// draw something at transformed position
ellipse(0, 0, 100, 100);
pop();
// back to original coordinate system
```
### Transform Functions
| Function | Effect |
|----------|--------|
| `translate(x, y)` | Move origin |
| `rotate(angle)` | Rotate around origin (radians) |
| `scale(s)` / `scale(sx, sy)` | Scale from origin |
| `shearX(angle)` | Skew X axis |
| `shearY(angle)` | Skew Y axis |
| `applyMatrix(a, b, c, d, e, f)` | Arbitrary 2D affine transform |
| `resetMatrix()` | Clear all transforms |
### Composition Pattern: Rotate Around Center
```javascript
push();
translate(cx, cy); // move origin to center
rotate(angle); // rotate around that center
translate(-cx, -cy); // move origin back
// draw at original coordinates, but rotated around (cx, cy)
rect(cx - 50, cy - 50, 100, 100);
pop();
```
## Offscreen Buffers (createGraphics)
Offscreen buffers are separate canvases you can draw to and composite. Essential for:
- **Layered composition** — background, midground, foreground
- **Persistent trails** — draw to buffer, fade with semi-transparent rect, never clear
- **Masking** — draw mask to buffer, apply with `image()` or pixel operations
- **Post-processing** — render scene to buffer, apply effects, draw to main canvas
```javascript
let layer;
function setup() {
createCanvas(1920, 1080);
layer = createGraphics(width, height);
}
function draw() {
// Draw to offscreen buffer
layer.background(0, 10); // semi-transparent clear = trails
layer.fill(255);
layer.ellipse(mouseX, mouseY, 20);
// Composite to main canvas
image(layer, 0, 0);
}
```
### Trail Effect Pattern
```javascript
let trailBuffer;
function setup() {
createCanvas(1920, 1080);
trailBuffer = createGraphics(width, height);
trailBuffer.background(0);
}
function draw() {
// Fade previous frame (lower alpha = longer trails)
trailBuffer.noStroke();
trailBuffer.fill(0, 0, 0, 15); // RGBA — 15/255 alpha
trailBuffer.rect(0, 0, width, height);
// Draw new content
trailBuffer.fill(255);
trailBuffer.ellipse(mouseX, mouseY, 10);
// Show
image(trailBuffer, 0, 0);
}
```
### Multi-Layer Composition
```javascript
let bgLayer, contentLayer, fxLayer;
function setup() {
createCanvas(1920, 1080);
bgLayer = createGraphics(width, height);
contentLayer = createGraphics(width, height);
fxLayer = createGraphics(width, height);
}
function draw() {
// Background — drawn once or slowly evolving
renderBackground(bgLayer);
// Content — main visual elements
contentLayer.clear();
renderContent(contentLayer);
// FX — overlays, vignettes, grain
fxLayer.clear();
renderEffects(fxLayer);
// Composite with blend modes
image(bgLayer, 0, 0);
blendMode(ADD);
image(contentLayer, 0, 0);
blendMode(MULTIPLY);
image(fxLayer, 0, 0);
blendMode(BLEND); // reset
}
```
## Composition Patterns
### Grid Layout
```javascript
let cols = 10, rows = 10;
let cellW = width / cols;
let cellH = height / rows;
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let cx = cellW * (i + 0.5);
let cy = cellH * (j + 0.5);
// draw element at (cx, cy) within cell size (cellW, cellH)
}
}
```
### Radial Layout
```javascript
let n = 12;
for (let i = 0; i < n; i++) {
let angle = TWO_PI * i / n;
let r = 300;
let x = width/2 + cos(angle) * r;
let y = height/2 + sin(angle) * r;
// draw element at (x, y)
}
```
### Golden Ratio Spiral
```javascript
let phi = (1 + sqrt(5)) / 2;
let n = 500;
for (let i = 0; i < n; i++) {
let angle = i * TWO_PI / (phi * phi);
let r = sqrt(i) * 10;
let x = width/2 + cos(angle) * r;
let y = height/2 + sin(angle) * r;
let size = map(i, 0, n, 8, 2);
ellipse(x, y, size);
}
```
### Margin-Aware Composition
```javascript
const MARGIN = 80; // pixels from edge
const drawW = width - 2 * MARGIN;
const drawH = height - 2 * MARGIN;
// Map normalized [0,1] coordinates to drawable area
function mapX(t) { return MARGIN + t * drawW; }
function mapY(t) { return MARGIN + t * drawH; }
```
## Random and Noise
### Seeded Random
```javascript
randomSeed(42);
let x = random(100); // always same value for seed 42
let y = random(-1, 1); // range
let item = random(myArray); // random element
```
### Gaussian Random
```javascript
let x = randomGaussian(0, 1); // mean=0, stddev=1
// Useful for natural-looking distributions
```
### Perlin Noise
```javascript
noiseSeed(42);
noiseDetail(4, 0.5); // 4 octaves, 0.5 falloff
let v = noise(x * 0.01, y * 0.01); // returns 0.0 to 1.0
// Scale factor (0.01) controls feature size — smaller = smoother
```
## Math Utilities
| Function | Description |
|----------|-------------|
| `map(v, lo1, hi1, lo2, hi2)` | Remap value between ranges |
| `constrain(v, lo, hi)` | Clamp to range |
| `lerp(a, b, t)` | Linear interpolation |
| `norm(v, lo, hi)` | Normalize to 0-1 |
| `dist(x1, y1, x2, y2)` | Euclidean distance |
| `mag(x, y)` | Vector magnitude |
| `abs()`, `ceil()`, `floor()`, `round()` | Standard math |
| `sq(n)`, `sqrt(n)`, `pow(b, e)` | Powers |
| `sin()`, `cos()`, `tan()`, `atan2()` | Trig (radians) |
| `degrees(r)`, `radians(d)` | Angle conversion |
| `fract(n)` | Fractional part |
## p5.js 2.0 Changes
p5.js 2.0 (released Apr 2025, current: 2.2) introduces breaking changes. The p5.js editor defaults to 1.x until Aug 2026. Use 2.x only when you need its features.
### async setup() replaces preload()
```javascript
// p5.js 1.x
let img;
function preload() { img = loadImage('cat.jpg'); }
function setup() { createCanvas(800, 800); }
// p5.js 2.x
let img;
async function setup() {
createCanvas(800, 800);
img = await loadImage('cat.jpg');
}
```
### New Color Modes
```javascript
colorMode(OKLCH); // perceptually uniform — better gradients
// L: 0-1 (lightness), C: 0-0.4 (chroma), H: 0-360 (hue)
fill(0.7, 0.15, 200); // medium-bright saturated blue
colorMode(OKLAB); // perceptually uniform, no hue angle
colorMode(HWB); // Hue-Whiteness-Blackness
```
### splineVertex() replaces curveVertex()
No more doubling first/last control points:
```javascript
// p5.js 1.x — must repeat first and last
beginShape();
curveVertex(pts[0].x, pts[0].y); // doubled
for (let p of pts) curveVertex(p.x, p.y);
curveVertex(pts[pts.length-1].x, pts[pts.length-1].y); // doubled
endShape();
// p5.js 2.x — clean
beginShape();
for (let p of pts) splineVertex(p.x, p.y);
endShape();
```
### Shader .modify() API
Modify built-in shaders without writing full GLSL:
```javascript
let myShader = baseMaterialShader().modify({
vertexDeclarations: 'uniform float uTime;',
'vec4 getWorldPosition': `(vec4 pos) {
pos.y += sin(pos.x * 0.1 + uTime) * 20.0;
return pos;
}`
});
```
### Variable Fonts
```javascript
textWeight(700); // dynamic weight without loading multiple files
```
### textToContours() and textToModel()
```javascript
let contours = font.textToContours('HELLO', 0, 0, 200);
// Returns array of contour arrays (closed paths)
let geo = font.textToModel('HELLO', 0, 0, 200);
// Returns p5.Geometry for 3D extruded text
```
### CDN for p5.js 2.x
```html
<script src="https://cdn.jsdelivr.net/npm/p5@2/lib/p5.min.js"></script>
```

View File

@@ -0,0 +1,566 @@
# Export Pipeline
## PNG Export
### In-Sketch (Keyboard Shortcut)
```javascript
function keyPressed() {
if (key === 's' || key === 'S') {
saveCanvas('output', 'png');
// Downloads output.png immediately
}
}
```
### Timed Export (Static Generative)
```javascript
function setup() {
createCanvas(3840, 2160);
pixelDensity(1);
randomSeed(CONFIG.seed);
noiseSeed(CONFIG.seed);
noLoop();
}
function draw() {
// ... render everything ...
saveCanvas('output-seed-' + CONFIG.seed, 'png');
}
```
### High-Resolution Export
For resolutions beyond screen size, use `pixelDensity()` or a large offscreen buffer:
```javascript
function exportHighRes(scale) {
let buffer = createGraphics(width * scale, height * scale);
buffer.scale(scale);
// Re-render everything to buffer at higher resolution
renderScene(buffer);
buffer.save('highres-output.png');
}
```
### Batch Seed Export
```javascript
function exportBatch(startSeed, count) {
for (let i = 0; i < count; i++) {
CONFIG.seed = startSeed + i;
randomSeed(CONFIG.seed);
noiseSeed(CONFIG.seed);
// Render
background(0);
renderScene();
saveCanvas('seed-' + nf(CONFIG.seed, 5), 'png');
}
}
```
## GIF Export
### saveGif()
```javascript
function keyPressed() {
if (key === 'g' || key === 'G') {
saveGif('output', 5);
// Captures 5 seconds of animation
// Options: saveGif(filename, duration, options)
}
}
// With options
saveGif('output', 5, {
delay: 0, // delay before starting capture (seconds)
units: 'seconds' // or 'frames'
});
```
Limitations:
- GIF is 256 colors max — dithering artifacts on gradients
- Large canvases produce huge files
- Use a smaller canvas (640x360) for GIF, higher for PNG/MP4
- Frame rate is approximate
### Optimal GIF Settings
```javascript
// For GIF output, use smaller canvas and lower framerate
function setup() {
createCanvas(640, 360);
frameRate(15); // GIF standard
pixelDensity(1);
}
```
## Frame Sequence Export
### saveFrames()
```javascript
function keyPressed() {
if (key === 'f') {
saveFrames('frame', 'png', 10, 30);
// 10 seconds, 30 fps → 300 PNG files
// Downloads as individual files (browser may block bulk downloads)
}
}
```
### Manual Frame Export (More Control)
```javascript
let recording = false;
let frameNum = 0;
const TOTAL_FRAMES = 300;
function keyPressed() {
if (key === 'r') recording = !recording;
}
function draw() {
// ... render frame ...
if (recording) {
saveCanvas('frame-' + nf(frameNum, 4), 'png');
frameNum++;
if (frameNum >= TOTAL_FRAMES) {
recording = false;
noLoop();
console.log('Recording complete: ' + frameNum + ' frames');
}
}
}
```
### Deterministic Capture (Critical for Video)
The `noLoop()` + `redraw()` pattern is **required** for frame-perfect headless capture. Without it, p5's draw loop runs freely in Chrome while Puppeteer screenshots are slow — the sketch runs ahead and you get duplicate/missing frames.
```javascript
function setup() {
createCanvas(1920, 1080);
pixelDensity(1);
noLoop(); // STOP the automatic draw loop
window._p5Ready = true; // Signal to capture script
}
function draw() {
// This only runs when redraw() is called by the capture script
// frameCount increments exactly once per redraw()
}
```
The bundled `scripts/export-frames.js` detects `window._p5Ready` and switches to deterministic mode automatically. Without it, falls back to timed capture (less precise).
### ffmpeg: Frames to MP4
```bash
# Basic encoding
ffmpeg -framerate 30 -i frame-%04d.png -c:v libx264 -pix_fmt yuv420p output.mp4
# High quality
ffmpeg -framerate 30 -i frame-%04d.png \
-c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p \
output.mp4
# With audio
ffmpeg -framerate 30 -i frame-%04d.png -i audio.mp3 \
-c:v libx264 -c:a aac -shortest \
output.mp4
# Loop for social media (3 loops)
ffmpeg -stream_loop 2 -i output.mp4 -c copy output-looped.mp4
```
### Video Export Gotchas
**YUV420 clips dark values.** H.264 encodes in YUV420 color space, which rounds dark RGB values. Content below RGB(8,8,8) may become pure black. Subtle dark details (dim particle trails, faint noise textures) disappear in the encoded video even though they're visible in the PNG frames.
**Fix:** Ensure minimum brightness of ~10 for any visible content. Test by encoding a few frames and comparing the MP4 frame vs the source PNG.
```bash
# Extract a frame from MP4 for comparison
ffmpeg -i output.mp4 -vf "select=eq(n\,100)" -vframes 1 check.png
```
**Static frames look broken in video.** If an algorithm produces a single static image (like a pre-computed attractor heatmap), it reads as a freeze/glitch in video. Always add animation even to static content:
- Progressive reveal (expand from center, sweep across)
- Slow parameter drift (rotate color mapping, shift noise offset)
- Camera-like motion (slow zoom, slight pan)
- Overlay animated particles or grain
**Scene transitions are mandatory.** Hard cuts between visually different scenes are jarring. Use fade envelopes:
```javascript
const FADE_FRAMES = 15; // half-second at 30fps
let fade = 1;
if (localFrame < FADE_FRAMES) fade = localFrame / FADE_FRAMES;
if (localFrame > SCENE_FRAMES - FADE_FRAMES) fade = (SCENE_FRAMES - localFrame) / FADE_FRAMES;
fade = fade * fade * (3 - 2 * fade); // smoothstep
// Apply: multiply all alpha/brightness by fade
```
### Per-Clip Architecture (Multi-Scene Videos)
For videos with multiple scenes, render each as a separate HTML file + MP4 clip, then stitch with ffmpeg. This enables re-rendering individual scenes without touching the rest.
**Directory structure:**
```
project/
├── capture-scene.js # Shared: node capture-scene.js <html> <outdir> <frames>
├── render-all.sh # Renders all + stitches
├── scenes/
│ ├── 00-intro.html # Each scene is self-contained
│ ├── 01-particles.html
│ ├── 02-noise.html
│ └── 03-outro.html
└── clips/
├── 00-intro.mp4 # Each clip rendered independently
├── 01-particles.mp4
├── 02-noise.mp4
├── 03-outro.mp4
└── concat.txt
```
**Stitch clips with ffmpeg concat:**
```bash
# concat.txt (order determines final sequence)
file '00-intro.mp4'
file '01-particles.mp4'
file '02-noise.mp4'
file '03-outro.mp4'
# Lossless stitch (all clips must have same codec/resolution/fps)
ffmpeg -f concat -safe 0 -i concat.txt -c copy final.mp4
```
**Re-render a single scene:**
```bash
node capture-scene.js scenes/01-particles.html clips/01-particles 150
ffmpeg -y -framerate 30 -i clips/01-particles/frame-%04d.png \
-c:v libx264 -preset slow -crf 16 -pix_fmt yuv420p clips/01-particles.mp4
# Then re-stitch
ffmpeg -y -f concat -safe 0 -i clips/concat.txt -c copy final.mp4
```
**Re-order without re-rendering:** Just change the order in concat.txt and re-stitch. No frames need re-rendering.
**Each scene HTML must:**
- Call `noLoop()` in setup and set `window._p5Ready = true`
- Use `frameCount`-based timing (not `millis()`) for deterministic output
- Handle its own fade-in/fade-out envelope
- Be fully self-contained (no shared state between scenes)
### ffmpeg: Frames to GIF (Better Quality)
```bash
# Generate palette first for optimal colors
ffmpeg -i frame-%04d.png -vf "fps=15,palettegen=max_colors=256" palette.png
# Render GIF using palette
ffmpeg -i frame-%04d.png -i palette.png \
-lavfi "fps=15 [x]; [x][1:v] paletteuse=dither=bayer:bayer_scale=3" \
output.gif
```
## Headless Export (Puppeteer)
For automated, server-side, or CI rendering. Uses a headless Chrome browser to run the sketch.
### export-frames.js (Node.js Script)
See `scripts/export-frames.js` for the full implementation. Basic pattern:
```javascript
const puppeteer = require('puppeteer');
async function captureFrames(htmlPath, outputDir, options) {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setViewport({
width: options.width || 1920,
height: options.height || 1080,
deviceScaleFactor: 1
});
await page.goto(`file://${path.resolve(htmlPath)}`, {
waitUntil: 'networkidle0'
});
// Wait for sketch to initialize
await page.waitForSelector('canvas');
await page.waitForTimeout(1000);
for (let i = 0; i < options.frames; i++) {
const canvas = await page.$('canvas');
await canvas.screenshot({
path: path.join(outputDir, `frame-${String(i).padStart(4, '0')}.png`)
});
// Advance one frame
await page.evaluate(() => { redraw(); });
await page.waitForTimeout(1000 / options.fps);
}
await browser.close();
}
```
### render.sh (Full Pipeline)
See `scripts/render.sh` for the complete render script. Pipeline:
```
1. Launch Puppeteer → open sketch HTML
2. Capture N frames as PNG sequence
3. Pipe to ffmpeg → encode H.264 MP4
4. Optional: add audio track
5. Clean up temp frames
```
## SVG Export
### Using p5.js-svg Library
```html
<script src="https://unpkg.com/p5.js-svg@1.5.1"></script>
```
```javascript
function setup() {
createCanvas(1920, 1080, SVG); // SVG renderer
noLoop();
}
function draw() {
// Only vector operations (no pixels, no blend modes)
stroke(0);
noFill();
for (let i = 0; i < 100; i++) {
let x = random(width);
let y = random(height);
ellipse(x, y, random(10, 50));
}
save('output.svg');
}
```
Limitations:
- No `loadPixels()`, `updatePixels()`, `filter()`, `blendMode()`
- No WebGL
- No pixel-level effects
- Great for: line art, geometric patterns, plots
### Hybrid: Raster Background + SVG Overlay
Render background effects to PNG, then SVG for crisp vector elements on top.
## Export Format Decision Guide
| Need | Format | Method |
|------|--------|--------|
| Single still image | PNG | `saveCanvas()` or `keyPressed()` |
| Print-quality still | PNG (high-res) | `pixelDensity(1)` + large canvas |
| Short animated loop | GIF | `saveGif()` |
| Long animation | MP4 | Frame sequence + ffmpeg |
| Social media video | MP4 | `scripts/render.sh` |
| Vector/print | SVG | p5.js-svg renderer |
| Batch variations | PNG sequence | Seed loop + `saveCanvas()` |
| Interactive deployment | HTML | Single self-contained file |
| Headless rendering | PNG/MP4 | Puppeteer + ffmpeg |
## Tiling for Ultra-High-Resolution
For resolutions too large for a single canvas (e.g., 10000x10000 for print):
```javascript
function renderTiled(totalW, totalH, tileSize) {
let cols = ceil(totalW / tileSize);
let rows = ceil(totalH / tileSize);
for (let ty = 0; ty < rows; ty++) {
for (let tx = 0; tx < cols; tx++) {
let buffer = createGraphics(tileSize, tileSize);
buffer.push();
buffer.translate(-tx * tileSize, -ty * tileSize);
renderScene(buffer, totalW, totalH);
buffer.pop();
buffer.save(`tile-${tx}-${ty}.png`);
buffer.remove(); // free memory
}
}
// Stitch with ImageMagick:
// montage tile-*.png -tile 4x4 -geometry +0+0 final.png
}
```
## CCapture.js — Deterministic Video Capture
The built-in `saveFrames()` has limitations: small frame counts, memory issues, browser download blocking. CCapture.js solves all of these by hooking into the browser's timing functions to simulate constant time steps regardless of actual render speed.
```html
<script src="https://cdn.jsdelivr.net/npm/ccapture.js-npmfixed/build/CCapture.all.min.js"></script>
```
### Basic Setup
```javascript
let capturer;
let recording = false;
function setup() {
createCanvas(1920, 1080);
pixelDensity(1);
capturer = new CCapture({
format: 'webm', // 'webm', 'gif', 'png', 'jpg'
framerate: 30,
quality: 99, // 0-100 for webm/jpg
// timeLimit: 10, // auto-stop after N seconds
// motionBlurFrames: 4 // supersampled motion blur
});
}
function draw() {
// ... render frame ...
if (recording) {
capturer.capture(document.querySelector('canvas'));
}
}
function keyPressed() {
if (key === 'c') {
if (!recording) {
capturer.start();
recording = true;
console.log('Recording started');
} else {
capturer.stop();
capturer.save(); // triggers download
recording = false;
console.log('Recording saved');
}
}
}
```
### Format Comparison
| Format | Quality | Size | Browser Support |
|--------|---------|------|-----------------|
| **WebM** | High | Medium | Chrome only |
| **GIF** | 256 colors | Large | All (via gif.js worker) |
| **PNG sequence** | Lossless | Very large (TAR) | All |
| **JPEG sequence** | Lossy | Large (TAR) | All |
### Important: Timing Hook
CCapture.js overrides `Date.now()`, `setTimeout`, `requestAnimationFrame`, and `performance.now()`. This means:
- `millis()` returns simulated time (perfect for recording)
- `deltaTime` is constant (1000/framerate)
- Complex sketches that take 500ms per frame still record at smooth 30fps
- **Caveat**: Audio sync breaks (audio plays in real-time, not simulated time)
## Programmatic Export (canvas API)
For custom export workflows beyond `saveCanvas()`:
```javascript
// Canvas to Blob (for upload, processing)
document.querySelector('canvas').toBlob((blob) => {
// Upload to server, process, etc.
let url = URL.createObjectURL(blob);
console.log('Blob URL:', url);
}, 'image/png');
// Canvas to Data URL (for inline embedding)
let dataUrl = document.querySelector('canvas').toDataURL('image/png');
// Use in <img src="..."> or send as base64
```
## SVG Export (p5.js-svg)
```html
<script src="https://unpkg.com/p5.js-svg@1.6.0"></script>
```
```javascript
function setup() {
createCanvas(1920, 1080, SVG); // SVG renderer
noLoop();
}
function draw() {
// Only vector operations work (no pixel ops, no blendMode)
stroke(0);
noFill();
for (let i = 0; i < 100; i++) {
ellipse(random(width), random(height), random(10, 50));
}
save('output.svg');
}
```
**Critical SVG caveats:**
- **Must call `clear()` in `draw()`** for animated sketches — SVG DOM accumulates child elements, causing memory bloat
- `blendMode()` is **not implemented** in SVG renderer
- `filter()`, `loadPixels()`, `updatePixels()` don't work
- Requires **p5.js 1.11.x** — not compatible with p5.js 2.x
- Perfect for: line art, geometric patterns, pen plotter output
## Platform Export
### fxhash Conventions
```javascript
// Replace p5's random with fxhash's deterministic PRNG
const rng = $fx.rand;
// Declare features for rarity/filtering
$fx.features({
'Palette': paletteName,
'Complexity': complexity > 0.7 ? 'High' : 'Low',
'Has Particles': particleCount > 0
});
// Declare on-chain parameters
$fx.params([
{ id: 'density', name: 'Density', type: 'number',
options: { min: 1, max: 100, step: 1 } },
{ id: 'palette', name: 'Palette', type: 'select',
options: { options: ['Warm', 'Cool', 'Mono'] } },
{ id: 'accent', name: 'Accent Color', type: 'color' }
]);
// Read params
let density = $fx.getParam('density');
// Build: npx fxhash build → upload.zip
// Dev: npx fxhash dev → localhost:3300
```
### Art Blocks / Generic Platform
```javascript
// Platform provides a hash string
const hash = tokenData.hash; // Art Blocks convention
// Build deterministic PRNG from hash
function prngFromHash(hash) {
let seed = parseInt(hash.slice(0, 16), 16);
// xoshiro128** or similar
return function() { /* ... */ };
}
const rng = prngFromHash(hash);
```

View File

@@ -0,0 +1,398 @@
# Interaction
## Mouse Events
### Continuous State
```javascript
mouseX, mouseY // current position (relative to canvas)
pmouseX, pmouseY // previous frame position
mouseIsPressed // boolean
mouseButton // LEFT, RIGHT, CENTER (during press)
movedX, movedY // delta since last frame
winMouseX, winMouseY // relative to window (not canvas)
```
### Event Callbacks
```javascript
function mousePressed() {
// fires once on press
// mouseButton tells you which button
}
function mouseReleased() {
// fires once on release
}
function mouseClicked() {
// fires after press+release (same element)
}
function doubleClicked() {
// fires on double-click
}
function mouseMoved() {
// fires when mouse moves (no button pressed)
}
function mouseDragged() {
// fires when mouse moves WITH button pressed
}
function mouseWheel(event) {
// event.delta: positive = scroll down, negative = scroll up
zoom += event.delta * -0.01;
return false; // prevent page scroll
}
```
### Mouse Interaction Patterns
**Spawn on click:**
```javascript
function mousePressed() {
particles.push(new Particle(mouseX, mouseY));
}
```
**Mouse follow with spring:**
```javascript
let springX, springY;
function setup() {
springX = new Spring(width/2, width/2);
springY = new Spring(height/2, height/2);
}
function draw() {
springX.setTarget(mouseX);
springY.setTarget(mouseY);
let x = springX.update();
let y = springY.update();
ellipse(x, y, 50);
}
```
**Drag interaction:**
```javascript
let dragging = false;
let dragObj = null;
let offsetX, offsetY;
function mousePressed() {
for (let obj of objects) {
if (dist(mouseX, mouseY, obj.x, obj.y) < obj.radius) {
dragging = true;
dragObj = obj;
offsetX = mouseX - obj.x;
offsetY = mouseY - obj.y;
break;
}
}
}
function mouseDragged() {
if (dragging && dragObj) {
dragObj.x = mouseX - offsetX;
dragObj.y = mouseY - offsetY;
}
}
function mouseReleased() {
dragging = false;
dragObj = null;
}
```
**Mouse repulsion (particles flee cursor):**
```javascript
function draw() {
let mousePos = createVector(mouseX, mouseY);
for (let p of particles) {
let d = p.pos.dist(mousePos);
if (d < 150) {
let repel = p5.Vector.sub(p.pos, mousePos);
repel.normalize();
repel.mult(map(d, 0, 150, 5, 0));
p.applyForce(repel);
}
}
}
```
## Keyboard Events
### State
```javascript
keyIsPressed // boolean
key // last key as string ('a', 'A', ' ')
keyCode // numeric code (LEFT_ARROW, UP_ARROW, etc.)
```
### Event Callbacks
```javascript
function keyPressed() {
// fires once on press
if (keyCode === LEFT_ARROW) { /* ... */ }
if (key === 's') saveCanvas('output', 'png');
if (key === ' ') CONFIG.paused = !CONFIG.paused;
return false; // prevent default browser behavior
}
function keyReleased() {
// fires once on release
}
function keyTyped() {
// fires for printable characters only (not arrows, shift, etc.)
}
```
### Continuous Key State (Multiple Keys)
```javascript
let keys = {};
function keyPressed() { keys[keyCode] = true; }
function keyReleased() { keys[keyCode] = false; }
function draw() {
if (keys[LEFT_ARROW]) player.x -= 5;
if (keys[RIGHT_ARROW]) player.x += 5;
if (keys[UP_ARROW]) player.y -= 5;
if (keys[DOWN_ARROW]) player.y += 5;
}
```
### Key Constants
```
LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW
BACKSPACE, DELETE, ENTER, RETURN, TAB, ESCAPE
SHIFT, CONTROL, OPTION, ALT
```
## Touch Events
```javascript
touches // array of { x, y, id } — all current touches
function touchStarted() {
// fires on first touch
return false; // prevent default (stops scroll on mobile)
}
function touchMoved() {
// fires on touch drag
return false;
}
function touchEnded() {
// fires on touch release
}
```
### Pinch Zoom
```javascript
let prevDist = 0;
let zoomLevel = 1;
function touchMoved() {
if (touches.length === 2) {
let d = dist(touches[0].x, touches[0].y, touches[1].x, touches[1].y);
if (prevDist > 0) {
zoomLevel *= d / prevDist;
}
prevDist = d;
}
return false;
}
function touchEnded() {
prevDist = 0;
}
```
## DOM Elements
### Creating Controls
```javascript
function setup() {
createCanvas(800, 800);
// Slider
let slider = createSlider(0, 255, 100, 1); // min, max, default, step
slider.position(10, height + 10);
slider.input(() => { CONFIG.value = slider.value(); });
// Button
let btn = createButton('Reset');
btn.position(10, height + 40);
btn.mousePressed(() => { resetSketch(); });
// Checkbox
let check = createCheckbox('Show grid', false);
check.position(10, height + 70);
check.changed(() => { CONFIG.showGrid = check.checked(); });
// Select / dropdown
let sel = createSelect();
sel.position(10, height + 100);
sel.option('Mode A');
sel.option('Mode B');
sel.changed(() => { CONFIG.mode = sel.value(); });
// Color picker
let picker = createColorPicker('#ff0000');
picker.position(10, height + 130);
picker.input(() => { CONFIG.color = picker.value(); });
// Text input
let inp = createInput('Hello');
inp.position(10, height + 160);
inp.input(() => { CONFIG.text = inp.value(); });
}
```
### Styling DOM Elements
```javascript
let slider = createSlider(0, 100, 50);
slider.position(10, 10);
slider.style('width', '200px');
slider.class('my-slider');
slider.parent('controls-div'); // attach to specific DOM element
```
## Audio Input (p5.sound)
Requires `p5.sound.min.js` addon.
```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.3/addons/p5.sound.min.js"></script>
```
### Microphone Input
```javascript
let mic, fft, amplitude;
function setup() {
createCanvas(800, 800);
userStartAudio(); // required — user gesture to enable audio
mic = new p5.AudioIn();
mic.start();
fft = new p5.FFT(0.8, 256); // smoothing, bins
fft.setInput(mic);
amplitude = new p5.Amplitude();
amplitude.setInput(mic);
}
function draw() {
let level = amplitude.getLevel(); // 0.0 to 1.0 (overall volume)
let spectrum = fft.analyze(); // array of 256 frequency values (0-255)
let waveform = fft.waveform(); // array of 256 time-domain samples (-1 to 1)
// Get energy in frequency bands
let bass = fft.getEnergy('bass'); // 20-140 Hz
let lowMid = fft.getEnergy('lowMid'); // 140-400 Hz
let mid = fft.getEnergy('mid'); // 400-2600 Hz
let highMid = fft.getEnergy('highMid'); // 2600-5200 Hz
let treble = fft.getEnergy('treble'); // 5200-14000 Hz
// Each returns 0-255
}
```
### Audio File Playback
```javascript
let song, fft;
function preload() {
song = loadSound('track.mp3');
}
function setup() {
createCanvas(800, 800);
fft = new p5.FFT(0.8, 512);
fft.setInput(song);
}
function mousePressed() {
if (song.isPlaying()) {
song.pause();
} else {
song.play();
}
}
```
### Beat Detection (Simple)
```javascript
let prevBass = 0;
let beatThreshold = 30;
let beatCooldown = 0;
function detectBeat() {
let bass = fft.getEnergy('bass');
let isBeat = bass - prevBass > beatThreshold && beatCooldown <= 0;
prevBass = bass;
if (isBeat) beatCooldown = 10; // frames
beatCooldown--;
return isBeat;
}
```
## Scroll-Driven Animation
```javascript
let scrollProgress = 0;
function setup() {
let canvas = createCanvas(windowWidth, windowHeight);
canvas.style('position', 'fixed');
// Make page scrollable
document.body.style.height = '500vh';
}
window.addEventListener('scroll', () => {
let maxScroll = document.body.scrollHeight - window.innerHeight;
scrollProgress = window.scrollY / maxScroll;
});
function draw() {
background(0);
// Use scrollProgress (0 to 1) to drive animation
let x = lerp(0, width, scrollProgress);
ellipse(x, height/2, 50);
}
```
## Responsive Events
```javascript
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Recreate buffers
bgLayer = createGraphics(width, height);
// Recalculate layout
recalculateLayout();
}
// Visibility change (tab switching)
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
noLoop(); // pause when tab not visible
} else {
loop();
}
});
```

View File

@@ -0,0 +1,300 @@
# Shapes and Geometry
## 2D Primitives
```javascript
point(x, y);
line(x1, y1, x2, y2);
rect(x, y, w, h); // default: corner mode
rect(x, y, w, h, r); // rounded corners
rect(x, y, w, h, tl, tr, br, bl); // per-corner radius
square(x, y, size);
ellipse(x, y, w, h);
circle(x, y, d); // diameter, not radius
triangle(x1, y1, x2, y2, x3, y3);
quad(x1, y1, x2, y2, x3, y3, x4, y4);
arc(x, y, w, h, start, stop, mode); // mode: OPEN, CHORD, PIE
```
### Drawing Modes
```javascript
rectMode(CENTER); // x,y is center (default: CORNER)
rectMode(CORNERS); // x1,y1 to x2,y2
ellipseMode(CORNER); // x,y is top-left corner
ellipseMode(CENTER); // default — x,y is center
```
## Stroke and Fill
```javascript
fill(r, g, b, a); // or fill(gray), fill('#hex'), fill(h, s, b) in HSB mode
noFill();
stroke(r, g, b, a);
noStroke();
strokeWeight(2);
strokeCap(ROUND); // ROUND, SQUARE, PROJECT
strokeJoin(ROUND); // ROUND, MITER, BEVEL
```
## Custom Shapes with Vertices
### Basic vertex shape
```javascript
beginShape();
vertex(100, 100);
vertex(200, 50);
vertex(300, 100);
vertex(250, 200);
vertex(150, 200);
endShape(CLOSE); // CLOSE connects last vertex to first
```
### Shape modes
```javascript
beginShape(); // default: polygon connecting all vertices
beginShape(POINTS); // individual points
beginShape(LINES); // pairs of vertices as lines
beginShape(TRIANGLES); // triplets as triangles
beginShape(TRIANGLE_FAN);
beginShape(TRIANGLE_STRIP);
beginShape(QUADS); // groups of 4
beginShape(QUAD_STRIP);
```
### Contours (holes in shapes)
```javascript
beginShape();
// outer shape
vertex(100, 100);
vertex(300, 100);
vertex(300, 300);
vertex(100, 300);
// inner hole
beginContour();
vertex(150, 150);
vertex(150, 250);
vertex(250, 250);
vertex(250, 150);
endContour();
endShape(CLOSE);
```
## Bezier Curves
### Cubic Bezier
```javascript
bezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
// x1,y1 = start point
// cx1,cy1 = first control point
// cx2,cy2 = second control point
// x2,y2 = end point
```
### Bezier in custom shapes
```javascript
beginShape();
vertex(100, 200);
bezierVertex(150, 50, 250, 50, 300, 200);
// control1, control2, endpoint
endShape();
```
### Quadratic Bezier
```javascript
beginShape();
vertex(100, 200);
quadraticVertex(200, 50, 300, 200);
// single control point + endpoint
endShape();
```
### Interpolation along Bezier
```javascript
let x = bezierPoint(x1, cx1, cx2, x2, t); // t = 0..1
let y = bezierPoint(y1, cy1, cy2, y2, t);
let tx = bezierTangent(x1, cx1, cx2, x2, t); // tangent
```
## Catmull-Rom Splines
```javascript
curve(cpx1, cpy1, x1, y1, x2, y2, cpx2, cpy2);
// cpx1,cpy1 = control point before start
// x1,y1 = start point (visible)
// x2,y2 = end point (visible)
// cpx2,cpy2 = control point after end
curveVertex(x, y); // in beginShape() — smooth curve through all points
curveTightness(0); // 0 = Catmull-Rom, 1 = straight lines, -1 = loose
```
### Smooth curve through points
```javascript
let points = [/* array of {x, y} */];
beginShape();
curveVertex(points[0].x, points[0].y); // repeat first for tangent
for (let p of points) {
curveVertex(p.x, p.y);
}
curveVertex(points[points.length-1].x, points[points.length-1].y); // repeat last
endShape();
```
## p5.Vector
Essential for physics, particle systems, and geometric computation.
```javascript
let v = createVector(x, y);
// Arithmetic (modifies in place)
v.add(other); // vector addition
v.sub(other); // subtraction
v.mult(scalar); // scale
v.div(scalar); // inverse scale
v.normalize(); // unit vector (length 1)
v.limit(max); // cap magnitude
v.setMag(len); // set exact magnitude
// Queries (non-destructive)
v.mag(); // magnitude (length)
v.magSq(); // squared magnitude (faster, no sqrt)
v.heading(); // angle in radians
v.dist(other); // distance to other vector
v.dot(other); // dot product
v.cross(other); // cross product (3D)
v.angleBetween(other); // angle between vectors
// Static methods (return new vector)
p5.Vector.add(a, b); // a + b → new vector
p5.Vector.sub(a, b); // a - b → new vector
p5.Vector.fromAngle(a); // unit vector at angle
p5.Vector.random2D(); // random unit vector
p5.Vector.lerp(a, b, t); // interpolate
// Copy
let copy = v.copy();
```
## Signed Distance Fields (2D)
SDFs return the distance from a point to the nearest edge of a shape. Negative inside, positive outside. Useful for smooth shapes, glow effects, boolean operations.
```javascript
// Circle SDF
function sdCircle(px, py, cx, cy, r) {
return dist(px, py, cx, cy) - r;
}
// Box SDF
function sdBox(px, py, cx, cy, hw, hh) {
let dx = abs(px - cx) - hw;
let dy = abs(py - cy) - hh;
return sqrt(max(dx, 0) ** 2 + max(dy, 0) ** 2) + min(max(dx, dy), 0);
}
// Line segment SDF
function sdSegment(px, py, ax, ay, bx, by) {
let pa = createVector(px - ax, py - ay);
let ba = createVector(bx - ax, by - ay);
let t = constrain(pa.dot(ba) / ba.dot(ba), 0, 1);
let closest = p5.Vector.add(createVector(ax, ay), p5.Vector.mult(ba, t));
return dist(px, py, closest.x, closest.y);
}
// Smooth boolean union
function opSmoothUnion(d1, d2, k) {
let h = constrain(0.5 + 0.5 * (d2 - d1) / k, 0, 1);
return lerp(d2, d1, h) - k * h * (1 - h);
}
// Rendering SDF as glow
let d = sdCircle(x, y, width/2, height/2, 200);
let glow = exp(-abs(d) * 0.02); // exponential falloff
fill(glow * 255);
```
## Useful Geometry Patterns
### Regular Polygon
```javascript
function regularPolygon(cx, cy, r, sides) {
beginShape();
for (let i = 0; i < sides; i++) {
let a = TWO_PI * i / sides - HALF_PI;
vertex(cx + cos(a) * r, cy + sin(a) * r);
}
endShape(CLOSE);
}
```
### Star Shape
```javascript
function star(cx, cy, r1, r2, npoints) {
beginShape();
let angle = TWO_PI / npoints;
let halfAngle = angle / 2;
for (let a = -HALF_PI; a < TWO_PI - HALF_PI; a += angle) {
vertex(cx + cos(a) * r2, cy + sin(a) * r2);
vertex(cx + cos(a + halfAngle) * r1, cy + sin(a + halfAngle) * r1);
}
endShape(CLOSE);
}
```
### Rounded Line (Capsule)
```javascript
function capsule(x1, y1, x2, y2, weight) {
strokeWeight(weight);
strokeCap(ROUND);
line(x1, y1, x2, y2);
}
```
### Soft Body / Blob
```javascript
function blob(cx, cy, baseR, noiseScale, noiseOffset, detail = 64) {
beginShape();
for (let i = 0; i < detail; i++) {
let a = TWO_PI * i / detail;
let r = baseR + noise(cos(a) * noiseScale + noiseOffset,
sin(a) * noiseScale + noiseOffset) * baseR * 0.4;
vertex(cx + cos(a) * r, cy + sin(a) * r);
}
endShape(CLOSE);
}
```
## Clipping and Masking
```javascript
// Clip shape — everything drawn after is masked by the clip shape
beginClip();
circle(width/2, height/2, 400);
endClip();
// Only content inside the circle is visible
image(myImage, 0, 0);
// Or functional form
clip(() => {
circle(width/2, height/2, 400);
});
// Erase mode — cut holes
erase();
circle(mouseX, mouseY, 100); // this area becomes transparent
noErase();
```

View File

@@ -0,0 +1,532 @@
# Troubleshooting
## Performance
### Step Zero — Disable FES
The Friendly Error System (FES) adds massive overhead — up to 10x slowdown. Disable it in every production sketch:
```javascript
// BEFORE any p5 code
p5.disableFriendlyErrors = true;
// Or use p5.min.js instead of p5.js — FES is stripped from minified build
```
### Step One — pixelDensity(1)
Retina/HiDPI displays default to 2x or 3x density, multiplying pixel count by 4-9x:
```javascript
function setup() {
pixelDensity(1); // force 1:1 — always do this first
createCanvas(1920, 1080);
}
```
### Use Math.* in Hot Loops
p5's `sin()`, `cos()`, `random()`, `min()`, `max()`, `abs()` are wrapper functions with overhead. In hot loops (thousands of iterations per frame), use native `Math.*`:
```javascript
// SLOW — p5 wrappers
for (let p of particles) {
let a = sin(p.angle);
let d = dist(p.x, p.y, mx, my);
}
// FAST — native Math
for (let p of particles) {
let a = Math.sin(p.angle);
let dx = p.x - mx, dy = p.y - my;
let dSq = dx * dx + dy * dy; // skip sqrt entirely
}
```
Use `magSq()` instead of `mag()` for distance comparisons — avoids expensive `sqrt()`.
### Diagnosis
Open Chrome DevTools > Performance tab > Record while sketch runs.
Common bottlenecks:
1. **FES enabled** — 10x overhead on every p5 function call
2. **pixelDensity > 1** — 4x pixel count, 4x slower
3. **Too many draw calls** — thousands of `ellipse()`, `rect()` per frame
4. **Large canvas + pixel operations**`loadPixels()`/`updatePixels()` on 4K canvas
5. **Unoptimized particle systems** — checking all-vs-all distances (O(n^2))
6. **Memory leaks** — creating objects every frame without cleanup
7. **Shader compilation** — calling `createShader()` in `draw()` instead of `setup()`
8. **console.log() in draw()** — DOM write per frame, destroys performance
9. **DOM manipulation in draw()** — layout thrashing (400-500x slower than canvas ops)
### Solutions
**Reduce draw calls:**
```javascript
// BAD: 10000 individual circles
for (let p of particles) {
ellipse(p.x, p.y, p.size);
}
// GOOD: single shape with vertices
beginShape(POINTS);
for (let p of particles) {
vertex(p.x, p.y);
}
endShape();
// BEST: direct pixel manipulation
loadPixels();
for (let p of particles) {
let idx = 4 * (floor(p.y) * width + floor(p.x));
pixels[idx] = p.r;
pixels[idx+1] = p.g;
pixels[idx+2] = p.b;
pixels[idx+3] = 255;
}
updatePixels();
```
**Spatial hashing for neighbor queries:**
```javascript
class SpatialHash {
constructor(cellSize) {
this.cellSize = cellSize;
this.cells = new Map();
}
clear() { this.cells.clear(); }
_key(x, y) {
return `${floor(x / this.cellSize)},${floor(y / this.cellSize)}`;
}
insert(obj) {
let key = this._key(obj.pos.x, obj.pos.y);
if (!this.cells.has(key)) this.cells.set(key, []);
this.cells.get(key).push(obj);
}
query(x, y, radius) {
let results = [];
let minCX = floor((x - radius) / this.cellSize);
let maxCX = floor((x + radius) / this.cellSize);
let minCY = floor((y - radius) / this.cellSize);
let maxCY = floor((y + radius) / this.cellSize);
for (let cx = minCX; cx <= maxCX; cx++) {
for (let cy = minCY; cy <= maxCY; cy++) {
let key = `${cx},${cy}`;
let cell = this.cells.get(key);
if (cell) {
for (let obj of cell) {
if (dist(x, y, obj.pos.x, obj.pos.y) <= radius) {
results.push(obj);
}
}
}
}
}
return results;
}
}
```
**Object pooling:**
```javascript
class ParticlePool {
constructor(maxSize) {
this.pool = [];
this.active = [];
for (let i = 0; i < maxSize; i++) {
this.pool.push(new Particle(0, 0));
}
}
spawn(x, y) {
let p = this.pool.pop();
if (p) {
p.reset(x, y);
this.active.push(p);
}
}
update() {
for (let i = this.active.length - 1; i >= 0; i--) {
this.active[i].update();
if (this.active[i].isDead()) {
this.pool.push(this.active.splice(i, 1)[0]);
}
}
}
}
```
**Throttle heavy operations:**
```javascript
// Only update flow field every N frames
if (frameCount % 5 === 0) {
flowField.update(frameCount * 0.001);
}
```
### Frame Rate Targets
| Context | Target | Acceptable |
|---------|--------|------------|
| Interactive sketch | 60fps | 30fps |
| Ambient animation | 30fps | 20fps |
| Export/recording | 30fps render | Any (offline) |
| Mobile | 30fps | 20fps |
### Per-Pixel Rendering Budgets
Pixel-level operations (`loadPixels()` loops) are the most expensive common pattern. Budget depends on canvas size and computation per pixel.
| Canvas | Pixels | Simple noise (1 call) | fBM (4 octave) | Domain warp (3-layer fBM) |
|--------|--------|----------------------|----------------|--------------------------|
| 540x540 | 291K | ~5ms | ~20ms | ~80ms |
| 1080x1080 | 1.17M | ~20ms | ~80ms | ~300ms+ |
| 1920x1080 | 2.07M | ~35ms | ~140ms | ~500ms+ |
| 3840x2160 | 8.3M | ~140ms | ~560ms | WILL CRASH |
**Rules of thumb:**
- 1 `noise()` call per pixel at 1080x1080 = ~20ms/frame (OK at 30fps)
- 4-octave fBM per pixel at 1080x1080 = ~80ms/frame (borderline)
- Multi-layer domain warp at 1080x1080 = 300ms+ (too slow for real-time, fine for `noLoop()` export)
- **Headless Chrome is 2-5x slower** than desktop Chrome for pixel ops
**Solution: render at lower resolution, fill blocks:**
```javascript
let step = 3; // render 1/9 of pixels, fill 3x3 blocks
loadPixels();
for (let y = 0; y < H; y += step) {
for (let x = 0; x < W; x += step) {
let v = expensiveNoise(x, y);
for (let dy = 0; dy < step && y+dy < H; dy++)
for (let dx = 0; dx < step && x+dx < W; dx++) {
let i = 4 * ((y+dy) * W + (x+dx));
pixels[i] = v; pixels[i+1] = v; pixels[i+2] = v; pixels[i+3] = 255;
}
}
}
updatePixels();
```
Step=2 gives 4x speedup. Step=3 gives 9x. Visible at 1080p but acceptable for video (motion hides it).
## Common Mistakes
### 1. Forgetting to reset blend mode
```javascript
blendMode(ADD);
image(glowLayer, 0, 0);
// WRONG: everything after this is ADD blended
blendMode(BLEND); // ALWAYS reset
```
### 2. Creating objects in draw()
```javascript
// BAD: creates new font object every frame
function draw() {
let f = loadFont('font.otf'); // NEVER load in draw()
}
// GOOD: load in preload, use in draw
let f;
function preload() { f = loadFont('font.otf'); }
```
### 3. Not using push()/pop() with transforms
```javascript
// BAD: transforms accumulate
translate(100, 0);
rotate(0.1);
ellipse(0, 0, 50);
// Everything after this is also translated and rotated
// GOOD: isolated transforms
push();
translate(100, 0);
rotate(0.1);
ellipse(0, 0, 50);
pop();
```
### 4. Integer coordinates for crisp lines
```javascript
// BLURRY: sub-pixel rendering
line(10.5, 20.3, 100.7, 80.2);
// CRISP: integer + 0.5 for 1px lines
line(10.5, 20.5, 100.5, 80.5); // on pixel boundary
```
### 5. Pixel density confusion
```javascript
// WRONG: assuming pixel array matches canvas dimensions
loadPixels();
let idx = 4 * (y * width + x); // wrong if pixelDensity > 1
// RIGHT: account for pixel density
let d = pixelDensity();
loadPixels();
let idx = 4 * ((y * d) * (width * d) + (x * d));
// SIMPLEST: set pixelDensity(1) at the start
```
### 6. Color mode confusion
```javascript
// In HSB mode, fill(255) is NOT white
colorMode(HSB, 360, 100, 100);
fill(255); // This is hue=255, sat=100, bri=100 = vivid purple
// White in HSB:
fill(0, 0, 100); // any hue, 0 saturation, 100 brightness
// Black in HSB:
fill(0, 0, 0);
```
### 7. WebGL origin is center
```javascript
// In WEBGL mode, (0,0) is CENTER, not top-left
function draw() {
// This draws at the center, not the corner
rect(0, 0, 100, 100);
// For top-left behavior:
translate(-width/2, -height/2);
rect(0, 0, 100, 100); // now at top-left
}
```
### 8. createGraphics cleanup
```javascript
// BAD: memory leak — buffer never freed
function draw() {
let temp = createGraphics(width, height); // new buffer every frame!
// ...
}
// GOOD: create once, reuse
let temp;
function setup() {
temp = createGraphics(width, height);
}
function draw() {
temp.clear();
// ... reuse temp
}
// If you must create/destroy:
temp.remove(); // explicitly free
```
### 9. noise() returns 0-1, not -1 to 1
```javascript
let n = noise(x); // 0.0 to 1.0 (biased toward 0.5)
// For -1 to 1 range:
let n = noise(x) * 2 - 1;
// For a specific range:
let n = map(noise(x), 0, 1, -100, 100);
```
### 10. saveCanvas() in draw() saves every frame
```javascript
// BAD: saves a PNG every single frame
function draw() {
// ... render ...
saveCanvas('output', 'png'); // DON'T DO THIS
}
// GOOD: save once via keyboard
function keyPressed() {
if (key === 's') saveCanvas('output', 'png');
}
// GOOD: save once after rendering static piece
function draw() {
// ... render ...
saveCanvas('output', 'png');
noLoop(); // stop after saving
}
```
### 11. console.log() in draw()
```javascript
// BAD: writes to DOM console every frame — massive overhead
function draw() {
console.log(particles.length); // 60 DOM writes/second
}
// GOOD: log periodically or conditionally
function draw() {
if (frameCount % 60 === 0) console.log('FPS:', frameRate().toFixed(1));
}
```
### 12. DOM manipulation in draw()
```javascript
// BAD: layout thrashing — 400-500x slower than canvas ops
function draw() {
document.getElementById('counter').innerText = frameCount;
let el = document.querySelector('.info'); // DOM query per frame
}
// GOOD: cache DOM refs, update infrequently
let counterEl;
function setup() { counterEl = document.getElementById('counter'); }
function draw() {
if (frameCount % 30 === 0) counterEl.innerText = frameCount;
}
```
### 13. Not disabling FES in production
```javascript
// BAD: every p5 function call has error-checking overhead (up to 10x slower)
function setup() { createCanvas(800, 800); }
// GOOD: disable before any p5 code
p5.disableFriendlyErrors = true;
function setup() { createCanvas(800, 800); }
// ALSO GOOD: use p5.min.js (FES stripped from minified build)
```
## Browser Compatibility
### Safari Issues
- WebGL shader precision: always declare `precision mediump float;`
- `AudioContext` requires user gesture (`userStartAudio()`)
- Some `blendMode()` options behave differently
### Firefox Issues
- `textToPoints()` may return slightly different point counts
- WebGL extensions may differ from Chrome
- Color profile handling can shift colors
### Mobile Issues
- Touch events need `return false` to prevent scroll
- `devicePixelRatio` can be 2x or 3x — use `pixelDensity(1)` for performance
- Smaller canvas recommended (720p or less)
- Audio requires explicit user gesture to start
## CORS Issues
```javascript
// Loading images/fonts from external URLs requires CORS headers
// Local files need a server:
// python3 -m http.server 8080
// Or use a CORS proxy for external resources (not recommended for production)
```
## Memory Leaks
### Symptoms
- Framerate degrading over time
- Browser tab memory growing unbounded
- Page becomes unresponsive after minutes
### Common Causes
```javascript
// 1. Growing arrays
let history = [];
function draw() {
history.push(someData); // grows forever
}
// FIX: cap the array
if (history.length > 1000) history.shift();
// 2. Creating p5 objects in draw()
function draw() {
let v = createVector(0, 0); // allocation every frame
}
// FIX: reuse pre-allocated objects
// 3. Unreleased graphics buffers
let layers = [];
function reset() {
for (let l of layers) l.remove(); // free old buffers
layers = [];
}
// 4. Event listener accumulation
function setup() {
// BAD: adds new listener every time setup runs
window.addEventListener('resize', handler);
}
// FIX: use p5's built-in windowResized()
```
## Debugging Tips
### Console Logging
```javascript
// Log once (not every frame)
if (frameCount === 1) {
console.log('Canvas:', width, 'x', height);
console.log('Pixel density:', pixelDensity());
console.log('Renderer:', drawingContext.constructor.name);
}
// Log periodically
if (frameCount % 60 === 0) {
console.log('FPS:', frameRate().toFixed(1));
console.log('Particles:', particles.length);
}
```
### Visual Debugging
```javascript
// Show frame rate
function draw() {
// ... your sketch ...
if (CONFIG.debug) {
fill(255, 0, 0);
noStroke();
textSize(14);
textAlign(LEFT, TOP);
text('FPS: ' + frameRate().toFixed(1), 10, 10);
text('Particles: ' + particles.length, 10, 28);
text('Frame: ' + frameCount, 10, 46);
}
}
// Toggle debug with 'd' key
function keyPressed() {
if (key === 'd') CONFIG.debug = !CONFIG.debug;
}
```
### Isolating Issues
```javascript
// Comment out layers to find the slow one
function draw() {
renderBackground(); // comment out to test
// renderParticles(); // this might be slow
// renderPostEffects(); // or this
}
```

View File

@@ -0,0 +1,302 @@
# Typography
## Loading Fonts
### System Fonts
```javascript
textFont('Helvetica');
textFont('Georgia');
textFont('monospace');
```
### Custom Fonts (OTF/TTF/WOFF2)
```javascript
let myFont;
function preload() {
myFont = loadFont('path/to/font.otf');
// Requires local server or CORS-enabled URL
}
function setup() {
textFont(myFont);
}
```
### Google Fonts via CSS
```html
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<script>
function setup() {
textFont('Inter');
}
</script>
```
Google Fonts work without `loadFont()` but only for `text()` — not for `textToPoints()`. For particle text, you need `loadFont()` with an OTF/TTF file.
## Text Rendering
### Basic Text
```javascript
textSize(32);
textAlign(CENTER, CENTER);
text('Hello World', width/2, height/2);
```
### Text Properties
```javascript
textSize(48); // pixel size
textAlign(LEFT, TOP); // horizontal: LEFT, CENTER, RIGHT
// vertical: TOP, CENTER, BOTTOM, BASELINE
textLeading(40); // line spacing (for multi-line text)
textStyle(BOLD); // NORMAL, BOLD, ITALIC, BOLDITALIC
textWrap(WORD); // WORD or CHAR (for text() with max width)
```
### Text Metrics
```javascript
let w = textWidth('Hello'); // pixel width of string
let a = textAscent(); // height above baseline
let d = textDescent(); // height below baseline
let totalH = a + d; // full line height
```
### Text Bounding Box
```javascript
let bounds = myFont.textBounds('Hello', x, y, size);
// bounds = { x, y, w, h }
// Useful for positioning, collision, background rectangles
```
### Multi-Line Text
```javascript
// With max width — auto wraps
textWrap(WORD);
text('Long text that wraps within the given width', x, y, maxWidth);
// With max width AND height — clips
text('Very long text', x, y, maxWidth, maxHeight);
```
## textToPoints() — Text as Particles
Convert text outline to array of points. Requires a loaded font (OTF/TTF via `loadFont()`).
```javascript
let font;
let points;
function preload() {
font = loadFont('font.otf'); // MUST be loadFont, not CSS
}
function setup() {
createCanvas(1200, 600);
points = font.textToPoints('HELLO', 100, 400, 200, {
sampleFactor: 0.1, // lower = more points (0.1-0.5 typical)
simplifyThreshold: 0
});
}
function draw() {
background(0);
for (let pt of points) {
let n = noise(pt.x * 0.01, pt.y * 0.01, frameCount * 0.01);
fill(255, n * 255);
noStroke();
ellipse(pt.x + random(-2, 2), pt.y + random(-2, 2), 3);
}
}
```
### Particle Text Class
```javascript
class TextParticle {
constructor(target) {
this.target = createVector(target.x, target.y);
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxSpeed = 10;
this.maxForce = 0.5;
}
arrive() {
let desired = p5.Vector.sub(this.target, this.pos);
let d = desired.mag();
let speed = d < 100 ? map(d, 0, 100, 0, this.maxSpeed) : this.maxSpeed;
desired.setMag(speed);
let steer = p5.Vector.sub(desired, this.vel);
steer.limit(this.maxForce);
this.acc.add(steer);
}
flee(target, radius) {
let d = this.pos.dist(target);
if (d < radius) {
let desired = p5.Vector.sub(this.pos, target);
desired.setMag(this.maxSpeed);
let steer = p5.Vector.sub(desired, this.vel);
steer.limit(this.maxForce * 2);
this.acc.add(steer);
}
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
display() {
fill(255);
noStroke();
ellipse(this.pos.x, this.pos.y, 3);
}
}
// Usage: particles form text, scatter from mouse
let textParticles = [];
for (let pt of points) {
textParticles.push(new TextParticle(pt));
}
function draw() {
background(0);
for (let p of textParticles) {
p.arrive();
p.flee(createVector(mouseX, mouseY), 80);
p.update();
p.display();
}
}
```
## Kinetic Typography
### Wave Text
```javascript
function waveText(str, x, y, size, amplitude, frequency) {
textSize(size);
textAlign(LEFT, BASELINE);
let xOff = 0;
for (let i = 0; i < str.length; i++) {
let yOff = sin(frameCount * 0.05 + i * frequency) * amplitude;
text(str[i], x + xOff, y + yOff);
xOff += textWidth(str[i]);
}
}
```
### Typewriter Effect
```javascript
class Typewriter {
constructor(str, x, y, speed = 50) {
this.str = str;
this.x = x;
this.y = y;
this.speed = speed; // ms per character
this.startTime = millis();
this.cursor = true;
}
display() {
let elapsed = millis() - this.startTime;
let chars = min(floor(elapsed / this.speed), this.str.length);
let visible = this.str.substring(0, chars);
textAlign(LEFT, TOP);
text(visible, this.x, this.y);
// Blinking cursor
if (chars < this.str.length && floor(millis() / 500) % 2 === 0) {
let cursorX = this.x + textWidth(visible);
line(cursorX, this.y, cursorX, this.y + textAscent() + textDescent());
}
}
isDone() { return millis() - this.startTime >= this.str.length * this.speed; }
}
```
### Character-by-Character Animation
```javascript
function animatedText(str, x, y, size, delay = 50) {
textSize(size);
textAlign(LEFT, BASELINE);
let xOff = 0;
for (let i = 0; i < str.length; i++) {
let charStart = i * delay;
let t = constrain((millis() - charStart) / 500, 0, 1);
let et = easeOutElastic(t);
push();
translate(x + xOff, y);
scale(et);
let alpha = t * 255;
fill(255, alpha);
text(str[i], 0, 0);
pop();
xOff += textWidth(str[i]);
}
}
```
## Text as Mask
```javascript
let textBuffer;
function setup() {
createCanvas(800, 800);
textBuffer = createGraphics(width, height);
textBuffer.background(0);
textBuffer.fill(255);
textBuffer.textSize(200);
textBuffer.textAlign(CENTER, CENTER);
textBuffer.text('MASK', width/2, height/2);
}
function draw() {
// Draw content
background(0);
// ... render something colorful
// Apply text mask (show content only where text is white)
loadPixels();
textBuffer.loadPixels();
for (let i = 0; i < pixels.length; i += 4) {
let maskVal = textBuffer.pixels[i]; // white = show, black = hide
pixels[i + 3] = maskVal; // set alpha from mask
}
updatePixels();
}
```
## Responsive Text Sizing
```javascript
function responsiveTextSize(baseSize, baseWidth = 1920) {
return baseSize * (width / baseWidth);
}
// Usage
textSize(responsiveTextSize(48));
text('Scales with canvas', width/2, height/2);
```

View File

@@ -0,0 +1,895 @@
# Visual Effects
## Noise
### Perlin Noise Basics
```javascript
noiseSeed(42);
noiseDetail(4, 0.5); // octaves, falloff
// 1D noise — smooth undulation
let y = noise(x * 0.01); // returns 0.0 to 1.0
// 2D noise — terrain/texture
let v = noise(x * 0.005, y * 0.005);
// 3D noise — animated 2D field (z = time)
let v = noise(x * 0.005, y * 0.005, frameCount * 0.005);
```
The scale factor (0.005 etc.) is critical:
- `0.001` — very smooth, large features
- `0.005` — smooth, medium features
- `0.01` — standard generative art scale
- `0.05` — detailed, small features
- `0.1` — near-random, grainy
### Fractal Brownian Motion (fBM)
Layered noise octaves for natural-looking texture. Each octave adds detail at smaller scale.
```javascript
function fbm(x, y, octaves = 6, lacunarity = 2.0, gain = 0.5) {
let value = 0;
let amplitude = 1.0;
let frequency = 1.0;
let maxValue = 0;
for (let i = 0; i < octaves; i++) {
value += noise(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= gain;
frequency *= lacunarity;
}
return value / maxValue;
}
```
### Domain Warping
Feed noise output back as input coordinates for flowing organic distortion.
```javascript
function domainWarp(x, y, scale, strength, time) {
// First warp pass
let qx = fbm(x + 0.0, y + 0.0);
let qy = fbm(x + 5.2, y + 1.3);
// Second warp pass (feed back)
let rx = fbm(x + strength * qx + 1.7, y + strength * qy + 9.2, 4, 2, 0.5);
let ry = fbm(x + strength * qx + 8.3, y + strength * qy + 2.8, 4, 2, 0.5);
return fbm(x + strength * rx + time, y + strength * ry + time);
}
```
### Curl Noise
Divergence-free noise field. Particles following curl noise never converge or diverge — they flow in smooth, swirling patterns.
```javascript
function curlNoise(x, y, scale, time) {
let eps = 0.001;
// Partial derivatives via finite differences
let dndx = (noise(x * scale + eps, y * scale, time) -
noise(x * scale - eps, y * scale, time)) / (2 * eps);
let dndy = (noise(x * scale, y * scale + eps, time) -
noise(x * scale, y * scale - eps, time)) / (2 * eps);
// Curl = perpendicular to gradient
return createVector(dndy, -dndx);
}
```
## Flow Fields
A grid of vectors that steer particles. The foundational generative art technique.
```javascript
class FlowField {
constructor(resolution, noiseScale) {
this.resolution = resolution;
this.cols = ceil(width / resolution);
this.rows = ceil(height / resolution);
this.field = new Array(this.cols * this.rows);
this.noiseScale = noiseScale;
}
update(time) {
for (let i = 0; i < this.cols; i++) {
for (let j = 0; j < this.rows; j++) {
let angle = noise(i * this.noiseScale, j * this.noiseScale, time) * TWO_PI * 2;
this.field[i + j * this.cols] = p5.Vector.fromAngle(angle);
}
}
}
lookup(x, y) {
let col = constrain(floor(x / this.resolution), 0, this.cols - 1);
let row = constrain(floor(y / this.resolution), 0, this.rows - 1);
return this.field[col + row * this.cols].copy();
}
}
```
### Flow Field Particle
```javascript
class FlowParticle {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.prev = this.pos.copy();
this.maxSpeed = 2;
this.life = 1.0;
}
follow(field) {
let force = field.lookup(this.pos.x, this.pos.y);
force.mult(0.5); // force magnitude
this.acc.add(force);
}
update() {
this.prev = this.pos.copy();
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
this.life -= 0.001;
}
edges() {
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
this.prev = this.pos.copy(); // prevent wrap line
}
display(buffer) {
buffer.stroke(255, this.life * 30);
buffer.strokeWeight(0.5);
buffer.line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
}
}
```
## Particle Systems
### Basic Physics Particle
```javascript
class Particle {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D().mult(random(1, 3));
this.acc = createVector(0, 0);
this.life = 255;
this.decay = random(1, 5);
this.size = random(3, 8);
}
applyForce(f) { this.acc.add(f); }
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);
this.life -= this.decay;
}
display() {
noStroke();
fill(255, this.life);
ellipse(this.pos.x, this.pos.y, this.size);
}
isDead() { return this.life <= 0; }
}
```
### Attractor-Driven Particles
```javascript
class Attractor {
constructor(x, y, strength) {
this.pos = createVector(x, y);
this.strength = strength;
}
attract(particle) {
let force = p5.Vector.sub(this.pos, particle.pos);
let d = constrain(force.mag(), 5, 200);
force.normalize();
force.mult(this.strength / (d * d));
particle.applyForce(force);
}
}
```
### Boid Flocking
```javascript
class Boid {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D().mult(random(2, 4));
this.acc = createVector(0, 0);
this.maxForce = 0.2;
this.maxSpeed = 4;
this.perceptionRadius = 50;
}
flock(boids) {
let alignment = createVector(0, 0);
let cohesion = createVector(0, 0);
let separation = createVector(0, 0);
let total = 0;
for (let other of boids) {
let d = this.pos.dist(other.pos);
if (other !== this && d < this.perceptionRadius) {
alignment.add(other.vel);
cohesion.add(other.pos);
let diff = p5.Vector.sub(this.pos, other.pos);
diff.div(d * d);
separation.add(diff);
total++;
}
}
if (total > 0) {
alignment.div(total).setMag(this.maxSpeed).sub(this.vel).limit(this.maxForce);
cohesion.div(total).sub(this.pos).setMag(this.maxSpeed).sub(this.vel).limit(this.maxForce);
separation.div(total).setMag(this.maxSpeed).sub(this.vel).limit(this.maxForce);
}
this.acc.add(alignment.mult(1.0));
this.acc.add(cohesion.mult(1.0));
this.acc.add(separation.mult(1.5));
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
}
```
## Pixel Manipulation
### Reading and Writing Pixels
```javascript
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let idx = 4 * (y * width + x);
let r = pixels[idx];
let g = pixels[idx + 1];
let b = pixels[idx + 2];
let a = pixels[idx + 3];
// Modify
pixels[idx] = 255 - r; // invert red
pixels[idx + 1] = 255 - g; // invert green
pixels[idx + 2] = 255 - b; // invert blue
}
}
updatePixels();
```
### Pixel-Level Noise Texture
```javascript
loadPixels();
for (let i = 0; i < pixels.length; i += 4) {
let x = (i / 4) % width;
let y = floor((i / 4) / width);
let n = noise(x * 0.01, y * 0.01, frameCount * 0.02);
let c = n * 255;
pixels[i] = c;
pixels[i + 1] = c;
pixels[i + 2] = c;
pixels[i + 3] = 255;
}
updatePixels();
```
### Built-in Filters
```javascript
filter(BLUR, 3); // Gaussian blur (radius)
filter(THRESHOLD, 0.5); // Black/white threshold
filter(INVERT); // Color inversion
filter(POSTERIZE, 4); // Reduce color levels
filter(GRAY); // Desaturate
filter(ERODE); // Thin bright areas
filter(DILATE); // Expand bright areas
filter(OPAQUE); // Remove transparency
```
## Texture Generation
### Stippling / Pointillism
```javascript
function stipple(buffer, density, minSize, maxSize) {
buffer.loadPixels();
for (let i = 0; i < density; i++) {
let x = floor(random(width));
let y = floor(random(height));
let idx = 4 * (y * width + x);
let brightness = (buffer.pixels[idx] + buffer.pixels[idx+1] + buffer.pixels[idx+2]) / 3;
let size = map(brightness, 0, 255, maxSize, minSize);
if (random() < map(brightness, 0, 255, 0.8, 0.1)) {
noStroke();
fill(buffer.pixels[idx], buffer.pixels[idx+1], buffer.pixels[idx+2]);
ellipse(x, y, size);
}
}
}
```
### Halftone
```javascript
function halftone(sourceBuffer, dotSpacing, maxDotSize) {
sourceBuffer.loadPixels();
background(255);
fill(0);
noStroke();
for (let y = 0; y < height; y += dotSpacing) {
for (let x = 0; x < width; x += dotSpacing) {
let idx = 4 * (y * width + x);
let brightness = (sourceBuffer.pixels[idx] + sourceBuffer.pixels[idx+1] + sourceBuffer.pixels[idx+2]) / 3;
let dotSize = map(brightness, 0, 255, maxDotSize, 0);
ellipse(x + dotSpacing/2, y + dotSpacing/2, dotSize);
}
}
}
```
### Cross-Hatching
```javascript
function crossHatch(x, y, w, h, value, spacing) {
// value: 0 (dark) to 1 (light)
let numLayers = floor(map(value, 0, 1, 4, 0));
let angles = [PI/4, -PI/4, 0, PI/2];
for (let layer = 0; layer < numLayers; layer++) {
push();
translate(x + w/2, y + h/2);
rotate(angles[layer]);
let s = spacing + layer * 2;
for (let i = -max(w, h); i < max(w, h); i += s) {
line(i, -max(w, h), i, max(w, h));
}
pop();
}
}
```
## Feedback Loops
### Frame Feedback (Echo/Trail)
```javascript
let feedback;
function setup() {
createCanvas(800, 800);
feedback = createGraphics(width, height);
}
function draw() {
// Copy current feedback, slightly zoomed and rotated
let temp = feedback.get();
feedback.push();
feedback.translate(width/2, height/2);
feedback.scale(1.005); // slow zoom
feedback.rotate(0.002); // slow rotation
feedback.translate(-width/2, -height/2);
feedback.tint(255, 245); // slight fade
feedback.image(temp, 0, 0);
feedback.pop();
// Draw new content to feedback
feedback.noStroke();
feedback.fill(255);
feedback.ellipse(mouseX, mouseY, 20);
// Show
image(feedback, 0, 0);
}
```
### Bloom / Glow (Post-Processing)
Downsample the scene to a small buffer, blur it, overlay additively. Creates soft glow around bright areas. This is the standard generative art bloom technique.
```javascript
let scene, bloomBuf;
function setup() {
createCanvas(1080, 1080);
scene = createGraphics(width, height);
bloomBuf = createGraphics(width, height);
}
function draw() {
// 1. Render scene to offscreen buffer
scene.background(0);
scene.fill(255, 200, 100);
scene.noStroke();
// ... draw bright elements to scene ...
// 2. Build bloom: downsample → blur → upscale
bloomBuf.clear();
bloomBuf.image(scene, 0, 0, width / 4, height / 4); // 4x downsample
bloomBuf.filter(BLUR, 6); // blur the small version
// 3. Composite: scene + additive bloom
background(0);
image(scene, 0, 0); // base layer
blendMode(ADD); // additive = glow
tint(255, 80); // control bloom intensity (0-255)
image(bloomBuf, 0, 0, width, height); // upscale back to full size
noTint();
blendMode(BLEND); // ALWAYS reset blend mode
}
```
**Tuning:**
- Downsample ratio (1/4 is standard, 1/8 for softer, 1/2 for tighter)
- Blur radius (4-8 typical, higher = wider glow)
- Tint alpha (40-120, controls glow intensity)
- Update bloom every N frames to save perf: `if (frameCount % 2 === 0) { ... }`
**Common mistake:** Forgetting `blendMode(BLEND)` after the ADD pass — everything drawn after will be additive.
### Trail Buffer Brightness
Trail accumulation via `createGraphics()` + semi-transparent fade rect is the standard technique for particle trails, but **trails are always dimmer than you expect**. The fade rect's alpha compounds multiplicatively every frame.
```javascript
// The fade rect alpha controls trail length AND brightness:
trailBuf.fill(0, 0, 0, alpha);
trailBuf.rect(0, 0, width, height);
// alpha=5 → very long trails, very dim (content fades to 50% in ~35 frames)
// alpha=10 → long trails, dim
// alpha=20 → medium trails, visible
// alpha=40 → short trails, bright
// alpha=80 → very short trails, crisp
```
**The trap:** You set alpha=5 for long trails, but particle strokes at alpha=30 are invisible because they fade before accumulating enough density. Either:
- **Boost stroke alpha** to 80-150 (not the intuitive 20-40)
- **Reduce fade alpha** but accept shorter trails
- **Use additive blending** for the strokes: bright particles accumulate, dim ones stay dark
```javascript
// WRONG: low fade + low stroke = invisible
trailBuf.fill(0, 0, 0, 5); // long trails
trailBuf.rect(0, 0, W, H);
trailBuf.stroke(255, 30); // too dim to ever accumulate
trailBuf.line(px, py, x, y);
// RIGHT: low fade + high stroke = visible long trails
trailBuf.fill(0, 0, 0, 5);
trailBuf.rect(0, 0, W, H);
trailBuf.stroke(255, 100); // bright enough to persist through fade
trailBuf.line(px, py, x, y);
```
### Reaction-Diffusion (Gray-Scott)
```javascript
class ReactionDiffusion {
constructor(w, h) {
this.w = w;
this.h = h;
this.a = new Float32Array(w * h).fill(1);
this.b = new Float32Array(w * h).fill(0);
this.nextA = new Float32Array(w * h);
this.nextB = new Float32Array(w * h);
this.dA = 1.0;
this.dB = 0.5;
this.feed = 0.055;
this.kill = 0.062;
}
seed(cx, cy, r) {
for (let y = cy - r; y < cy + r; y++) {
for (let x = cx - r; x < cx + r; x++) {
if (dist(x, y, cx, cy) < r) {
let idx = y * this.w + x;
this.b[idx] = 1;
}
}
}
}
step() {
for (let y = 1; y < this.h - 1; y++) {
for (let x = 1; x < this.w - 1; x++) {
let idx = y * this.w + x;
let a = this.a[idx], b = this.b[idx];
let lapA = this.laplacian(this.a, x, y);
let lapB = this.laplacian(this.b, x, y);
let abb = a * b * b;
this.nextA[idx] = constrain(a + this.dA * lapA - abb + this.feed * (1 - a), 0, 1);
this.nextB[idx] = constrain(b + this.dB * lapB + abb - (this.kill + this.feed) * b, 0, 1);
}
}
[this.a, this.nextA] = [this.nextA, this.a];
[this.b, this.nextB] = [this.nextB, this.b];
}
laplacian(arr, x, y) {
let w = this.w;
return arr[(y-1)*w+x] + arr[(y+1)*w+x] + arr[y*w+(x-1)] + arr[y*w+(x+1)]
- 4 * arr[y*w+x];
}
}
```
## Pixel Sorting
```javascript
function pixelSort(buffer, threshold, direction = 'horizontal') {
buffer.loadPixels();
let px = buffer.pixels;
if (direction === 'horizontal') {
for (let y = 0; y < height; y++) {
let spans = findSpans(px, y, width, threshold, true);
for (let span of spans) {
sortSpan(px, span.start, span.end, y, true);
}
}
}
buffer.updatePixels();
}
function findSpans(px, row, w, threshold, horizontal) {
let spans = [];
let start = -1;
for (let i = 0; i < w; i++) {
let idx = horizontal ? 4 * (row * w + i) : 4 * (i * w + row);
let brightness = (px[idx] + px[idx+1] + px[idx+2]) / 3;
if (brightness > threshold && start === -1) {
start = i;
} else if (brightness <= threshold && start !== -1) {
spans.push({ start, end: i });
start = -1;
}
}
if (start !== -1) spans.push({ start, end: w });
return spans;
}
```
## Advanced Generative Techniques
### L-Systems (Lindenmayer Systems)
Grammar-based recursive growth for trees, plants, fractals.
```javascript
class LSystem {
constructor(axiom, rules) {
this.axiom = axiom;
this.rules = rules; // { 'F': 'F[+F]F[-F]F' }
this.sentence = axiom;
}
generate(iterations) {
for (let i = 0; i < iterations; i++) {
let next = '';
for (let ch of this.sentence) {
next += this.rules[ch] || ch;
}
this.sentence = next;
}
}
draw(len, angle) {
for (let ch of this.sentence) {
switch (ch) {
case 'F': line(0, 0, 0, -len); translate(0, -len); break;
case '+': rotate(angle); break;
case '-': rotate(-angle); break;
case '[': push(); break;
case ']': pop(); break;
}
}
}
}
// Usage: fractal plant
let lsys = new LSystem('X', {
'X': 'F+[[X]-X]-F[-FX]+X',
'F': 'FF'
});
lsys.generate(5);
translate(width/2, height);
lsys.draw(4, radians(25));
```
### Circle Packing
Fill a space with non-overlapping circles of varying size.
```javascript
class PackedCircle {
constructor(x, y, r) {
this.x = x; this.y = y; this.r = r;
this.growing = true;
}
grow() { if (this.growing) this.r += 0.5; }
overlaps(other) {
let d = dist(this.x, this.y, other.x, other.y);
return d < this.r + other.r + 2; // +2 gap
}
atEdge() {
return this.x - this.r < 0 || this.x + this.r > width ||
this.y - this.r < 0 || this.y + this.r > height;
}
}
let circles = [];
function packStep() {
// Try to place new circle
for (let attempts = 0; attempts < 100; attempts++) {
let x = random(width), y = random(height);
let valid = true;
for (let c of circles) {
if (dist(x, y, c.x, c.y) < c.r + 2) { valid = false; break; }
}
if (valid) { circles.push(new PackedCircle(x, y, 1)); break; }
}
// Grow existing circles
for (let c of circles) {
if (!c.growing) continue;
c.grow();
if (c.atEdge()) { c.growing = false; continue; }
for (let other of circles) {
if (c !== other && c.overlaps(other)) { c.growing = false; break; }
}
}
}
```
### Voronoi Diagram (Fortune's Algorithm Approximation)
```javascript
// Simple brute-force Voronoi (for small point counts)
function drawVoronoi(points, colors) {
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let minDist = Infinity;
let closest = 0;
for (let i = 0; i < points.length; i++) {
let d = (x - points[i].x) ** 2 + (y - points[i].y) ** 2; // magSq
if (d < minDist) { minDist = d; closest = i; }
}
let idx = 4 * (y * width + x);
let c = colors[closest % colors.length];
pixels[idx] = red(c);
pixels[idx+1] = green(c);
pixels[idx+2] = blue(c);
pixels[idx+3] = 255;
}
}
updatePixels();
}
```
### Fractal Trees
```javascript
function fractalTree(x, y, len, angle, depth, branchAngle) {
if (depth <= 0 || len < 2) return;
let x2 = x + Math.cos(angle) * len;
let y2 = y + Math.sin(angle) * len;
strokeWeight(map(depth, 0, 10, 0.5, 4));
line(x, y, x2, y2);
let shrink = 0.67 + noise(x * 0.01, y * 0.01) * 0.15;
fractalTree(x2, y2, len * shrink, angle - branchAngle, depth - 1, branchAngle);
fractalTree(x2, y2, len * shrink, angle + branchAngle, depth - 1, branchAngle);
}
// Usage
fractalTree(width/2, height, 120, -HALF_PI, 10, PI/6);
```
### Strange Attractors
```javascript
// Clifford Attractor
function cliffordAttractor(a, b, c, d, iterations) {
let x = 0, y = 0;
beginShape(POINTS);
for (let i = 0; i < iterations; i++) {
let nx = Math.sin(a * y) + c * Math.cos(a * x);
let ny = Math.sin(b * x) + d * Math.cos(b * y);
x = nx; y = ny;
let px = map(x, -3, 3, 0, width);
let py = map(y, -3, 3, 0, height);
vertex(px, py);
}
endShape();
}
// De Jong Attractor
function deJongAttractor(a, b, c, d, iterations) {
let x = 0, y = 0;
beginShape(POINTS);
for (let i = 0; i < iterations; i++) {
let nx = Math.sin(a * y) - Math.cos(b * x);
let ny = Math.sin(c * x) - Math.cos(d * y);
x = nx; y = ny;
let px = map(x, -2.5, 2.5, 0, width);
let py = map(y, -2.5, 2.5, 0, height);
vertex(px, py);
}
endShape();
}
```
### Poisson Disk Sampling
Even distribution that looks natural — better than pure random for placing elements.
```javascript
function poissonDiskSampling(r, k = 30) {
let cellSize = r / Math.sqrt(2);
let cols = Math.ceil(width / cellSize);
let rows = Math.ceil(height / cellSize);
let grid = new Array(cols * rows).fill(-1);
let points = [];
let active = [];
function gridIndex(x, y) {
return Math.floor(x / cellSize) + Math.floor(y / cellSize) * cols;
}
// Seed
let p0 = createVector(random(width), random(height));
points.push(p0);
active.push(p0);
grid[gridIndex(p0.x, p0.y)] = 0;
while (active.length > 0) {
let idx = Math.floor(Math.random() * active.length);
let pos = active[idx];
let found = false;
for (let n = 0; n < k; n++) {
let angle = Math.random() * TWO_PI;
let mag = r + Math.random() * r;
let sample = createVector(pos.x + Math.cos(angle) * mag, pos.y + Math.sin(angle) * mag);
if (sample.x < 0 || sample.x >= width || sample.y < 0 || sample.y >= height) continue;
let col = Math.floor(sample.x / cellSize);
let row = Math.floor(sample.y / cellSize);
let ok = true;
for (let dy = -2; dy <= 2; dy++) {
for (let dx = -2; dx <= 2; dx++) {
let nc = col + dx, nr = row + dy;
if (nc >= 0 && nc < cols && nr >= 0 && nr < rows) {
let gi = nc + nr * cols;
if (grid[gi] !== -1 && points[grid[gi]].dist(sample) < r) { ok = false; }
}
}
}
if (ok) {
points.push(sample);
active.push(sample);
grid[gridIndex(sample.x, sample.y)] = points.length - 1;
found = true;
break;
}
}
if (!found) active.splice(idx, 1);
}
return points;
}
```
## Addon Libraries
### p5.brush — Natural Media
Hand-drawn, organic aesthetics. Watercolor, charcoal, pen, marker. Requires **p5.js 2.x + WEBGL**.
```html
<script src="https://cdn.jsdelivr.net/npm/p5.brush@latest/dist/p5.brush.js"></script>
```
```javascript
function setup() {
createCanvas(1200, 1200, WEBGL);
brush.scaleBrushes(3); // essential for proper sizing
translate(-width/2, -height/2); // WEBGL origin is center
brush.pick('2B'); // pencil brush
brush.stroke(50, 50, 50);
brush.strokeWeight(2);
brush.line(100, 100, 500, 500);
brush.pick('watercolor');
brush.fill('#4a90d9', 150);
brush.circle(400, 400, 200);
}
```
Built-in brushes: `2B`, `HB`, `2H`, `cpencil`, `pen`, `rotring`, `spray`, `marker`, `charcoal`, `hatch_brush`.
Built-in vector fields: `hand`, `curved`, `zigzag`, `waves`, `seabed`, `spiral`, `columns`.
### p5.grain — Film Grain & Texture
```html
<script src="https://cdn.jsdelivr.net/npm/p5.grain@0.7.0/p5.grain.min.js"></script>
```
```javascript
function draw() {
// ... render scene ...
applyMonochromaticGrain(42); // uniform grain
// or: applyChromaticGrain(42); // per-channel randomization
}
```
### CCapture.js — Deterministic Video Capture
Records canvas at fixed framerate regardless of actual render speed. Essential for complex generative art.
```html
<script src="https://cdn.jsdelivr.net/npm/ccapture.js-npmfixed/build/CCapture.all.min.js"></script>
```
```javascript
let capturer;
function setup() {
createCanvas(1920, 1080);
capturer = new CCapture({
format: 'webm',
framerate: 60,
quality: 99,
// timeLimit: 10, // auto-stop after N seconds
// motionBlurFrames: 4 // supersampled motion blur
});
}
function startRecording() {
capturer.start();
}
function draw() {
// ... render frame ...
if (capturer) capturer.capture(document.querySelector('canvas'));
}
function stopRecording() {
capturer.stop();
capturer.save(); // triggers download
}
```

View File

@@ -0,0 +1,423 @@
# WebGL and 3D
## WebGL Mode Setup
```javascript
function setup() {
createCanvas(1920, 1080, WEBGL);
// Origin is CENTER, not top-left
// Y-axis points UP (opposite of 2D mode)
// Z-axis points toward viewer
}
```
### Coordinate Conversion (WEBGL to P2D-like)
```javascript
function draw() {
translate(-width/2, -height/2); // shift origin to top-left
// Now coordinates work like P2D
}
```
## 3D Primitives
```javascript
box(w, h, d); // rectangular prism
sphere(radius, detailX, detailY);
cylinder(radius, height, detailX, detailY);
cone(radius, height, detailX, detailY);
torus(radius, tubeRadius, detailX, detailY);
plane(width, height); // flat rectangle
ellipsoid(rx, ry, rz); // stretched sphere
```
### 3D Transforms
```javascript
push();
translate(x, y, z);
rotateX(angleX);
rotateY(angleY);
rotateZ(angleZ);
scale(s);
box(100);
pop();
```
## Camera
### Default Camera
```javascript
camera(
eyeX, eyeY, eyeZ, // camera position
centerX, centerY, centerZ, // look-at target
upX, upY, upZ // up direction
);
// Default: camera(0, 0, (height/2)/tan(PI/6), 0, 0, 0, 0, 1, 0)
```
### Orbit Control
```javascript
function draw() {
orbitControl(); // mouse drag to rotate, scroll to zoom
box(200);
}
```
### createCamera
```javascript
let cam;
function setup() {
createCanvas(800, 800, WEBGL);
cam = createCamera();
cam.setPosition(300, -200, 500);
cam.lookAt(0, 0, 0);
}
// Camera methods
cam.setPosition(x, y, z);
cam.lookAt(x, y, z);
cam.move(dx, dy, dz); // relative to camera orientation
cam.pan(angle); // horizontal rotation
cam.tilt(angle); // vertical rotation
cam.roll(angle); // z-axis rotation
cam.slerp(otherCam, t); // smooth interpolation between cameras
```
### Perspective and Orthographic
```javascript
// Perspective (default)
perspective(fov, aspect, near, far);
// fov: field of view in radians (PI/3 default)
// aspect: width/height
// near/far: clipping planes
// Orthographic (no depth foreshortening)
ortho(-width/2, width/2, -height/2, height/2, 0, 2000);
```
## Lighting
```javascript
// Ambient (uniform, no direction)
ambientLight(50, 50, 50); // dim fill light
// Directional (parallel rays, like sun)
directionalLight(255, 255, 255, 0, -1, 0); // color + direction
// Point (radiates from position)
pointLight(255, 200, 150, 200, -300, 400); // color + position
// Spot (cone from position toward target)
spotLight(255, 255, 255, // color
0, -300, 300, // position
0, 1, -1, // direction
PI / 4, 5); // angle, concentration
// Image-based lighting
imageLight(myHDRI);
// No lights (flat shading)
noLights();
// Quick default lighting
lights();
```
### Three-Point Lighting Setup
```javascript
function setupLighting() {
ambientLight(30, 30, 40); // dim blue fill
// Key light (main, warm)
directionalLight(255, 240, 220, -1, -1, -1);
// Fill light (softer, cooler, opposite side)
directionalLight(80, 100, 140, 1, -0.5, -1);
// Rim light (behind subject, for edge definition)
pointLight(200, 200, 255, 0, -200, -400);
}
```
## Materials
```javascript
// Normal material (debug — colors from surface normals)
normalMaterial();
// Ambient (responds only to ambientLight)
ambientMaterial(200, 100, 100);
// Emissive (self-lit, no shadows)
emissiveMaterial(255, 0, 100);
// Specular (shiny reflections)
specularMaterial(255);
shininess(50); // 1-200 (higher = tighter highlight)
metalness(100); // 0-200 (metallic reflection)
// Fill works too (no lighting response)
fill(255, 0, 0);
```
### Texture
```javascript
let img;
function preload() { img = loadImage('texture.jpg'); }
function draw() {
texture(img);
textureMode(NORMAL); // UV coords 0-1
// textureMode(IMAGE); // UV coords in pixels
textureWrap(REPEAT); // or CLAMP, MIRROR
box(200);
}
```
## Custom Geometry
### buildGeometry
```javascript
let myShape;
function setup() {
createCanvas(800, 800, WEBGL);
myShape = buildGeometry(() => {
for (let i = 0; i < 50; i++) {
push();
translate(random(-200, 200), random(-200, 200), random(-200, 200));
sphere(10);
pop();
}
});
}
function draw() {
model(myShape); // renders once-built geometry efficiently
}
```
### beginGeometry / endGeometry
```javascript
beginGeometry();
// draw shapes here
box(50);
translate(100, 0, 0);
sphere(30);
let geo = endGeometry();
model(geo); // reuse
```
### Manual Geometry (p5.Geometry)
```javascript
let geo = new p5.Geometry(detailX, detailY, function() {
for (let i = 0; i <= detailX; i++) {
for (let j = 0; j <= detailY; j++) {
let u = i / detailX;
let v = j / detailY;
let x = cos(u * TWO_PI) * (100 + 30 * cos(v * TWO_PI));
let y = sin(u * TWO_PI) * (100 + 30 * cos(v * TWO_PI));
let z = 30 * sin(v * TWO_PI);
this.vertices.push(createVector(x, y, z));
this.uvs.push(u, v);
}
}
this.computeFaces();
this.computeNormals();
});
```
## GLSL Shaders
### createShader (Vertex + Fragment)
```javascript
let myShader;
function setup() {
createCanvas(800, 800, WEBGL);
let vert = `
precision mediump float;
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
void main() {
vTexCoord = aTexCoord;
vec4 pos = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
gl_Position = pos;
}
`;
let frag = `
precision mediump float;
varying vec2 vTexCoord;
uniform float uTime;
uniform vec2 uResolution;
void main() {
vec2 uv = vTexCoord;
vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4));
gl_FragColor = vec4(col, 1.0);
}
`;
myShader = createShader(vert, frag);
}
function draw() {
shader(myShader);
myShader.setUniform('uTime', millis() / 1000.0);
myShader.setUniform('uResolution', [width, height]);
rect(0, 0, width, height);
resetShader();
}
```
### createFilterShader (Post-Processing)
Simpler — only needs a fragment shader. Automatically gets the canvas as a texture.
```javascript
let blurShader;
function setup() {
createCanvas(800, 800, WEBGL);
blurShader = createFilterShader(`
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D tex0;
uniform vec2 texelSize;
void main() {
vec4 sum = vec4(0.0);
for (int x = -2; x <= 2; x++) {
for (int y = -2; y <= 2; y++) {
sum += texture2D(tex0, vTexCoord + vec2(float(x), float(y)) * texelSize);
}
}
gl_FragColor = sum / 25.0;
}
`);
}
function draw() {
// Draw scene normally
background(0);
fill(255, 0, 0);
sphere(100);
// Apply post-processing filter
filter(blurShader);
}
```
### Common Shader Uniforms
```javascript
myShader.setUniform('uTime', millis() / 1000.0);
myShader.setUniform('uResolution', [width, height]);
myShader.setUniform('uMouse', [mouseX / width, mouseY / height]);
myShader.setUniform('uTexture', myGraphics); // pass p5.Graphics as texture
myShader.setUniform('uValue', 0.5); // float
myShader.setUniform('uColor', [1.0, 0.0, 0.5, 1.0]); // vec4
```
### Shader Recipes
**Chromatic Aberration:**
```glsl
vec4 r = texture2D(tex0, vTexCoord + vec2(0.005, 0.0));
vec4 g = texture2D(tex0, vTexCoord);
vec4 b = texture2D(tex0, vTexCoord - vec2(0.005, 0.0));
gl_FragColor = vec4(r.r, g.g, b.b, 1.0);
```
**Vignette:**
```glsl
float d = distance(vTexCoord, vec2(0.5));
float v = smoothstep(0.7, 0.4, d);
gl_FragColor = texture2D(tex0, vTexCoord) * v;
```
**Scanlines:**
```glsl
float scanline = sin(vTexCoord.y * uResolution.y * 3.14159) * 0.04;
vec4 col = texture2D(tex0, vTexCoord);
gl_FragColor = col - scanline;
```
## Framebuffers
```javascript
let fbo;
function setup() {
createCanvas(800, 800, WEBGL);
fbo = createFramebuffer();
}
function draw() {
// Render to framebuffer
fbo.begin();
clear();
rotateY(frameCount * 0.01);
box(200);
fbo.end();
// Use framebuffer as texture
texture(fbo.color);
plane(width, height);
}
```
### Multi-Pass Rendering
```javascript
let sceneBuffer, blurBuffer;
function setup() {
createCanvas(800, 800, WEBGL);
sceneBuffer = createFramebuffer();
blurBuffer = createFramebuffer();
}
function draw() {
// Pass 1: render scene
sceneBuffer.begin();
clear();
lights();
rotateY(frameCount * 0.01);
box(200);
sceneBuffer.end();
// Pass 2: blur
blurBuffer.begin();
shader(blurShader);
blurShader.setUniform('uTexture', sceneBuffer.color);
rect(0, 0, width, height);
resetShader();
blurBuffer.end();
// Final: composite
texture(blurBuffer.color);
plane(width, height);
}
```