refinement
This commit is contained in:
110
src/scene/media.js
Normal file
110
src/scene/media.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/* ============================================================
|
||||
media.js — the physical-artifact & human layer.
|
||||
What a real bubble-chamber frame carried beyond the tracks:
|
||||
• grease-pencil (chinagraph) hand marks — a scanner ringing a
|
||||
"good event", an arrow, the event number, an angle/ticks;
|
||||
• film furniture — sprocket holes, a data box (roll/frame/
|
||||
date), edge stock printing, a frame rebate;
|
||||
• a réseau — the precise grid of small crosses imaged for
|
||||
distortion correction;
|
||||
• a tape splice — long rolls were cut and joined, so a strip
|
||||
is literally taped together, often slightly misregistered.
|
||||
All seeded. Geometry in the logical [-1,1] square.
|
||||
============================================================ */
|
||||
import { gauss, pick, chance } from '../rng.js';
|
||||
|
||||
const TWO_PI = Math.PI * 2;
|
||||
|
||||
function wobblyRing(cx, cy, r, rng, wob = 0.07) {
|
||||
const n = 52, pts = [];
|
||||
const start = rng() * TWO_PI;
|
||||
const turn = TWO_PI * (1.04 + rng() * 0.13); // overshoots — doesn't close cleanly
|
||||
const dx = gauss(rng) * r * 0.05, dy = gauss(rng) * r * 0.05;
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const a = start + turn * (i / n);
|
||||
const rr = r * (1 + gauss(rng) * wob);
|
||||
pts.push({ x: cx + Math.cos(a) * rr + dx * (i / n), y: cy + Math.sin(a) * rr + dy * (i / n) });
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
function wobblyLine(x1, y1, x2, y2, rng, seg = 9, amp = 0.006) {
|
||||
const pts = [];
|
||||
for (let i = 0; i <= seg; i++) {
|
||||
const t = i / seg;
|
||||
pts.push({ x: x1 + (x2 - x1) * t + gauss(rng) * amp, y: y1 + (y2 - y1) * t + gauss(rng) * amp });
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
export function generateMedia(params, scene, rng) {
|
||||
const out = { grease: [], reseau: null, film: null, splice: null };
|
||||
|
||||
/* ---- grease-pencil (chinagraph) hand marks ---- */
|
||||
if ((params.annotate ?? 0) > 0) {
|
||||
const full = params.annotate >= 0.66; // restrained vs studied
|
||||
const v = scene.vertex || { x: 0, y: 0 };
|
||||
// ring a δ-ray curl if there is one, else the vertex region
|
||||
const deltas = scene.tracks.filter(t => t.kind === 'delta' && t.pts.length > 6);
|
||||
let cx, cy, cr;
|
||||
if (deltas.length && chance(rng, 0.75)) {
|
||||
const d = pick(rng, deltas), p = d.pts[Math.floor(d.pts.length * 0.4)];
|
||||
cx = p.x; cy = p.y; cr = 0.05 + rng() * 0.035;
|
||||
} else { cx = v.x + gauss(rng) * 0.05; cy = v.y + gauss(rng) * 0.05; cr = 0.11 + rng() * 0.05; }
|
||||
out.grease.push({ kind: 'ring', pts: wobblyRing(cx, cy, cr, rng), width: 2.4 });
|
||||
|
||||
// arrow from open space toward the ring
|
||||
const side = cx <= 0 ? 1 : -1;
|
||||
const ax = cx + side * (0.34 + rng() * 0.12), ay = cy - (0.30 + rng() * 0.12);
|
||||
const tipx = cx - side * cr * 1.25, tipy = cy - cr * 1.15;
|
||||
const ang = Math.atan2(tipy - ay, tipx - ax);
|
||||
out.grease.push({ kind: 'arrow', shaft: wobblyLine(ax, ay, tipx, tipy, rng), tip: { x: tipx, y: tipy, ang }, width: 2.0 });
|
||||
|
||||
// event number by the ring
|
||||
out.grease.push({ kind: 'text', x: cx + cr * 1.05, y: cy - cr * 1.05, text: `Nº ${scene.plate}`, size: 0.045, rot: -0.05 + gauss(rng) * 0.03 });
|
||||
|
||||
if (full) {
|
||||
// angle arc + reading at the vertex
|
||||
const a0 = rng() * TWO_PI, a1 = a0 + (0.5 + rng() * 0.6);
|
||||
out.grease.push({ kind: 'arc', x: v.x, y: v.y, r: 0.12 + rng() * 0.05, a0, a1, width: 1.6 });
|
||||
out.grease.push({ kind: 'text', x: v.x + 0.15, y: v.y + 0.03, text: `${Math.round((a1 - a0) * 57)}°`, size: 0.034, rot: 0 });
|
||||
// a few scale ticks along a primary track
|
||||
const prim = scene.tracks.find(t => t.kind === 'primary' && t.pts.length > 30);
|
||||
if (prim) {
|
||||
for (let k = 0; k < 4; k++) {
|
||||
const p = prim.pts[Math.floor(prim.pts.length * (0.3 + k * 0.15))];
|
||||
const th = (p.theta ?? 0) + Math.PI / 2, tl = 0.018;
|
||||
out.grease.push({ kind: 'tick', x1: p.x - Math.cos(th) * tl, y1: p.y - Math.sin(th) * tl, x2: p.x + Math.cos(th) * tl, y2: p.y + Math.sin(th) * tl, width: 1.2 });
|
||||
}
|
||||
}
|
||||
// a margin scrawl
|
||||
out.grease.push({ kind: 'text', x: -0.92, y: 0.9, text: pick(rng, ['good event', 'check θ', 'V⁰ ?', 'measure', 're-scan']), size: 0.04, rot: 0.04 });
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- réseau grid (distortion-correction crosses) ---- */
|
||||
if ((params.reseau ?? 0) > 0) {
|
||||
const step = 0.1818, marks = [];
|
||||
for (let gx = -0.9; gx <= 0.901; gx += step)
|
||||
for (let gy = -0.9; gy <= 0.901; gy += step) marks.push({ x: gx, y: gy });
|
||||
out.reseau = { marks, size: 0.011, opacity: 0.32 * params.reseau };
|
||||
}
|
||||
|
||||
/* ---- film furniture ---- */
|
||||
if (params.filmEdge) {
|
||||
const n = 9, sprockets = [];
|
||||
for (let i = 0; i < n; i++) sprockets.push(-0.9 + 1.8 * (i / (n - 1)));
|
||||
const roll = (parseInt(scene.hash.slice(0, 4), 16) % 900 + 100);
|
||||
out.film = {
|
||||
sprockets,
|
||||
edgeText: 'KODAK SAFETY FILM',
|
||||
dataBox: [`ROLL ${roll}`, `FRAME ${scene.plate}`, scene.exposure],
|
||||
};
|
||||
}
|
||||
|
||||
/* ---- tape splice ---- */
|
||||
if (params.splice) {
|
||||
out.splice = { y: -0.05 + gauss(rng) * 0.28, h: 0.05 + rng() * 0.025, tilt: gauss(rng) * 0.02, opacity: 0.16 + rng() * 0.06 };
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export function paramsFromSeed(seed) {
|
||||
invert: true, palette: 'mono', saturation: 1.0, // canonical look is B&W; colour is opt-in
|
||||
hueShift: 0, hueCycles: 3, diskSpectrum: 0, halo: 0, haloHue: 0.55,
|
||||
paperTone: 'cream', toneStrength: 1.0, paperBright: 1.0, glow: 0.5,
|
||||
annotate: 0, reseau: 0, filmEdge: false, splice: false, // media & hand layer (opt-in)
|
||||
};
|
||||
|
||||
if (arch === 'dense') {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { spawnVDecay } from './vdecay.js';
|
||||
import { generateShock } from './shock.js';
|
||||
import { generateArtifacts } from './artifacts.js';
|
||||
import { generateInstrument } from './instrument.js';
|
||||
import { generateMedia } from './media.js';
|
||||
import { cyrb53 } from '../rng.js';
|
||||
|
||||
const LABS = ['BEBC · CERN', 'GARGAMELLE · CERN', '2m HBC · CERN', '82" HBC · SLAC', 'MIRABELLE · IHEP'];
|
||||
@@ -144,5 +145,8 @@ export function generateScene(params) {
|
||||
const exposure = `${year}.${String(month).padStart(2, '0')}.${String(day).padStart(2, '0')}`;
|
||||
const lab = pick(makeRng(params.seed, 'lab'), LABS);
|
||||
|
||||
return { tracks, vertex: fgVertex, shock, artifacts, instrument, hash, plate, exposure, lab };
|
||||
const out = { tracks, vertex: fgVertex, shock, artifacts, instrument, hash, plate, exposure, lab };
|
||||
// physical-artifact & human layer (grease pencil, film furniture, réseau, splice)
|
||||
out.media = generateMedia(params, out, makeRng(params.seed, 'media'));
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user