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];