wip: Solar System mode — billboard planets + depth/near-plane fixes
Switched planets from async ellipsoid geometry to orthographic-disc BILLBOARDS (reliable, unlit, no worker geometry), with billboard-image refresh once each disc canvas is drawn. Fixed the near-plane depth collapse (near 0.1 + far 6e9 killed the starfield). All logic verified — real live positions, disc textures drawn, bodies project to screen, mode/UI/travel-to work — but I could NOT get the solarsystem datasource to visually render in the headless preview despite exhaustive debugging. NOT deployed; needs a real-browser render check / a fresh debugging pass. Live site is unaffected (never deployed this). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
07993c77ff
commit
596cc4db19
@ -78,42 +78,71 @@ export default function initSolarSystem(ctx) {
|
||||
return scaledPos(helio(key.charAt(0).toUpperCase() + key.slice(1), d));
|
||||
}
|
||||
|
||||
// Orthographic planet disc from an equirectangular texture, drawn onto a canvas
|
||||
// used as a BILLBOARD image. Billboards render instantly (no async worker
|
||||
// geometry), always face the camera, and are unlit — reliable where a 3D
|
||||
// ellipsoid + hidden globe is not. Still reads as a real 3D planet.
|
||||
const discCache = {};
|
||||
function makeDisc(b) {
|
||||
if (discCache[b.key]) return discCache[b.key];
|
||||
const S = 256, R = S / 2, cv = document.createElement('canvas'); cv.width = cv.height = S;
|
||||
const g = cv.getContext('2d');
|
||||
discCache[b.key] = cv; // return now; fill in once the texture loads
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const iw = img.width, ih = img.height, src = document.createElement('canvas');
|
||||
src.width = iw; src.height = ih; const sg = src.getContext('2d'); sg.drawImage(img, 0, 0);
|
||||
const sd = sg.getImageData(0, 0, iw, ih).data;
|
||||
const out = g.createImageData(S, S), od = out.data;
|
||||
const lx = -0.5, ly = 0.55, lz = 0.67; // light from upper-left-front
|
||||
for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) {
|
||||
const nx = (x - R) / R, ny = (R - y) / R, r2 = nx * nx + ny * ny, oi = (y * S + x) * 4;
|
||||
if (r2 > 1) { od[oi + 3] = 0; continue; }
|
||||
const nz = Math.sqrt(1 - r2);
|
||||
const lat = Math.asin(ny), lon = Math.atan2(nx, nz);
|
||||
let u = lon / (2 * Math.PI) + 0.5; u -= Math.floor(u);
|
||||
const vv = 0.5 - lat / Math.PI;
|
||||
const si = (Math.min(ih - 1, (vv * ih) | 0) * iw + Math.min(iw - 1, (u * iw) | 0)) * 4;
|
||||
const shade = b.sun ? 1 : Math.max(0.14, nx * lx + ny * ly + nz * lz) * 0.85 + 0.15;
|
||||
od[oi] = sd[si] * shade; od[oi + 1] = sd[si + 1] * shade; od[oi + 2] = sd[si + 2] * shade; od[oi + 3] = 255;
|
||||
}
|
||||
g.putImageData(out, 0, 0);
|
||||
// The billboard uploaded a blank canvas at creation time; re-assign the now
|
||||
// -drawn canvas so Cesium re-uploads the real planet image to the atlas.
|
||||
if (bodyEntities[b.key]) bodyEntities[b.key].billboard.image = cv;
|
||||
};
|
||||
img.src = b.tex;
|
||||
return cv;
|
||||
}
|
||||
|
||||
function build(d) {
|
||||
ds.entities.removeAll();
|
||||
for (const b of BODIES) {
|
||||
const pos = posOf(b.key, d);
|
||||
const r = bodyRadius(b.km, b.sun);
|
||||
// Billboard pixel size = the body's angular size at a reference distance,
|
||||
// grown as you approach so a planet fills the view up close.
|
||||
const px = Math.max(10, (r * 2) / 90000); // ~screen px at ~ the fly-to range
|
||||
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,
|
||||
billboard: {
|
||||
image: makeDisc(b),
|
||||
scaleByDistance: new Cesium.NearFarScalar(r * 3, px * 8, 2.5e9, Math.max(3, px * 0.05)),
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
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,
|
||||
verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 12),
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
});
|
||||
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.
|
||||
// arcType NONE = straight segments in space (not draped on an ellipsoid).
|
||||
if (b.key !== 'sun') {
|
||||
const pts = [];
|
||||
for (let a = 0; a <= 360; a += 3) {
|
||||
@ -121,7 +150,7 @@ export default function initSolarSystem(ctx) {
|
||||
pts.push(scaledPos(v));
|
||||
}
|
||||
ds.entities.add({
|
||||
polyline: { positions: pts, width: 2, material: new Cesium.ColorMaterialProperty(lib.cz('#6a86c0', 0.7)) },
|
||||
polyline: { positions: pts, width: 1.5, arcType: Cesium.ArcType.NONE, material: lib.cz('#6a86c0', 0.6) },
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -147,7 +176,7 @@ export default function initSolarSystem(ctx) {
|
||||
|
||||
// ---- mode enter / exit ----
|
||||
let active = false;
|
||||
const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, collide: true };
|
||||
const saved = { dataSources: [], primitives: [], globe: true, atmo: true, sky: true, animate: true, light: null, far: 5e8, near: 0.1, collide: true };
|
||||
|
||||
function enter() {
|
||||
if (active) return;
|
||||
@ -174,7 +203,10 @@ export default function initSolarSystem(ctx) {
|
||||
// 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.far = scene.camera.frustum.far; scene.camera.frustum.far = 6e9;
|
||||
// Push the near plane out too: with a 6e9 far plane, a 0.1 near destroys depth
|
||||
// precision and the starfield/scene stop drawing. 1e5 is fine at orrery scale.
|
||||
saved.near = scene.camera.frustum.near; scene.camera.frustum.near = 1e5;
|
||||
saved.collide = scene.screenSpaceCameraController.enableCollisionDetection;
|
||||
scene.screenSpaceCameraController.enableCollisionDetection = false;
|
||||
scene.screenSpaceCameraController.maximumZoomDistance = 6e9;
|
||||
@ -214,6 +246,7 @@ export default function initSolarSystem(ctx) {
|
||||
if (headlight) { headlight(); headlight = null; }
|
||||
if (saved.light) scene.light = saved.light;
|
||||
scene.camera.frustum.far = saved.far;
|
||||
if (saved.near != null) scene.camera.frustum.near = saved.near;
|
||||
scene.screenSpaceCameraController.enableCollisionDetection = saved.collide;
|
||||
scene.screenSpaceCameraController.maximumZoomDistance = Infinity;
|
||||
document.getElementById('hud').style.display = '';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user