Geocron ist drin
This commit is contained in:
@@ -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 });
|
||||
|
||||
}());
|
||||
Reference in New Issue
Block a user