Smoother animations
This commit is contained in:
fraxle
2026-06-07 10:05:20 +01:00
parent 1459740d96
commit b3c9ed2068
+17 -12
View File
@@ -79,7 +79,7 @@ const GROUND_SPEC = {
// desert-green, parasol warm, boat sail-coloured, etc. Run through the cycle // desert-green, parasol warm, boat sail-coloured, etc. Run through the cycle
// (objTone) at render time so they darken at night, warm at sunset, etc. // (objTone) at render time so they darken at night, warm at sunset, etc.
const MATERIAL = { const MATERIAL = {
oak: '#4f7d39', pine: '#356040', bush: '#5a8a44', hedge: '#4c7a3e', oak: '#4f7d39', pine: '#356040', bush: '#2e5218', hedge: '#4c7a3e',
cactus: '#5f8a4a', palm: '#4a7d3e', parasol: '#bb5a46', reed: '#74803c', cactus: '#5f8a4a', palm: '#4a7d3e', parasol: '#bb5a46', reed: '#74803c',
boat: '#ccc4ad', boat: '#ccc4ad',
}; };
@@ -540,19 +540,23 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
// running instead of restarting (which looked like the clouds shaking). // running instead of restarting (which looked like the clouds shaking).
const cloudDim = isDay ? 1 : 0.55; // moonlit clouds read dimmer at night const cloudDim = isDay ? 1 : 0.55; // moonlit clouds read dimmer at night
function buildCloudLayer(cov, yBand, scale, durSec, seed, key) { function buildCloudLayer(cov, yBand, scale, durSec, seed, key) {
if (cov == null || cov < 8) return null; // Always render the maximum 4 sprites so existing <image> elements persist
const f = Math.min(1, cov / 100); // in the DOM and their opacity can CSS-transition instead of blinking in/out.
const count = Math.round(1 + f * 3); // 1..4 sprites const maxCount = 4;
const f = cov != null ? Math.min(1, cov / 100) : 0;
const activeCount = (cov != null && cov >= 8) ? Math.round(1 + f * 3) : 0;
const baseOp = Math.min(0.9, (0.22 + f * 0.6) * cloudDim);
const w = 92 * scale, hh = 48 * scale; const w = 92 * scale, hh = 48 * scale;
const op = Math.min(0.9, (0.22 + f * 0.6) * cloudDim);
const imgs = []; const imgs = [];
for (let k = 0; k < count; k++) { for (let k = 0; k < maxCount; k++) {
const px = (seed * 47 + k * (200 / count)) % 200; const px = (seed * 47 + k * (200 / maxCount)) % 200;
const py = yBand + (((seed + k * 7) % 12) - 6); const py = yBand + (((seed + k * 7) % 12) - 6);
const spriteOp = k < activeCount ? baseOp : 0;
[px, px + 200].forEach((X, j) => { [px, px + 200].forEach((X, j) => {
imgs.push(html`<image key=${k + '-' + j} href=${CLOUD_IMG} imgs.push(html`<image key=${k + '-' + j} href=${CLOUD_IMG}
x=${(X - w / 2).toFixed(1)} y=${(py - hh / 2).toFixed(1)} x=${(X - w / 2).toFixed(1)} y=${(py - hh / 2).toFixed(1)}
width=${w.toFixed(1)} height=${hh.toFixed(1)} opacity=${op.toFixed(2)} width=${w.toFixed(1)} height=${hh.toFixed(1)} opacity=${spriteOp.toFixed(2)}
style="transition:opacity 2s ease"
preserveAspectRatio="xMidYMid meet" />`); preserveAspectRatio="xMidYMid meet" />`);
}); });
} }
@@ -1006,11 +1010,11 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
<circle cx=${cx} cy=${cy} r=${lensR} /> <circle cx=${cx} cy=${cy} r=${lensR} />
</clipPath> </clipPath>
<linearGradient id="scope-lens-sky" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="scope-lens-sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color=${skyTopTint} /> <stop offset="0%" stop-color=${skyTopTint} style="transition:stop-color 2s ease" />
<stop offset="100%" stop-color=${skyBotTint} /> <stop offset="100%" stop-color=${skyBotTint} style="transition:stop-color 2s ease" />
</linearGradient> </linearGradient>
<linearGradient id="scope-lens-grass" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="scope-lens-grass" x1="0" y1="0" x2="0" y2="1">
${groundStops.map((s, i) => html`<stop key=${i} offset=${s.off + '%'} stop-color=${s.col} />`)} ${groundStops.map((s, i) => html`<stop key=${i} offset=${s.off + '%'} stop-color=${s.col} style="transition:stop-color 2s ease" />`)}
</linearGradient> </linearGradient>
<!-- Soft scrim behind the readout so the numbers stay legible over busy ground --> <!-- Soft scrim behind the readout so the numbers stay legible over busy ground -->
<radialGradient id="scope-readout-scrim" gradientUnits="userSpaceOnUse" cx=${cx} cy="142" r="50"> <radialGradient id="scope-readout-scrim" gradientUnits="userSpaceOnUse" cx=${cx} cy="142" r="50">
@@ -1065,7 +1069,8 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
<circle cx=${cx} cy="142" r="50" fill="url(#scope-readout-scrim)" /> <circle cx=${cx} cy="142" r="50" fill="url(#scope-readout-scrim)" />
<g key="scope-wx"> <g key="scope-wx">
${cloudLayers} ${cloudLayers}
${fogAlpha > 0 && html`<rect x="0" y="0" width="200" height="200" fill=${`rgba(${fogColor},${fogAlpha.toFixed(2)})`} />`} <rect x="0" y="0" width="200" height="200" fill=${`rgb(${fogColor})`}
opacity=${fogAlpha.toFixed(2)} style="transition:opacity 2s ease" />
</g> </g>
</g> </g>
${(() => { ${(() => {