130 lines
8.5 KiB
JavaScript
130 lines
8.5 KiB
JavaScript
|
|
/* ============================================================
|
||
|
|
perspective3.mjs — 12 compositions built through the REFACTORED
|
||
|
|
grouped schema (renderComposition). Shows off what the refactor
|
||
|
|
unlocked: disk & collision decoupled (independent centre / rotation
|
||
|
|
/ scale), frame-level fiduciary arrows that span the frame to find a
|
||
|
|
distant event, and per-layer transforms on the fields themselves.
|
||
|
|
Usage: node tools/perspective3.mjs [size]
|
||
|
|
============================================================ */
|
||
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
||
|
|
import { renderComposition } from '../src/compose/composition.js';
|
||
|
|
|
||
|
|
const SIZE = +(process.argv[2] || 1500);
|
||
|
|
const OUT = 'output/layering/perspective3';
|
||
|
|
mkdirSync(OUT, { recursive: true });
|
||
|
|
const F = Math.PI * 2, D = Math.PI / 180;
|
||
|
|
|
||
|
|
const merge = (a, b) => {
|
||
|
|
const o = { ...a };
|
||
|
|
for (const k in b) o[k] = (b[k] && typeof b[k] === 'object' && !Array.isArray(b[k])) ? merge(a[k] || {}, b[k]) : b[k];
|
||
|
|
return o;
|
||
|
|
};
|
||
|
|
|
||
|
|
const BASE = {
|
||
|
|
size: SIZE, seed: 'MESON-5113',
|
||
|
|
background: { color: 'rgb(229,222,203)', film: { opacity: 0.6, density: 0.5, seed: 8 }, aging: { opacity: 0.5, scratches: 5, dust: 0.45, foxing: 0.5, seed: 5 }, grain: { opacity: 0.42, intensity: 0.42, seed: 19 } },
|
||
|
|
fieldSea: { enabled: true, transform: { x: 0, y: 0, rotation: 0, scale: 1 }, seed: 'VACUUM-5113', color: { hueBack: 0.54, hueFront: 0.47, sat: 0.6 }, layers: 3, blurPerLayer: [2.6, 1.1, 0], chaos: 0.3, blips: 0.7, mound: 0.3, horizonY: 0.36, lines: 46 },
|
||
|
|
fieldGrid: { enabled: true, pos: 'over', transform: { x: 0, y: 0, rotation: 0, scale: 1 }, color: { hue: 0.56, hue2: 0.5, sat: 0.26, lightNear: 0.44, lightFar: 0.66 }, opacity: 0.38, vp: [0, 0], dir: 0, spread: F, rays: 30, depthLines: 17, rMin: 0.04, rMax: 3.0, ripple: { amp: 0.07, ampRad: 0.06, freqR: 2.4, freqA: 5, phase: 0 }, layers: 2, blurPerLayer: [1.1, 0] },
|
||
|
|
disk: { enabled: true, transform: { x: 0, y: -0.25, rotation: 0, scale: 0.95 }, hue: 0.06, sat: 0.82, size: 0.16, pressure: 0.85 },
|
||
|
|
bubble: { enabled: true, transform: { x: 0.28, y: -0.1, rotation: 12, scale: 0.82 }, palette: 'magentarise', saturation: 1.05, primaries: 18, sweepers: 5, eloss: 0.34 },
|
||
|
|
fiduciaries: { enabled: true, label: 'No 001', pencil: '#39312a', width: 1.0, arrow: true },
|
||
|
|
};
|
||
|
|
const mk = (name, over) => ({ name, comp: merge(BASE, over) });
|
||
|
|
|
||
|
|
const VARIATIONS = [
|
||
|
|
// 1 — the WITNESS and the EVENT: a quiet sun, the collision flung to a far
|
||
|
|
// corner; the hand's arrow has to reach all the way across to name it.
|
||
|
|
mk('01_witness-and-event', {
|
||
|
|
disk: { transform: { x: -0.05, y: -0.3, scale: 0.62 }, hue: 0.08 },
|
||
|
|
bubble: { transform: { x: 0.52, y: 0.44, rotation: -8, scale: 0.5 } },
|
||
|
|
fiduciaries: { from: [-0.02, -0.22], label: 'No 001', caption: 'good event' },
|
||
|
|
}),
|
||
|
|
// 2 — ECLIPSE: an enormous low-pressure sun, the event tucked at its rim.
|
||
|
|
mk('02_eclipse', {
|
||
|
|
disk: { transform: { x: 0, y: -0.06, scale: 1.55 }, hue: 0.0, sat: 0.9, size: 0.18, pressure: 0.95 },
|
||
|
|
bubble: { transform: { x: 0.34, y: -0.16, scale: 0.46 } },
|
||
|
|
fieldGrid: { opacity: 0.3, vp: [0, -0.05] },
|
||
|
|
}),
|
||
|
|
// 3 — INVERSION: sun sunk into the sea, the collision risen high above it.
|
||
|
|
mk('03_sunk-sun-risen-event', {
|
||
|
|
disk: { transform: { x: -0.02, y: 0.4, scale: 0.8 }, hue: 0.05 },
|
||
|
|
bubble: { transform: { x: 0.05, y: -0.46, rotation: 20, scale: 0.7 } },
|
||
|
|
fieldGrid: { pos: 'behind', opacity: 0.34 },
|
||
|
|
}),
|
||
|
|
// 4 — TILTED ARCHIVE: the whole event canted 32°; studied with corners + caption.
|
||
|
|
mk('04_tilted-archive', {
|
||
|
|
disk: { transform: { x: -0.32, y: -0.22, scale: 0.66 }, hue: 0.11 },
|
||
|
|
bubble: { transform: { x: 0.22, y: 0.0, rotation: 32, scale: 0.82 } },
|
||
|
|
fiduciaries: { corners: true, caption: 'check θ', from: [0.5, -0.35] },
|
||
|
|
}),
|
||
|
|
// 5 — VORTEX PULL: the grid is a whirlpool; the collision caught at its mouth,
|
||
|
|
// a small far sun looking on.
|
||
|
|
mk('05_vortex-pull', {
|
||
|
|
fieldGrid: { vp: [0.08, -0.04], color: { hue: 0.5, hue2: 0.55, sat: 0.34, lightNear: 0.42, lightFar: 0.64 }, opacity: 0.46, ripple: { amp: 0.11, ampRad: 0.09, freqR: 2.9, freqA: 7, phase: 0.8 }, rMax: 2.6, rays: 34 },
|
||
|
|
disk: { transform: { x: -0.42, y: -0.34, scale: 0.55 }, hue: 0.95 },
|
||
|
|
bubble: { transform: { x: 0.08, y: -0.03, scale: 0.72 } },
|
||
|
|
}),
|
||
|
|
// 6 — GRID ASKEW: the depth grid itself rotated + zoomed (per-layer transform).
|
||
|
|
mk('06_grid-askew', {
|
||
|
|
fieldGrid: { transform: { x: 0.1, y: -0.05, rotation: 16, scale: 1.15 }, vp: [-0.35, -0.2], spread: 90 * D, dir: 35 * D, rMax: 3.6, color: { hue: 0.6, hue2: 0.6, sat: 0.1, lightNear: 0.46, lightFar: 0.66 } },
|
||
|
|
disk: { transform: { x: 0.05, y: -0.26, scale: 0.8 }, hue: 0.06 },
|
||
|
|
bubble: { transform: { x: -0.18, y: 0.06, rotation: -10, scale: 0.78 } },
|
||
|
|
}),
|
||
|
|
// 7 — CORNER DAWN: a rose sun in the upper-left, the event mid-frame, long reach.
|
||
|
|
mk('07_corner-dawn-rose', {
|
||
|
|
disk: { transform: { x: -0.56, y: -0.52, scale: 0.5 }, hue: 0.95, sat: 0.7, pressure: 0.5 },
|
||
|
|
bubble: { transform: { x: 0.16, y: 0.08, scale: 0.86 } },
|
||
|
|
fiduciaries: { from: [-0.5, -0.4] },
|
||
|
|
}),
|
||
|
|
// 8 — SCALE RHYME: two small foci mirrored across the frame — one teal sun,
|
||
|
|
// one event rotated 180°, the rhyme across the void.
|
||
|
|
mk('08_scale-rhyme-mirror', {
|
||
|
|
disk: { transform: { x: -0.42, y: -0.12, scale: 0.5 }, hue: 0.5, sat: 0.6 },
|
||
|
|
bubble: { transform: { x: 0.42, y: 0.12, rotation: 180, scale: 0.5 } },
|
||
|
|
fieldGrid: { opacity: 0.3 },
|
||
|
|
}),
|
||
|
|
// 9 — MEASURED DAWN: classic — sun on the horizon, studied event, registration
|
||
|
|
// corners, a faint cool grid. The archive at its calmest.
|
||
|
|
mk('09_measured-dawn', {
|
||
|
|
disk: { transform: { x: 0, y: -0.26, scale: 0.72 }, hue: 0.07 },
|
||
|
|
bubble: { transform: { x: 0.3, y: -0.08, rotation: 6, scale: 0.74 } },
|
||
|
|
fiduciaries: { corners: true, caption: 'good event' },
|
||
|
|
fieldGrid: { opacity: 0.32 },
|
||
|
|
}),
|
||
|
|
// 10 — COLD CATHEDRAL: a violet vaulting grid from on high (behind), a teal
|
||
|
|
// sun, a verdigris sea. The numinous, gone cold.
|
||
|
|
mk('10_cold-cathedral', {
|
||
|
|
fieldSea: { color: { hueBack: 0.46, hueFront: 0.42, sat: 0.42 }, chaos: 0.28 },
|
||
|
|
fieldGrid: { pos: 'behind', vp: [0, -0.66], dir: 95 * D, spread: 150 * D, color: { hue: 0.7, hue2: 0.66, sat: 0.3, lightNear: 0.44, lightFar: 0.66 }, opacity: 0.5, ripple: { amp: 0.1, ampRad: 0.08, freqR: 3.0, freqA: 8, phase: 1.2 } },
|
||
|
|
disk: { transform: { x: 0.05, y: -0.24, scale: 0.78 }, hue: 0.5, sat: 0.6 },
|
||
|
|
bubble: { transform: { x: -0.22, y: -0.06, scale: 0.8 } },
|
||
|
|
}),
|
||
|
|
// 11 — GIANT GRID, TINY EVENT: the coordinate-system dwarfs the collision;
|
||
|
|
// the field zoomed huge, a small ember far off.
|
||
|
|
mk('11_giant-grid-tiny-event', {
|
||
|
|
fieldGrid: { transform: { scale: 1.4, x: -0.1 }, opacity: 0.42, vp: [0.15, 0.05], ripple: { amp: 0.09, ampRad: 0.07, freqR: 2.6, freqA: 6, phase: 0.4 } },
|
||
|
|
disk: { transform: { x: 0.46, y: -0.42, scale: 0.42 }, hue: 0.0, pressure: 0.9 },
|
||
|
|
bubble: { transform: { x: 0.4, y: -0.3, scale: 0.4 } },
|
||
|
|
fiduciaries: { from: [0.1, -0.2] },
|
||
|
|
}),
|
||
|
|
// 12 — DISSOLVING COORDINATES: the grid a warm near-paper whisper to the edges;
|
||
|
|
// the event the only firm thing. (white pencil — backlit-intent, faint unlit.)
|
||
|
|
mk('12_dissolving-coordinates', {
|
||
|
|
fieldGrid: { color: { hue: 0.1, hue2: 0.08, sat: 0.16, lightNear: 0.5, lightFar: 0.68 }, opacity: 0.26, rMax: 3.7, ripple: { amp: 0.12, ampRad: 0.1, freqR: 3.1, freqA: 7, phase: 1.0 } },
|
||
|
|
disk: { transform: { x: 0, y: -0.24, scale: 0.74 }, hue: 0.04, pressure: 0.85 },
|
||
|
|
bubble: { transform: { x: 0.26, y: -0.1, scale: 0.78 } },
|
||
|
|
fiduciaries: { pencil: '#f4efe6', caption: 'No 001' },
|
||
|
|
}),
|
||
|
|
];
|
||
|
|
|
||
|
|
console.log(`perspective3 — ${VARIATIONS.length} schema-built compositions → ${OUT}/`);
|
||
|
|
for (const v of VARIATIONS) {
|
||
|
|
writeFileSync(`${OUT}/${v.name}.svg`, renderComposition(v.comp, SIZE));
|
||
|
|
console.log(` ${v.name}`);
|
||
|
|
}
|
||
|
|
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
|
||
|
|
writeFileSync(`${OUT}/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>perspective3 · schema-built · decoupled</title>
|
||
|
|
<style>html,body{margin:0;background:#1a1a1a}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
||
|
|
<div class="grid">${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
|
||
|
|
console.log(`contact sheet -> ${OUT}/m.html`);
|