Map Moon phases and labeled planets

This commit is contained in:
vini 2026-07-19 15:11:33 -03:00
parent 2bcb973020
commit 44fb0b09a6
3 changed files with 440 additions and 4 deletions

View file

@ -1199,6 +1199,7 @@ body.chuva #sunChuvaFace { opacity: 1; transition: opacity 5s ease; }
</div><!-- .scene -->
<script src="astronomy.browser.min.js"></script>
<script src="starcatalog.js"></script>
<script src="constellations.js"></script>
<script>
@ -1267,6 +1268,16 @@ const HORIZON_F = 0.585; // fração da altura da tela até o horizonte
const TWINKLE_MAG = 4.0; // até esta magnitude a estrela cintila; acima é estática
const DEG = Math.PI / 180;
window.__skyTimeOffset = 0; // debug: ms somados ao relógio p/ testar a rotação
const PLANET_SPECS = [
[Astronomy.Body.Mercury, 'Mercúrio', [205, 190, 165]],
[Astronomy.Body.Venus, 'Vênus', [255, 232, 180]],
[Astronomy.Body.Mars, 'Marte', [255, 120, 82]],
[Astronomy.Body.Jupiter, 'Júpiter', [255, 218, 170]],
[Astronomy.Body.Saturn, 'Saturno', [238, 205, 135]],
[Astronomy.Body.Uranus, 'Urano', [145, 225, 235]],
[Astronomy.Body.Neptune, 'Netuno', [105, 150, 255]],
];
let solarSystemFrame = { moon: null, planets: [] };
let skyTimeScale = 1;
let skyClockRealMs = Date.now();
let skyClockSimMs = skyClockRealMs;
@ -1375,7 +1386,7 @@ const E2G = [
para plate carrée por tools/build_skymap.py. A geometria é real; apenas
exposição, contraste e saturação são estilizados para o wallpaper. */
const MW_BLACK = 46;
const MW_EXPOSURE = 0.96;
const MW_EXPOSURE = 0.78;
const MW_SAMPLE_SCALE = 1;
const mwTex = { data: null, w: 0, h: 0 };
const mwCanvas = document.createElement('canvas');
@ -1772,6 +1783,149 @@ let constellationPreviewFileVal = null;
.finally(() => setTimeout(pollConstellationPreviewFile, 800));
})();
/* ── Lua e planetas: efemérides topocêntricas do Astronomy Engine ── */
function buildMoonPhaseTexture(phase) {
const size = 96, radius = size * 0.46;
const out = document.createElement('canvas');
out.width = out.height = size;
const oc = out.getContext('2d');
const image = oc.createImageData(size, size);
const px = image.data;
const elongation = Math.min(phase, 360 - phase) * DEG;
/* A textura nasce com o lado claro à direita; drawSolarSystem a gira para
apontar ao Sol astronômico na esfera local. */
const sunX = Math.sin(elongation), sunZ = -Math.cos(elongation);
for (let y = 0, i = 0; y < size; y++) {
for (let x = 0; x < size; x++, i += 4) {
const nx = (x + 0.5 - size / 2) / radius;
const ny = (y + 0.5 - size / 2) / radius;
const rr = nx * nx + ny * ny;
if (rr >= 1) continue;
const nz = Math.sqrt(1 - rr);
const sunlight = nx * sunX + nz * sunZ;
const limb = 0.68 + 0.32 * nz;
const maria = 0.92 + 0.045 * Math.sin(nx * 19 + ny * 7) +
0.035 * Math.sin(nx * 8 - ny * 23);
const light = sunlight > 0
? (0.42 + 0.58 * sunlight) * limb * maria
: 0.055 * limb;
px[i] = 225 * light;
px[i + 1] = 230 * light;
px[i + 2] = 218 * light + (sunlight <= 0 ? 9 : 0);
px[i + 3] = 255 * Math.min(1, (1 - rr) * 42);
}
}
oc.putImageData(image, 0, 0);
return out;
}
function updateSolarSystem(date, lst, W, H, horizonPx) {
if (typeof Astronomy === 'undefined') return;
try {
const observer = new Astronomy.Observer(obsLat, obsLon, 0);
const bodyCoordinates = body => {
const eq = Astronomy.Equator(body, date, observer, true, true);
const aa = altAz(eq.ra * 15, eq.dec, lst);
const sc = skyToScreen(aa.alt, aa.az, W, horizonPx);
return { eq, aa, sc };
};
const projectBody = body => {
const coords = bodyCoordinates(body);
return coords.sc ? { ...coords.sc, alt: coords.aa.alt, az: coords.aa.az } : null;
};
const moonCoords = bodyCoordinates(Astronomy.Body.Moon);
const sunCoords = bodyCoordinates(Astronomy.Body.Sun);
const moonScreen = moonCoords.sc
? { ...moonCoords.sc, alt: moonCoords.aa.alt, az: moonCoords.aa.az }
: null;
const phase = Astronomy.MoonPhase(date);
const moonIllumination = Astronomy.Illumination(Astronomy.Body.Moon, date);
const moonRadius = Math.max(12, Math.min(22, H * 0.0085));
const deltaAz = (((sunCoords.aa.az - moonCoords.aa.az) % 360 + 540) % 360 - 180) * DEG;
const moonAlt = moonCoords.aa.alt * DEG, sunAlt = sunCoords.aa.alt * DEG;
const bearing = Math.atan2(
Math.sin(deltaAz) * Math.cos(sunAlt),
Math.cos(moonAlt) * Math.sin(sunAlt) -
Math.sin(moonAlt) * Math.cos(sunAlt) * Math.cos(deltaAz)
);
const brightDx = Math.sin(bearing) /
Math.max(0.16, Math.abs(Math.cos(moonAlt))) * (W / FOV_AZ);
const brightDy = -Math.cos(bearing) * (horizonPx / ALT_TOP);
const brightAngle = Math.atan2(brightDy, brightDx);
const moon = moonScreen ? {
...moonScreen,
radius: moonRadius,
phase,
illuminated: moonIllumination.phase_fraction,
brightAngle,
texture: buildMoonPhaseTexture(phase),
} : null;
const planets = [];
for (const [body, name, color] of PLANET_SPECS) {
const screen = projectBody(body);
if (!screen) continue;
const illumination = Astronomy.Illumination(body, date);
planets.push({
...screen, name, color, body,
magnitude: illumination.mag,
radius: Math.max(1.8, Math.min(5.2, 3.45 - illumination.mag * 0.46)),
});
}
solarSystemFrame = { moon, planets };
} catch (error) {
console.error('[skeledance] falha nas efemérides:', error);
}
}
function drawSolarSystem(c) {
const { moon, planets } = solarSystemFrame;
if (moon) {
c.save();
const fade = extinction(moon.alt);
c.globalAlpha = fade;
c.shadowColor = 'rgba(205,220,255,0.7)';
c.shadowBlur = moon.radius * 1.25;
c.translate(moon.x, moon.y);
c.rotate(moon.brightAngle);
c.drawImage(moon.texture, -moon.radius, -moon.radius,
moon.radius * 2, moon.radius * 2);
c.restore();
}
c.save();
c.font = `${Math.max(11, Math.round(canvas.height * 0.0095))}px ui-monospace, monospace`;
c.textAlign = 'left';
c.textBaseline = 'middle';
for (const planet of planets) {
const fade = extinction(planet.alt);
const [r, g, b] = planet.color;
c.save();
c.globalAlpha = fade;
c.shadowColor = `rgba(${r},${g},${b},0.95)`;
c.shadowBlur = planet.radius * 3.2;
c.fillStyle = `rgb(${r},${g},${b})`;
c.beginPath();
c.arc(planet.x, planet.y, planet.radius, 0, Math.PI * 2);
c.fill();
if (planet.body === Astronomy.Body.Saturn) {
c.strokeStyle = `rgba(${r},${g},${b},0.82)`;
c.lineWidth = Math.max(1, planet.radius * 0.34);
c.beginPath();
c.ellipse(planet.x, planet.y, planet.radius * 1.8,
planet.radius * 0.62, -0.18, 0, Math.PI * 2);
c.stroke();
}
c.shadowColor = 'rgba(4,8,20,0.95)';
c.shadowBlur = 4;
c.fillStyle = `rgba(${r},${g},${b},0.92)`;
c.fillText(planet.name, planet.x + planet.radius + 7, planet.y - 1);
c.restore();
}
c.restore();
}
/* ── 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 fora da tela; só as brilhantes cintilam por frame.
@ -1797,7 +1951,8 @@ function projectSky() {
const bctx = renderCanvas.getContext('2d');
bctx.clearRect(0, 0, W, bufferH);
viewAz = currentCameraAz();
const lst = localSiderealDeg(skyNowMs());
const skyDate = new Date(skyNowMs());
const lst = localSiderealDeg(skyDate.getTime());
const glareOn = !document.body.classList.contains('balada') &&
!document.body.classList.contains('chuva');
const nextTwinkList = [];
@ -1807,10 +1962,10 @@ function projectSky() {
bctx.imageSmoothingQuality = 'high';
bctx.save();
bctx.globalCompositeOperation = 'lighter';
bctx.globalAlpha = 0.17;
bctx.globalAlpha = 0.13;
bctx.filter = `blur(${Math.max(7, H * 0.009)}px) saturate(150%)`;
bctx.drawImage(mwCanvas, 0, 0, W, horizonPx);
bctx.globalAlpha = 0.88;
bctx.globalAlpha = 0.72;
bctx.filter = 'contrast(116%) saturate(158%)';
bctx.drawImage(mwCanvas, 0, 0, W, horizonPx);
bctx.restore();
@ -1847,6 +2002,7 @@ function projectSky() {
bctx.fillRect(sc.x - r, sc.y - r, r * 2, r * 2);
}
}
updateSolarSystem(skyDate, lst, W, H, horizonPx);
/* 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. */
@ -1909,6 +2065,7 @@ function drawStars(ts) {
ctx.fillStyle = `rgba(${s.cr},${s.cg},${s.cb},${(s.a * tw).toFixed(3)})`;
ctx.fill();
}
drawSolarSystem(ctx);
requestAnimationFrame(drawStars);
}