Draw Saturn's rings in the system view, lit, at the radii their texture draws
Audit #47. The body page's rings already lie in Saturn's equator (76fb386),
but the system view drew Saturn as a bare sphere, and the page sized the rings
to 1.4-2.6 planet radii regardless of what saturn_ring.png draws where.
The strip runs straight out from its left edge to its right. Read off its
alpha, the C ring's inner edge (74 490 km) is at px 91 of 1 280, the B ring's
inner and outer edges (92 000 and 117 580 km) at 404.5 and 860, the A ring's
outer edge (136 775 km) at 1 204 and the F ring (140 180 km) at 1 267.5: one
scale of 55.9 km a pixel fits all five within 1.8 px, so the strip spans
69 400 to 141 000 km. Only the Cassini Division's outer edge misses, drawn
30 px (1 700 km) too far in. Sized to the brief's 74 500 and 140 220 km (the C
ring's inner edge and the F ring) instead, the B ring's inner edge would sit
3 300 km out.
saturnRing (texture-catalog.ts) now builds the rings for both views: flat in
the XZ plane of a sphere built round +Y, sized against the planet as drawn,
MeshStandardMaterial lit from both faces, the strip's own alpha as opacity
(the page used the texture as its own alphaMap too, multiplying its alpha by
its green channel, at 0.85 opacity). In the system view the ring is a child of
Saturn's marker, so the IAU pole turns it into the equator and the pixel floor
scales it with the planet; a ray through it picks Saturn (memberForObject
accepts a marker's child). On the page it now reaches 2.42 radii, not 2.6.
Jupiter's, Uranus's and Neptune's rings are left out: dark, narrow or dusty,
too faint to see at any scale drawn here.
Measured in the running app, the ring's opening to Earth against Horizons'
sub-Earth latitude on Saturn (planetodetic, taken back to planetocentric with
f = 0.09796): 26.963 against 26.966 degrees on 2017-10-16, 0.075 against
0.042 on 2025-03-23, the plane crossing, and -7.764 against -7.813 on
2026-09-24. The Sun stood 26.64, 0.70 and -7.50 degrees above the ring plane
on those dates. At the closest the system view allows, 0.05 AU, Saturn is
about 5 px in radius and its rings reach about 12 px; on the plane-crossing
date they vanish edge-on.
Tests: the three openings against frozen Horizons values and a pick through
the B ring (system-orbits-renderer.spec.ts); the rings' extent, their lying
in the sphere's equator, and the strip sampled outwards so its B ring starts
at 92 000 km and its A ring ends at 136 775 (texture-catalog.spec.ts). Unit
suite 792 -> 799 tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,54 @@ export function atmosphereColorFor(id: string): THREE.ColorRepresentation | unde
|
||||
return ATMOSPHERE_BY_ID[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* The radii, in km from Saturn's centre, that `saturn_ring.png`'s left and right edges stand for.
|
||||
*
|
||||
* The strip runs straight out from its left edge to its right, and read off its alpha the ring
|
||||
* edges fall where one scale puts them: the C ring's inner edge (74 490 km) at 91 of its 1 280 px,
|
||||
* the B ring's inner edge (92 000) at 404.5 and outer (117 580) at 860, the A ring's outer edge
|
||||
* (136 775) at 1 204 and the F ring (140 180) at 1 267.5 — all within 1.8 px of 55.9 km a pixel.
|
||||
* The one miss is the Cassini Division's outer edge (122 170), which the strip draws 30 px (1 700
|
||||
* km) too far in. The edges are not the 74 500 and 140 220 km of the C ring and the F ring: sized to
|
||||
* those, the B ring's inner edge would sit 3 300 km out.
|
||||
*/
|
||||
export const SATURN_RING_INNER_KM = 69_400;
|
||||
export const SATURN_RING_OUTER_KM = 141_000;
|
||||
|
||||
/**
|
||||
* Saturn's rings, flat in the equator of a sphere built round +Y — its XZ plane — and sized
|
||||
* against the planet as drawn: `drawnRadius` for Saturn's `planetRadiusKm`, so the rings keep their
|
||||
* true proportion to the planet wherever it is drawn and however it is scaled.
|
||||
*
|
||||
* `RingGeometry`'s own UVs wrap round the angle, so u is set to the distance from the centre instead,
|
||||
* which is the way the strip runs. Lit, from both faces: the face turned to the Sun is lit by the
|
||||
* height of the Sun above the ring plane, and the other falls dark. Nothing in the app casts a
|
||||
* shadow, so neither the planet on the rings nor the rings on the planet do.
|
||||
*/
|
||||
export function saturnRing(planetRadiusKm: number, drawnRadius: number): THREE.Mesh {
|
||||
const unitsPerKm = drawnRadius / planetRadiusKm;
|
||||
const inner = SATURN_RING_INNER_KM * unitsPerKm;
|
||||
const outer = SATURN_RING_OUTER_KM * unitsPerKm;
|
||||
const geometry = new THREE.RingGeometry(inner, outer, 128, 1).rotateX(-Math.PI / 2);
|
||||
const position = geometry.attributes['position'];
|
||||
const uv = geometry.attributes['uv'];
|
||||
const vertex = new THREE.Vector3();
|
||||
for (let i = 0; i < position.count; i++) {
|
||||
vertex.fromBufferAttribute(position, i);
|
||||
uv.setXY(i, THREE.MathUtils.clamp((vertex.length() - inner) / (outer - inner), 0, 1), 1);
|
||||
}
|
||||
// The strip's own alpha is the rings' opacity: dense in the B ring, thin in the C ring.
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
map: loadCachedTexture(SATURN_RING_TEXTURE_PATH),
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
roughness: 1,
|
||||
metalness: 0
|
||||
});
|
||||
return new THREE.Mesh(geometry, material);
|
||||
}
|
||||
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
const loadedTextures = new Map<string, THREE.Texture>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user