61 lines
2.8 KiB
JavaScript
61 lines
2.8 KiB
JavaScript
|
|
/* ============================================================
|
||
|
|
qft/palette.js — per-field hue gradients + substrate variants.
|
||
|
|
Each FIELD (one lattice) is parameterised as a slider:
|
||
|
|
hueStart, hueEnd, saturation, lightness, opacity
|
||
|
|
The renderer asks for the colour at a t∈[0,1] along an edge (or
|
||
|
|
at an (x,y) position when the gradient mode is radial/linear).
|
||
|
|
"Hue gradient over time" = per-edge progression, the QFT analog
|
||
|
|
of the bubble-chamber lifecycle palette.
|
||
|
|
|
||
|
|
Self-contained: no dependency on the bubble-chamber palette
|
||
|
|
registry. Reuses only the generic hslToRgb util.
|
||
|
|
============================================================ */
|
||
|
|
import { hslToRgb } from '../render/palette.js';
|
||
|
|
|
||
|
|
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
||
|
|
|
||
|
|
/* Substrate variants — the "chemistry" the lattices sit on. */
|
||
|
|
export const SUBSTRATES = {
|
||
|
|
vintage: { paper: { flat: [42, 32, 22], glowIn: [62, 48, 32], glowOut: [22, 16, 10] },
|
||
|
|
vign: [10, 7, 4], feature: [178, 138, 78] },
|
||
|
|
void: { paper: { flat: [10, 10, 14], glowIn: [22, 22, 30], glowOut: [3, 3, 6] },
|
||
|
|
vign: [0, 0, 0], feature: [150, 150, 180] },
|
||
|
|
cream: { paper: { flat: [207, 200, 180], glowIn: [226, 219, 199], glowOut: [179, 170, 146] },
|
||
|
|
vign: [56, 46, 32], feature: [42, 30, 20] },
|
||
|
|
selenium: { paper: { flat: [190, 181, 197], glowIn: [210, 201, 217], glowOut: [156, 146, 166] },
|
||
|
|
vign: [42, 38, 54], feature: [58, 50, 72] },
|
||
|
|
cyanotype:{ paper: { flat: [22, 48, 82], glowIn: [38, 70, 110], glowOut: [10, 22, 44] },
|
||
|
|
vign: [6, 12, 22], feature: [180, 210, 235] },
|
||
|
|
};
|
||
|
|
|
||
|
|
export function resolveSubstrate(id = 'vintage') {
|
||
|
|
return SUBSTRATES[id] || SUBSTRATES.vintage;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Sample the gradient parameter s ∈ [0, 1] for a point on an edge.
|
||
|
|
- along-edge: s = t (the per-edge t the renderer passes in)
|
||
|
|
- radial: s = min(1, |(x, y)|) (centre → 0, corners → 1)
|
||
|
|
- linear-x: s = (x + 1) / 2 (left → 0, right → 1)
|
||
|
|
- linear-y: s = (y + 1) / 2 (top → 0, bottom → 1) */
|
||
|
|
export function gradientS(t, x, y, mode = 'along-edge') {
|
||
|
|
switch (mode) {
|
||
|
|
case 'radial': return clamp(Math.hypot(x, y), 0, 1);
|
||
|
|
case 'linear-x': return clamp((x + 1) / 2, 0, 1);
|
||
|
|
case 'linear-y': return clamp((y + 1) / 2, 0, 1);
|
||
|
|
case 'along-edge':
|
||
|
|
default: return clamp(t, 0, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Compute the colour for a field at gradient parameter s. Hue rotates from
|
||
|
|
hueStart → hueEnd by the SHORT way around the circle (so a small Δ stays
|
||
|
|
small even when crossing 1.0). */
|
||
|
|
export function fieldColor(field, s) {
|
||
|
|
let dh = field.hueEnd - field.hueStart;
|
||
|
|
// wrap to shortest-path delta (-0.5 .. +0.5)
|
||
|
|
if (dh > 0.5) dh -= 1;
|
||
|
|
if (dh < -0.5) dh += 1;
|
||
|
|
const h = field.hueStart + s * dh;
|
||
|
|
return hslToRgb(h, field.saturation, field.lightness);
|
||
|
|
}
|