Set the field of view and read the matrices through the camera port

setFov holds the vertical angle it is given across resizes (Cesium
keeps the wider one), waits for a frustum with a field of view, and
rejects angles outside (0, pi). matrices returns Cesium's view and
projection matrices, ECEF to eye in 3D, the only mode Core runs. The
conformance run checks both on Cesium.
This commit is contained in:
Sameh Khamis committed 2026-11-06 16:32:07 -08:00
1 parent c6f043d3fd
commit 764b40950f
4 files changed
+60 -4

No files matched your search

-2
View File
@@ -333,8 +333,6 @@ const EXPECTED_MISSING = [
'camera.controls.suspend',
'camera.controls.zoomFactor',
'camera.controls.zoomInputs',
'camera.matrices',
'camera.setFov',
'draw.atlas',
'draw.clampState',
'draw.on',
+39 -1
View File
@@ -91,7 +91,10 @@ export function createCesiumCamera({ scene, camera, report, widget }) {
report,
});
removers.push(
scene.postUpdate.addEventListener(() => cameraEvents.stepChanged()),
scene.postUpdate.addEventListener(() => {
applyFov();
cameraEvents.stepChanged();
}),
camera.moveStart.addEventListener(() => cameraEvents.emit('moveStart')),
camera.moveEnd.addEventListener(() => cameraEvents.emit('moveEnd')),
);
@@ -176,6 +179,23 @@ export function createCesiumCamera({ scene, camera, report, widget }) {
return handle;
}
/**
* The vertical field of view `setFov` asked for, held across resizes:
* Cesium's `fov` is the wider of the two angles (the horizontal one when
* the view is wider than tall), and it keeps that one when the aspect
* changes. Before the first resize, and on a frustum without a field of
* view, it waits.
*/
let heldFovy = null;
function applyFov() {
const frustum = camera.frustum;
const aspect = frustum?.aspectRatio;
if (heldFovy === null || !('fov' in frustum) || !(aspect > 0)) return;
const fov =
aspect > 1 ? 2 * Math.atan(Math.tan(heldFovy / 2) * aspect) : heldFovy;
if (frustum.fov !== fov) frustum.fov = fov;
}
const cameraPort = {
on: (event, cb, options) => cameraEvents.on(event, cb, options),
onCommand: (cb) => commandHooks.on(cb),
@@ -288,6 +308,24 @@ export function createCesiumCamera({ scene, camera, report, widget }) {
else camera.zoomIn(metres);
scene.requestRender();
},
setFov({ vertical }) {
// Cesium would take a bad angle and draw a garbage projection.
if (!(vertical > 0 && vertical < Math.PI))
throw new RangeError(`no vertical field of view of ${vertical}`);
heldFovy = vertical;
applyFov();
scene.requestRender();
},
// The scene's own frame: ECEF in 3D, the only mode Core runs (Cesium's
// 2D and Columbus view frames are projected map coordinates).
matrices() {
return {
view: Float64Array.from(Cesium.Matrix4.toArray(camera.viewMatrix)),
projection: Float64Array.from(
Cesium.Matrix4.toArray(camera.frustum.projectionMatrix),
),
};
},
viewRectangle() {
const rect = camera.computeViewRectangle(Cesium.Ellipsoid.WGS84);
if (!rect) return null;
+19 -1
View File
@@ -25,7 +25,7 @@ const QUERY_RECORDS = new Set([
'occluded',
]);
/** Commands and records the port does not have yet. */
const MISSING = new Set(['matrices']);
const MISSING = new Set([]);
const runnable = CAMERA_SCRIPTS.filter((script) =>
script.steps.every(
@@ -232,3 +232,21 @@ test('a Cesium viewer with no renderer bound gets a camera port over its own cam
assert.throws(() => cameraOf({ scene, camera: {} }), /no renderer camera/);
});
});
test('setFov holds the vertical angle across resizes, and waits for a first one', () => {
const { scene, camera } = headlessCamera();
scene.postUpdate = new Cesium.Event();
scene.requestRender = () => {};
camera.frustum.aspectRatio = NaN;
const { port } = createCesiumCamera({ scene, camera, report: () => {} });
port.setFov({ vertical: 0.5 });
for (const aspect of [16 / 9, 0.6, 4 / 3]) {
camera.frustum.aspectRatio = aspect;
scene.postUpdate.raiseEvent(scene);
assert.ok(Math.abs(camera.frustum.fovy - 0.5) < 1e-12, `aspect ${aspect}`);
}
camera.switchToOrthographicFrustum();
assert.doesNotThrow(() => port.setFov({ vertical: 0.4 }));
assert.throws(() => port.setFov({ vertical: Math.PI }), RangeError);
assert.throws(() => port.setFov({ vertical: NaN }), RangeError);
});
+2
View File
@@ -27,6 +27,8 @@ export const CESIUM_CHECKS = new Set([
'camera.trace.flyTo with a duration of 0 completes inside the call',
'camera.trace.flyTo where the camera already is completes inside the call',
'camera.viewRectangle',
'camera.fov',
'camera.trace.view and projection matrices',
'draw.drillPick',
'draw.ids',
'draw.models',