// 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); } }, }; }