diff --git a/src/data/scenePick.js b/src/data/scenePick.js index 52bab7a21..c1e164833 100644 --- a/src/data/scenePick.js +++ b/src/data/scenePick.js @@ -1,3 +1,5 @@ +import { isGlobeEngine } from '../globe/contract.js'; + /** * 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 * 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 * @returns {{x: number, y: number, z: number}|null} A Cartesian3. */ -export function pickGroundPosition(viewer, screenPosition) { - const scene = viewer?.scene; +export function pickGroundPosition(target, screenPosition) { + 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; - if ( - scene?.pickPositionSupported && - typeof scene.pickPosition === 'function' - ) { + if (depth) { try { - position = scene.pickPosition(screenPosition); + position = depth.pickPosition(screenPosition); } catch { position = null; } } - if ( - !isPickedWorldPosition(position) && - typeof viewer?.camera?.pickEllipsoid === 'function' - ) { + if (!isPickedWorldPosition(position) && ellipsoid) { try { - position = viewer.camera.pickEllipsoid(screenPosition); + position = ellipsoid.pickEllipsoid(screenPosition); } catch { position = null; } @@ -83,8 +99,8 @@ export function pickGroundPosition(viewer, screenPosition) { } /** - * Whether a `scene.pick()` result is the map itself: nothing, or 3D tile - * content (which has no id). + * Whether a pick result (`engine.picker.pick()` or `scene.pick()`) is the + * map itself: nothing, or 3D tile content (which has no id). * * @param {object|null|undefined} picked * @returns {boolean} diff --git a/src/data/scenePick.test.mjs b/src/data/scenePick.test.mjs index 630ce4003..533ff214b 100644 --- a/src/data/scenePick.test.mjs +++ b/src/data/scenePick.test.mjs @@ -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({ 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)); + } +});