Files
gods-eye-view/src/app/viewer.js
T
German KhokhlovandClaude Opus 5.5 8c33c6f349 docs(sky): shorten the sky comments and README, fix three stale ones
The far-plane comment said z = w (the code puts z just under w), the Milky
Way constant is a brightness, not an opacity, and the CDS acknowledgement
lives in the Data attribution popover, not DATA_SOURCES.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-20 13:56:44 +04:00

155 lines
4.7 KiB
JavaScript

import * as Cesium from 'cesium';
import { applyModelAtmosphereWorkaround } from './atmosphereCompat.js';
import { StarrySky } from '../sky/starrySky.js';
const PINCH_ZOOM_MULTIPLIER = 8;
const MAX_PINCH_PIXEL_DELTA = 120;
function boundedPinchDelta(delta) {
if (!Number.isFinite(delta) || delta === 0) return delta;
return (
Math.sign(delta) *
Math.min(Math.abs(delta) * PINCH_ZOOM_MULTIPLIER, MAX_PINCH_PIXEL_DELTA)
);
}
/**
* Add browser trackpad pinch to Cesium's zoom inputs and return its disposer.
* Browsers expose this gesture as a small pixel-mode Ctrl+wheel event.
*/
export function installTrackpadPinchZoom(
viewer,
{ createWheelEvent = (type, init) => new WheelEvent(type, init) } = {},
) {
const controller = viewer?.scene?.screenSpaceCameraController;
const container = viewer?.container;
const canvas = viewer?.canvas;
if (!controller || !container || !canvas)
throw new TypeError('A complete Cesium viewer is required');
const originalZoomEventTypes = controller.zoomEventTypes;
const zoomEventTypes = Array.isArray(originalZoomEventTypes)
? originalZoomEventTypes
: originalZoomEventTypes === undefined
? []
: [originalZoomEventTypes];
const alreadyHandlesControlWheel = zoomEventTypes.some(
(binding) =>
binding?.eventType === Cesium.CameraEventType.WHEEL &&
binding?.modifier === Cesium.KeyboardEventModifier.CTRL,
);
const configuredZoomEventTypes = alreadyHandlesControlWheel
? originalZoomEventTypes
: [
...zoomEventTypes,
{
eventType: Cesium.CameraEventType.WHEEL,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
];
if (!alreadyHandlesControlWheel)
controller.zoomEventTypes = configuredZoomEventTypes;
const relayedEvents = new WeakSet();
const relayPinch = (event) => {
if (
!event.ctrlKey ||
relayedEvents.has(event) ||
event.deltaMode !== 0 ||
!Number.isFinite(event.deltaY) ||
event.deltaY === 0
)
return;
let relayed;
try {
relayed = createWheelEvent('wheel', {
deltaX: event.deltaX,
deltaY: boundedPinchDelta(event.deltaY),
deltaZ: event.deltaZ,
deltaMode: event.deltaMode,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,
clientY: event.clientY,
ctrlKey: true,
bubbles: true,
cancelable: true,
view: globalThis.window,
});
} catch {
// The registered Ctrl+wheel binding can still consume the original.
return;
}
relayedEvents.add(relayed);
event.preventDefault();
event.stopPropagation();
canvas.dispatchEvent(relayed);
};
container.addEventListener('wheel', relayPinch, {
capture: true,
passive: false,
});
let active = true;
return () => {
if (!active) return;
active = false;
container.removeEventListener('wheel', relayPinch, true);
if (
!alreadyHandlesControlWheel &&
controller.zoomEventTypes === configuredZoomEventTypes
)
controller.zoomEventTypes = originalZoomEventTypes;
};
}
/** Cesium.Viewer options of the standard globe viewer. */
export function applicationViewerOptions(creditContainer) {
return {
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
vrButton: false,
selectionIndicator: false,
infoBox: false,
baseLayer: false,
// Replaces the blocky Tycho-2 default. An option, not `skyBox: false`,
// which would also stop CesiumWidget from creating the Sun and Moon.
skyBox: new StarrySky(),
creditContainer,
msaaSamples: 4,
contextOptions: { webgl: { preserveDrawingBuffer: true } },
};
}
/** Create the standard globe viewer in caller-owned, visible containers. */
export function createApplicationViewer({ container, creditContainer }) {
if (!container || !creditContainer)
throw new TypeError('Viewer and credit containers are required');
const viewer = new Cesium.Viewer(
container,
applicationViewerOptions(creditContainer),
);
try {
viewer.targetFrameRate = 60;
// Before any tile builds a draw command: Cesium's per-vertex model
// atmosphere fails to LINK on Apple's Metal backend and kills the
// render loop. See app/atmosphereCompat.js.
applyModelAtmosphereWorkaround(viewer.scene);
viewer.scene.globe.show = false;
viewer.scene.skyAtmosphere.show = true;
viewer.scene.skyAtmosphere.atmosphereLightIntensity = 18;
viewer.scene.skyAtmosphere.saturationShift = -0.12;
viewer.scene.skyAtmosphere.brightnessShift = -0.08;
return viewer;
} catch (error) {
viewer.destroy();
throw error;
}
}