refactor(pick): use the globe engine

pickGroundPosition takes the globe engine (picker.pickPosition, then
camera.pickEllipsoid) and still accepts a Cesium viewer from callers that
hold one (layers until phase 1c), with one shared ground-or-ellipsoid
rule. isSurfacePick classifies engine pick results exactly as the Cesium
picks they wrap.
This commit is contained in:
German Khokhlov committed 2026-10-15 18:32:45 +04:00
1 parent aaa6074663
commit c2a0d20f3d
2 files changed
+77 -15

No files matched your search

+31 -15
View File
@@ -1,3 +1,5 @@
import { isGlobeEngine } from '../globe/contract.js';
/** /**
* Validation for positions that came back from a Cesium screen pick. * Validation for positions that came back from a Cesium screen pick.
* *
@@ -52,29 +54,43 @@ export function isPickedWorldPosition(position) {
* The world position under a screen point: the rendered surface where the * The world position under a screen point: the rendered surface where the
* depth buffer has it, else the ellipsoid; null over the sky. * depth buffer has it, else the ellipsoid; null over the sky.
* *
* @param {object} viewer A Cesium viewer. * @param {object} target The globe engine (`engine.picker.pickPosition`, then
* `engine.camera.pickEllipsoid`), or a Cesium viewer from callers that
* still hold one (`scene.pickPosition`, then `camera.pickEllipsoid`).
* @param {{x: number, y: number}} screenPosition * @param {{x: number, y: number}} screenPosition
* @returns {{x: number, y: number, z: number}|null} A Cartesian3. * @returns {{x: number, y: number, z: number}|null} A Cartesian3.
*/ */
export function pickGroundPosition(viewer, screenPosition) { export function pickGroundPosition(target, screenPosition) {
const scene = viewer?.scene; if (isGlobeEngine(target)) {
const { picker, camera } = target;
return groundUnder(
screenPosition,
picker.pickPositionSupported ? picker : null,
camera,
);
}
const scene = target?.scene;
return groundUnder(
screenPosition,
scene?.pickPositionSupported && typeof scene.pickPosition === 'function'
? scene
: null,
typeof target?.camera?.pickEllipsoid === 'function' ? target.camera : null,
);
}
function groundUnder(screenPosition, depth, ellipsoid) {
let position = null; let position = null;
if ( if (depth) {
scene?.pickPositionSupported &&
typeof scene.pickPosition === 'function'
) {
try { try {
position = scene.pickPosition(screenPosition); position = depth.pickPosition(screenPosition);
} catch { } catch {
position = null; position = null;
} }
} }
if ( if (!isPickedWorldPosition(position) && ellipsoid) {
!isPickedWorldPosition(position) &&
typeof viewer?.camera?.pickEllipsoid === 'function'
) {
try { try {
position = viewer.camera.pickEllipsoid(screenPosition); position = ellipsoid.pickEllipsoid(screenPosition);
} catch { } catch {
position = null; position = null;
} }
@@ -83,8 +99,8 @@ export function pickGroundPosition(viewer, screenPosition) {
} }
/** /**
* Whether a `scene.pick()` result is the map itself: nothing, or 3D tile * Whether a pick result (`engine.picker.pick()` or `scene.pick()`) is the
* content (which has no id). * map itself: nothing, or 3D tile content (which has no id).
* *
* @param {object|null|undefined} picked * @param {object|null|undefined} picked
* @returns {boolean} * @returns {boolean}
+46
View File
@@ -184,3 +184,49 @@ test('a surface pick is nothing, or Google 3D tile content without an id', async
assert.equal(isSurfacePick({ id: 'flight:1', content: {} }), false); assert.equal(isSurfacePick({ id: 'flight:1', content: {} }), false);
assert.equal(isSurfacePick({ primitive: {} }), false, 'an unnamed primitive'); assert.equal(isSurfacePick({ primitive: {} }), false, 'an unnamed primitive');
}); });
test('on a globe engine the ground is the picked surface, else the ellipsoid, never the sky', async () => {
const { pickGroundPosition } = await import('./scenePick.js');
const { createFakeEngine } = await import('../globe/testing/fakeEngine.js');
const ground = { x: 6378137, y: 0, z: 0 };
const sky = { x: 500, y: 0, z: 0 };
const screen = { x: 10, y: 20 };
const engine = createFakeEngine();
engine.fake.script('pickPosition', ground);
engine.fake.script('pickEllipsoid', undefined);
assert.equal(pickGroundPosition(engine, screen), ground);
assert.deepEqual(engine.fake.callsOf('picker.pickPosition')[0].args, [screen]);
engine.fake.script('pickPosition', sky);
engine.fake.script('pickEllipsoid', ground);
assert.equal(pickGroundPosition(engine, screen), ground);
engine.fake.script('pickPosition', () => {
throw new Error('depth read failed');
});
assert.equal(pickGroundPosition(engine, screen), ground);
engine.fake.script('pickEllipsoid', undefined);
assert.equal(pickGroundPosition(engine, screen), null);
const unsupported = createFakeEngine();
unsupported.picker.pickPositionSupported = false;
unsupported.fake.script('pickEllipsoid', ground);
assert.equal(pickGroundPosition(unsupported, screen), ground);
assert.equal(unsupported.fake.callsOf('picker.pickPosition').length, 0);
});
test('engine pick results classify like the Cesium picks they wrap', async () => {
const { isSurfacePick } = await import('./scenePick.js');
const { createCesiumPicker } = await import('../globe/cesium/picker.js');
const { createFakeViewer } = await import('../globe/cesium/testing/fakeViewer.mjs');
const raws = [
undefined,
{ content: {}, primitive: {} },
{ primitive: { isCesium3DTileset: true } },
{ id: 'flight:1', content: {} },
{ primitive: {} },
];
for (const raw of raws) {
const viewer = createFakeViewer();
viewer.scene.pick = () => raw;
const { picker } = createCesiumPicker(viewer);
assert.equal(isSurfacePick(picker.pick({ x: 1, y: 2 })), isSurfacePick(raw), JSON.stringify(raw));
}
});