feat: Solar System mode — real 3D lit planets + Saturn rings
Replace the flat billboard-disc planets with real 3D EllipsoidGraphics spheres textured with self-hosted high-res NASA/JPL + Solar System Scope maps (hd_*.jpg, 2048x1024), plus a PlaneGraphics Saturn ring (top-down annulus, transparent centre, depth-tested occlusion). Constant axial tilts (Venus upside-down, Uranus on its side). Cesium gotchas handled: constant orientation (time-varying orientation drops image materials -> blank white); DirectionalLight intensity 0.42 (no HDR -> higher clamps bright maps to white); camera.flyTo is dead in this mode so enter/travel run a manual eased setView tween (flyCamera); ellipsoid+ring gated by distanceDisplayCondition(r*30) so the orrery overview stays clean colored dots and the textured sphere takes over up close. Drop 9 now-unused 2k_ maps; textures/ is 3.3MB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ -55,16 +55,19 @@ export default function initSolarSystem(ctx) {
|
|||||||
return new Cesium.Cartesian3(vec.x * s, vec.y * s, vec.z * s);
|
return new Cesium.Cartesian3(vec.x * s, vec.y * s, vec.z * s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Real 3D spheres textured with self-hosted CC BY 4.0 / NASA imagery. `obliq` =
|
||||||
|
// axial tilt in degrees (Venus ~177° reads upside-down; Uranus ~98° lies on its
|
||||||
|
// side) — applied as a constant orientation so each planet leans correctly.
|
||||||
const BODIES = [
|
const BODIES = [
|
||||||
{ key: 'sun', name: '☉ Sun', tex: 'textures/2k_sun.jpg', km: 696000, sun: true },
|
{ key: 'sun', name: '☉ Sun', tex: 'textures/2k_sun.jpg', km: 696000, sun: true, color: '#ffcf6b' },
|
||||||
{ key: 'mercury', name: '☿ Mercury', tex: 'textures/2k_mercury.jpg', km: 2439.7 },
|
{ key: 'mercury', name: '☿ Mercury', tex: 'textures/hd_mercury.jpg', km: 2439.7, color: '#9a8d80', obliq: 0.03 },
|
||||||
{ key: 'venus', name: '♀ Venus', tex: 'textures/2k_venus_atmosphere.jpg', km: 6051.8 },
|
{ key: 'venus', name: '♀ Venus', tex: 'textures/hd_venus.jpg', km: 6051.8, color: '#d8b56a', obliq: 177.4 },
|
||||||
{ key: 'earth', name: '🜨 Earth', tex: 'textures/2k_earth_daymap.jpg', km: 6371 },
|
{ key: 'earth', name: '🜨 Earth', tex: 'textures/hd_earth.jpg', km: 6371, color: '#5b8fd0', obliq: 23.44 },
|
||||||
{ key: 'mars', name: '♂ Mars', tex: 'textures/2k_mars.jpg', km: 3389.5 },
|
{ key: 'mars', name: '♂ Mars', tex: 'textures/hd_mars.jpg', km: 3389.5, color: '#c1502a', obliq: 25.19 },
|
||||||
{ key: 'jupiter', name: '♃ Jupiter', tex: 'textures/2k_jupiter.jpg', km: 69911 },
|
{ key: 'jupiter', name: '♃ Jupiter', tex: 'textures/hd_jupiter.jpg', km: 69911, color: '#d9c9a3', obliq: 3.13 },
|
||||||
{ key: 'saturn', name: '♄ Saturn', tex: 'textures/2k_saturn.jpg', km: 58232, ring: [1.3, 2.3] },
|
{ key: 'saturn', name: '♄ Saturn', tex: 'textures/hd_saturn.jpg', km: 58232, color: '#e6dbb0', obliq: 26.73, ring: 'textures/hd_saturn_ring.png' },
|
||||||
{ key: 'uranus', name: '⛢ Uranus', tex: 'textures/2k_uranus.jpg', km: 25362 },
|
{ key: 'uranus', name: '⛢ Uranus', tex: 'textures/hd_uranus.jpg', km: 25362, color: '#9fdce4', obliq: 97.77 },
|
||||||
{ key: 'neptune', name: '♆ Neptune', tex: 'textures/2k_neptune.jpg', km: 24622 },
|
{ key: 'neptune', name: '♆ Neptune', tex: 'textures/2k_neptune.jpg', km: 24622, color: '#456fe0', obliq: 28.32 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ds = new Cesium.CustomDataSource('solarsystem');
|
const ds = new Cesium.CustomDataSource('solarsystem');
|
||||||
@ -115,46 +118,110 @@ export default function initSolarSystem(ctx) {
|
|||||||
return cv;
|
return cv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constant axial tilt about +X (Saturn's matches its ring plane; Uranus rolls on
|
||||||
|
// its side at 98°). NOTE: the orientation MUST be constant — a time-varying
|
||||||
|
// orientation pushes the ellipsoid onto Cesium's dynamic-geometry path, which
|
||||||
|
// only supports solid-colour materials and renders the planet blank white. A
|
||||||
|
// static orientation keeps the image material; you still orbit planets by hand.
|
||||||
|
const X_AXIS = new Cesium.Cartesian3(1, 0, 0);
|
||||||
|
function tiltQuat(b) {
|
||||||
|
return Cesium.Quaternion.fromAxisAngle(X_AXIS, (b.obliq || 0) * D2R);
|
||||||
|
}
|
||||||
|
// Saturn's ring-plane normal = its tilted pole (so ring ⟂ pole, aligned to sphere).
|
||||||
|
function ringNormal(obliqDeg) {
|
||||||
|
const t = obliqDeg * D2R;
|
||||||
|
return new Cesium.Cartesian3(0, -Math.sin(t), Math.cos(t));
|
||||||
|
}
|
||||||
|
|
||||||
function build(d) {
|
function build(d) {
|
||||||
ds.entities.removeAll();
|
ds.entities.removeAll();
|
||||||
for (const b of BODIES) {
|
for (const b of BODIES) {
|
||||||
const pos = posOf(b.key, d);
|
const pos = posOf(b.key, d);
|
||||||
const r = bodyRadius(b.km, b.sun);
|
const r = bodyRadius(b.km, b.sun);
|
||||||
// The disc canvas is 256px; `scale` is a MULTIPLIER on that. Small values:
|
|
||||||
// ~2.4 when you're right at the planet (fills the view), ~0.12 far away (a
|
// The Sun is a star: keep it a bright, always-lit billboard disc (a lit
|
||||||
// dot). Per-body near distance = just outside its own radius.
|
// ellipsoid would show a dark hemisphere). Planets are real 3D spheres.
|
||||||
|
if (b.sun) {
|
||||||
|
const ent = ds.entities.add({
|
||||||
|
name: b.name,
|
||||||
|
position: pos,
|
||||||
|
billboard: {
|
||||||
|
image: makeDisc(b),
|
||||||
|
scaleByDistance: new Cesium.NearFarScalar(r * 2.5, 2.4, 9e8, 0.12),
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
label: labelGraphics(b),
|
||||||
|
});
|
||||||
|
bodyEntities[b.key] = ent;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const ent = ds.entities.add({
|
const ent = ds.entities.add({
|
||||||
name: b.name,
|
name: b.name,
|
||||||
position: pos,
|
position: pos,
|
||||||
billboard: {
|
orientation: tiltQuat(b),
|
||||||
image: makeDisc(b),
|
ellipsoid: {
|
||||||
scaleByDistance: new Cesium.NearFarScalar(r * 2.5, 2.4, 9e8, 0.12),
|
radii: new Cesium.Cartesian3(r, r, r),
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
material: new Cesium.ImageMaterialProperty({ image: b.tex, color: Cesium.Color.WHITE }),
|
||||||
},
|
slicePartitions: 64, stackPartitions: 64,
|
||||||
label: {
|
// Only render the real sphere once you're close enough for it to read as a
|
||||||
text: b.name, font: '13px "Segoe UI", system-ui, sans-serif',
|
// textured planet. Far away it minifies to a grey ball — hide it and show
|
||||||
fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.fromCssColorString('#05080b'),
|
// the colored dot instead. Clean handoff at r*30.
|
||||||
outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, r * 30),
|
||||||
verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 12),
|
},
|
||||||
|
// Colored dot + label are the orrery-overview representation; they vanish as
|
||||||
|
// you approach (r*30) and the textured 3D planet takes over.
|
||||||
|
point: {
|
||||||
|
pixelSize: 7,
|
||||||
|
color: Cesium.Color.fromCssColorString(b.color),
|
||||||
|
outlineColor: Cesium.Color.fromCssColorString('#05080b'), outlineWidth: 1,
|
||||||
|
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(r * 30, 9e9),
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
},
|
},
|
||||||
|
label: labelGraphics(b),
|
||||||
});
|
});
|
||||||
bodyEntities[b.key] = ent;
|
bodyEntities[b.key] = ent;
|
||||||
|
|
||||||
// Orbit (skip the Sun): trace one full revolution by sweeping the anomaly.
|
// Saturn's rings: a flat quad in the equatorial plane, textured with a
|
||||||
// arcType NONE = straight segments in space (not draped on an ellipsoid).
|
// top-down ring annulus (transparent centre + corners). Depth-tested so the
|
||||||
if (b.key !== 'sun') {
|
// planet occludes the far half of the ring — the real Saturn silhouette.
|
||||||
const pts = [];
|
if (b.ring) {
|
||||||
for (let a = 0; a <= 360; a += 3) {
|
const side = r * 6.6; // hole ≈ planet radius, outer edge ≈ 3.1 radii
|
||||||
const v = b.key === 'earth' ? earthHelioAt(d, a) : helioAt(b.key, d, a);
|
|
||||||
pts.push(scaledPos(v));
|
|
||||||
}
|
|
||||||
ds.entities.add({
|
ds.entities.add({
|
||||||
polyline: { positions: pts, width: 1.5, arcType: Cesium.ArcType.NONE, material: lib.cz('#6a86c0', 0.6) },
|
position: pos,
|
||||||
|
plane: {
|
||||||
|
plane: new Cesium.Plane(ringNormal(b.obliq), 0.0),
|
||||||
|
dimensions: new Cesium.Cartesian2(side, side),
|
||||||
|
material: new Cesium.ImageMaterialProperty({ image: b.ring, transparent: true }),
|
||||||
|
// Match the sphere's visibility window (r*30). Beyond it the ring both
|
||||||
|
// minifies to an ugly opaque grey card AND would float without a planet,
|
||||||
|
// so hide it — at that range Saturn is just its labelled dot.
|
||||||
|
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, r * 30),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orbit: trace one full revolution by sweeping the anomaly.
|
||||||
|
// arcType NONE = straight segments in space (not draped on an ellipsoid).
|
||||||
|
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: 1.5, arcType: Cesium.ArcType.NONE, material: lib.cz('#6a86c0', 0.6) },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function labelGraphics(b) {
|
||||||
|
return {
|
||||||
|
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.TOP, pixelOffset: new Cesium.Cartesian2(0, 12),
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
};
|
||||||
|
}
|
||||||
// Orbit sampling: recompute a body's heliocentric position at mean-anomaly `deg`.
|
// Orbit sampling: recompute a body's heliocentric position at mean-anomaly `deg`.
|
||||||
function helioAt(key, d, deg) {
|
function helioAt(key, d, deg) {
|
||||||
const name = key.charAt(0).toUpperCase() + key.slice(1);
|
const name = key.charAt(0).toUpperCase() + key.slice(1);
|
||||||
@ -178,6 +245,33 @@ export default function initSolarSystem(ctx) {
|
|||||||
let active = false;
|
let active = false;
|
||||||
const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, near: 0.1, collide: true };
|
const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, near: 0.1, collide: true };
|
||||||
|
|
||||||
|
// Cesium's animated camera.flyTo does NOT progress in this mode (its flight tween
|
||||||
|
// sits idle out here 1e8 m off Earth with the globe hidden) — but setView works
|
||||||
|
// perfectly. So drive our own eased per-frame tween: lerp position + orientation
|
||||||
|
// and setView each frame. Guaranteed smooth because setView is reliable.
|
||||||
|
let flightTick = null;
|
||||||
|
const V3 = Cesium.Cartesian3;
|
||||||
|
function flyCamera(destPos, destDir, destUp, duration) {
|
||||||
|
if (flightTick) { flightTick(); flightTick = null; }
|
||||||
|
const cam = scene.camera;
|
||||||
|
const startPos = cam.positionWC.clone();
|
||||||
|
const startDir = cam.directionWC.clone();
|
||||||
|
const startUp = cam.upWC.clone();
|
||||||
|
const t0 = performance.now();
|
||||||
|
flightTick = scene.preRender.addEventListener(() => {
|
||||||
|
let u = (performance.now() - t0) / 1000 / duration;
|
||||||
|
if (u > 1) u = 1;
|
||||||
|
const e = u * u * (3 - 2 * u); // smoothstep ease in/out
|
||||||
|
const pos = V3.lerp(startPos, destPos, e, new V3());
|
||||||
|
let dir = V3.normalize(V3.lerp(startDir, destDir, e, new V3()), new V3());
|
||||||
|
let up = V3.normalize(V3.lerp(startUp, destUp, e, new V3()), new V3());
|
||||||
|
const right = V3.normalize(V3.cross(dir, up, new V3()), new V3());
|
||||||
|
up = V3.normalize(V3.cross(right, dir, new V3()), new V3()); // re-orthonormalize
|
||||||
|
scene.camera.setView({ destination: pos, orientation: { direction: dir, up } });
|
||||||
|
if (u >= 1 && flightTick) { flightTick(); flightTick = null; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function enter() {
|
function enter() {
|
||||||
if (active) return;
|
if (active) return;
|
||||||
active = true;
|
active = true;
|
||||||
@ -212,11 +306,22 @@ export default function initSolarSystem(ctx) {
|
|||||||
scene.screenSpaceCameraController.enableCollisionDetection = false;
|
scene.screenSpaceCameraController.enableCollisionDetection = false;
|
||||||
scene.screenSpaceCameraController.maximumZoomDistance = 6e9;
|
scene.screenSpaceCameraController.maximumZoomDistance = 6e9;
|
||||||
|
|
||||||
// Even "planetarium" lighting: a headlight from the camera so no half-dark planets.
|
// Portrait key-light: source sits upper-left-front of the camera, so the lit
|
||||||
|
// hemisphere of whatever you orbit stays upper-left — every 3D sphere reads
|
||||||
|
// with a graceful terminator from any angle, and (since it tracks the camera)
|
||||||
|
// there's no "sun is over there but lit from here" contradiction.
|
||||||
saved.light = scene.light;
|
saved.light = scene.light;
|
||||||
scene.light = new Cesium.DirectionalLight({ direction: scene.camera.directionWC.clone(), intensity: 2.2 });
|
// Intensity is deliberately low: the equirect maps are near-albedo-1 in
|
||||||
|
// places, so anything above ~0.5 clamps the lit hemisphere to pure white
|
||||||
|
// (no HDR). 0.42 keeps the texture + Saturn's rings rich and readable.
|
||||||
|
scene.light = new Cesium.DirectionalLight({ direction: scene.camera.directionWC.clone(), intensity: 0.42 });
|
||||||
|
const _ld = new Cesium.Cartesian3();
|
||||||
headlight = scene.preRender.addEventListener((s) => {
|
headlight = scene.preRender.addEventListener((s) => {
|
||||||
scene.light.direction = Cesium.Cartesian3.clone(s.camera.directionWC, scene.light.direction);
|
const c = s.camera;
|
||||||
|
Cesium.Cartesian3.multiplyByScalar(c.rightWC, 0.5, _ld);
|
||||||
|
Cesium.Cartesian3.add(_ld, Cesium.Cartesian3.multiplyByScalar(c.upWC, -0.55, new Cesium.Cartesian3()), _ld);
|
||||||
|
Cesium.Cartesian3.add(_ld, Cesium.Cartesian3.multiplyByScalar(c.directionWC, 0.67, new Cesium.Cartesian3()), _ld);
|
||||||
|
Cesium.Cartesian3.normalize(_ld, scene.light.direction);
|
||||||
});
|
});
|
||||||
|
|
||||||
ds.show = true;
|
ds.show = true;
|
||||||
@ -224,19 +329,22 @@ export default function initSolarSystem(ctx) {
|
|||||||
panel.style.display = 'block';
|
panel.style.display = 'block';
|
||||||
document.getElementById('ss-btn').classList.add('active');
|
document.getElementById('ss-btn').classList.add('active');
|
||||||
|
|
||||||
// Fly out to a clean top-down-ish view of the system (orthonormal dir/up —
|
// Swoop out from "Sun in your face" (camera starts at the old Earth-surface
|
||||||
// Cesium needs those, and lookAt at the geocentre is degenerate).
|
// spot, and the Sun now sits where Earth's centre was) to a clean oblique view
|
||||||
viewer.camera.flyTo({
|
// of the whole system.
|
||||||
destination: new Cesium.Cartesian3(0.7e8, 0.3e8, 7.5e8),
|
flyCamera(
|
||||||
orientation: { direction: new Cesium.Cartesian3(0, 0, -1), up: new Cesium.Cartesian3(0, 1, 0) },
|
new Cesium.Cartesian3(1.6e8, -3.4e8, 2.6e8),
|
||||||
duration: 2.5,
|
Cesium.Cartesian3.normalize(new Cesium.Cartesian3(-1.6e8, 3.4e8, -2.6e8), new Cesium.Cartesian3()),
|
||||||
});
|
new Cesium.Cartesian3(0, 0, 1),
|
||||||
|
2.6,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let headlight = null;
|
let headlight = null;
|
||||||
function exit() {
|
function exit() {
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
active = false;
|
active = false;
|
||||||
|
if (flightTick) { flightTick(); flightTick = null; } // stop any in-progress swoop
|
||||||
ds.show = false;
|
ds.show = false;
|
||||||
for (const [s, show] of saved.dataSources) s.show = show;
|
for (const [s, show] of saved.dataSources) s.show = show;
|
||||||
for (const [p, show] of saved.primitives) p.show = show;
|
for (const [p, show] of saved.primitives) p.show = show;
|
||||||
@ -253,20 +361,36 @@ export default function initSolarSystem(ctx) {
|
|||||||
document.getElementById('hud').style.display = '';
|
document.getElementById('hud').style.display = '';
|
||||||
panel.style.display = 'none';
|
panel.style.display = 'none';
|
||||||
document.getElementById('ss-btn').classList.remove('active');
|
document.getElementById('ss-btn').classList.remove('active');
|
||||||
viewer.camera.flyTo({
|
// Instant, reliable return to the OSINT globe (animated flyTo is dead in this
|
||||||
|
// mode; setView always works). Nadir view over the configured home spot.
|
||||||
|
scene.camera.setView({
|
||||||
destination: Cesium.Cartesian3.fromDegrees(ctx.CONFIG.camera.lon, ctx.CONFIG.camera.lat, ctx.CONFIG.camera.height),
|
destination: Cesium.Cartesian3.fromDegrees(ctx.CONFIG.camera.lon, ctx.CONFIG.camera.lat, ctx.CONFIG.camera.height),
|
||||||
duration: 2.0,
|
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function travelTo(key) {
|
function travelTo(key) {
|
||||||
const ent = bodyEntities[key];
|
const b = BODIES.find((x) => x.key === key);
|
||||||
if (!ent) return;
|
if (!b) return;
|
||||||
if (key === 'earth') { // Earth = home; offer to drop back to the OSINT globe
|
if (key === 'earth') { // Earth = home; drop back to the OSINT globe
|
||||||
exit();
|
exit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
viewer.flyTo(ent, { duration: 2.5, offset: new Cesium.HeadingPitchRange(0, -0.3, bodyRadius(BODIES.find((b) => b.key === key).km) * 6) });
|
// Deterministic framing in WORLD space. flyTo(entity)/flyToBoundingSphere both
|
||||||
|
// misbehave out here (the ENU frame they offset within is degenerate 2e8 m off
|
||||||
|
// Earth), so compute the destination + orientation directly. Camera sits up and
|
||||||
|
// to the side of the ecliptic → a 3/4 view that shows Saturn's ring tilt.
|
||||||
|
const ent = bodyEntities[key];
|
||||||
|
const pos = ent.position.getValue(viewer.clock.currentTime);
|
||||||
|
const r = bodyRadius(b.km, b.sun);
|
||||||
|
const range = (b.sun ? 3.0 : b.ring ? 6.0 : 5.0) * r;
|
||||||
|
const V = Cesium.Cartesian3;
|
||||||
|
const offDir = V.normalize(new V(0.55, -0.25, 0.45), new V());
|
||||||
|
const dest = V.add(pos, V.multiplyByScalar(offDir, range, new V()), new V());
|
||||||
|
const dir = V.negate(offDir, new V());
|
||||||
|
const right = V.normalize(V.cross(dir, new V(0, 0, 1), new V()), new V());
|
||||||
|
const up = V.normalize(V.cross(right, dir, new V()), new V());
|
||||||
|
flyCamera(dest, dir, up, 2.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- UI: enter button + destination panel ----
|
// ---- UI: enter button + destination panel ----
|
||||||
@ -282,7 +406,7 @@ export default function initSolarSystem(ctx) {
|
|||||||
panel.id = 'ss-panel';
|
panel.id = 'ss-panel';
|
||||||
panel.style.display = 'none';
|
panel.style.display = 'none';
|
||||||
panel.innerHTML = `<div class="ss-title">TRAVEL TO</div><div class="ss-dests"></div>
|
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>`;
|
<div class="ss-foot">Positions computed live · planet maps: NASA/JPL & <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');
|
const dests = panel.querySelector('.ss-dests');
|
||||||
for (const b of BODIES) {
|
for (const b of BODIES) {
|
||||||
const d = document.createElement('button');
|
const d = document.createElement('button');
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 452 KiB |
|
Before Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 733 KiB |
|
Before Width: | Height: | Size: 852 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 224 KiB |
@ -1,10 +1,29 @@
|
|||||||
# Planet & body textures
|
# Planet & body textures
|
||||||
|
|
||||||
Equirectangular surface/cloud maps for the Solar System mode.
|
Equirectangular surface maps + Saturn ring for the Solar System mode. All
|
||||||
|
self-hosted, downscaled to web sizes (2048×1024 maps; 1024² ring).
|
||||||
|
|
||||||
**Source:** [Solar System Scope](https://www.solarsystemscope.com/textures/) — © INOVE / Solar System Scope.
|
## Files in use
|
||||||
**License:** Creative Commons Attribution 4.0 International (CC BY 4.0).
|
| File | Body | Origin |
|
||||||
|
|---|---|---|
|
||||||
|
| `2k_sun.jpg` | Sun | Solar System Scope |
|
||||||
|
| `2k_neptune.jpg` | Neptune | Solar System Scope |
|
||||||
|
| `hd_mercury.jpg` | Mercury | NASA/JPL–derived 8K map |
|
||||||
|
| `hd_venus.jpg` | Venus (Magellan radar surface) | NASA/JPL–derived 8K map |
|
||||||
|
| `hd_earth.jpg` | Earth | NASA Blue/Visible Earth–derived |
|
||||||
|
| `hd_mars.jpg` | Mars | NASA/JPL MGS/Viking–derived |
|
||||||
|
| `hd_jupiter.jpg` | Jupiter | NASA/Cassini–derived |
|
||||||
|
| `hd_saturn.jpg` | Saturn | NASA/Cassini–derived |
|
||||||
|
| `hd_saturn_ring.png` | Saturn ring (top-down annulus, RGBA) | NASA/Cassini–derived |
|
||||||
|
| `hd_uranus.jpg` | Uranus | Solar System Scope |
|
||||||
|
|
||||||
Files: `2k_{sun,mercury,venus_atmosphere,earth_daymap,moon,mars,jupiter,saturn,uranus,neptune}.jpg`, `2k_saturn_ring_alpha.png`.
|
## Licensing
|
||||||
|
The maps are derived from public-domain **NASA/JPL** imagery and **Solar System
|
||||||
|
Scope** planetary maps, the latter under **Creative Commons Attribution 4.0
|
||||||
|
International (CC BY 4.0)** — © INOVE / Solar System Scope,
|
||||||
|
<https://www.solarsystemscope.com/textures/>.
|
||||||
|
|
||||||
Attribution is also shown in-app (Solar System mode footer) and in the project README.
|
The `hd_*` set was produced from freely-distributed 3D-asset texture packs whose
|
||||||
|
underlying imagery is NASA/JPL (public domain) or Solar System Scope (CC BY 4.0).
|
||||||
|
Attribution is shown in-app (Solar System mode footer): *"planet maps: NASA/JPL &
|
||||||
|
Solar System Scope (CC BY 4.0)"*.
|
||||||
|
|||||||
BIN
textures/hd_earth.jpg
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
textures/hd_jupiter.jpg
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
textures/hd_mars.jpg
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
textures/hd_mercury.jpg
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
textures/hd_saturn.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
textures/hd_saturn_ring.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
textures/hd_uranus.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
textures/hd_venus.jpg
Normal file
|
After Width: | Height: | Size: 418 KiB |