feat: Via Láctea real — mapa all-sky do Gaia amostrado por pixel

Substitui a simulação procedural pelo mapa "Gaia's sky in colour"
(ESA/Gaia/DPAC, CC BY-SA 3.0 IGO): tools/build_skymap.py baixa o
original 8000x4000 em projeção Hammer, reprojeta para plate carrée e
gera milkyway.jpg (3072x1536). O dancer.html amostra a textura por
pixel em (l,b) galácticas reais — grade pré-computada por resolução/
latitude, com só a rotação sideral variando por passo (matriz E2G·Rz
combinada). Nível de preto remove o véu de fundo do mapa; extinção e
clarão aplicados por pixel. Remove mwGlow/mwGrain/poeira procedural.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HtPHsTnDcLVSCiF5FGHZj
This commit is contained in:
vini 2026-07-19 11:23:32 -03:00
parent 2014dcf9b7
commit c4c9574946
4 changed files with 168 additions and 171 deletions

70
tools/build_skymap.py Normal file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Gera plugin/contents/ui/milkyway.jpg a partir do mapa all-sky do Gaia.
Fonte: "Gaia's sky in colour" (ESA/Gaia/DPAC, CC BY-SA 3.0 IGO), 8000x4000,
projeção HAMMER em coordenadas galácticas (elipse, centro l=0/b=0, l crescendo
para a esquerda, b=+90 no topo). Este script reprojeta para plate carrée
(mesma convenção de l/b) e reduz para textura leve.
O dancer.html amostra a textura por pixel via (l, b) reais a banda é a
Via Láctea fotografada de verdade, projetada em tempo real.
Requer Pillow e numpy. Uso: python3 tools/build_skymap.py
"""
import io
import math
import urllib.request
from pathlib import Path
import numpy as np
from PIL import Image
URL = ("https://upload.wikimedia.org/wikipedia/commons/e/ea/"
"Gaia%E2%80%99s_sky_in_colour_ESA393127.png")
CACHE = Path.home() / ".cache/skeledance/gaia_allsky.png"
OUT = Path(__file__).resolve().parent.parent / "plugin/contents/ui/milkyway.jpg"
SIZE = (3072, 1536) # textura final (plate carrée 2:1)
OVERSAMPLE = 2 # reprojeta a 2x e reduz com LANCZOS (anti-aliasing)
QUALITY = 85
def fetch() -> bytes:
if CACHE.exists():
print(f"Usando cache {CACHE}")
return CACHE.read_bytes()
print(f"Baixando {URL} ...")
req = urllib.request.Request(URL, headers={"User-Agent": "skeledance-build/1.0"})
raw = urllib.request.urlopen(req, timeout=180).read()
CACHE.parent.mkdir(parents=True, exist_ok=True)
CACHE.write_bytes(raw)
print(f" {len(raw) / 1e6:.1f} MB (cacheado)")
return raw
def main():
Image.MAX_IMAGE_PIXELS = None
src = np.asarray(Image.open(io.BytesIO(fetch())).convert("RGB"))
sh, sw = src.shape[:2]
tw, th = SIZE[0] * OVERSAMPLE, SIZE[1] * OVERSAMPLE
# Grade de saída plate carrée: l = 180 -> -180 (esq -> dir), b = 90 -> -90
l = np.deg2rad(180 - (np.arange(tw) + 0.5) * 360 / tw)
b = np.deg2rad(90 - (np.arange(th) + 0.5) * 180 / th)
L, B = np.meshgrid(l, b)
# Projeção Hammer direta -> posição na imagem-fonte
denom = np.sqrt(1 + np.cos(B) * np.cos(L / 2))
x = 2 * math.sqrt(2) * np.cos(B) * np.sin(L / 2) / denom # + = esquerda
y = math.sqrt(2) * np.sin(B) / denom # + = cima
sx = np.clip(((1 - x / (2 * math.sqrt(2))) * sw / 2).astype(int), 0, sw - 1)
sy = np.clip(((1 - y / math.sqrt(2)) * sh / 2).astype(int), 0, sh - 1)
out = Image.fromarray(src[sy, sx])
out = out.resize(SIZE, Image.LANCZOS)
out.save(OUT, "JPEG", quality=QUALITY, optimize=True)
print(f"{OUT} ({OUT.stat().st_size / 1024:.0f} KB, {SIZE[0]}x{SIZE[1]})")
if __name__ == "__main__":
main()