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