Stage 3. moons.js: 21 moons as lit spheres with local per-parent compression (clear the exaggerated parent, ordering preserved), retrograde via negative period. rings.js: Saturn textured annulus (true 74500–140220 km scaled with parent) + Uranus thin schematic ring, tilted with axial tilt, DoubleSide. Moons draw at the parent's exaggeration so Io/Jupiter ≈ 0.026 ratio holds. Label visibility gated via CSS2DObject.visible (CSS2DRenderer clobbers div.style.display every frame — fixed). Gate: Galilean laps 1.00:0.50:0.25 (1:2:4), Triton retrograde (Δθ<0), moons ordered io<europa<ganymede<callisto. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.7 KiB
JavaScript
67 lines
2.7 KiB
JavaScript
// 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;
|
|
}
|