Initial
This commit is contained in:
27
tools/gallery.sh
Executable file
27
tools/gallery.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Render a curated set of plates into output/ — tuned toward the CERN reference.
|
||||
# Usage: tools/gallery.sh [size_px] (default 2000)
|
||||
set -e
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SIZE="${1:-1600}"
|
||||
OUT="$ROOT/output"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
# A reference-leaning custom parameter string (busy, multi-scale, big disk, fan lines)
|
||||
REF="primaries=22&burst=0.85&vdecay=4&cosmics=9&sweepers=5&bfield=1.2&eloss=0.6&pspread=0.78&deltaRate=0.92&deltaTight=0.85&shockIntensity=0.88&shockSize=0.33&shockStriations=0.78&shockY=0.5&instrument=0.5&bgEvents=6&bgIntensity=0.45&density=1.28&size=1.0&bloom=0.55&mottle=0.6&grain=0.5&vign=0.5&artifacts=0.72"
|
||||
|
||||
render () { # name query
|
||||
"$ROOT/tools/shoot.sh" "$OUT/$1.png" "$2&size_px=$SIZE" >/dev/null
|
||||
echo " output/$1.png"
|
||||
}
|
||||
|
||||
echo "Rendering plates → output/ (${SIZE}px)…"
|
||||
render "01_bebc-archival-1973" "preset=BEBC%20Archival"
|
||||
render "02_bebc-gargamelle" "preset=BEBC%20Archival&seed=GARGAMELLE-4471"
|
||||
render "03_dense-chaos-omega" "preset=Dense%20Chaos&seed=OMEGA-7782"
|
||||
render "04_cosmic-sheet" "preset=Cosmic%20Sheet"
|
||||
render "05_ref-match-strange" "$REF&seed=STRANGE-1187"
|
||||
render "06_ref-match-hyperon" "$REF&seed=HYPERON-0440"
|
||||
render "07_ref-match-kaon" "$REF&seed=KAON-2291&shockY=0.46&shockSize=0.36"
|
||||
render "08_negative-plate" "preset=Negative%20Plate&seed=GLASS-NEG-77"
|
||||
echo "done."
|
||||
52
tools/inspire.sh
Executable file
52
tools/inspire.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Inspiration randomizer: generate N plates from random seeds, each named by its
|
||||
# seed, plus an HTML contact sheet to browse them. Because every parameter is
|
||||
# derived from the seed, picking a favourite just means noting its seed and
|
||||
# running: tools/render.sh <SEED>
|
||||
#
|
||||
# Usage: tools/inspire.sh [count] [thumb_px]
|
||||
# tools/inspire.sh 32 600
|
||||
set -e
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
COUNT="${1:-24}"
|
||||
SIZE="${2:-600}"
|
||||
OUT="$ROOT/output/inspiration"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
WORDS=(MUON KAON PION LAMBDA SIGMA XI OMEGA TAU GLUON QUARK HADRON BARYON LEPTON \
|
||||
NEUTRINO BOSON STRANGE CHARM HYPERON ANTIPROTON POSITRON MESON FERMION NUCLEON \
|
||||
ISOSPIN CASCADE RESONANCE PARITY GLUEBALL PENTAQUARK PHOTON)
|
||||
|
||||
SEEDS_FILE="$OUT/seeds.txt"
|
||||
: > "$SEEDS_FILE"
|
||||
|
||||
echo "Generating $COUNT inspiration plates (${SIZE}px) → output/inspiration/"
|
||||
for ((i = 1; i <= COUNT; i++)); do
|
||||
W=${WORDS[$RANDOM % ${#WORDS[@]}]}
|
||||
N=$((RANDOM % 9000 + 1000))
|
||||
SEED="$W-$N"
|
||||
"$ROOT/tools/shoot.sh" "$OUT/$SEED.png" "fromseed=1&seed=$SEED&size_px=$SIZE" >/dev/null
|
||||
echo "$SEED" >> "$SEEDS_FILE"
|
||||
printf " [%2d/%2d] %s\n" "$i" "$COUNT" "$SEED"
|
||||
done
|
||||
|
||||
# Build the contact sheet
|
||||
SHEET="$OUT/index.html"
|
||||
{
|
||||
echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Bubble Chamber — inspiration</title>'
|
||||
echo '<style>body{margin:0;background:#111;color:#bbb;font:12px/1.4 ui-monospace,Menlo,monospace;padding:24px}'
|
||||
echo 'h1{font-weight:400;letter-spacing:.2em;text-transform:uppercase;font-size:13px;color:#d4a574}'
|
||||
echo '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:18px;margin-top:18px}'
|
||||
echo 'figure{margin:0;background:#000;border:1px solid #2a2a2a}img{width:100%;display:block}'
|
||||
echo 'figcaption{padding:6px 8px;display:flex;justify-content:space-between;align-items:center}'
|
||||
echo '.s{color:#e8e4d8;letter-spacing:.05em}.c{color:#555;user-select:all}</style></head><body>'
|
||||
echo "<h1>Bubble Chamber · inspiration · $(date +%Y-%m-%d)</h1>"
|
||||
echo '<p>Click a seed to select it, then run <span class="c">tools/render.sh <SEED></span> for the print master.</p>'
|
||||
echo '<div class="grid">'
|
||||
while IFS= read -r S; do
|
||||
echo "<figure><img src=\"$S.png\" loading=\"lazy\"><figcaption><span class=\"s\">$S</span><span class=\"c\">render.sh $S</span></figcaption></figure>"
|
||||
done < "$SEEDS_FILE"
|
||||
echo '</div></body></html>'
|
||||
} > "$SHEET"
|
||||
|
||||
echo "done. open: output/inspiration/index.html"
|
||||
40
tools/preview.html
Normal file
40
tools/preview.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"><style>html,body{margin:0;background:#000}canvas{display:block}</style></head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<script type="module">
|
||||
import { generateScene } from '../src/scene/scene.js';
|
||||
import { renderCanvasPhoto } from '../src/render/canvasPhoto.js';
|
||||
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
|
||||
import { paramsFromSeed } from '../src/scene/params.js';
|
||||
|
||||
const q = new URLSearchParams(location.search);
|
||||
|
||||
// fromseed=1 : derive the ENTIRE parameter set deterministically from the seed
|
||||
let params;
|
||||
if (q.get('fromseed') === '1') {
|
||||
params = { ...FIXED, ...paramsFromSeed(q.get('seed') || 'ENTROPY-001') };
|
||||
} else {
|
||||
params = { ...FIXED, seed: 'ENTROPY-001' };
|
||||
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
|
||||
for (const t of TOGGLES) params[t.id] = t.value;
|
||||
if (q.get('preset') && PRESETS[q.get('preset')]) Object.assign(params, PRESETS[q.get('preset')]);
|
||||
}
|
||||
if (q.get('seed')) params.seed = q.get('seed');
|
||||
for (const [k, v] of q) {
|
||||
if (k === 'seed' || k === 'preset' || k === 'size_px') continue;
|
||||
if (k in params) params[k] = (v === 'true') ? true : (v === 'false') ? false : (isNaN(+v) ? v : +v);
|
||||
}
|
||||
|
||||
const SIZE = +(q.get('size_px') || 1200);
|
||||
const c = document.getElementById('c');
|
||||
c.width = c.height = SIZE;
|
||||
const ctx = c.getContext('2d', { willReadFrequently: true });
|
||||
const scene = generateScene(params);
|
||||
renderCanvasPhoto(ctx, SIZE, SIZE, scene, params, { preview: q.get('hq') !== '1' });
|
||||
document.title = 'done';
|
||||
window.__done = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
33
tools/render-pdf.mjs
Normal file
33
tools/render-pdf.mjs
Normal file
@@ -0,0 +1,33 @@
|
||||
/* Render a scene to a print-ready CMYK PDF from the CLI.
|
||||
Two modes:
|
||||
node tools/render-pdf.mjs --seed SEED [outfile] [pagePt] (seed → all params)
|
||||
node tools/render-pdf.mjs [preset] [seed] [outfile] (legacy preset mode)
|
||||
*/
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { generateScene } from '../src/scene/scene.js';
|
||||
import { buildPDF } from '../src/render/pdf.js';
|
||||
import { paramsFromSeed } from '../src/scene/params.js';
|
||||
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
let params, out, page = 1728;
|
||||
|
||||
if (argv[0] === '--seed') {
|
||||
const seed = argv[1] || 'ENTROPY-001';
|
||||
params = { ...FIXED, ...paramsFromSeed(seed) };
|
||||
out = argv[2];
|
||||
if (argv[3]) page = +argv[3];
|
||||
} else {
|
||||
const [preset, seed, o] = argv;
|
||||
params = { ...FIXED, seed: seed || 'ENTROPY-001' };
|
||||
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
|
||||
for (const t of TOGGLES) params[t.id] = t.value;
|
||||
if (preset && PRESETS[preset]) Object.assign(params, PRESETS[preset]);
|
||||
if (seed) params.seed = seed;
|
||||
out = o;
|
||||
}
|
||||
|
||||
const pdf = buildPDF(generateScene(params), params, page);
|
||||
const file = out || `/tmp/bc-${params.seed}.pdf`;
|
||||
writeFileSync(file, pdf);
|
||||
console.log(`PDF -> ${file} (${(pdf.length / 1024).toFixed(0)} KB)`);
|
||||
33
tools/render-svg.mjs
Normal file
33
tools/render-svg.mjs
Normal file
@@ -0,0 +1,33 @@
|
||||
/* Render a scene to SVG from the CLI (shares the scene module).
|
||||
Two modes:
|
||||
node tools/render-svg.mjs --seed SEED [outfile] [sizePx] (seed → all params)
|
||||
node tools/render-svg.mjs [preset] [seed] [outfile] (legacy preset mode)
|
||||
*/
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { generateScene } from '../src/scene/scene.js';
|
||||
import { renderSVG } from '../src/render/svgVector.js';
|
||||
import { paramsFromSeed } from '../src/scene/params.js';
|
||||
import { GROUPS, TOGGLES, FIXED, PRESETS } from '../src/ui/controls.js';
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
let params, out, size = 4800;
|
||||
|
||||
if (argv[0] === '--seed') {
|
||||
const seed = argv[1] || 'ENTROPY-001';
|
||||
params = { ...FIXED, ...paramsFromSeed(seed) };
|
||||
out = argv[2];
|
||||
if (argv[3]) size = +argv[3];
|
||||
} else {
|
||||
const [preset, seed, o] = argv;
|
||||
params = { ...FIXED, seed: seed || 'ENTROPY-001' };
|
||||
for (const g of GROUPS) for (const c of g.controls) params[c.id] = c.value;
|
||||
for (const t of TOGGLES) params[t.id] = t.value;
|
||||
if (preset && PRESETS[preset]) Object.assign(params, PRESETS[preset]);
|
||||
if (seed) params.seed = seed;
|
||||
out = o;
|
||||
}
|
||||
|
||||
const svg = renderSVG(generateScene(params), params, size);
|
||||
const file = out || `/tmp/bc-${params.seed}.svg`;
|
||||
writeFileSync(file, svg);
|
||||
console.log(`SVG -> ${file} (${(svg.length / 1024).toFixed(0)} KB)`);
|
||||
29
tools/render.sh
Executable file
29
tools/render.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Print-master renderer for a chosen seed. Produces, into output/masters/:
|
||||
# <SEED>.svg vector geometry — INFINITE resolution, the true master
|
||||
# <SEED>.pdf vector CMYK — print-shop ready, 24" page
|
||||
# <SEED>_<size>.png photographic — carries grain / bloom / stain
|
||||
#
|
||||
# Every parameter derives from the seed, so this is identical to the inspiration
|
||||
# thumbnail of the same seed, just at full resolution.
|
||||
#
|
||||
# Usage: tools/render.sh <SEED> [png_px]
|
||||
# tools/render.sh STRANGENESS-7
|
||||
# tools/render.sh STRANGENESS-7 12000
|
||||
set -e
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SEED="$1"
|
||||
SIZE="${2:-9000}"
|
||||
if [ -z "$SEED" ]; then echo "usage: tools/render.sh <SEED> [png_px]"; exit 1; fi
|
||||
OUT="$ROOT/output/masters"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
echo "Rendering master for seed: $SEED"
|
||||
echo " • vector geometry (infinite res)…"
|
||||
node "$ROOT/tools/render-svg.mjs" --seed "$SEED" "$OUT/$SEED.svg" 6000 | sed 's/^/ /'
|
||||
echo " • vector CMYK PDF (print shop)…"
|
||||
node "$ROOT/tools/render-pdf.mjs" --seed "$SEED" "$OUT/$SEED.pdf" | sed 's/^/ /'
|
||||
echo " • photographic raster (${SIZE}px)…"
|
||||
"$ROOT/tools/shoot.sh" "$OUT/${SEED}_${SIZE}.png" "fromseed=1&seed=$SEED&size_px=$SIZE" | sed 's/^/ /'
|
||||
|
||||
echo "done → output/masters/ ($SEED .svg/.pdf/_${SIZE}.png)"
|
||||
37
tools/shoot.sh
Executable file
37
tools/shoot.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Render the artwork headlessly for the compare loop.
|
||||
# Usage: tools/shoot.sh [outfile] [query]
|
||||
# tools/shoot.sh /tmp/bc.png "preset=BEBC%20Archival&size_px=1200"
|
||||
# tools/shoot.sh /tmp/bc.png "seed=KAON-0088&shockIntensity=0.9"
|
||||
set -e
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT="${1:-/tmp/bc.png}"
|
||||
QUERY="${2:-size_px=1200}"
|
||||
PORT=8731
|
||||
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
|
||||
# start a static server if not already up
|
||||
if ! curl -s "http://localhost:$PORT/tools/preview.html" >/dev/null 2>&1; then
|
||||
(cd "$ROOT" && python3 -m http.server "$PORT" >/tmp/bc_server.log 2>&1 &)
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# pull size for the window
|
||||
SIZE=$(echo "$QUERY" | sed -n 's/.*size_px=\([0-9]*\).*/\1/p'); SIZE="${SIZE:-1200}"
|
||||
|
||||
# scale the virtual-time budget with resolution; cap with a hard watchdog so a
|
||||
# slow/hung render can never block forever.
|
||||
BUDGET=4000; WATCHDOG=45
|
||||
if [ "$SIZE" -ge 1800 ]; then BUDGET=8000; WATCHDOG=90; fi
|
||||
|
||||
"$CHROME" --headless=new --disable-gpu --hide-scrollbars \
|
||||
--force-device-scale-factor=1 --virtual-time-budget="$BUDGET" \
|
||||
--run-all-compositor-stages-before-draw \
|
||||
--screenshot="$OUT" --window-size="$SIZE,$SIZE" \
|
||||
"http://localhost:$PORT/tools/preview.html?$QUERY" 2>/dev/null &
|
||||
CPID=$!
|
||||
( sleep "$WATCHDOG"; kill -9 "$CPID" 2>/dev/null ) & WPID=$!
|
||||
wait "$CPID" 2>/dev/null
|
||||
kill "$WPID" 2>/dev/null || true
|
||||
|
||||
if [ -f "$OUT" ]; then echo "shot -> $OUT ($QUERY)"; else echo "FAILED -> $OUT ($QUERY)"; exit 1; fi
|
||||
Reference in New Issue
Block a user