mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 14:22:21 +00:00
Classify the Navara launch pad zone onto terrain and 3D tiles with Cesium's fill, rim and emission
The zone was a flat disc at ellipsoid height in Navara's opaque scene: it floated over low pads and sank under raised ground or 3D tiles, with hard edges and an unlit rim. A screen-space pass now paints the ground pixels within the radius of the pad from the scene depth, as Cesium's GroundPrimitive classifies them, using one style shared by both engines (LAUNCH_PAD_ZONE_STYLE).
This commit is contained in:
1 parent
f0d38897f4
commit
d8ef366ab1
6 files changed
+317
-132
No files matched your search
@@ -3689,6 +3689,7 @@
|
|||||||
"src/layers/localAdsb/render/navara.js",
|
"src/layers/localAdsb/render/navara.js",
|
||||||
"src/layers/vessels/render/navara.js",
|
"src/layers/vessels/render/navara.js",
|
||||||
"src/layers/launches/render/navara.js",
|
"src/layers/launches/render/navara.js",
|
||||||
|
"src/layers/launches/render/navaraPadGround.js",
|
||||||
"src/layers/satellites/render/navara.js",
|
"src/layers/satellites/render/navara.js",
|
||||||
"src/layers/shared/render/engineView.js",
|
"src/layers/shared/render/engineView.js",
|
||||||
"src/celestialRing/render/cesium.js",
|
"src/celestialRing/render/cesium.js",
|
||||||
|
|||||||
@@ -88,6 +88,22 @@ export const MISSION_ORBIT_DASHES_PER_GROUP = 100;
|
|||||||
|
|
||||||
export const LAUNCH_PAD_ZONE_RADIUS_M = 500;
|
export const LAUNCH_PAD_ZONE_RADIUS_M = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pad zone's look on both engines, as a fraction of the radius: a faint
|
||||||
|
* fill, a bright rim between rimStart and rimEnd whose colour gains the
|
||||||
|
* emission, and a soft edge from rimEnd to the radius. The zone is classified
|
||||||
|
* onto the surface under the pad (terrain and 3D tiles), never drawn at a
|
||||||
|
* height.
|
||||||
|
*/
|
||||||
|
export const LAUNCH_PAD_ZONE_STYLE = Object.freeze({
|
||||||
|
color: '#22e6e6',
|
||||||
|
fillAlpha: 0.105,
|
||||||
|
rimAlpha: 0.72,
|
||||||
|
rimStart: 0.952,
|
||||||
|
rimEnd: 0.985,
|
||||||
|
emission: 0.35,
|
||||||
|
});
|
||||||
|
|
||||||
export const LAUNCH_PAD_ZONE_MAX_CAMERA_HEIGHT_M = 120000;
|
export const LAUNCH_PAD_ZONE_MAX_CAMERA_HEIGHT_M = 120000;
|
||||||
|
|
||||||
export const LAUNCH_PAD_ZONE_MAX_CAMERA_DISTANCE_M = 180000;
|
export const LAUNCH_PAD_ZONE_MAX_CAMERA_DISTANCE_M = 180000;
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import {
|
|||||||
MISSION_ORBIT_PATTERN_GROUPS,
|
MISSION_ORBIT_PATTERN_GROUPS,
|
||||||
MISSION_ORBIT_DASHES_PER_GROUP,
|
MISSION_ORBIT_DASHES_PER_GROUP,
|
||||||
LAUNCH_PAD_ZONE_RADIUS_M,
|
LAUNCH_PAD_ZONE_RADIUS_M,
|
||||||
|
LAUNCH_PAD_ZONE_STYLE,
|
||||||
} from '../policy.js';
|
} from '../policy.js';
|
||||||
const HOVER_PICK_INTERVAL_MS = 120;
|
const HOVER_PICK_INTERVAL_MS = 120;
|
||||||
|
/** A GLSL float literal (always with a decimal point). */
|
||||||
|
const glslFloat = (value) => value.toFixed(4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bound hover picking independently of precise click selection. A move inside
|
* Bound hover picking independently of precise click selection. A move inside
|
||||||
@@ -209,19 +212,19 @@ export function createLaunchesCesiumRenderer(engine, { viewer } = {}) {
|
|||||||
fabric: {
|
fabric: {
|
||||||
type: 'GevLaunchPadZone',
|
type: 'GevLaunchPadZone',
|
||||||
uniforms: {
|
uniforms: {
|
||||||
color: Cesium.Color.fromCssColorString('#22e6e6'),
|
color: Cesium.Color.fromCssColorString(LAUNCH_PAD_ZONE_STYLE.color),
|
||||||
fillAlpha: 0.105,
|
fillAlpha: LAUNCH_PAD_ZONE_STYLE.fillAlpha,
|
||||||
rimAlpha: 0.72,
|
rimAlpha: LAUNCH_PAD_ZONE_STYLE.rimAlpha,
|
||||||
},
|
},
|
||||||
source: `
|
source: `
|
||||||
czm_material czm_getMaterial(czm_materialInput materialInput) {
|
czm_material czm_getMaterial(czm_materialInput materialInput) {
|
||||||
czm_material material = czm_getDefaultMaterial(materialInput);
|
czm_material material = czm_getDefaultMaterial(materialInput);
|
||||||
vec2 centered = (materialInput.st - vec2(0.5)) * 2.0;
|
vec2 centered = (materialInput.st - vec2(0.5)) * 2.0;
|
||||||
float radius = length(centered);
|
float radius = length(centered);
|
||||||
float inside = 1.0 - smoothstep(0.985, 1.0, radius);
|
float inside = 1.0 - smoothstep(${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimEnd)}, 1.0, radius);
|
||||||
float rim = smoothstep(0.952, 0.985, radius) * inside;
|
float rim = smoothstep(${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimStart)}, ${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimEnd)}, radius) * inside;
|
||||||
material.diffuse = color.rgb;
|
material.diffuse = color.rgb;
|
||||||
material.emission = color.rgb * rim * 0.35;
|
material.emission = color.rgb * rim * ${glslFloat(LAUNCH_PAD_ZONE_STYLE.emission)};
|
||||||
material.alpha = color.a * inside * mix(fillAlpha, rimAlpha, rim);
|
material.alpha = color.a * inside * mix(fillAlpha, rimAlpha, rim);
|
||||||
return material;
|
return material;
|
||||||
}`,
|
}`,
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
// Mission points, paths and the documented flat pad disc on Navara.
|
// Mission points, paths and the pad zone on Navara.
|
||||||
import * as geo from '../../../geo/index.js';
|
import * as geo from '../../../geo/index.js';
|
||||||
import {
|
|
||||||
Group,
|
|
||||||
Mesh,
|
|
||||||
CircleGeometry,
|
|
||||||
RingGeometry,
|
|
||||||
MeshBasicMaterial,
|
|
||||||
DoubleSide,
|
|
||||||
} from 'three';
|
|
||||||
import { createMoverBank } from '../../aircraft/render/navara.js';
|
import { createMoverBank } from '../../aircraft/render/navara.js';
|
||||||
import { createEngineLayerView } from '../../shared/render/engineView.js';
|
import { createEngineLayerView } from '../../shared/render/engineView.js';
|
||||||
import { registerViewerEngine } from '../../../globe/cesium/engineFromViewer.js';
|
import { registerViewerEngine } from '../../../globe/cesium/engineFromViewer.js';
|
||||||
import { selectionDispatcherFor } from '../../../data/selectionDispatcher.js';
|
import { selectionDispatcherFor } from '../../../data/selectionDispatcher.js';
|
||||||
import { LAUNCH_PAD_ZONE_RADIUS_M } from '../policy.js';
|
import { createPadGround } from './navaraPadGround.js';
|
||||||
|
|
||||||
const PAD_KIND = 'gevLaunchPad';
|
|
||||||
const registered = new WeakSet();
|
|
||||||
const read = (v, time) =>
|
const read = (v, time) =>
|
||||||
typeof v?.getValue === 'function' ? v.getValue(time) : v;
|
typeof v?.getValue === 'function' ? v.getValue(time) : v;
|
||||||
const property = (v) =>
|
const property = (v) =>
|
||||||
@@ -23,7 +13,7 @@ const property = (v) =>
|
|||||||
const rgba = (c) => (c ? [c.red, c.green, c.blue, c.alpha] : [1, 1, 1, 1]);
|
const rgba = (c) => (c ? [c.red, c.green, c.blue, c.alpha] : [1, 1, 1, 1]);
|
||||||
export function createLaunchesNavaraRenderer(
|
export function createLaunchesNavaraRenderer(
|
||||||
engine,
|
engine,
|
||||||
{ view: nativeView, MeshDesc } = {},
|
{ view: nativeView, EffectDesc, NavaraEffect } = {},
|
||||||
) {
|
) {
|
||||||
let destroyed = false,
|
let destroyed = false,
|
||||||
sequence = 0,
|
sequence = 0,
|
||||||
@@ -355,113 +345,31 @@ export function createLaunchesNavaraRenderer(
|
|||||||
return source;
|
return source;
|
||||||
};
|
};
|
||||||
const createPad = (launch) => {
|
const createPad = (launch) => {
|
||||||
const group = new Group();
|
// Classified onto the surface under the pad as Cesium's GroundPrimitive
|
||||||
group.matrixAutoUpdate = false;
|
// (render/navaraPadGround.js); nothing is drawn at a height.
|
||||||
group.frustumCulled = false;
|
const ground =
|
||||||
const fill = new Mesh(
|
nativeView?.registerEffect && EffectDesc && NavaraEffect
|
||||||
new CircleGeometry(LAUNCH_PAD_ZONE_RADIUS_M, 96),
|
? createPadGround(nativeView, { EffectDesc, NavaraEffect }, launch)
|
||||||
new MeshBasicMaterial({
|
: null;
|
||||||
color: 0x22e6e6,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.105,
|
|
||||||
side: DoubleSide,
|
|
||||||
depthWrite: false,
|
|
||||||
polygonOffset: true,
|
|
||||||
polygonOffsetFactor: -1,
|
|
||||||
polygonOffsetUnits: -4,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const rim = new Mesh(
|
|
||||||
new RingGeometry(
|
|
||||||
LAUNCH_PAD_ZONE_RADIUS_M * 0.952,
|
|
||||||
LAUNCH_PAD_ZONE_RADIUS_M * 0.985,
|
|
||||||
96,
|
|
||||||
),
|
|
||||||
new MeshBasicMaterial({
|
|
||||||
color: 0x22e6e6,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.687,
|
|
||||||
side: DoubleSide,
|
|
||||||
depthWrite: false,
|
|
||||||
polygonOffset: true,
|
|
||||||
polygonOffsetFactor: -1,
|
|
||||||
polygonOffsetUnits: -4,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
fill.frustumCulled = false;
|
|
||||||
rim.frustumCulled = false;
|
|
||||||
rim.renderOrder = 1;
|
|
||||||
group.add(fill, rim);
|
|
||||||
let padHeightM = 0;
|
|
||||||
let dead = false,
|
let dead = false,
|
||||||
freed = false;
|
shown = true;
|
||||||
const dispose = () => {
|
engine.render.request('launch-pad');
|
||||||
if (freed) return;
|
|
||||||
freed = true;
|
|
||||||
for (const mesh of [fill, rim]) {
|
|
||||||
mesh.geometry.dispose();
|
|
||||||
mesh.material.dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const place = (height) => {
|
|
||||||
if (dead) return;
|
|
||||||
padHeightM = Number.isFinite(height) ? height : 0;
|
|
||||||
group.matrix.fromArray(
|
|
||||||
geo.transforms.eastNorthUpToFixedFrame(
|
|
||||||
geo.cartesian3.fromDegrees(
|
|
||||||
launch.lon,
|
|
||||||
launch.lat,
|
|
||||||
Number.isFinite(height) ? height : 0,
|
|
||||||
),
|
|
||||||
geo.ellipsoid.WGS84,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
group.matrixWorldNeedsUpdate = true;
|
|
||||||
engine.render.request('launch-pad');
|
|
||||||
};
|
|
||||||
// W1 parity decision: a flat ellipsoid disc, not terrain classification.
|
|
||||||
// A coarse tile height sampled during the focus flight can be kilometres off.
|
|
||||||
place(0);
|
|
||||||
let handle;
|
|
||||||
if (nativeView) {
|
|
||||||
if (!registered.has(nativeView)) {
|
|
||||||
registered.add(nativeView);
|
|
||||||
nativeView.registerMesh(
|
|
||||||
PAD_KIND,
|
|
||||||
class extends MeshDesc {
|
|
||||||
constructor(view, ctx, config) {
|
|
||||||
super(view, ctx, config);
|
|
||||||
this.pad = config[PAD_KIND];
|
|
||||||
}
|
|
||||||
createMesh() {
|
|
||||||
return this.pad.group;
|
|
||||||
}
|
|
||||||
onDestroy() {
|
|
||||||
this.pad.dispose();
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
handle = nativeView.addMesh({ [PAD_KIND]: { group, dispose } });
|
|
||||||
}
|
|
||||||
const pad = {
|
const pad = {
|
||||||
get heightM() {
|
/** Classified, as on Cesium: the zone has no height of its own. */
|
||||||
return padHeightM;
|
heightM: null,
|
||||||
},
|
|
||||||
get show() {
|
get show() {
|
||||||
return group.visible;
|
return shown;
|
||||||
},
|
},
|
||||||
set show(value) {
|
set show(value) {
|
||||||
if (group.visible === Boolean(value)) return;
|
if (shown === Boolean(value)) return;
|
||||||
group.visible = Boolean(value);
|
shown = Boolean(value);
|
||||||
|
if (ground) ground.visible = shown;
|
||||||
engine.render.request('launch-pad');
|
engine.render.request('launch-pad');
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
if (dead) return;
|
if (dead) return;
|
||||||
dead = true;
|
dead = true;
|
||||||
handle?.delete();
|
ground?.destroy();
|
||||||
dispose();
|
|
||||||
primitives.delete(pad);
|
primitives.delete(pad);
|
||||||
engine.render.request('launch-pad');
|
engine.render.request('launch-pad');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import assert from 'node:assert/strict';
|
|||||||
import * as geo from '../../../geo/index.js';
|
import * as geo from '../../../geo/index.js';
|
||||||
import { createFakeEngine } from '../../../globe/testing/fakeEngine.js';
|
import { createFakeEngine } from '../../../globe/testing/fakeEngine.js';
|
||||||
import { WHITE } from '../../shared/color.js';
|
import { WHITE } from '../../shared/color.js';
|
||||||
|
import { PerspectiveCamera, RGBADepthPacking } from 'three';
|
||||||
import { createLaunchesNavaraRenderer } from './navara.js';
|
import { createLaunchesNavaraRenderer } from './navara.js';
|
||||||
|
import { PAD_GROUND_KEY, padGroundShader } from './navaraPadGround.js';
|
||||||
|
import { LAUNCH_PAD_ZONE_STYLE } from '../policy.js';
|
||||||
|
|
||||||
const position = geo.cartesian3.fromDegrees(0, 0, 1000);
|
const position = geo.cartesian3.fromDegrees(0, 0, 1000);
|
||||||
test('mission bank growth, callbacks and source visibility write actual mover stores', async () => {
|
test('mission bank growth, callbacks and source visibility write actual mover stores', async () => {
|
||||||
@@ -130,41 +133,116 @@ test('mission hover uses engine drillPick and obeys disabled state', () => {
|
|||||||
engine.destroy();
|
engine.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('flat pad uses a native mesh description, ellipsoid ENU placement and idempotent disposal', () => {
|
test("the pad zone is classified onto the surface with Cesium's fill, rim and emission", () => {
|
||||||
const engine = createFakeEngine();
|
const engine = createFakeEngine();
|
||||||
engine.fake.script('sampleHeight', 125);
|
engine.fake.script('sampleHeight', 125);
|
||||||
let config,
|
const effects = [];
|
||||||
removed = 0;
|
let Desc;
|
||||||
|
class EffectDesc {
|
||||||
|
constructor(view, ctx, config) {
|
||||||
|
this.view = view;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
find() {
|
||||||
|
return mrt;
|
||||||
|
}
|
||||||
|
onDestroy() {}
|
||||||
|
}
|
||||||
|
class NavaraEffect {
|
||||||
|
constructor(camera, effect) {
|
||||||
|
this.effect = effect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mrt = {
|
||||||
|
depthBuffer: { isTexture: true },
|
||||||
|
depthBufferPacking: RGBADepthPacking,
|
||||||
|
};
|
||||||
|
const camera = new PerspectiveCamera();
|
||||||
|
camera.position.set(6378137 + 2000, 0, 0);
|
||||||
|
camera.updateMatrixWorld();
|
||||||
const view = {
|
const view = {
|
||||||
registerMesh() {},
|
camera: { raw: camera },
|
||||||
addMesh(value) {
|
registerEffect(key, desc) {
|
||||||
config = value;
|
assert.equal(key, PAD_GROUND_KEY);
|
||||||
|
assert.ok(desc.insertAfter.includes('toneMapping'));
|
||||||
|
Desc = desc;
|
||||||
|
},
|
||||||
|
addEffect(config) {
|
||||||
|
const desc = new Desc(
|
||||||
|
view,
|
||||||
|
{ getRenderTarget: () => ({ depthTexture: {} }) },
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
desc.createPass();
|
||||||
|
const record = { desc, visible: config.visible, deleted: false };
|
||||||
|
effects.push(record);
|
||||||
return {
|
return {
|
||||||
delete() {
|
update: ({ visible }) => (record.visible = visible),
|
||||||
removed++;
|
delete: () => {
|
||||||
|
record.deleted = true;
|
||||||
|
desc.onDestroy();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const renderer = createLaunchesNavaraRenderer(engine, {
|
const renderer = createLaunchesNavaraRenderer(engine, {
|
||||||
view,
|
view,
|
||||||
MeshDesc: class {},
|
EffectDesc,
|
||||||
|
NavaraEffect,
|
||||||
});
|
});
|
||||||
const pad = renderer.createPad({ id: 'x', lon: 0, lat: 0 });
|
const pad = renderer.createPad({ id: 'x', lon: 0, lat: 0 });
|
||||||
assert.equal(
|
assert.equal(
|
||||||
engine.fake.callsOf('surface.sampleHeight').length,
|
engine.fake.callsOf('surface.sampleHeight').length,
|
||||||
0,
|
0,
|
||||||
'flat pad must not sample a coarse tile',
|
'classified onto the surface: no coarse tile height is sampled',
|
||||||
);
|
);
|
||||||
const group = config.gevLaunchPad.group;
|
assert.equal(pad.heightM, null, 'no height of its own, as on Cesium');
|
||||||
assert.equal(group.children.length, 2);
|
assert.equal(effects.length, 1);
|
||||||
assert.equal(group.matrix.elements[12], 6378137);
|
const { desc } = effects[0];
|
||||||
assert.equal(group.children[0].material.polygonOffsetUnits, -4);
|
desc.updateFrame();
|
||||||
|
const u = desc.effect.uniforms;
|
||||||
|
// The pad 2 km below the camera, the ENU axes of lon 0, lat 0.
|
||||||
|
assert.deepEqual(u.get('uPadOffset').value.toArray(), [-2000, 0, 0]);
|
||||||
|
assert.deepEqual(u.get('uUp').value.toArray(), [1, 0, 0]);
|
||||||
|
assert.deepEqual(
|
||||||
|
u
|
||||||
|
.get('uEast')
|
||||||
|
.value.toArray()
|
||||||
|
.map((v) => Math.round(v) + 0),
|
||||||
|
[0, 1, 0],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
u
|
||||||
|
.get('uNorth')
|
||||||
|
.value.toArray()
|
||||||
|
.map((v) => Math.round(v) + 0),
|
||||||
|
[0, 0, 1],
|
||||||
|
);
|
||||||
|
assert.equal(u.get('tAllDepth').value, mrt.depthBuffer);
|
||||||
|
// Cesium's GevLaunchPadZone material, from the same style.
|
||||||
|
const rgb = u
|
||||||
|
.get('uPadColor')
|
||||||
|
.value.toArray()
|
||||||
|
.map((v) => Math.round(v * 255));
|
||||||
|
assert.deepEqual(rgb, [0x22, 0xe6, 0xe6]);
|
||||||
|
for (const part of [
|
||||||
|
`smoothstep(${LAUNCH_PAD_ZONE_STYLE.rimEnd.toFixed(4)},1.0,radius)`,
|
||||||
|
`smoothstep(${LAUNCH_PAD_ZONE_STYLE.rimStart.toFixed(4)},${LAUNCH_PAD_ZONE_STYLE.rimEnd.toFixed(4)},radius)`,
|
||||||
|
`mix(${LAUNCH_PAD_ZONE_STYLE.fillAlpha.toFixed(4)},${LAUNCH_PAD_ZONE_STYLE.rimAlpha.toFixed(4)},rim)`,
|
||||||
|
`(1.0+${LAUNCH_PAD_ZONE_STYLE.emission.toFixed(4)}*rim)`,
|
||||||
|
'/500.0000',
|
||||||
|
'if(depth<surface-2e-7) return;',
|
||||||
|
])
|
||||||
|
assert.ok(padGroundShader.includes(part), part);
|
||||||
pad.show = false;
|
pad.show = false;
|
||||||
assert.equal(group.visible, false);
|
assert.equal(effects[0].visible, false);
|
||||||
|
pad.show = true;
|
||||||
|
assert.equal(effects[0].visible, true);
|
||||||
pad.destroy();
|
pad.destroy();
|
||||||
pad.destroy();
|
pad.destroy();
|
||||||
assert.equal(removed, 1);
|
assert.equal(effects[0].deleted, true);
|
||||||
|
assert.equal(effects.filter((e) => e.deleted).length, 1);
|
||||||
renderer.destroy();
|
renderer.destroy();
|
||||||
engine.destroy();
|
engine.destroy();
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
// The launch pad zone on Navara, classified onto the surface as Cesium's
|
||||||
|
// GroundPrimitive does (render/cesium.js): a screen-space pass reads the depth
|
||||||
|
// of each pixel, puts it back in the world and paints the pixels of the
|
||||||
|
// ground (terrain or 3D tiles, not a model in front of them) that lie within
|
||||||
|
// the zone's radius of the pad, with Cesium's fill, rim and emission. Nothing
|
||||||
|
// is drawn at a height, so the zone neither floats over nor sinks under the
|
||||||
|
// ground, and no height is sampled.
|
||||||
|
import { Effect, EffectAttribute } from 'postprocessing';
|
||||||
|
import { Matrix3, Uniform, Vector3 } from 'three';
|
||||||
|
import * as geo from '../../../geo/index.js';
|
||||||
|
import {
|
||||||
|
groundSurfaceUniforms,
|
||||||
|
updateGroundSurface,
|
||||||
|
} from '../../shared/render/navaraGroundSurface.js';
|
||||||
|
import { LAUNCH_PAD_ZONE_RADIUS_M, LAUNCH_PAD_ZONE_STYLE } from '../policy.js';
|
||||||
|
|
||||||
|
export const PAD_GROUND_KEY = 'gevLaunchPadGround';
|
||||||
|
// The surface above or below the pad that the zone still classifies, as
|
||||||
|
// Cesium's classification volume spans the terrain heights there.
|
||||||
|
export const PAD_GROUND_BAND_M = 9000;
|
||||||
|
const registered = new WeakSet();
|
||||||
|
const glslFloat = (value) => Number(value).toFixed(4);
|
||||||
|
/** A #rrggbb colour as display (sRGB) values, as Cesium's material reads it. */
|
||||||
|
const displayRgb = (hex) =>
|
||||||
|
[1, 3, 5].map((at) => parseInt(hex.slice(at, at + 2), 16) / 255);
|
||||||
|
|
||||||
|
export const padGroundShader = /* glsl */ `
|
||||||
|
uniform sampler2D tAllDepth;
|
||||||
|
uniform sampler2D tSurfaceDepth;
|
||||||
|
uniform mat4 uInverseProjection;
|
||||||
|
uniform float uLogDepthFC;
|
||||||
|
uniform mat3 uViewToWorld;
|
||||||
|
uniform vec3 uPadOffset;
|
||||||
|
uniform vec3 uEast;
|
||||||
|
uniform vec3 uNorth;
|
||||||
|
uniform vec3 uUp;
|
||||||
|
uniform vec3 uPadColor;
|
||||||
|
vec3 toDisplay(vec3 c) {
|
||||||
|
return mix(12.92*c,1.055*pow(max(c,vec3(0.0)),vec3(1.0/2.4))-0.055,step(vec3(0.0031308),c));
|
||||||
|
}
|
||||||
|
vec3 toLinear(vec3 c) {
|
||||||
|
return mix(c/12.92,pow(max((c+0.055)/1.055,vec3(0.0)),vec3(2.4)),step(vec3(0.04045),c));
|
||||||
|
}
|
||||||
|
void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor) {
|
||||||
|
outputColor=inputColor;
|
||||||
|
float depth=unpackRGBAToDepth(texture2D(tAllDepth,uv));
|
||||||
|
if(depth>=1.0) return;
|
||||||
|
// Only the ground: a mesh or model in front of it keeps its own colour.
|
||||||
|
float surface=texture2D(tSurfaceDepth,uv).r;
|
||||||
|
if(depth<surface-2e-7) return;
|
||||||
|
float distance=exp2(2.0*depth/uLogDepthFC)-1.0;
|
||||||
|
vec4 ray=uInverseProjection*vec4(uv*2.0-1.0,-1.0,1.0);
|
||||||
|
vec3 point=ray.xyz*(distance/-ray.z);
|
||||||
|
// Relative to the pad, from offsets small enough for single precision.
|
||||||
|
vec3 rel=uViewToWorld*point-uPadOffset;
|
||||||
|
if(abs(dot(rel,uUp))>${glslFloat(PAD_GROUND_BAND_M)}) return;
|
||||||
|
float radius=length(vec2(dot(rel,uEast),dot(rel,uNorth)))/${glslFloat(LAUNCH_PAD_ZONE_RADIUS_M)};
|
||||||
|
if(radius>=1.0) return;
|
||||||
|
// Cesium's GevLaunchPadZone material, blended as in its framebuffer.
|
||||||
|
float inside=1.0-smoothstep(${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimEnd)},1.0,radius);
|
||||||
|
float rim=smoothstep(${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimStart)},${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimEnd)},radius)*inside;
|
||||||
|
float alpha=inside*mix(${glslFloat(LAUNCH_PAD_ZONE_STYLE.fillAlpha)},${glslFloat(LAUNCH_PAD_ZONE_STYLE.rimAlpha)},rim);
|
||||||
|
vec3 color=min(uPadColor*(1.0+${glslFloat(LAUNCH_PAD_ZONE_STYLE.emission)}*rim),vec3(1.0));
|
||||||
|
outputColor=vec4(toLinear(mix(toDisplay(inputColor.rgb),color,alpha)),inputColor.a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/** The pad's ECEF position and its east, north and up axes. */
|
||||||
|
export function padFrame(launch) {
|
||||||
|
const m = geo.transforms.eastNorthUpToFixedFrame(
|
||||||
|
geo.cartesian3.fromDegrees(launch.lon, launch.lat, 0),
|
||||||
|
geo.ellipsoid.WGS84,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
origin: [m[12], m[13], m[14]],
|
||||||
|
east: [m[0], m[1], m[2]],
|
||||||
|
north: [m[4], m[5], m[6]],
|
||||||
|
up: [m[8], m[9], m[10]],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function padGroundClass(EffectDesc, NavaraEffect) {
|
||||||
|
return class LaunchPadGroundDesc extends EffectDesc {
|
||||||
|
static key = PAD_GROUND_KEY;
|
||||||
|
static insertAfter = [
|
||||||
|
'gevEarthquakeGround',
|
||||||
|
'gevObservedWeather',
|
||||||
|
'gevNepalEvent',
|
||||||
|
'gevRecentImagery',
|
||||||
|
'gevFirmsGround',
|
||||||
|
'toneMapping',
|
||||||
|
];
|
||||||
|
static insertBefore = [
|
||||||
|
'gevPreSharpenOverlay',
|
||||||
|
'gevSharpen',
|
||||||
|
'smaa',
|
||||||
|
'fxaa',
|
||||||
|
'gevOverlay',
|
||||||
|
'final',
|
||||||
|
];
|
||||||
|
constructor(view, context, config) {
|
||||||
|
super(view, context, config);
|
||||||
|
this.owner = config[PAD_GROUND_KEY].owner;
|
||||||
|
}
|
||||||
|
createPass() {
|
||||||
|
this.effect = new Effect('GevLaunchPadGround', padGroundShader, {
|
||||||
|
attributes: EffectAttribute.CONVOLUTION,
|
||||||
|
uniforms: new Map([
|
||||||
|
...groundSurfaceUniforms(),
|
||||||
|
['uViewToWorld', new Uniform(new Matrix3())],
|
||||||
|
['uPadOffset', new Uniform(new Vector3())],
|
||||||
|
['uEast', new Uniform(new Vector3())],
|
||||||
|
['uNorth', new Uniform(new Vector3())],
|
||||||
|
['uUp', new Uniform(new Vector3())],
|
||||||
|
[
|
||||||
|
'uPadColor',
|
||||||
|
new Uniform(
|
||||||
|
new Vector3(...displayRgb(LAUNCH_PAD_ZONE_STYLE.color)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
return new NavaraEffect(this.view.camera.raw, this.effect);
|
||||||
|
}
|
||||||
|
updateFrame() {
|
||||||
|
const frame = this.owner.frame,
|
||||||
|
camera = this.view.camera.raw;
|
||||||
|
if (!this.effect || !frame || !camera) return;
|
||||||
|
updateGroundSurface(this.effect, camera, this.ctx, this.find('mrt'));
|
||||||
|
const u = this.effect.uniforms;
|
||||||
|
u.get('uViewToWorld').value.setFromMatrix4(camera.matrixWorld);
|
||||||
|
const e = camera.matrixWorld.elements;
|
||||||
|
u.get('uPadOffset').value.set(
|
||||||
|
frame.origin[0] - e[12],
|
||||||
|
frame.origin[1] - e[13],
|
||||||
|
frame.origin[2] - e[14],
|
||||||
|
);
|
||||||
|
u.get('uEast').value.fromArray(frame.east);
|
||||||
|
u.get('uNorth').value.fromArray(frame.north);
|
||||||
|
u.get('uUp').value.fromArray(frame.up);
|
||||||
|
}
|
||||||
|
onDestroy() {
|
||||||
|
this.effect?.dispose();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show one pad zone on a Navara view; `show` toggles it, `destroy` removes it.
|
||||||
|
* @param {object} view The Navara view.
|
||||||
|
* @param {{ EffectDesc: Function, NavaraEffect: Function }} sdk
|
||||||
|
* @param {{ lon: number, lat: number }} launch
|
||||||
|
*/
|
||||||
|
export function createPadGround(view, { EffectDesc, NavaraEffect }, launch) {
|
||||||
|
if (!registered.has(view)) {
|
||||||
|
view.registerEffect(
|
||||||
|
PAD_GROUND_KEY,
|
||||||
|
padGroundClass(EffectDesc, NavaraEffect),
|
||||||
|
);
|
||||||
|
registered.add(view);
|
||||||
|
}
|
||||||
|
const owner = { frame: padFrame(launch) };
|
||||||
|
let handle = view.addEffect({
|
||||||
|
[PAD_GROUND_KEY]: { owner },
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
owner,
|
||||||
|
set visible(value) {
|
||||||
|
handle?.update({ visible: Boolean(value) });
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
handle?.delete();
|
||||||
|
handle = null;
|
||||||
|
owner.frame = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in new issue
Block a user