From e59fe5a27aa7719f5204aa81a9ffdef761f36e9d Mon Sep 17 00:00:00 2001 From: vini Date: Sun, 19 Jul 2026 13:21:52 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20estiliza=20Via=20L=C3=A1ctea=20como=20a?= =?UTF-8?q?strofoto=20de=20longa=20exposi=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/contents/ui/dancer.html | 102 ++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/plugin/contents/ui/dancer.html b/plugin/contents/ui/dancer.html index f5790ea..786c076 100644 --- a/plugin/contents/ui/dancer.html +++ b/plugin/contents/ui/dancer.html @@ -1275,7 +1275,8 @@ const G2E = [ A textura é calculada no espaço galáctico em uma camada de baixa resolução. Assim a faixa, o bojo e a poeira acompanham a projeção real sem denunciar sprites circulares ou custar um passe em resolução cheia. */ -const MW_RENDER_SCALE = 0.25; +const MW_RENDER_SCALE = 0.50; +const MW_NOISE_PERIODS = [5, 10, 20, 40, 80]; const mwLayer = document.createElement('canvas'); function mwHash(ix, iy) { @@ -1289,11 +1290,12 @@ function mwValueNoise(x, y, periodX) { const tx0 = x - fx, ty0 = y - fy; const tx = tx0 * tx0 * (3 - 2 * tx0); const ty = ty0 * ty0 * (3 - 2 * ty0); - const wrap = n => ((n % periodX) + periodX) % periodX; - const a = mwHash(wrap(fx), fy); - const b = mwHash(wrap(fx + 1), fy); - const c = mwHash(wrap(fx), fy + 1); - const d = mwHash(wrap(fx + 1), fy + 1); + const x0 = ((fx % periodX) + periodX) % periodX; + const x1 = (x0 + 1) % periodX; + const a = mwHash(x0, fy); + const b = mwHash(x1, fy); + const c = mwHash(x0, fy + 1); + const d = mwHash(x1, fy + 1); const top = a + (b - a) * tx; const bot = c + (d - c) * tx; return top + (bot - top) * ty; @@ -1302,44 +1304,64 @@ function mwValueNoise(x, y, periodX) { /* Ruído periódico em longitude: não deixa emenda em l=0°/360°. */ function mwFbm(l, b) { let value = 0, norm = 0, amp = 1; - for (const period of [4, 8, 16, 32]) { + for (const period of MW_NOISE_PERIODS) { value += amp * mwValueNoise(l / 360 * period, (b + 90) / 360 * period, period); norm += amp; - amp *= 0.52; + amp *= 0.50; } return value / norm; } +function mwFineNoise(l, b) { + const n1 = mwValueNoise(l / 360 * 160, (b + 90) / 360 * 160, 160); + const n2 = mwValueNoise(l / 360 * 320, (b + 90) / 360 * 320, 320); + return n1 * 0.68 + n2 * 0.32; +} + function mwAngularDistance(l, target) { const d = Math.abs(l - target); return Math.min(d, 360 - d); } +function mwCloud(l, b, cl, cb, wl, wb, strength) { + const x = mwAngularDistance(l, cl) / wl; + const y = (b - cb) / wb; + return strength * Math.exp(-0.5 * (x * x + y * y)); +} + /* Densidade luminosa em coordenadas galácticas. O detalhe vem do ruído, enquanto as formas grandes continuam astronomicamente reconhecíveis. */ function mwDensity(l, b) { const dl = Math.min(l, 360 - l); const core = Math.exp(-((dl / 72) ** 2)); - const width = 4.2 + 5.2 * core; const cloud = mwFbm(l, b); - const fine = mwFbm((l * 2) % 360, b * 2.2); + const fine = mwFineNoise(l, b); + const warpedB = b - (cloud - 0.5) * (2.2 + 1.8 * core); + const width = 3.7 + 5.8 * core + 1.3 * cloud; + const plane = Math.exp(-0.5 * (warpedB / width) ** 2); - let density = Math.exp(-0.5 * (b / width) ** 2) * - (0.34 + 1.05 * cloud ** 1.7); - density += 0.16 * Math.exp(-0.5 * (b / (width * 2.6)) ** 2) * - (0.45 + 0.55 * cloud); - density += 1.05 * Math.exp(-0.5 * ((dl / 19) ** 2 + (b / 11) ** 2)) * - (0.65 + 0.45 * cloud); + /* Luz integrada: halo largo, nuvens irregulares e grão fino de estrelas. */ + let density = plane * (0.20 + 1.45 * cloud ** 2.1); + density += 0.12 * Math.exp(-0.5 * (warpedB / (width * 2.9)) ** 2) * + (0.35 + 0.65 * cloud); + density += plane * Math.max(0, fine - 0.46) ** 2 * 3.8; + + /* Grandes nuvens estelares reais, exageradas como numa longa exposição. */ + density += mwCloud(l, b, 0, 0.0, 20, 11, 1.55); // bojo de Sagitário + density += mwCloud(l, b, 25, 0.5, 10, 4, 0.62); // Scutum + density += mwCloud(l, b, 330, -0.7, 11, 5, 0.52); + density += mwCloud(l, b, 285, -0.4, 13, 5, 0.55); // Carina + density += mwCloud(l, b, 80, 0.8, 12, 4, 0.38); // Cygnus /* Grande Fenda: duas trilhas estreitas, deformadas e com força irregular. */ const riftReach = 1 - Math.max(0, Math.min(1, (dl - 70) / 55)); const lr = l * DEG; - const riftA = 0.7 + 1.45 * Math.sin(lr * 2.1 + 0.5) + 0.45 * Math.sin(lr * 7.0); - const riftB = -2.3 + 0.75 * Math.sin(lr * 1.4 + 2.2); - const darkA = Math.exp(-0.5 * ((b - riftA) / (1.0 + 0.7 * fine)) ** 2) * - (0.58 + 0.28 * fine); - const darkB = Math.exp(-0.5 * ((b - riftB) / 1.15) ** 2) * 0.48; - density *= 1 - Math.min(0.90, (darkA + darkB) * riftReach); + const riftA = 0.6 + 1.55 * Math.sin(lr * 2.1 + 0.5) + 0.55 * Math.sin(lr * 7.0); + const riftB = -2.5 + 0.85 * Math.sin(lr * 1.4 + 2.2); + const darkA = Math.exp(-0.5 * ((b - riftA) / (0.72 + 0.72 * fine)) ** 2) * + (0.66 + 0.30 * fine); + const darkB = Math.exp(-0.5 * ((b - riftB) / 0.95) ** 2) * 0.58; + density *= 1 - Math.min(0.94, (darkA + darkB) * riftReach); /* Saco de Carvão junto ao Cruzeiro do Sul. */ const coal = Math.exp(-0.5 * ((mwAngularDistance(l, 303) / 3.8) ** 2 + @@ -1349,15 +1371,18 @@ function mwDensity(l, b) { return Math.max(0, density * (0.24 + 0.76 * Math.exp(-((dl / 105) ** 2)))); } -function mwRgb(dl) { - const t = Math.min(1, dl / 145); - const warm = [255, 220, 181], neutral = [222, 226, 235], cool = [177, 207, 244]; - const a = t < 0.5 ? warm : neutral; - const z = t < 0.5 ? neutral : cool; +function mwWriteRgb(pixels, offset, dl) { + const t = Math.min(1, dl / 150); const u = t < 0.5 ? t * 2 : (t - 0.5) * 2; - return [a[0] + (z[0] - a[0]) * u, - a[1] + (z[1] - a[1]) * u, - a[2] + (z[2] - a[2]) * u]; + if (t < 0.5) { + pixels[offset] = 255 + (226 - 255) * u; + pixels[offset + 1] = 199 + (218 - 199) * u; + pixels[offset + 2] = 145 + (239 - 145) * u; + } else { + pixels[offset] = 226 + (148 - 226) * u; + pixels[offset + 1] = 218 + (201 - 218) * u; + pixels[offset + 2] = 239 + (255 - 239) * u; + } } function renderMilkyWay(W, H, horizonPx, lst, glareOn) { @@ -1405,12 +1430,11 @@ function renderMilkyWay(W, H, horizonPx, lst, glareOn) { if (density < 0.012) continue; const screenX = (x + 0.5) / rw * W; const glare = glareFactor(screenX, screenY, W, H, glareOn); - const alpha = Math.min(0.26, density * 0.145) * ext * glare; + /* Curva de exposição comprime o núcleo sem apagar estruturas tênues. */ + const exposure = 1 - Math.exp(-density * 0.72); + const alpha = Math.min(0.46, exposure * 0.42) * ext * glare; if (alpha < 0.002) continue; - const rgb = mwRgb(Math.min(l, 360 - l)); - pixels[offset] = rgb[0]; - pixels[offset + 1] = rgb[1]; - pixels[offset + 2] = rgb[2]; + mwWriteRgb(pixels, offset, Math.min(l, 360 - l)); pixels[offset + 3] = alpha * 255; } } @@ -1439,7 +1463,15 @@ function projectSky() { renderMilkyWay(W, H, horizonPx, lst, glareOn); bctx.imageSmoothingEnabled = true; bctx.imageSmoothingQuality = 'high'; + bctx.save(); + bctx.globalCompositeOperation = 'lighter'; + bctx.globalAlpha = 0.48; + bctx.filter = `blur(${Math.max(10, H * 0.014)}px) saturate(155%)`; bctx.drawImage(mwLayer, 0, 0, W, horizonPx); + bctx.globalAlpha = 0.92; + bctx.filter = 'contrast(118%) saturate(128%)'; + bctx.drawImage(mwLayer, 0, 0, W, horizonPx); + bctx.restore(); for (let i = 0; i < STAR_DATA.length; i++) { const [ra, dec, mag, ci] = STAR_DATA[i];