merge stage/3-retinue → main: moons + rings, gate green (opus)
This commit is contained in:
commit
fedf05d8a2
43
js/layers/moons.js
Normal file
43
js/layers/moons.js
Normal file
@ -0,0 +1,43 @@
|
||||
// SOLARGOD moons layer (Stage 3) — every moon in the registry as a lit sphere,
|
||||
// positioned by ctx.bodyWorld (main.js computes the local compressed offset in the
|
||||
// parent's equatorial plane; retrograde via negative period). Moons draw at the
|
||||
// SAME exaggeration as their parent, so the true radius ratio (e.g. Io/Jupiter
|
||||
// ≈ 0.026) is preserved. Labels appear only when the parent (or the moon) is the
|
||||
// focus, to keep the wide view clean (brief §5).
|
||||
|
||||
export default function create(ctx) {
|
||||
const { THREE, scene, CONFIG, ui, scale, bodies, focus, toLocal, effectiveE, makeLabel, registerPick } = ctx;
|
||||
|
||||
const entries = [];
|
||||
for (const id in bodies) {
|
||||
const b = bodies[id];
|
||||
if (b.type !== 'moon') continue;
|
||||
const mat = new THREE.MeshStandardMaterial({ color: new THREE.Color(b.color), roughness: 1, metalness: 0 });
|
||||
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 24, 16), mat);
|
||||
mesh.visible = true;
|
||||
scene.add(mesh);
|
||||
registerPick(mesh, id);
|
||||
const lbl = makeLabel(mesh, b.name, 'body-label moon');
|
||||
lbl.obj.visible = false; // CSS2DRenderer honors obj.visible (not div.style.display)
|
||||
entries.push({ id, b, mesh, lbl });
|
||||
}
|
||||
|
||||
let show = true;
|
||||
ui.addLayer('moons', 'Moons', true, (on) => { show = on; for (const e of entries) e.mesh.visible = on; });
|
||||
ui.setStatus('moons', `${entries.length} moons`, 'ok');
|
||||
|
||||
return {
|
||||
id: 'moons',
|
||||
onClockTick(simMs, jd) {
|
||||
for (const e of entries) {
|
||||
const abs = ctx.bodyWorld[e.id];
|
||||
if (!abs) continue;
|
||||
toLocal(abs, e.mesh.position);
|
||||
e.mesh.scale.setScalar(scale.drawRadius(e.b.radiusKm, effectiveE(e.b.parent)));
|
||||
const near = focus.id === e.b.parent || focus.id === e.id;
|
||||
e.lbl.obj.visible = show && near;
|
||||
e.lbl.div.classList.toggle('focused', e.id === focus.id);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
66
js/layers/rings.js
Normal file
66
js/layers/rings.js
Normal file
@ -0,0 +1,66 @@
|
||||
// SOLARGOD rings layer (Stage 3) — Saturn's textured annulus + Uranus's thin
|
||||
// schematic ring. Sized in true km scaled by the parent's draw scale, tilted with
|
||||
// the parent's axial tilt, DoubleSide + transparent (brief §7). Texture optional:
|
||||
// a flat color fallback keeps the ring visible if the PNG is missing.
|
||||
|
||||
export default function create(ctx) {
|
||||
const { THREE, scene, lib, ui, scale, bodies, toLocal, effectiveE, CONFIG } = ctx;
|
||||
const AU_KM = lib.AU_KM;
|
||||
const RING_FALLBACK = { saturn: '#c9b98f', uranus: '#8fa3a8' };
|
||||
|
||||
const entries = [];
|
||||
for (const id of ['saturn', 'uranus']) {
|
||||
const b = bodies[id];
|
||||
if (!b.rings) continue;
|
||||
const ratio = b.rings.innerKm / b.rings.outerKm;
|
||||
const geo = new THREE.RingGeometry(ratio, 1, 128, 1);
|
||||
radialUV(geo); // remap UV so a radial ring texture maps across width
|
||||
const mat = new THREE.MeshBasicMaterial({
|
||||
color: new THREE.Color(b.rings.color || RING_FALLBACK[id] || '#bbb'),
|
||||
side: THREE.DoubleSide, transparent: true, opacity: id === 'saturn' ? 0.9 : 0.45, depthWrite: false,
|
||||
});
|
||||
if (b.rings.texture) {
|
||||
new THREE.TextureLoader().load(CONFIG.textures.path + b.rings.texture, (tex) => {
|
||||
tex.colorSpace = THREE.SRGBColorSpace; mat.map = tex; mat.color.set(0xffffff); mat.needsUpdate = true;
|
||||
}, undefined, () => { /* keep flat color */ });
|
||||
}
|
||||
const group = new THREE.Group();
|
||||
group.rotation.z = (b.axialTiltDeg || 0) * lib.DEG; // same tilt as the planet group
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.rotation.x = -Math.PI / 2; // lay flat into the equatorial (XZ) plane
|
||||
group.add(mesh);
|
||||
scene.add(group);
|
||||
entries.push({ id, b, group, mesh });
|
||||
}
|
||||
|
||||
let show = true;
|
||||
ui.addLayer('rings', 'Rings', true, (on) => { show = on; for (const e of entries) e.group.visible = on; });
|
||||
ui.setStatus('rings', 'Saturn · Uranus', 'ok');
|
||||
|
||||
return {
|
||||
id: 'rings',
|
||||
onClockTick() {
|
||||
const K = scale.getK();
|
||||
for (const e of entries) {
|
||||
const abs = ctx.bodyWorld[e.id];
|
||||
if (!abs) continue;
|
||||
toLocal(abs, e.group.position);
|
||||
const E = effectiveE(e.id);
|
||||
e.mesh.scale.setScalar((e.b.rings.outerKm / AU_KM) * K * E); // outer km → view units
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Remap RingGeometry UVs so u runs 0→1 across the radius (for a 1-D ring texture).
|
||||
function radialUV(geo) {
|
||||
const pos = geo.attributes.position, uv = geo.attributes.uv;
|
||||
const inner = Math.hypot(pos.getX(0), pos.getY(0));
|
||||
let outer = inner;
|
||||
for (let i = 0; i < pos.count; i++) outer = Math.max(outer, Math.hypot(pos.getX(i), pos.getY(i)));
|
||||
for (let i = 0; i < pos.count; i++) {
|
||||
const r = Math.hypot(pos.getX(i), pos.getY(i));
|
||||
uv.setXY(i, (r - inner) / (outer - inner || 1), 1);
|
||||
}
|
||||
uv.needsUpdate = true;
|
||||
}
|
||||
@ -339,7 +339,7 @@ const ctx = {
|
||||
moonsOf, PLANET_ORDER,
|
||||
};
|
||||
|
||||
const LAYER_MODULES = ['./layers/planets.js'];
|
||||
const LAYER_MODULES = ['./layers/planets.js', './layers/moons.js', './layers/rings.js'];
|
||||
const activeLayers = [];
|
||||
async function loadLayers() {
|
||||
const results = await Promise.allSettled(LAYER_MODULES.map((p) => import(p)));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user