PRE-REFINEMENTS
This commit is contained in:
112
src/render/stack.js
Normal file
112
src/render/stack.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/* ============================================================
|
||||
stack.js — split one scene across the printable planes of a
|
||||
physical acrylic stack (front + back of several sheets, air
|
||||
gaps, backlit). The scene model's per-track z and age stop
|
||||
being opacity tricks and become actual millimetres.
|
||||
|
||||
A PLAN describes the piece:
|
||||
{ id, seed, size, geometry:{thickness,gap}, params:{...},
|
||||
planes: [ { sheet:'A', face:'back', label,
|
||||
tracks: false | true | {kinds, z:[lo,hi], age:[lo,hi]},
|
||||
disk, boundary, optics, damage, // flags
|
||||
media: false | true | {reseau,grease,film,splice},
|
||||
fiducials, header, // flags
|
||||
params: {...per-plane overrides},
|
||||
fx: { blur, opacity, scale } }, ... ] }
|
||||
|
||||
The scene is generated ONCE from the merged params; each plane
|
||||
renders a filtered copy through the untouched renderSVG with
|
||||
transparentPaper (the backlight is the paper). Bloom/core face
|
||||
pairs must use IDENTICAL track selectors and equal density/size
|
||||
so their salted bubble draws match — same filtered list, same
|
||||
positions; only fx differ.
|
||||
|
||||
Per-plane depth/aging default LOW: the physical stack now
|
||||
carries depth; double-encoding it as opacity fights the medium.
|
||||
============================================================ */
|
||||
import { generateScene } from '../scene/scene.js';
|
||||
import { paramsFromSeed } from '../scene/params.js';
|
||||
import { renderSVG } from './svgVector.js';
|
||||
|
||||
const inRange = (v, r) => !r || (v >= r[0] && v <= r[1]);
|
||||
|
||||
function filterTracks(tracks, sel) {
|
||||
if (!sel) return [];
|
||||
if (sel === true) return tracks;
|
||||
return tracks.filter(t =>
|
||||
(!sel.kinds || sel.kinds.includes(t.kind)) &&
|
||||
inRange(t.z ?? 0, sel.z) &&
|
||||
inRange(t.age ?? 0, sel.age));
|
||||
}
|
||||
|
||||
function filterMedia(media, sel) {
|
||||
if (!media || !sel) return null;
|
||||
if (sel === true) return media;
|
||||
return {
|
||||
reseau: sel.reseau ? media.reseau : null,
|
||||
grease: sel.grease ? media.grease : [],
|
||||
film: sel.film ? media.film : null,
|
||||
splice: sel.splice ? media.splice : null,
|
||||
};
|
||||
}
|
||||
|
||||
/* wrap a rendered plane in optional blur / opacity / centre-scale
|
||||
(blur is in u = px per 1000; scale is for deliberate moiré offsets) */
|
||||
function applyFx(svg, fx, size) {
|
||||
const { blur = 0, opacity = 1, scale = 1 } = fx || {};
|
||||
if (!blur && opacity === 1 && scale === 1) return svg;
|
||||
const u = size / 1000, c = size / 2;
|
||||
const filter = blur
|
||||
? `<filter id="planefx" x="-15%" y="-15%" width="130%" height="130%"><feGaussianBlur stdDeviation="${(blur * u).toFixed(2)}"/></filter>`
|
||||
: '';
|
||||
const g = `<defs>${filter}</defs><g${blur ? ' filter="url(#planefx)"' : ''}`
|
||||
+ `${opacity !== 1 ? ` opacity="${opacity}"` : ''}`
|
||||
+ `${scale !== 1 ? ` transform="translate(${c} ${c}) scale(${scale}) translate(${-c} ${-c})"` : ''}>`;
|
||||
return svg.replace(/(<svg[^>]*>)/, `$1\n${g}`).replace(/<\/svg>\s*$/, '</g></svg>\n');
|
||||
}
|
||||
|
||||
export function renderStack(plan) {
|
||||
const base = { ...paramsFromSeed(plan.seed), ...(plan.params || {}), invert: true };
|
||||
const scene = generateScene(base);
|
||||
const size = plan.size || 1400;
|
||||
|
||||
const planes = plan.planes.map((plane) => {
|
||||
const emit = [];
|
||||
if (plane.disk) emit.push('disk');
|
||||
if (plane.tracks || plane.optics || plane.damage || plane.boundary) emit.push('bubble');
|
||||
if (plane.media || plane.fiducials || plane.header) emit.push('fiduciaries');
|
||||
|
||||
const ps = {
|
||||
...base,
|
||||
transparentPaper: true, vign: 0,
|
||||
showBoundary: !!plane.boundary,
|
||||
showFiducials: !!plane.fiducials,
|
||||
showHeader: !!plane.header,
|
||||
depth: 0.15, aging: 0.2, // the stack is the depth model now
|
||||
emit,
|
||||
...(plane.params || {}),
|
||||
};
|
||||
const sc = {
|
||||
...scene,
|
||||
tracks: filterTracks(scene.tracks, plane.tracks ?? false),
|
||||
shock: plane.disk ? scene.shock : null,
|
||||
instrument: plane.optics ? scene.instrument : null,
|
||||
artifacts: plane.damage ? scene.artifacts : null,
|
||||
media: filterMedia(scene.media, plane.media),
|
||||
};
|
||||
const svg = applyFx(renderSVG(sc, ps, size), plane.fx, size);
|
||||
return { sheet: plane.sheet, face: plane.face, label: plane.label || '', svg };
|
||||
});
|
||||
|
||||
return { plan, scene, planes };
|
||||
}
|
||||
|
||||
/* physical z (mm) of each plane from stack geometry; sheet order = first
|
||||
appearance in the plan (back of the stack first) */
|
||||
export function planeDepths(plan) {
|
||||
const t = plan.geometry?.thickness ?? 3;
|
||||
const g = plan.geometry?.gap ?? 6;
|
||||
const sheets = [...new Set(plan.planes.map(p => p.sheet))];
|
||||
return plan.planes.map(p =>
|
||||
sheets.indexOf(p.sheet) * (t + g) + (p.face === 'front' ? t : 0));
|
||||
}
|
||||
Reference in New Issue
Block a user