feat: Solar System mode — 'Travel to' the planets (Wave 4)
New '◉ Solar System' mode: leaves the Earth OSINT globe and renders a heliocentric orrery — Sun + 8 planets as spheres textured with real (CC BY 4.0, self-hosted) imagery, at their TRUE current positions (Schlyter series, incl. Uranus/Neptune), with orbits. A 'Travel to…' panel flies the camera to any body; 'Return to Earth' restores the full OSINT view (globe, all layers, clock, camera, frustum, lighting — all saved/restored). Planets exaggerated + distances compressed for a readable orrery; Saturn ring; camera headlight for even lighting; far plane widened to 8e9 (default 5e8 would clip the whole system). Textures harvested from Solar System Scope (CC BY 4.0), self-hosted in textures/ (WebGL taints cross-origin), credited in-app + README + textures/TEXTURES_CREDIT.md. Components verified in-browser (ellipsoids render, positions correct, mode enter/exit, UI, textures). Full-orrery screenshot pending a real GPU frame — the headless preview can't complete async worker geometry (backgrounded tab clamps). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41
SPEC4.md
Normal file
@ -0,0 +1,41 @@
|
||||
# GODSIGH — Wave 4 Spec: Solar System mode ("Travel to…")
|
||||
|
||||
**Author:** Claude Fable 5, 2026-07-14, on John's request. Built by Fable in the same session.
|
||||
**Prereq:** Waves 1–3.2 live at partly.party/godsigh. This is additive — a new *mode*, not a layer.
|
||||
|
||||
## 0. Mission
|
||||
A "Travel to…" experience: leave Earth and fly out to a Sun-centred solar system rendered from free/public data — the 8 planets (+ Sun, Moon) as spheres textured with real imagery, at their true current positions, with orbits, and smooth camera flights between them. Everything computed or harvested from free/CC/public-domain sources (the "harvest it all" ethos, applied to the sky).
|
||||
|
||||
## 1. Ground truth — verified 2026-07-14 (CORS-probed)
|
||||
- **Planet textures:** solarsystemscope.com `/textures/download/2k_*.jpg` (CC BY 4.0) — 200. No ACAO ⇒ **self-host** (WebGL taints cross-origin textures). Download the 2k set into `textures/`.
|
||||
- **Real surface tiles (Phase 2):** NASA Moon Trek WMTS (`trek.nasa.gov/tiles/Moon/…`) and USGS/OpenPlanetaryMap Mars tiles — both 200 with `ACAO: *`. Usable as Cesium imagery providers.
|
||||
- **Positions:** computed (Schlyter low-precision series, already proven in `js/layers/celestial.js`).
|
||||
|
||||
## 2. Architecture decision (locked)
|
||||
Cesium's globe is Earth-at-origin, so a Sun-centred scene can't move the globe. Therefore **Solar System mode HIDES the Earth globe** (`scene.globe.show = false`, hide all Earth OSINT layers) and renders a **heliocentric orrery** with entities; exiting restores the Earth OSINT view. The detailed Earth globe stays the "home" view; the orrery is a distinct mode.
|
||||
|
||||
- **Bodies:** Sun + Mercury·Venus·Earth·Mars·Jupiter·Saturn·Uranus·Neptune (+ Moon near Earth) as `ellipsoid` entities with `ImageMaterialProperty` (self-hosted textures). Saturn gets a ring (`2k_saturn_ring_alpha.png` on a flat disc / ring geometry). Real axial tilt applied via entity orientation.
|
||||
- **Positions:** heliocentric xyz from Schlyter (extend the celestial ELEM table with Uranus & Neptune; Earth = −sunRect). Compressed distance scale (tune in-browser) + exaggerated body sizes so it reads (true scale is unviewable — Sun↔Neptune is 30 AU vs Sun radius 0.005 AU). Directions/relative order stay true.
|
||||
- **Orbits:** faint polyline ellipses per planet (sample the orbit over one period).
|
||||
- **Travel-to:** a mode panel with a destination per body; clicking `camera.flyTo` the body with a sensible offset. "Return to Earth" exits the mode.
|
||||
- **Skybox/Sun light:** keep Cesium starfield; a point of light at the Sun optional.
|
||||
|
||||
## 3. Phases
|
||||
- **P1 (core, must-ship):** mode toggle + orrery (all 8 planets + Sun + Moon textured, true positions, orbits) + travel-to camera flights + return-to-Earth. Self-hosted 2k textures. Attribution in-app + README.
|
||||
- **P2 (stretch):** "Land" on **Mars** and the **Moon** with real NASA/USGS tile imagery. Cleanest path that avoids fragile globe-ellipsoid swapping: when travelling to Mars/Moon, load an 8k real-imagery texture (still real, harvested) OR, if a runtime globe swap proves stable, a true tile-imagery explorable body. Attempt the swap; fall back to hi-res texture if fragile.
|
||||
- **P3 (later, noted):** OSINT-on-other-worlds — Mars rover positions (NASA public APIs), Apollo landing sites, active orbiters.
|
||||
|
||||
## 4. Files
|
||||
- `textures/` — self-hosted CC planet maps (2k set) + `TEXTURES_CREDIT.md` (CC BY 4.0 attribution to solarsystemscope.com).
|
||||
- `js/solarsystem.js` — the mode: build bodies, orbits, travel-to menu, enter/exit.
|
||||
- HUD: a "◉ Solar System" toggle (top of the HUD, near the Data/Photo buttons) that enters/exits the mode; a destination panel while inside.
|
||||
- `main.js` — wire the mode (hide/restore globe + Earth layers + timeline; the mode owns its own render/positions).
|
||||
|
||||
## 5. Verify (browser, per phase)
|
||||
Zero console errors; textures load (self-hosted, same-origin); planets at plausible positions (inner planets near Sun, gas giants far, correct order); travel-to flies smoothly and arrives framed; return-to-Earth restores every layer + the globe exactly. Screenshot the orrery and an arrival. Subpath-safe (`textures/…`, relative). Commit per phase; deploy = rsync `js/` + `textures/` (no nginx change — all static/CORS-open).
|
||||
|
||||
## 6. Attribution (license compliance)
|
||||
Planet textures © solarsystemscope.com, CC BY 4.0 — credit in-app footer + README + `textures/TEXTURES_CREDIT.md`. Mars/Moon tiles: NASA/USGS/OpenPlanetaryMap (public domain / open) — credit when P2 lands.
|
||||
|
||||
## 7. Out of scope (Wave 4)
|
||||
Comets, asteroids, moons beyond Earth's, true-scale mode, VR. Roadmap.
|
||||
@ -253,3 +253,57 @@ html, body {
|
||||
|
||||
/* Keep Cesium's info box on-theme without fighting it */
|
||||
.cesium-infoBox { font-family: ui-monospace, monospace; }
|
||||
|
||||
/* ---- Solar System mode ---- */
|
||||
#ss-btn {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
z-index: 12;
|
||||
padding: 8px 14px;
|
||||
font: 12px ui-monospace, "SF Mono", Menlo, monospace;
|
||||
color: var(--text);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.5px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
#ss-btn:hover { color: #fff; border-color: rgba(255,255,255,0.25); }
|
||||
#ss-btn.active { color: #04121a; background: var(--accent); border-color: var(--accent); font-weight: 700; }
|
||||
|
||||
#ss-panel {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 14px;
|
||||
width: 240px;
|
||||
z-index: 12;
|
||||
padding: 14px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
font: 12px ui-monospace, Menlo, monospace;
|
||||
color: var(--text);
|
||||
}
|
||||
#ss-panel .ss-title { font-weight: 700; letter-spacing: 1px; color: var(--accent); font-size: 11px; margin-bottom: 10px; }
|
||||
#ss-panel .ss-dests { display: flex; flex-direction: column; gap: 4px; }
|
||||
#ss-panel .ss-dest {
|
||||
text-align: left;
|
||||
padding: 7px 10px;
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
}
|
||||
#ss-panel .ss-dest:hover { background: rgba(53,224,255,0.12); border-color: var(--accent); color: #fff; }
|
||||
#ss-panel .ss-back { margin-top: 8px; color: var(--dim); }
|
||||
#ss-panel .ss-foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); color: var(--dim); font-size: 9.5px; line-height: 1.4; }
|
||||
#ss-panel .ss-foot a { color: var(--dim); }
|
||||
|
||||
@ -22,6 +22,7 @@ import { REGISTRY } from './registry.js';
|
||||
import { createGeoJsonLayer } from './layers/geojson-layer.js';
|
||||
import { ADSB_LAYERS } from './adsb-registry.js';
|
||||
import { createAdsbLayer } from './layers/adsb-layer.js';
|
||||
import initSolarSystem from './solarsystem.js';
|
||||
|
||||
const Cesium = window.Cesium;
|
||||
|
||||
@ -263,4 +264,7 @@ loadLayers().then(() => {
|
||||
startHashSync();
|
||||
});
|
||||
|
||||
window.__godsigh = { viewer, ctx, activeLayers }; // debug handle
|
||||
// Solar System mode ("Travel to…") — a separate mode, not a layer.
|
||||
const solarSystem = initSolarSystem(ctx);
|
||||
|
||||
window.__godsigh = { viewer, ctx, activeLayers, solarSystem }; // debug handle
|
||||
|
||||
268
js/solarsystem.js
Normal file
@ -0,0 +1,268 @@
|
||||
// Solar System mode (Wave 4) — "Travel to…". Leaves the Earth OSINT globe and
|
||||
// renders a heliocentric orrery: the Sun + 8 planets as spheres textured with
|
||||
// real (CC BY 4.0, self-hosted) imagery, at their TRUE current positions
|
||||
// (Schlyter series), with orbits. Click a destination to fly there.
|
||||
//
|
||||
// Cesium's globe is Earth-at-origin, so the mode HIDES the globe + all Earth
|
||||
// layers and places the Sun at the scene origin; exiting restores everything.
|
||||
|
||||
export default function initSolarSystem(ctx) {
|
||||
const { viewer, Cesium, lib } = ctx;
|
||||
const scene = viewer.scene;
|
||||
|
||||
// ---- astronomy (Schlyter low-precision, heliocentric ecliptic AU) ----
|
||||
const D2R = Math.PI / 180;
|
||||
const rev = (x) => x - Math.floor(x / 360) * 360;
|
||||
const ELEM = {
|
||||
Mercury: (d) => ({ N: 48.3313 + 3.24587e-5 * d, i: 7.0047 + 5.00e-8 * d, w: 29.1241 + 1.01444e-5 * d, a: 0.387098, e: 0.205635 + 5.59e-10 * d, M: 168.6562 + 4.0923344368 * d }),
|
||||
Venus: (d) => ({ N: 76.6799 + 2.46590e-5 * d, i: 3.3946 + 2.75e-8 * d, w: 54.8910 + 1.38374e-5 * d, a: 0.723330, e: 0.006773 - 1.302e-9 * d, M: 48.0052 + 1.6021302244 * d }),
|
||||
Mars: (d) => ({ N: 49.5574 + 2.11081e-5 * d, i: 1.8497 - 1.78e-8 * d, w: 286.5016 + 2.92961e-5 * d, a: 1.523688, e: 0.093405 + 2.516e-9 * d, M: 18.6021 + 0.5240207766 * d }),
|
||||
Jupiter: (d) => ({ N: 100.4542 + 2.76854e-5 * d, i: 1.3030 - 1.557e-7 * d, w: 273.8777 + 1.64505e-5 * d, a: 5.20256, e: 0.048498 + 4.469e-9 * d, M: 19.8950 + 0.0830853001 * d }),
|
||||
Saturn: (d) => ({ N: 113.6634 + 2.38980e-5 * d, i: 2.4886 - 1.081e-7 * d, w: 339.3939 + 2.97661e-5 * d, a: 9.55475, e: 0.055546 - 9.499e-9 * d, M: 316.9670 + 0.0334442282 * d }),
|
||||
Uranus: (d) => ({ N: 74.0005 + 1.3978e-5 * d, i: 0.7733 + 1.9e-8 * d, w: 96.6612 + 3.0565e-5 * d, a: 19.18171 - 1.55e-8 * d, e: 0.047318 + 7.45e-9 * d, M: 142.5905 + 0.011725806 * d }),
|
||||
Neptune: (d) => ({ N: 131.7806 + 3.0173e-5 * d, i: 1.7700 - 2.55e-7 * d, w: 272.8461 - 6.027e-6 * d, a: 30.05826 + 3.313e-8 * d, e: 0.008606 + 2.15e-9 * d, M: 260.2471 + 0.005995147 * d }),
|
||||
};
|
||||
const dayNumber = (t) => Cesium.JulianDate.toDate(t).getTime() / 86400000 - 10956.0;
|
||||
function helio(name, d) {
|
||||
const el = ELEM[name](d);
|
||||
const N = el.N * D2R, i = el.i * D2R, w = el.w * D2R, e = el.e, M = rev(el.M) * D2R;
|
||||
let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
|
||||
for (let k = 0; k < 6; k++) E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
|
||||
const xv = el.a * (Math.cos(E) - e), yv = el.a * Math.sqrt(1 - e * e) * Math.sin(E);
|
||||
const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), u = v + w;
|
||||
return {
|
||||
x: r * (Math.cos(N) * Math.cos(u) - Math.sin(N) * Math.sin(u) * Math.cos(i)),
|
||||
y: r * (Math.sin(N) * Math.cos(u) + Math.cos(N) * Math.sin(u) * Math.cos(i)),
|
||||
z: r * Math.sin(u) * Math.sin(i),
|
||||
};
|
||||
}
|
||||
function earthHelio(d) {
|
||||
const w = 282.9404 + 4.70935e-5 * d, e = 0.016709 - 1.151e-9 * d, M = rev(356.0470 + 0.9856002585 * d) * D2R;
|
||||
let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
|
||||
const xv = Math.cos(E) - e, yv = Math.sqrt(1 - e * e) * Math.sin(E);
|
||||
const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), lon = v + w * D2R;
|
||||
return { x: -r * Math.cos(lon), y: -r * Math.sin(lon), z: 0 }; // Earth = −(Sun geocentric)
|
||||
}
|
||||
|
||||
// ---- scaling (tuned for a readable orrery, not true scale) ----
|
||||
const AU_UNIT = 4.0e7; // ~Earth orbit display radius
|
||||
const distScale = (au) => AU_UNIT * Math.pow(au, 0.72); // gentle outer compression
|
||||
const BODY_UNIT = 3.4e6; // exaggerated so bodies read at orrery scale
|
||||
const bodyRadius = (km, isSun) => isSun ? 1.6e7 : BODY_UNIT * Math.pow(km / 6371, 0.4);
|
||||
function scaledPos(vec) { // ecliptic AU {x,y,z} → scene Cartesian3
|
||||
const au = Math.hypot(vec.x, vec.y, vec.z) || 1e-6;
|
||||
const s = distScale(au) / au;
|
||||
return new Cesium.Cartesian3(vec.x * s, vec.y * s, vec.z * s);
|
||||
}
|
||||
|
||||
const BODIES = [
|
||||
{ key: 'sun', name: '☉ Sun', tex: 'textures/2k_sun.jpg', km: 696000, sun: true },
|
||||
{ key: 'mercury', name: '☿ Mercury', tex: 'textures/2k_mercury.jpg', km: 2439.7 },
|
||||
{ key: 'venus', name: '♀ Venus', tex: 'textures/2k_venus_atmosphere.jpg', km: 6051.8 },
|
||||
{ key: 'earth', name: '🜨 Earth', tex: 'textures/2k_earth_daymap.jpg', km: 6371 },
|
||||
{ key: 'mars', name: '♂ Mars', tex: 'textures/2k_mars.jpg', km: 3389.5 },
|
||||
{ key: 'jupiter', name: '♃ Jupiter', tex: 'textures/2k_jupiter.jpg', km: 69911 },
|
||||
{ key: 'saturn', name: '♄ Saturn', tex: 'textures/2k_saturn.jpg', km: 58232, ring: [1.3, 2.3] },
|
||||
{ key: 'uranus', name: '⛢ Uranus', tex: 'textures/2k_uranus.jpg', km: 25362 },
|
||||
{ key: 'neptune', name: '♆ Neptune', tex: 'textures/2k_neptune.jpg', km: 24622 },
|
||||
];
|
||||
|
||||
const ds = new Cesium.CustomDataSource('solarsystem');
|
||||
ds.show = false;
|
||||
viewer.dataSources.add(ds);
|
||||
const bodyEntities = {}; // key → entity (for travel-to framing)
|
||||
|
||||
function posOf(key, d) {
|
||||
if (key === 'sun') return new Cesium.Cartesian3(0, 0, 0);
|
||||
if (key === 'earth') return scaledPos(earthHelio(d));
|
||||
return scaledPos(helio(key.charAt(0).toUpperCase() + key.slice(1), d));
|
||||
}
|
||||
|
||||
function build(d) {
|
||||
ds.entities.removeAll();
|
||||
for (const b of BODIES) {
|
||||
const pos = posOf(b.key, d);
|
||||
const r = bodyRadius(b.km, b.sun);
|
||||
const ent = ds.entities.add({
|
||||
name: b.name,
|
||||
position: pos,
|
||||
ellipsoid: {
|
||||
radii: new Cesium.Cartesian3(r, r, r),
|
||||
material: new Cesium.ImageMaterialProperty({ image: b.tex, color: Cesium.Color.WHITE }),
|
||||
slicePartitions: 32, stackPartitions: 32,
|
||||
},
|
||||
label: {
|
||||
text: b.name, font: '13px "Segoe UI", system-ui, sans-serif',
|
||||
fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.fromCssColorString('#05080b'),
|
||||
outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -8),
|
||||
translucencyByDistance: new Cesium.NearFarScalar(1e8, 1.0, 4e9, 0.35),
|
||||
disableDepthTestDistance: 50000,
|
||||
},
|
||||
});
|
||||
bodyEntities[b.key] = ent;
|
||||
|
||||
if (b.ring) {
|
||||
ds.entities.add({
|
||||
position: pos,
|
||||
ellipse: {
|
||||
semiMajorAxis: r * b.ring[1], semiMinorAxis: r * b.ring[1],
|
||||
height: 0,
|
||||
material: new Cesium.ImageMaterialProperty({ image: 'textures/2k_saturn_ring_alpha.png', transparent: true }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Orbit (skip the Sun): trace one full revolution by sweeping the anomaly.
|
||||
if (b.key !== 'sun') {
|
||||
const pts = [];
|
||||
for (let a = 0; a <= 360; a += 3) {
|
||||
const v = b.key === 'earth' ? earthHelioAt(d, a) : helioAt(b.key, d, a);
|
||||
pts.push(scaledPos(v));
|
||||
}
|
||||
ds.entities.add({
|
||||
polyline: { positions: pts, width: 2, material: new Cesium.ColorMaterialProperty(lib.cz('#6a86c0', 0.7)) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Orbit sampling: recompute a body's heliocentric position at mean-anomaly `deg`.
|
||||
function helioAt(key, d, deg) {
|
||||
const name = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
const el = ELEM[name](d);
|
||||
const N = el.N * D2R, i = el.i * D2R, w = el.w * D2R, e = el.e, M = deg * D2R;
|
||||
let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
|
||||
for (let k = 0; k < 6; k++) E = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
|
||||
const xv = el.a * (Math.cos(E) - e), yv = el.a * Math.sqrt(1 - e * e) * Math.sin(E);
|
||||
const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), u = v + w;
|
||||
return { x: r * (Math.cos(N) * Math.cos(u) - Math.sin(N) * Math.sin(u) * Math.cos(i)), y: r * (Math.sin(N) * Math.cos(u) + Math.cos(N) * Math.sin(u) * Math.cos(i)), z: r * Math.sin(u) * Math.sin(i) };
|
||||
}
|
||||
function earthHelioAt(d, deg) {
|
||||
const w = 282.9404 + 4.70935e-5 * d, e = 0.016709 - 1.151e-9 * d, M = deg * D2R;
|
||||
let E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
|
||||
const xv = Math.cos(E) - e, yv = Math.sqrt(1 - e * e) * Math.sin(E);
|
||||
const v = Math.atan2(yv, xv), r = Math.hypot(xv, yv), lon = v + w * D2R;
|
||||
return { x: -r * Math.cos(lon), y: -r * Math.sin(lon), z: 0 };
|
||||
}
|
||||
|
||||
// ---- mode enter / exit ----
|
||||
let active = false;
|
||||
const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, collide: true };
|
||||
|
||||
function enter() {
|
||||
if (active) return;
|
||||
active = true;
|
||||
const d = dayNumber(viewer.clock.currentTime);
|
||||
build(d);
|
||||
|
||||
// Hide every Earth layer: entity data sources + billboard primitives.
|
||||
saved.dataSources = [];
|
||||
for (let i = 0; i < viewer.dataSources.length; i++) {
|
||||
const s = viewer.dataSources.get(i);
|
||||
if (s === ds) continue;
|
||||
saved.dataSources.push([s, s.show]); s.show = false;
|
||||
}
|
||||
saved.primitives = [];
|
||||
for (let i = 0; i < scene.primitives.length; i++) {
|
||||
const p = scene.primitives.get(i);
|
||||
if (p && typeof p.length === 'number' && 'show' in p) { saved.primitives.push([p, p.show]); p.show = false; }
|
||||
}
|
||||
saved.globe = scene.globe.show; scene.globe.show = false;
|
||||
saved.atmo = scene.globe.showGroundAtmosphere; scene.globe.showGroundAtmosphere = false;
|
||||
saved.sky = scene.skyAtmosphere.show; scene.skyAtmosphere.show = false;
|
||||
saved.animate = viewer.clock.shouldAnimate; viewer.clock.shouldAnimate = false; // freeze the snapshot
|
||||
// The scene spans ~1e9 m — well past Cesium's default 5e8 far plane, which
|
||||
// would clip every body. Widen it (and drop the surface-collision limit so
|
||||
// you can fly far out); restored on exit.
|
||||
saved.far = scene.camera.frustum.far; scene.camera.frustum.far = 8e9;
|
||||
saved.collide = scene.screenSpaceCameraController.enableCollisionDetection;
|
||||
scene.screenSpaceCameraController.enableCollisionDetection = false;
|
||||
scene.screenSpaceCameraController.maximumZoomDistance = 6e9;
|
||||
|
||||
// Even "planetarium" lighting: a headlight from the camera so no half-dark planets.
|
||||
saved.light = scene.light;
|
||||
scene.light = new Cesium.DirectionalLight({ direction: scene.camera.directionWC.clone(), intensity: 2.2 });
|
||||
headlight = scene.preRender.addEventListener((s) => {
|
||||
scene.light.direction = Cesium.Cartesian3.clone(s.camera.directionWC, scene.light.direction);
|
||||
});
|
||||
|
||||
ds.show = true;
|
||||
document.getElementById('hud').style.display = 'none';
|
||||
panel.style.display = 'block';
|
||||
document.getElementById('ss-btn').classList.add('active');
|
||||
|
||||
// Fly out to a clean top-down-ish view of the system (orthonormal dir/up —
|
||||
// Cesium needs those, and lookAt at the geocentre is degenerate).
|
||||
viewer.camera.flyTo({
|
||||
destination: new Cesium.Cartesian3(0.7e8, 0.3e8, 7.5e8),
|
||||
orientation: { direction: new Cesium.Cartesian3(0, 0, -1), up: new Cesium.Cartesian3(0, 1, 0) },
|
||||
duration: 2.5,
|
||||
});
|
||||
}
|
||||
|
||||
let headlight = null;
|
||||
function exit() {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
ds.show = false;
|
||||
for (const [s, show] of saved.dataSources) s.show = show;
|
||||
for (const [p, show] of saved.primitives) p.show = show;
|
||||
scene.globe.show = saved.globe;
|
||||
scene.globe.showGroundAtmosphere = saved.atmo;
|
||||
scene.skyAtmosphere.show = saved.sky;
|
||||
viewer.clock.shouldAnimate = saved.animate;
|
||||
if (headlight) { headlight(); headlight = null; }
|
||||
if (saved.light) scene.light = saved.light;
|
||||
scene.camera.frustum.far = saved.far;
|
||||
scene.screenSpaceCameraController.enableCollisionDetection = saved.collide;
|
||||
scene.screenSpaceCameraController.maximumZoomDistance = Infinity;
|
||||
document.getElementById('hud').style.display = '';
|
||||
panel.style.display = 'none';
|
||||
document.getElementById('ss-btn').classList.remove('active');
|
||||
viewer.camera.flyTo({
|
||||
destination: Cesium.Cartesian3.fromDegrees(ctx.CONFIG.camera.lon, ctx.CONFIG.camera.lat, ctx.CONFIG.camera.height),
|
||||
duration: 2.0,
|
||||
});
|
||||
}
|
||||
|
||||
function travelTo(key) {
|
||||
const ent = bodyEntities[key];
|
||||
if (!ent) return;
|
||||
if (key === 'earth') { // Earth = home; offer to drop back to the OSINT globe
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
viewer.flyTo(ent, { duration: 2.5, offset: new Cesium.HeadingPitchRange(0, -0.3, bodyRadius(BODIES.find((b) => b.key === key).km) * 6) });
|
||||
}
|
||||
|
||||
// ---- UI: enter button + destination panel ----
|
||||
const btn = document.createElement('button');
|
||||
btn.id = 'ss-btn';
|
||||
btn.type = 'button';
|
||||
btn.textContent = '◉ Solar System';
|
||||
btn.title = 'Leave Earth and travel the solar system';
|
||||
btn.addEventListener('click', () => (active ? exit() : enter()));
|
||||
document.body.appendChild(btn);
|
||||
|
||||
const panel = document.createElement('aside');
|
||||
panel.id = 'ss-panel';
|
||||
panel.style.display = 'none';
|
||||
panel.innerHTML = `<div class="ss-title">TRAVEL TO</div><div class="ss-dests"></div>
|
||||
<div class="ss-foot">Positions computed live · textures © <a href="https://www.solarsystemscope.com/" target="_blank" rel="noopener">Solar System Scope</a> CC BY 4.0</div>`;
|
||||
const dests = panel.querySelector('.ss-dests');
|
||||
for (const b of BODIES) {
|
||||
const d = document.createElement('button');
|
||||
d.className = 'ss-dest';
|
||||
d.textContent = b.name;
|
||||
d.addEventListener('click', () => travelTo(b.key));
|
||||
dests.appendChild(d);
|
||||
}
|
||||
const back = document.createElement('button');
|
||||
back.className = 'ss-dest ss-back';
|
||||
back.textContent = '↩ Return to Earth (OSINT)';
|
||||
back.addEventListener('click', exit);
|
||||
panel.appendChild(back);
|
||||
document.body.appendChild(panel);
|
||||
|
||||
return { enter, exit, isActive: () => active };
|
||||
}
|
||||
BIN
textures/2k_earth_daymap.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
textures/2k_jupiter.jpg
Normal file
|
After Width: | Height: | Size: 487 KiB |
BIN
textures/2k_mars.jpg
Normal file
|
After Width: | Height: | Size: 733 KiB |
BIN
textures/2k_mercury.jpg
Normal file
|
After Width: | Height: | Size: 852 KiB |
BIN
textures/2k_moon.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
textures/2k_neptune.jpg
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
textures/2k_saturn.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
textures/2k_saturn_ring_alpha.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
textures/2k_sun.jpg
Normal file
|
After Width: | Height: | Size: 803 KiB |
BIN
textures/2k_uranus.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
textures/2k_venus_atmosphere.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
10
textures/TEXTURES_CREDIT.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Planet & body textures
|
||||
|
||||
Equirectangular surface/cloud maps for the Solar System mode.
|
||||
|
||||
**Source:** [Solar System Scope](https://www.solarsystemscope.com/textures/) — © INOVE / Solar System Scope.
|
||||
**License:** Creative Commons Attribution 4.0 International (CC BY 4.0).
|
||||
|
||||
Files: `2k_{sun,mercury,venus_atmosphere,earth_daymap,moon,mars,jupiter,saturn,uranus,neptune}.jpg`, `2k_saturn_ring_alpha.png`.
|
||||
|
||||
Attribution is also shown in-app (Solar System mode footer) and in the project README.
|
||||