mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(sky): cut what the sky does not need
- qa-anime-sky keeps only the Milky Way case; the satellites and keyless cases checked an anime shader this branch no longer changes. - Drop qa-sky-sun-moon.mjs: the viewer-options unit test covers it. - VisualEffects sets the sky's visibility directly: the viewer always carries a StarrySky and never outlives the effects. - Drop the unused star intensity knob, the malformed-row tolerance for our own pack, the test-only Transforms parameter and a private-name test. - DATA_SOURCES points to sky/README.md instead of repeating it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d94cc35c81
commit
b00c7c12c8
10 files changed
+131
-515
No files matched your search
+11
-33
@@ -4,30 +4,16 @@ import { governorRequestRender } from '../renderGovernor.js';
|
||||
import { buildStarVertices, STAR_VERTEX_FLOATS } from './stars.js';
|
||||
|
||||
/**
|
||||
* Space background: catalogue stars drawn as point sprites over a soft Milky
|
||||
* Way cube map, replacing Cesium's default Tycho-2 sky box (six 1024² JPEGs
|
||||
* stretched 3–6× on screen, which read as compression blocks around the
|
||||
* globe).
|
||||
*
|
||||
* Narrow, version-tested engine adapter. It takes the `Scene#skyBox` slot:
|
||||
* Cesium calls `update(frameState, useHdr)` once per frame and executes the
|
||||
* returned command first in the environment pass, before the atmosphere, sun,
|
||||
* moon and every globe or tile command — so the sky stays behind everything
|
||||
* without depth testing. Only `execute()` and `derivedCommands` of that
|
||||
* command are read. An engine upgrade must revalidate this adapter.
|
||||
*
|
||||
* Both layers share one ICRF→Earth-fixed rotation (IAU 2006 precession,
|
||||
* nutation and Earth rotation at the scene clock), so stars and the Milky
|
||||
* Way agree with each other and with the real sky over the globe. Until
|
||||
* Cesium's ICRF tables load, the TEME approximation the default sky box uses
|
||||
* fills in; the arrival renders one frame through the idle governor.
|
||||
* Space background in the `Scene#skyBox` slot: catalogue stars as point
|
||||
* sprites over a soft Milky Way cube map, both turned by one ICRF→fixed
|
||||
* rotation at the scene clock (TEME until Cesium's tables load). Scene runs
|
||||
* the command from `update()` first in the environment pass, so the sky
|
||||
* stays behind everything without depth. Revalidate on engine upgrades.
|
||||
*/
|
||||
export const STARRY_SKY_CESIUM_VERSION = '1.138.0';
|
||||
|
||||
/** Opacity of the Milky Way glow over black; the stars carry the sky. */
|
||||
export const MILKY_WAY_INTENSITY = 0.4;
|
||||
/** Overall star brightness multiplier. */
|
||||
export const STAR_INTENSITY = 1;
|
||||
/** Clock change, in seconds, below which the sky rotation is reused. */
|
||||
export const SKY_ROTATION_EPSILON_SECONDS = 1;
|
||||
|
||||
@@ -118,14 +104,13 @@ void main()
|
||||
`;
|
||||
|
||||
const STAR_FS = `
|
||||
uniform float u_intensity;
|
||||
in vec4 v_color;
|
||||
in float v_sigma;
|
||||
in float v_radius;
|
||||
void main()
|
||||
{
|
||||
vec2 offset = (gl_PointCoord - 0.5) * (2.0 * v_radius);
|
||||
float alpha = v_color.a * u_intensity *
|
||||
float alpha = v_color.a *
|
||||
exp(-0.5 * dot(offset, offset) / (v_sigma * v_sigma));
|
||||
if (alpha < 1.0 / 512.0) discard;
|
||||
out_FragColor = vec4(czm_gammaCorrect(v_color.rgb) * alpha, alpha);
|
||||
@@ -133,15 +118,12 @@ void main()
|
||||
`;
|
||||
|
||||
/** Pick the ICRF→fixed rotation for `time`, or TEME while tables load. */
|
||||
export function computeSkyRotation(
|
||||
time,
|
||||
result,
|
||||
transforms = Cesium.Transforms,
|
||||
) {
|
||||
const icrf = transforms.computeIcrfToFixedMatrix(time, result);
|
||||
export function computeSkyRotation(time, result) {
|
||||
const { Transforms } = Cesium;
|
||||
const icrf = Transforms.computeIcrfToFixedMatrix(time, result);
|
||||
if (icrf) return { matrix: icrf, precise: true };
|
||||
return {
|
||||
matrix: transforms.computeTemeToPseudoFixedMatrix(time, result),
|
||||
matrix: Transforms.computeTemeToPseudoFixedMatrix(time, result),
|
||||
precise: false,
|
||||
};
|
||||
}
|
||||
@@ -169,7 +151,6 @@ export class StarrySky {
|
||||
this.milkyWayIntensity = MILKY_WAY_INTENSITY;
|
||||
/** 0..1 multiplier a style sets to hide the glow (see visualEffects.js). */
|
||||
this.milkyWayVisibility = 1;
|
||||
this.starIntensity = STAR_INTENSITY;
|
||||
this._requestRender = requestRender;
|
||||
this._rotation = Cesium.Matrix3.clone(Cesium.Matrix3.IDENTITY);
|
||||
this._rotationTime = undefined;
|
||||
@@ -197,10 +178,7 @@ export class StarrySky {
|
||||
u_milkyWay: () => this._milkyWayCubeMap,
|
||||
u_intensity: () => this.milkyWayIntensity * this.milkyWayVisibility,
|
||||
};
|
||||
this._starUniforms = {
|
||||
...uniforms,
|
||||
u_intensity: () => this.starIntensity,
|
||||
};
|
||||
this._starUniforms = uniforms;
|
||||
const commands = [];
|
||||
this._commands = commands;
|
||||
// The environment pass reads `derivedCommands` (log depth, HDR, picking)
|
||||
|
||||
+19
-21
@@ -25,19 +25,25 @@ test('the ICRF rotation is preferred and TEME fills in while tables load', () =>
|
||||
const time = Cesium.JulianDate.fromIso8601('2026-10-19T00:00:00Z');
|
||||
const icrf = Cesium.Matrix3.fromRotationZ(0.3);
|
||||
const teme = Cesium.Matrix3.fromRotationZ(0.2);
|
||||
const precise = computeSkyRotation(time, new Cesium.Matrix3(), {
|
||||
computeIcrfToFixedMatrix: (_, result) => Cesium.Matrix3.clone(icrf, result),
|
||||
computeTemeToPseudoFixedMatrix: () => assert.fail('TEME not needed'),
|
||||
});
|
||||
assert.equal(precise.precise, true);
|
||||
assert.ok(Cesium.Matrix3.equals(precise.matrix, icrf));
|
||||
const approximate = computeSkyRotation(time, new Cesium.Matrix3(), {
|
||||
computeIcrfToFixedMatrix: () => undefined,
|
||||
computeTemeToPseudoFixedMatrix: (_, result) =>
|
||||
Cesium.Matrix3.clone(teme, result),
|
||||
});
|
||||
assert.equal(approximate.precise, false);
|
||||
assert.ok(Cesium.Matrix3.equals(approximate.matrix, teme));
|
||||
const { Transforms } = Cesium;
|
||||
const original = { ...Transforms };
|
||||
try {
|
||||
Transforms.computeIcrfToFixedMatrix = (_, result) =>
|
||||
Cesium.Matrix3.clone(icrf, result);
|
||||
Transforms.computeTemeToPseudoFixedMatrix = () =>
|
||||
assert.fail('TEME not needed');
|
||||
const precise = computeSkyRotation(time, new Cesium.Matrix3());
|
||||
assert.equal(precise.precise, true);
|
||||
assert.ok(Cesium.Matrix3.equals(precise.matrix, icrf));
|
||||
Transforms.computeIcrfToFixedMatrix = () => undefined;
|
||||
Transforms.computeTemeToPseudoFixedMatrix = (_, result) =>
|
||||
Cesium.Matrix3.clone(teme, result);
|
||||
const approximate = computeSkyRotation(time, new Cesium.Matrix3());
|
||||
assert.equal(approximate.precise, false);
|
||||
assert.ok(Cesium.Matrix3.equals(approximate.matrix, teme));
|
||||
} finally {
|
||||
Object.assign(Transforms, original);
|
||||
}
|
||||
});
|
||||
|
||||
test('the TEME fallback keeps the celestial pole on the Earth axis', () => {
|
||||
@@ -127,11 +133,3 @@ test('the Milky Way glow scales with its visibility, for styles that hide it', (
|
||||
assert.equal(sky._milkyWayUniforms.u_intensity(), 0);
|
||||
sky.destroy();
|
||||
});
|
||||
|
||||
test('the Milky Way stays out of the environment map that lights models', () => {
|
||||
// Scene#render hands `skyBox._cubeMap` to image-based lighting, which
|
||||
// expects Cesium's own TEME, flipY layout; this ICRF map would be rotated.
|
||||
const sky = new StarrySky({ requestRender: () => {} });
|
||||
assert.equal('_cubeMap' in sky, false);
|
||||
sky.destroy();
|
||||
});
|
||||
+6
-27
@@ -109,38 +109,17 @@ export function bvToRgb(bv) {
|
||||
return rgb.map((channel) => channel / peak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a catalogue pack into interleaved vertex data, `STAR_VERTEX_FLOATS`
|
||||
* per star. Malformed rows are skipped, never thrown on: a damaged pack must
|
||||
* not take the globe down with it.
|
||||
*/
|
||||
export function buildStarVertices(pack) {
|
||||
const stars = Array.isArray(pack?.stars) ? pack.stars : [];
|
||||
const count = Math.floor(stars.length / 4);
|
||||
/** Decode a catalogue pack into interleaved vertex data, `STAR_VERTEX_FLOATS` per star. */
|
||||
export function buildStarVertices({ stars }) {
|
||||
const count = stars.length / 4;
|
||||
const vertices = new Float32Array(count * STAR_VERTEX_FLOATS);
|
||||
let written = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const [raDeg, decDeg, vmag, bv] = stars.slice(i * 4, i * 4 + 4);
|
||||
if (
|
||||
!Number.isFinite(raDeg) ||
|
||||
!Number.isFinite(decDeg) ||
|
||||
!Number.isFinite(vmag)
|
||||
)
|
||||
continue;
|
||||
const [x, y, z] = raDecToUnitVector(raDeg, decDeg);
|
||||
const { sigmaPx, alpha } = starAppearance(vmag);
|
||||
const [r, g, b] = bvToRgb(bv);
|
||||
vertices.set(
|
||||
[x, y, z, sigmaPx, r, g, b, alpha],
|
||||
written * STAR_VERTEX_FLOATS,
|
||||
[...raDecToUnitVector(raDeg, decDeg), sigmaPx, ...bvToRgb(bv), alpha],
|
||||
i * STAR_VERTEX_FLOATS,
|
||||
);
|
||||
written += 1;
|
||||
}
|
||||
return {
|
||||
count: written,
|
||||
vertices:
|
||||
written === count
|
||||
? vertices
|
||||
: vertices.slice(0, written * STAR_VERTEX_FLOATS),
|
||||
};
|
||||
return { count, vertices };
|
||||
}
|
||||
@@ -82,9 +82,9 @@ test('B−V colours run from blue-white to orange with the peak channel at 1', (
|
||||
assert.deepEqual(bvToRgb(9), bvToRgb(2));
|
||||
});
|
||||
|
||||
test('vertex data is interleaved per star and skips malformed rows', () => {
|
||||
test('vertex data is interleaved per star', () => {
|
||||
const { count, vertices } = buildStarVertices({
|
||||
stars: [0, 0, 1, 0.5, 'bad', 10, 2, 0, 90, 45, 3, null, 10, 20],
|
||||
stars: [0, 0, 1, 0.5, 90, 45, 3, null],
|
||||
});
|
||||
assert.equal(count, 2);
|
||||
assert.equal(vertices.length, 2 * STAR_VERTEX_FLOATS);
|
||||
@@ -93,10 +93,6 @@ test('vertex data is interleaved per star and skips malformed rows', () => {
|
||||
close(vertices[STAR_VERTEX_FLOATS + 2], Math.SQRT1_2, 1e-6);
|
||||
close(vertices[3], starAppearance(1).sigmaPx, 1e-6);
|
||||
close(vertices[7], starAppearance(1).alpha, 1e-6);
|
||||
assert.deepEqual(buildStarVertices(null), {
|
||||
count: 0,
|
||||
vertices: new Float32Array(0),
|
||||
});
|
||||
});
|
||||
|
||||
test('the committed catalogue holds the Yale Bright Star Catalogue', () => {
|
||||
|
||||
+3
-12
@@ -116,11 +116,8 @@ export class VisualEffects {
|
||||
// a post-process cannot tell the sky from far geometry reliably (near
|
||||
// plane clamps, keyless terrain above the ellipsoid horizon): fade the
|
||||
// glow out with the style instead. Bright stars survive as banded points.
|
||||
if (stage === this.stages.anime) {
|
||||
const sky = this.viewer.scene?.skyBox;
|
||||
if (sky && 'milkyWayVisibility' in sky)
|
||||
sky.milkyWayVisibility = 1 - value;
|
||||
}
|
||||
if (stage === this.stages.anime)
|
||||
this.viewer.scene.skyBox.milkyWayVisibility = 1 - value;
|
||||
if (stage.enabled && stage.uniforms.time !== undefined)
|
||||
this.startAnimationLoop();
|
||||
this.requestRender('style-stage');
|
||||
@@ -228,7 +225,7 @@ export class VisualEffects {
|
||||
this.transitions.clear();
|
||||
}
|
||||
|
||||
/** Remove owned stages and restore the borrowed bloom stage and sky after callers release them. */
|
||||
/** Remove owned stages and restore the borrowed bloom stage after callers release it. */
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.stop();
|
||||
@@ -240,11 +237,5 @@ export class VisualEffects {
|
||||
Object.assign(this.bloomStage.uniforms, this.previousBloom.uniforms);
|
||||
this.bloomStage.enabled = this.previousBloom.enabled;
|
||||
}
|
||||
// Anime may have faded the Milky Way out; a surviving viewer gets it back.
|
||||
const sky = this.viewer.scene.skyBox;
|
||||
if (sky && 'milkyWayVisibility' in sky) {
|
||||
sky.milkyWayVisibility = 1;
|
||||
this.requestRender('style-stage');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,9 @@ function fixture() {
|
||||
},
|
||||
};
|
||||
const effects = new VisualEffects({
|
||||
viewer: { scene: { postProcessStages: pipeline } },
|
||||
viewer: {
|
||||
scene: { postProcessStages: pipeline, skyBox: { milkyWayVisibility: 1 } },
|
||||
},
|
||||
requestRender: (reason) => requests.push(reason),
|
||||
holdRender: (reason) => holds.add(reason),
|
||||
releaseRender: (reason) => holds.delete(reason),
|
||||
@@ -174,32 +176,9 @@ test('the anime stage fades the Milky Way out with its own intensity', () => {
|
||||
f.effects.setStageIntensity(f.effects.stages.anime, 0);
|
||||
assert.equal(glow(), MILKY_WAY_INTENSITY);
|
||||
sky.destroy();
|
||||
// A sky box without the knob (Cesium's own) is left alone.
|
||||
const plain = {};
|
||||
f.effects.viewer.scene.skyBox = plain;
|
||||
f.effects.setStageIntensity(f.effects.stages.anime, 1);
|
||||
assert.deepEqual(plain, {});
|
||||
f.effects.destroy();
|
||||
});
|
||||
|
||||
test('destroying the effects gives a surviving viewer its Milky Way back', () => {
|
||||
const f = fixture();
|
||||
const sky = { milkyWayVisibility: 1 };
|
||||
f.effects.viewer.scene.skyBox = sky;
|
||||
f.effects.initStyles();
|
||||
f.effects.startTransition('anime', 0, 1);
|
||||
f.tick(250);
|
||||
f.effects.stop();
|
||||
const frozen = sky.milkyWayVisibility;
|
||||
assert.ok(
|
||||
frozen > 0 && frozen < 1,
|
||||
'stop keeps the stages, and the fade, as they are',
|
||||
);
|
||||
f.effects.destroy();
|
||||
assert.equal(sky.milkyWayVisibility, 1);
|
||||
assert.equal(f.requests.at(-1), 'style-stage');
|
||||
});
|
||||
|
||||
test('bloom retains its dead zone, normalized intensity and independent toggle', () => {
|
||||
const f = fixture();
|
||||
f.effects.initPostProcess();
|
||||
|
||||
Reference in new issue
Block a user