Add anchored zodiac artwork

This commit is contained in:
vini 2026-07-19 14:12:11 -03:00
parent 3df13f9a0c
commit 16dfd792ce
16 changed files with 97 additions and 3 deletions

View file

@ -0,0 +1,11 @@
# Stellarium Modern constellation illustrations
The twelve PNG illustrations in this directory are taken from the
[Stellarium Modern sky culture](https://github.com/Stellarium/stellarium/tree/master/skycultures/modern/illustrations).
The Stellarium sky-culture description licenses the illustrations under the
[Free Art License 1.3](https://artlibre.org/licence/lal/en/).
The images are redistributed here with their original names and are rendered
with an affine transformation so their three Hipparcos anchor stars remain
aligned with the simulated sky.

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because one or more lines are too long

View file

@ -1219,6 +1219,17 @@ const ctx = canvas.getContext('2d');
let obsLat = -24.72, obsLon = -53.74; // Toledo-PR; sobrescrito pela config
let showConstellations = false;
const constellationArtImages = new Map();
if (typeof CONSTELLATIONS !== 'undefined') {
for (const constellation of CONSTELLATIONS) {
if (!constellation.art) continue;
const img = new Image();
img.onload = () => projectSky();
img.onerror = () => console.error('[skeledance] falha na arte de ' + constellation.name);
img.src = constellation.art.file;
constellationArtImages.set(constellation.id, img);
}
}
const VIEW_AZ = 90; // centro da tela olha para o LESTE — a tela física do
// usuário está voltada p/ leste, então o wallpaper age
// como janela: estrelas nascem subindo dentro da cena
@ -1400,8 +1411,62 @@ function renderMilkyWay(lstDeg, glareOn) {
return true;
}
/* Resolve a transformação afim que prende três pixels da ilustração às suas
três estrelas Hipparcos projetadas. */
function constellationArtTransform(source, target) {
const [p1, p2, p3] = source;
const [q1, q2, q3] = target;
const den = p1[0] * (p2[1] - p3[1]) + p2[0] * (p3[1] - p1[1]) +
p3[0] * (p1[1] - p2[1]);
if (Math.abs(den) < 1e-6) return null;
const solve = (v1, v2, v3) => ({
x: (v1 * (p2[1] - p3[1]) + v2 * (p3[1] - p1[1]) +
v3 * (p1[1] - p2[1])) / den,
y: (v1 * (p3[0] - p2[0]) + v2 * (p1[0] - p3[0]) +
v3 * (p2[0] - p1[0])) / den,
t: (v1 * (p2[0] * p3[1] - p3[0] * p2[1]) +
v2 * (p3[0] * p1[1] - p1[0] * p3[1]) +
v3 * (p1[0] * p2[1] - p2[0] * p1[1])) / den,
});
const x = solve(q1.x, q2.x, q3.x);
const y = solve(q1.y, q2.y, q3.y);
return [x.x, y.x, x.y, y.y, x.t, y.t];
}
function drawConstellationArt(c, constellation, lst, W, horizonPx) {
const art = constellation.art;
const img = constellationArtImages.get(constellation.id);
if (!art || !img || !img.complete || !img.naturalWidth) return;
const target = art.anchors.map(anchor => {
const aa = altAz(anchor.sky[0], anchor.sky[1], lst);
const daz = ((aa.az - VIEW_AZ) % 360 + 540) % 360 - 180;
return {
x: (0.5 + daz / FOV_AZ) * W,
y: horizonPx * (1 - aa.alt / ALT_TOP),
alt: aa.alt,
};
});
if (target.every(p => p.alt < -8) || target.every(p => p.alt > ALT_TOP + 8)) return;
const matrix = constellationArtTransform(
art.anchors.map(anchor => anchor.pos), target
);
if (!matrix || matrix.some(v => !Number.isFinite(v))) return;
c.save();
c.beginPath();
c.rect(0, 0, W, horizonPx);
c.clip();
c.globalCompositeOperation = 'screen';
c.globalAlpha = 0.19;
c.setTransform(...matrix);
c.drawImage(img, 0, 0, art.size[0], art.size[1]);
c.restore();
}
/* Figuras convencionais dos 12 signos zodiacais, ancoradas em RA/Dec J2000.
São desenhadas antes das estrelas para os pontos reais ficarem por cima. */
Arte, linhas e estrelas reais ficam sobrepostas nesta ordem. */
function drawConstellations(c, lst, W, H, horizonPx) {
if (!showConstellations || typeof CONSTELLATIONS === 'undefined') return;
const rankAlpha = [0, 0.52, 0.38, 0.25];
@ -1412,6 +1477,9 @@ function drawConstellations(c, lst, W, H, horizonPx) {
c.lineJoin = 'round';
c.shadowColor = 'rgba(60,185,255,0.65)';
c.shadowBlur = 7;
for (const constellation of CONSTELLATIONS) {
drawConstellationArt(c, constellation, lst, W, horizonPx);
}
for (const constellation of CONSTELLATIONS) {
const rank = Math.max(1, Math.min(3, constellation.rank || 3));
c.strokeStyle = `rgba(105,205,255,${rankAlpha[rank]})`;

View file

@ -68,7 +68,7 @@ def rounded_point(point: list[float]) -> list[float]:
def main() -> None:
sky_culture = fetch(STELLARIUM_URL)
star_data = fetch(D3_BASE + "stars.6.json")["features"]
star_data = fetch(D3_BASE + "stars.8.json")["features"]
name_data = fetch(D3_BASE + "constellations.json")["features"]
stars = {
int(feature["id"]): rounded_point(feature["geometry"]["coordinates"])
@ -93,12 +93,27 @@ def main() -> None:
if missing:
raise RuntimeError(f"{ident}: HIP ausente no catálogo: {missing}")
lines.append([stars[hip] for hip in hip_line])
image = constellation.get("image")
art = None
if image:
anchors = []
for anchor in image["anchors"]:
hip = int(anchor["hip"])
if hip not in stars:
raise RuntimeError(f"{ident}: âncora HIP ausente: {hip}")
anchors.append({"pos": anchor["pos"], "sky": stars[hip]})
art = {
"file": "constellation-art/" + Path(image["file"]).name,
"size": image["size"],
"anchors": anchors,
}
merged[ident] = {
"id": ident,
"name": ZODIAC[ident],
"rank": 1 if ident in {"Ari", "Tau", "Gem", "Leo", "Vir", "Sco", "Sgr"} else 2,
"label": metadata.get(ident, {}).get("label"),
"lines": lines,
"art": art,
}
payload = json.dumps(