72 lines
3.9 KiB
JavaScript
72 lines
3.9 KiB
JavaScript
|
|
/* ============================================================
|
||
|
|
qft-perspective-sweep.mjs — workshop the QFT cartesian/wavy grid
|
||
|
|
viewpoint. Isolates the cubic lattice (other fields off) and sweeps
|
||
|
|
the new camera: yaw / pitch / roll / perspective / distance.
|
||
|
|
Usage: node tools/qft-perspective-sweep.mjs [size]
|
||
|
|
============================================================ */
|
||
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
||
|
|
import { generateQFTScene } from '../src/qft/scene.js';
|
||
|
|
import { paramsFromSeed } from '../src/qft/params.js';
|
||
|
|
import { renderQFTSVG } from '../src/qft/renderer.js';
|
||
|
|
|
||
|
|
const SIZE = +(process.argv[2] || 1100);
|
||
|
|
const OUT = 'output/qft/perspective';
|
||
|
|
mkdirSync(OUT, { recursive: true });
|
||
|
|
const SEED = 'LATTICE-1003';
|
||
|
|
|
||
|
|
const F = (h0, h1, s, l, o, st) => ({ hueStart: h0, hueEnd: h1, saturation: s, lightness: l, opacity: o, stroke: st });
|
||
|
|
const OFF = F(0, 0, 0, 0.5, 0, 0);
|
||
|
|
|
||
|
|
// isolate the cartesian grid: cubic only, mid teal, thin-ish wavy photon edges
|
||
|
|
const BASE = {
|
||
|
|
substrate: 'cream', showHeader: false, glow: 0.16, vign: 0.12,
|
||
|
|
cubicN: 1, photonCyclesPerUnit: 7, segmentsPerEdge: 12, stroke: 1.3,
|
||
|
|
cubicScale: 1.05, cubicRot: 0, linkCount: 0, e8Count: 0,
|
||
|
|
fields: {
|
||
|
|
cubic: F(0.52, 0.57, 0.6, 0.34, 1.0, 1.3), schlegel: OFF, e8: OFF, ripple: OFF, links: OFF,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const D = Math.PI / 180;
|
||
|
|
const cam = (name, label, c) => ({ name, label, cam: c });
|
||
|
|
const SWEEP = [
|
||
|
|
// baseline isometric
|
||
|
|
cam('01_iso-default', 'isometric · default 3/4', { }),
|
||
|
|
// yaw (spin) at iso pitch
|
||
|
|
cam('02_yaw-front', 'yaw 0° · facing a face', { yaw: 0, pitch: 35 * D }),
|
||
|
|
cam('03_yaw-deep', 'yaw -70° · spun round', { yaw: -70 * D, pitch: 35 * D }),
|
||
|
|
// pitch (tip)
|
||
|
|
cam('04_pitch-low', 'pitch 12° · near eye-level', { yaw: -45 * D, pitch: 12 * D }),
|
||
|
|
cam('05_pitch-steep', 'pitch 58° · looking down', { yaw: -45 * D, pitch: 58 * D }),
|
||
|
|
cam('06_pitch-top', 'pitch 78° · near top-down', { yaw: -45 * D, pitch: 78 * D }),
|
||
|
|
// perspective (vanishing point) at the 3/4 angle
|
||
|
|
cam('07_persp-mild', 'perspective 0.45 · gentle depth', { persp: 0.45, dist: 4.2 }),
|
||
|
|
cam('08_persp-strong','perspective 0.9 · dist 2.6 · dramatic', { persp: 0.9, dist: 2.6 }),
|
||
|
|
cam('09_persp-corner','into a corner · 1-pt-ish', { yaw: 30 * D, pitch: 30 * D, persp: 0.7, dist: 3.0 }),
|
||
|
|
// roll / cant
|
||
|
|
cam('10_roll-cant', 'roll 18° · canted + persp 0.4', { roll: 18 * D, persp: 0.4, dist: 3.6 }),
|
||
|
|
// dramatic hero angles
|
||
|
|
cam('11_hero-tunnel', 'low + strong persp · tunnel', { yaw: -45 * D, pitch: 18 * D, persp: 0.95, dist: 2.3 }),
|
||
|
|
cam('12_hero-vault', 'steep + persp · vaulted ceiling', { yaw: -20 * D, pitch: 62 * D, persp: 0.8, dist: 2.8 }),
|
||
|
|
];
|
||
|
|
|
||
|
|
console.log(`Perspective sweep (${SWEEP.length}) → ${OUT}/ seed=${SEED}`);
|
||
|
|
for (const v of SWEEP) {
|
||
|
|
const p = {
|
||
|
|
...paramsFromSeed(SEED), ...BASE,
|
||
|
|
cubicYaw: v.cam.yaw ?? -45 * D, cubicPitch: v.cam.pitch ?? 35.26 * D,
|
||
|
|
cubicRoll: v.cam.roll ?? 0, cubicPersp: v.cam.persp ?? 0, cubicDist: v.cam.dist ?? 3.4,
|
||
|
|
};
|
||
|
|
writeFileSync(`${OUT}/${v.name}.svg`, renderQFTSVG(generateQFTScene(p), p, SIZE));
|
||
|
|
console.log(` ${v.name} — ${v.label}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const cap = (v) => `${v.name.replace(/^\d+_/, '').replace(/-/g, ' ')} · ${v.label}`;
|
||
|
|
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
|
||
|
|
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:10px;width:2100px}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:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
|
||
|
|
<div class="grid">
|
||
|
|
${SWEEP.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
|
||
|
|
</div></body></html>`;
|
||
|
|
writeFileSync(`${OUT}/m.html`, m);
|
||
|
|
console.log(`contact sheet -> ${OUT}/m.html`);
|