Geocron ist drin

This commit is contained in:
Eskimue
2026-04-02 15:40:06 +02:00
parent 4adb5a6065
commit 1156cb66dc
39 changed files with 3195 additions and 0 deletions
+226
View File
@@ -0,0 +1,226 @@
/**
* plugin-terminator.js – Nachtmaske, Dämmerungszonen & Terminator-Linien
* =========================================================================
* Abhängigkeit: earth-core.js
*
* Erstellt 2 Canvas-Layer:
* #nightCanvas (z-index 2) – per-Pixel Dämmerungsmaske über Nachtkarte
* #terminatorCanvas (z-index 5) – Terminator-Linien
*
* Feuert zusätzlich das Event earth:terminator mit
* { pts, northIsNight }
* damit plugin-clouds.js den Terminator zum Clipping nutzen kann.
*/
(function () {
// ═══════════════════════════════════════════════════════════════
// KONFIGURATION
// ═══════════════════════════════════════════════════════════════
const CFG = {
// ── Nachtbild ─────────────────────────────────────────────
IMG_NIGHT_DIR: "../images/planets/earth/night/",
IMG_EXT: ".jpg",
// ── Dämmerungsmaske ───────────────────────────────────────
// Alpha-Werte pro Sonnenhöhenwinkel-Zone
NIGHT_ALPHA: {
0: 80, // Bürgerliche Dämmerung (Sonne < 0°)
"-6": 150, // Nautische Dämmerung (Sonne < 6°)
"-12": 200, // Astronomische Dämmerung (Sonne < 12°)
"-18": 255, // Vollständige Nacht (Sonne < 18°)
},
// Auflösung der Dämmerungsmaske (höher = schärfer, mehr CPU)
MASK_W: 2440,
MASK_H: 1720,
// ── Terminator-Linien ─────────────────────────────────────
TERMINATOR_LINES: [
{ elev: 0, color: "rgba(255, 210, 80, 0.3)", shadow: "rgba(255, 180, 30, 0.2)" },
{ elev: -6, color: "rgba(255, 140, 50, 0.1)", shadow: "rgba(255, 100, 10, 0.2)" },
{ elev: -12, color: "rgba(200, 80, 200, 0.1)", shadow: "rgba(160, 40, 160, 0.2)" },
{ elev: -18, color: "rgba( 80, 120, 255, 0.1)", shadow: "rgba( 40, 70, 200, 0.2)" },
],
// Linienstärke: Canvas-Breite / Wert (Minimum 1 px)
LINE_WIDTH_DIV: 1000,
// Leuchtschein-Stärke um Terminator-Linien
SHADOW_BLUR: 4,
// Berechnungspunkte pro Kurve (höher = glatter)
STEPS: 5000,
};
// ── Kurz-Referenz auf EarthCore-Helfer ────────────────────────
const { toRad, toDeg, toX, toY, createCanvas, syncCanvas } = EarthCore;
// ── Canvas & Context ──────────────────────────────────────────
const nightCanvas = createCanvas("nightCanvas", 2);
const termCanvas = createCanvas("terminatorCanvas", 5);
const ctxNight = nightCanvas.getContext("2d");
const ctxTerm = termCanvas.getContext("2d");
// ── Nachtbild laden ───────────────────────────────────────────
const nightImg = new Image();
nightImg.src = `${CFG.IMG_NIGHT_DIR}${new Date().getMonth() + 1}${CFG.IMG_EXT}`;
// ═══════════════════════════════════════════════════════════════
// ASTRONOMISCHE HILFSFUNKTIONEN
// ═══════════════════════════════════════════════════════════════
function isNight(lat, lon, sLat, sLon) {
return (
Math.sin(toRad(lat)) * Math.sin(toRad(sLat)) +
Math.cos(toRad(lat)) * Math.cos(toRad(sLat)) *
Math.cos(toRad(lon - sLon))
) < 0;
}
/** Terminator-Kurve als Parameterkurve (Kleinstkreis) */
function terminatorCurve(sunLat, sunLon, elev = 0) {
const steps = CFG.STEPS;
const sinE = Math.sin(toRad(elev));
const cosE = Math.sqrt(Math.max(0, 1 - sinE * sinE));
const sLat = toRad(sunLat), sLon = toRad(sunLon);
const Sx = Math.cos(sLat) * Math.cos(sLon);
const Sy = Math.cos(sLat) * Math.sin(sLon);
const Sz = Math.sin(sLat);
let ux, uy, uz;
if (Math.abs(Sz) < 0.9) {
const len = Math.sqrt(Sx * Sx + Sy * Sy);
ux = -Sy / len; uy = Sx / len; uz = 0;
} else {
const len = Math.sqrt(Sy * Sy + Sz * Sz);
ux = 0; uy = -Sz / len; uz = Sy / len;
}
const vx = Sy * uz - Sz * uy;
const vy = Sz * ux - Sx * uz;
const vz = Sx * uy - Sy * ux;
const pts = [];
for (let i = 0; i <= steps; i++) {
const t = (2 * Math.PI * i) / steps;
const ct = Math.cos(t), st = Math.sin(t);
const px = sinE * Sx + cosE * (ct * ux + st * vx);
const py = sinE * Sy + cosE * (ct * uy + st * vy);
const pz = sinE * Sz + cosE * (ct * uz + st * vz);
pts.push({
lat: toDeg(Math.asin(Math.max(-1, Math.min(1, pz)))),
lon: toDeg(Math.atan2(py, px)),
});
}
return pts;
}
/** Einfacher lon-sortierter Terminator (für Clipping in Clouds-Plugin) */
function terminatorPtsSimple(sunLat, sunLon, steps = 720) {
const sR = toRad(sunLat);
const pts = [];
for (let i = 0; i <= steps; i++) {
const lon = -180 + (360 / steps) * i;
const dLon = toRad(lon - sunLon);
const lat = Math.abs(sunLat) < 0.001
? (Math.cos(dLon) >= 0 ? 90 : -90)
: toDeg(Math.atan(-Math.cos(dLon) / Math.tan(sR)));
pts.push({ lon, lat });
}
return pts;
}
// ═══════════════════════════════════════════════════════════════
// ZEICHNEN
// ═══════════════════════════════════════════════════════════════
/** Nachtmaske mit per-Pixel Dämmerungsübergängen */
function drawNightMask(sunLat, sunLon, w, h) {
if (!nightImg.complete || !nightImg.naturalWidth) return;
const MW = CFG.MASK_W, MH = CFG.MASK_H;
const offNight = document.createElement("canvas");
offNight.width = MW; offNight.height = MH;
const octx = offNight.getContext("2d");
octx.drawImage(nightImg, 0, 0, MW, MH);
const imgData = octx.getImageData(0, 0, MW, MH);
const d = imgData.data;
const sinSLat = Math.sin(toRad(sunLat));
const cosSLat = Math.cos(toRad(sunLat));
for (let py = 0; py < MH; py++) {
const lat = 90 - (py / MH) * 180;
const sinLat = Math.sin(toRad(lat));
const cosLat = Math.cos(toRad(lat));
for (let px = 0; px < MW; px++) {
const lon = (px / MW) * 360 - 180;
const sinAlt = sinLat * sinSLat + cosLat * cosSLat * Math.cos(toRad(lon - sunLon));
const alt = toDeg(Math.asin(Math.max(-1, Math.min(1, sinAlt))));
let a = 0;
if (alt < 0) a = CFG.NIGHT_ALPHA[0];
if (alt < -6) a = CFG.NIGHT_ALPHA["-6"];
if (alt < -12) a = CFG.NIGHT_ALPHA["-12"];
if (alt < -18) a = CFG.NIGHT_ALPHA["-18"];
d[(py * MW + px) * 4 + 3] = a;
}
}
octx.putImageData(imgData, 0, 0);
ctxNight.clearRect(0, 0, w, h);
ctxNight.drawImage(offNight, 0, 0, w, h);
}
/** Terminator-Kurve zeichnen (Stift bei Meridian-Wrap heben) */
function strokeCurve(ctx, pts, w, h) {
ctx.beginPath();
let first = true, prevLon = null;
for (const p of pts) {
if (prevLon !== null && Math.abs(p.lon - prevLon) > 180) first = true;
const x = toX(p.lon, w), y = toY(p.lat, h);
first ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
first = false; prevLon = p.lon;
}
ctx.stroke();
}
// ── Haupt-Render ──────────────────────────────────────────────
function render({ sun, w, h }) {
if (!w || !h) return;
// Größe synchronisieren
syncCanvas(nightCanvas, w, h);
syncCanvas(termCanvas, w, h);
// Nachtmaske
drawNightMask(sun.lat, sun.lon, w, h);
// Terminator-Linien
ctxTerm.clearRect(0, 0, w, h);
for (const line of CFG.TERMINATOR_LINES) {
const curve = terminatorCurve(sun.lat, sun.lon, line.elev);
ctxTerm.strokeStyle = line.color;
ctxTerm.lineWidth = Math.max(1, w / CFG.LINE_WIDTH_DIV);
ctxTerm.shadowColor = line.shadow;
ctxTerm.shadowBlur = CFG.SHADOW_BLUR;
strokeCurve(ctxTerm, curve, w, h);
}
// Event für Clouds-Plugin feuern
const pts = terminatorPtsSimple(sun.lat, sun.lon);
const northIsNight = isNight(89, 0, sun.lat, sun.lon);
window.dispatchEvent(new CustomEvent("earth:terminator", {
detail: { pts, northIsNight, w, h }
}));
}
// ── Events ────────────────────────────────────────────────────
window.addEventListener("earth:slow", e => render(e.detail));
window.addEventListener("earth:resize", e => render({ sun: EarthCore.sun, ...e.detail }));
nightImg.onload = () => render({ sun: EarthCore.sun, ...EarthCore.size });
}());