mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
iPad and iPhone show "An error occurred while rendering. Rendering has
stopped." on load. Cesium's AtmosphereStageVS calls
czm_computeGroundAtmosphereScattering with its `out` parameters bound
directly to varyings:
czm_computeGroundAtmosphereScattering(
v_positionWC, lightDirection,
v_atmosphereRayleighColor, v_atmosphereMieColor,
v_atmosphereOpacity);
ANGLE's Metal backend wraps each `out` argument in an ANGLE_out(...)
helper taking `thread T &`, but vertex-output members live in
`__metal_generic` and cannot bind to `thread`, so the program fails to
LINK and Cesium tears down the render loop.
This is the only shader in Cesium that binds varyings to `out` params --
SkyAtmosphere (czm_computeScattering) and the ground-atmosphere fragment
path both route through locals, so they are unaffected and stay on.
ModelSceneGraph.configurePipeline attaches the stage under a single
condition, `fog.enabled && fog.renderable`, so clearing `fog.renderable`
keeps the broken program from ever being built.
`renderable` rather than `enabled` is deliberate: Fog.update returns
early when `enabled` is false and forfeits the density that drives 3D
Tiles screen-space-error scaling, and visualSettings saves/restores
`fog.enabled` around IR boost, which would resurrect the broken shader
mid-session.
Detection is a WebGL2 link probe of the same out-param/varying pattern,
so a future driver fix silently restores the effect with no code change,
OR'd with iOS/iPadOS platform detection as a backstop for when no probe
context can be created. iPadOS desktop mode is identified by the
Macintosh UA token plus maxTouchPoints > 1, since real Macs report zero
and navigator.platform is deprecated.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HoRD3yHVgfTHeikTmsUoHf
143 lines
4.3 KiB
JavaScript
143 lines
4.3 KiB
JavaScript
import * as Cesium from 'cesium';
|
|
import { applyModelAtmosphereWorkaround } from './atmosphereCompat.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;
|
|
};
|
|
}
|
|
|
|
/** 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, {
|
|
timeline: false,
|
|
animation: false,
|
|
baseLayerPicker: false,
|
|
geocoder: false,
|
|
homeButton: false,
|
|
sceneModePicker: false,
|
|
navigationHelpButton: false,
|
|
fullscreenButton: false,
|
|
vrButton: false,
|
|
selectionIndicator: false,
|
|
infoBox: false,
|
|
baseLayer: false,
|
|
creditContainer,
|
|
msaaSamples: 4,
|
|
contextOptions: { webgl: { preserveDrawingBuffer: true } },
|
|
});
|
|
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;
|
|
}
|
|
}
|