From 8c1389557b13bbd79de815d3c8a9591a8b66d0b4 Mon Sep 17 00:00:00 2001 From: vini Date: Sun, 19 Jul 2026 14:17:54 -0300 Subject: [PATCH] Smooth periodic sky reprojection --- plugin/contents/ui/dancer.html | 55 +++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/plugin/contents/ui/dancer.html b/plugin/contents/ui/dancer.html index c4382ac..8fe137a 100644 --- a/plugin/contents/ui/dancer.html +++ b/plugin/contents/ui/dancer.html @@ -1576,22 +1576,31 @@ let constellationsFileVal = null; /* ── Passo de projeção: recalcula posições de tela (a cada 30s o céu gira ~0.125°, imperceptível). Camada estática (Via Láctea + estrelas fracas) - é pré-renderizada em baseCanvas; só as brilhantes cintilam por frame ── */ -const baseCanvas = document.createElement('canvas'); + é pré-renderizada fora da tela; só as brilhantes cintilam por frame. + Três buffers impedem que o compositor veja a textura sendo substituída. ── */ +let baseCanvas = document.createElement('canvas'); +let previousBaseCanvas = document.createElement('canvas'); +let renderCanvas = document.createElement('canvas'); +let hasSkyFrame = false; +let skyTransitionStart = -Infinity; +const SKY_TRANSITION_MS = 1400; let twinkList = []; let lastProjMs = -Infinity; function projectSky() { const W = canvas.width, H = canvas.height; if (!W || !H) return; - baseCanvas.width = W; - baseCanvas.height = H; - const bctx = baseCanvas.getContext('2d'); + if (renderCanvas.width !== W || renderCanvas.height !== H) { + renderCanvas.width = W; + renderCanvas.height = H; + } + const bctx = renderCanvas.getContext('2d'); + bctx.clearRect(0, 0, W, H); const horizonPx = H * HORIZON_F; const lst = localSiderealDeg(Date.now() + window.__skyTimeOffset); const glareOn = !document.body.classList.contains('balada') && !document.body.classList.contains('chuva'); - twinkList = []; + const nextTwinkList = []; if (renderMilkyWay(lst, glareOn)) { bctx.imageSmoothingEnabled = true; @@ -1621,7 +1630,7 @@ function projectSky() { const c = STAR_PALETTE[ci]; if (mag <= TWINKLE_MAG) { /* fase/freq determinísticas por índice — cintilação estável */ - twinkList.push({ + nextTwinkList.push({ x: sc.x, y: sc.y, r, a, cr: c[0], cg: c[1], cb: c[2], phase: i * 2.3999, @@ -1638,6 +1647,22 @@ function projectSky() { bctx.fillRect(sc.x - r, sc.y - r, r * 2, r * 2); } } + + /* O quadro só se torna visível depois de estar completo. O antigo fica + intacto para um crossfade que disfarça o pequeno avanço sideral. */ + if (hasSkyFrame) { + const recycled = previousBaseCanvas; + previousBaseCanvas = baseCanvas; + baseCanvas = renderCanvas; + renderCanvas = recycled; + skyTransitionStart = performance.now(); + } else { + const recycled = baseCanvas; + baseCanvas = renderCanvas; + renderCanvas = recycled; + hasSkyFrame = true; + } + twinkList = nextTwinkList; } function setObserverLocation(lat, lon) { @@ -1660,7 +1685,21 @@ function resizeCanvas() { function drawStars(ts) { if (ts - lastProjMs > 30000) { lastProjMs = ts; projectSky(); } ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(baseCanvas, 0, 0); + const transition = Math.max(0, Math.min(1, + (ts - skyTransitionStart) / SKY_TRANSITION_MS)); + if (transition < 1 && previousBaseCanvas.width === canvas.width && + previousBaseCanvas.height === canvas.height) { + const blend = transition * transition * (3 - 2 * transition); + ctx.globalAlpha = 1 - blend; + ctx.drawImage(previousBaseCanvas, 0, 0); + ctx.globalCompositeOperation = 'lighter'; + ctx.globalAlpha = blend; + ctx.drawImage(baseCanvas, 0, 0); + ctx.globalCompositeOperation = 'source-over'; + ctx.globalAlpha = 1; + } else { + ctx.drawImage(baseCanvas, 0, 0); + } const t = ts * 0.001; for (const s of twinkList) { const tw = 1 - s.amp * (0.5 + 0.5 * Math.sin(t * s.freq + s.phase));