initial remote

This commit is contained in:
2026-05-20 17:10:32 -04:00
parent dd138d5c4f
commit 3983cf2e0d
18 changed files with 553606 additions and 272 deletions

View File

@@ -135,7 +135,7 @@ export function renderCanvasPhoto(ctx, w, h, scene, params, opts = {}) {
ic.globalAlpha = 1;
// 3c. shock disk drawn into the ink layer so bloom catches it
if (scene.shock) drawShock(ic, scene.shock, tx, ty, scale, u, P);
if (scene.shock) drawShock(ic, scene.shock, tx, ty, scale, u, P, params, sprite);
/* ---------- Pass 4: halation / bloom ---------- */
// composite a blurred copy of the ink under the sharp ink for soft spread
@@ -195,51 +195,63 @@ export function renderCanvasPhoto(ctx, w, h, scene, params, opts = {}) {
}
/* ---- shock disk ---- */
function drawShock(c, shock, tx, ty, scale, u, P) {
function drawShock(c, shock, tx, ty, scale, u, P, params, sprite) {
const [r, g, b] = P.ink;
const px = tx(shock.x), py = ty(shock.y), R = shock.r * scale;
const useBubbles = params.diskBubbles !== false;
// disk body: dark annulus that keeps a lighter, detailed centre
// disk body: dark annulus that keeps a lighter, detailed centre.
// softer when the line work is bubbles (the bubbles carry the density).
const bodyK = useBubbles ? 0.6 : 1.0;
const core = c.createRadialGradient(px, py, 0, px, py, R);
core.addColorStop(0.0, `rgba(${r},${g},${b},${0.06 * shock.intensity})`);
core.addColorStop(0.35, `rgba(${r},${g},${b},${0.18 * shock.intensity})`);
core.addColorStop(0.72, `rgba(${r},${g},${b},${0.38 * shock.intensity})`);
core.addColorStop(0.94, `rgba(${r},${g},${b},${0.34 * shock.intensity})`);
core.addColorStop(0.0, `rgba(${r},${g},${b},${0.06 * shock.intensity * bodyK})`);
core.addColorStop(0.35, `rgba(${r},${g},${b},${0.18 * shock.intensity * bodyK})`);
core.addColorStop(0.72, `rgba(${r},${g},${b},${0.38 * shock.intensity * bodyK})`);
core.addColorStop(0.94, `rgba(${r},${g},${b},${0.34 * shock.intensity * bodyK})`);
core.addColorStop(1, `rgba(${r},${g},${b},0)`);
c.fillStyle = core;
c.beginPath(); c.arc(px, py, R, 0, Math.PI * 2); c.fill();
// radial striations (the sunburst)
c.lineCap = 'round';
for (const s of shock.striations) {
const ix = px + Math.cos(s.a) * s.inner * scale;
const iy = py + Math.sin(s.a) * s.inner * scale;
const ox = px + Math.cos(s.a) * s.outer * scale;
const oy = py + Math.sin(s.a) * s.outer * scale;
const mx = (ix + ox) / 2 + Math.cos(s.a + Math.PI / 2) * s.wobble * scale;
const my = (iy + oy) / 2 + Math.sin(s.a + Math.PI / 2) * s.wobble * scale;
c.strokeStyle = `rgba(${r},${g},${b},${s.opacity})`;
c.lineWidth = s.width * u;
c.beginPath();
c.moveTo(ix, iy);
c.quadraticCurveTo(mx, my, ox, oy);
c.stroke();
}
// inner pressure-front rings
for (const ring of shock.rings) {
c.strokeStyle = `rgba(${r},${g},${b},${ring.opacity})`;
c.lineWidth = ring.width * u;
c.beginPath(); c.arc(px, py, ring.rr * scale, 0, Math.PI * 2); c.stroke();
}
// rim as eroded arc segments
if (shock.rimSegs) {
for (const seg of shock.rimSegs) {
if (useBubbles && shock.bubbleStrokes) {
// describe striations / rings / rim / core with the particle method
const dRng = makeRng(params.seed, 'diskbubbles');
for (const stroke of shock.bubbleStrokes) {
const bubs = sampleBubbles(stroke, params, dRng);
c.globalAlpha = Math.min(1, 0.45 + stroke.weight * 0.5);
for (const bb of bubs) {
const rr = Math.max(bb.r * scale, 0.45);
const d = rr * 2.4;
c.drawImage(sprite, tx(bb.x) - d / 2, ty(bb.y) - d / 2, d, d);
}
}
c.globalAlpha = 1;
} else {
// legacy: clean vector strokes
c.lineCap = 'round';
for (const s of shock.striations) {
const ix = px + Math.cos(s.a) * s.inner * scale, iy = py + Math.sin(s.a) * s.inner * scale;
const ox = px + Math.cos(s.a) * s.outer * scale, oy = py + Math.sin(s.a) * s.outer * scale;
const mx = (ix + ox) / 2 + Math.cos(s.a + Math.PI / 2) * s.wobble * scale;
const my = (iy + oy) / 2 + Math.sin(s.a + Math.PI / 2) * s.wobble * scale;
c.strokeStyle = `rgba(${r},${g},${b},${s.opacity})`;
c.lineWidth = s.width * u;
c.beginPath(); c.moveTo(ix, iy); c.quadraticCurveTo(mx, my, ox, oy); c.stroke();
}
for (const ring of shock.rings) {
c.strokeStyle = `rgba(${r},${g},${b},${ring.opacity})`;
c.lineWidth = ring.width * u;
c.beginPath(); c.arc(px, py, ring.rr * scale, 0, Math.PI * 2); c.stroke();
}
for (const seg of (shock.rimSegs || [])) {
c.strokeStyle = `rgba(${r},${g},${b},${seg.opacity})`;
c.lineWidth = seg.width * u;
c.beginPath(); c.arc(px, py, shock.r * scale, seg.a0, seg.a1); c.stroke();
}
for (const k of shock.core) {
c.strokeStyle = `rgba(${r},${g},${b},${k.opacity})`;
c.lineWidth = k.width * u;
c.beginPath(); c.moveTo(tx(k.x1), ty(k.y1)); c.lineTo(tx(k.x2), ty(k.y2)); c.stroke();
}
}
// staining blotches: dark = grime, light = lifted/washed clean spots
@@ -263,13 +275,6 @@ function drawShock(c, shock, tx, ty, scale, u, P) {
c.globalCompositeOperation = 'source-over';
}
// textured core chords
for (const k of shock.core) {
c.strokeStyle = `rgba(${r},${g},${b},${k.opacity})`;
c.lineWidth = k.width * u;
c.beginPath(); c.moveTo(tx(k.x1), ty(k.y1)); c.lineTo(tx(k.x2), ty(k.y2)); c.stroke();
}
// keep a bright, detailed centre by clearing a soft hole in the ink
if (shock.bright) {
c.save();