web interface
This commit is contained in:
@@ -557,8 +557,11 @@ function generateScene(params) {
|
||||
const rng = makeRng(params.seed, 'scene');
|
||||
const tracks = [];
|
||||
|
||||
// Foreground event, slightly off-centre — near the focal plane, current trigger
|
||||
const fgVertex = { x: (rng() - 0.5) * 0.3, y: (rng() - 0.5) * 0.3 };
|
||||
// Foreground event, slightly off-centre — near the focal plane, current trigger.
|
||||
// eventX/eventY (in [-1,1] canvas coords) override the seed placement for
|
||||
// deliberate composition; rng() is still consumed so the rest stays deterministic.
|
||||
const rvx = (rng() - 0.5) * 0.3, rvy = (rng() - 0.5) * 0.3;
|
||||
const fgVertex = { x: params.eventX ?? rvx, y: params.eventY ?? rvy };
|
||||
const fgZ = gauss(rng) * 0.12;
|
||||
tracks.push(...generateOneEvent(params, fgVertex, 1.0, 'fg', fgZ, 0));
|
||||
|
||||
@@ -1199,6 +1202,7 @@ function generateMedia(params, scene, rng) {
|
||||
/* ---- grease-pencil (chinagraph) hand marks ---- */
|
||||
if ((params.annotate ?? 0) > 0) {
|
||||
const full = params.annotate >= 0.66; // restrained vs studied
|
||||
const aw = params.annoWidth ?? 1; // line-weight multiplier (thinner = <1)
|
||||
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);
|
||||
@@ -1207,19 +1211,22 @@ function generateMedia(params, scene, rng) {
|
||||
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 });
|
||||
if (params.annoRing !== false) out.grease.push({ kind: 'ring', pts: wobblyRing(cx, cy, cr, rng), width: 2.4 * aw });
|
||||
|
||||
// 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 });
|
||||
// arrow from open space toward the ring/find
|
||||
if (params.annoArrow !== false) {
|
||||
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 * aw });
|
||||
}
|
||||
|
||||
// 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 });
|
||||
// event number by the ring/find
|
||||
if (params.annoNum !== false)
|
||||
out.grease.push({ kind: 'text', x: cx + cr * 1.05, y: cy - cr * 1.05, text: params.annoLabel ?? `Nº ${scene.plate}`, size: 0.045, rot: -0.05 + gauss(rng) * 0.03 });
|
||||
|
||||
if (full) {
|
||||
if (full && params.annoExtras !== false) {
|
||||
// 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 });
|
||||
@@ -2383,6 +2390,9 @@ function renderSVG(scene, params, sizePx = 4800) {
|
||||
const paperRGB = pal.paper(); // {flat,glowIn,glowOut} rgb arrays
|
||||
const paperC = { flat: rgbHex(paperRGB.flat), glowIn: rgbHex(paperRGB.glowIn), glowOut: rgbHex(paperRGB.glowOut) };
|
||||
const ink = rgbHex(pal.feature()); // non-particle marks (optics, disk, damage, header)
|
||||
// diskPressure: a darker, denser core (compressed gas = higher pressure)
|
||||
const press = Math.max(0, Math.min(1, params.diskPressure ?? 0));
|
||||
const coreInk = rgbHex(mix(pal.feature(), [0, 0, 0], press * 0.7));
|
||||
const featKey = rgbKey(pal.feature());
|
||||
const colorMap = new Map([[featKey, pal.feature()]]); // distinct bubble colours → gradients
|
||||
|
||||
@@ -2391,7 +2401,9 @@ function renderSVG(scene, params, sizePx = 4800) {
|
||||
content ? `<g id="${id}" inkscape:groupmode="layer" inkscape:label="${attr(label)}" style="display:inline"${extra ? ' ' + extra : ''}>\n${content}\n</g>\n` : '';
|
||||
|
||||
/* ---------- Background ---------- */
|
||||
const bg = `<rect width="${w}" height="${h}" fill="${paperC.flat}"/>\n<rect width="${w}" height="${h}" fill="url(#paper)"/>`;
|
||||
// transparentPaper: omit paper + vignette so the event can float as an object
|
||||
// over another layer (e.g. the QFT carpet) without a paper rectangle.
|
||||
const bg = params.transparentPaper ? '' : `<rect width="${w}" height="${h}" fill="${paperC.flat}"/>\n<rect width="${w}" height="${h}" fill="url(#paper)"/>`;
|
||||
|
||||
/* ---------- Chamber optics ---------- */
|
||||
let optics = '';
|
||||
@@ -2545,7 +2557,7 @@ function renderSVG(scene, params, sizePx = 4800) {
|
||||
}
|
||||
|
||||
/* ---------- Vignette ---------- */
|
||||
const vign = params.vign > 0 ? `<rect width="${w}" height="${h}" fill="url(#vign)"/>` : '';
|
||||
const vign = (params.vign > 0 && !params.transparentPaper) ? `<rect width="${w}" height="${h}" fill="url(#vign)"/>` : '';
|
||||
|
||||
/* ---------- Header ---------- */
|
||||
let header = '';
|
||||
@@ -2597,7 +2609,8 @@ function renderSVG(scene, params, sizePx = 4800) {
|
||||
media += `</g>`;
|
||||
}
|
||||
if (M.grease && M.grease.length) {
|
||||
const ch = inv ? '#9c1e1e' : '#f0e296';
|
||||
// annotateInk overrides the chinagraph colour (e.g. white pencil)
|
||||
const ch = params.annotateInk || (inv ? '#9c1e1e' : '#f0e296');
|
||||
let g = `<g stroke="${ch}" fill="${ch}" stroke-linecap="round" stroke-linejoin="round">`;
|
||||
for (const gm of M.grease) {
|
||||
if (gm.kind === 'ring' || gm.kind === 'arrow') {
|
||||
@@ -2627,21 +2640,25 @@ function renderSVG(scene, params, sizePx = 4800) {
|
||||
let s = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
||||
s += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n`;
|
||||
s += `<metadata>Bubble Chamber · seed=${params.seed} · hash=${cyrb53(params.seed)} · palette=${params.palette || 'mono'}</metadata>\n`;
|
||||
s += defs({ paperC, ink, baseVign: pal.vign(), params, u, colorMap });
|
||||
s += layer('background', 'Background', bg);
|
||||
s += layer('optics', 'Chamber optics', optics);
|
||||
s += layer('shock', 'Shock disk', shock, params.diskSoften > 0 ? 'filter="url(#soften)"' : '');
|
||||
s += trackLayers;
|
||||
s += layer('damage', 'Plate damage', damage);
|
||||
s += layer('fiducials', 'Fiducials', fids);
|
||||
s += layer('vignette', 'Vignette', vign);
|
||||
s += layer('header', 'Archival header', header);
|
||||
s += layer('media', 'Media & hand', media);
|
||||
s += defs({ paperC, ink, coreInk, press, baseVign: pal.vign(), params, u, colorMap });
|
||||
// emit: optional array of layer-GROUPS to include, so the plate can be rendered
|
||||
// as decoupled sub-layers — 'background' | 'disk' | 'bubble' | 'fiduciaries'.
|
||||
const emit = params.emit;
|
||||
const keep = (gp) => !emit || emit.includes(gp);
|
||||
if (keep('background')) s += layer('background', 'Background', bg);
|
||||
if (keep('bubble')) s += layer('optics', 'Chamber optics', optics);
|
||||
if (keep('disk')) s += layer('shock', 'Shock disk', shock, params.diskSoften > 0 ? 'filter="url(#soften)"' : '');
|
||||
if (keep('bubble')) s += trackLayers;
|
||||
if (keep('bubble')) s += layer('damage', 'Plate damage', damage);
|
||||
if (keep('fiduciaries')) s += layer('fiducials', 'Fiducials', fids);
|
||||
if (keep('background')) s += layer('vignette', 'Vignette', vign);
|
||||
if (keep('fiduciaries')) s += layer('header', 'Archival header', header);
|
||||
if (keep('fiduciaries')) s += layer('media', 'Media & hand', media);
|
||||
s += `</svg>\n`;
|
||||
return s;
|
||||
}
|
||||
|
||||
function defs({ paperC, ink, baseVign, params, u, colorMap }) {
|
||||
function defs({ paperC, ink, coreInk, press = 0, baseVign, params, u, colorMap }) {
|
||||
const soften = params.diskSoften > 0
|
||||
? `<filter id="soften" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="${(params.diskSoften * u).toFixed(2)}"/></filter>`
|
||||
: '';
|
||||
@@ -2663,8 +2680,9 @@ function defs({ paperC, ink, baseVign, params, u, colorMap }) {
|
||||
</radialGradient>
|
||||
${bubGrads}
|
||||
<radialGradient id="shockcore" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="${ink}" stop-opacity="0.5"/>
|
||||
<stop offset="60%" stop-color="${ink}" stop-opacity="0.28"/>
|
||||
<stop offset="0%" stop-color="${coreInk || ink}" stop-opacity="${(0.5 + press * 0.45).toFixed(2)}"/>
|
||||
<stop offset="28%" stop-color="${coreInk || ink}" stop-opacity="${(0.32 + press * 0.32).toFixed(2)}"/>
|
||||
<stop offset="62%" stop-color="${ink}" stop-opacity="0.28"/>
|
||||
<stop offset="100%" stop-color="${ink}" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="shockstain" cx="50%" cy="50%" r="50%">
|
||||
|
||||
Reference in New Issue
Block a user