diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 124309141..256db0530 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -54,8 +54,10 @@ Above 300 km, outward movement from the controller's configured zoom bindings down towards 8° below the Earth's limb. Large steps reach the limb immediately; the remaining margin eases into place, including after zooming stops or reaches the ceiling. Continuous outward inertia remains guarded without a time cutoff. -Parallel two-finger tilts, modifier-specific tilts and other camera gestures -cancel the correction. Explicit navigation handoffs, including toolbar tilt, +Two-finger tilts, modifier-specific tilts and other camera gestures cancel the +correction. A two-finger gesture counts as a pinch zoom only while the finger +spacing changes more than the fingers move up or down, so spacing drift during +a tilt is ignored. Explicit navigation handoffs, including toolbar tilt, cancel it before animation begins. Zooming in, disabled zoom/input, non-3D modes, tracked transforms and flights also cancel it; external pose changes cancel stationary settling. Oblique views below 300 km are left alone. diff --git a/src/app/zoomOutGuard.js b/src/app/zoomOutGuard.js index 155f66566..5457b509b 100644 --- a/src/app/zoomOutGuard.js +++ b/src/app/zoomOutGuard.js @@ -47,10 +47,20 @@ function readInput(aggregator, bindings) { for (const { type, modifier } of INPUT_BINDINGS) { if (!aggregator.isMoving(type, modifier)) continue; const movement = aggregator.getMovement(type, modifier); - const distance = - type === Cesium.CameraEventType.PINCH ? movement.distance : movement; + const pinch = type === Cesium.CameraEventType.PINCH; + const distance = pinch ? movement.distance : movement; const delta = distance.endPosition.y - distance.startPosition.y; - if (matchesZoomBinding(bindings, type, modifier) && delta !== 0) { + // Cesium tilts by the fingers' midpoint while zooming by their spacing, + // both in the same quarter-pixel units. Fingers rarely keep a perfectly + // even spacing, so the larger of the two movements decides the gesture. + const tilt = pinch + ? movement.angleAndHeight.endPosition.y - + movement.angleAndHeight.startPosition.y + : 0; + if ( + matchesZoomBinding(bindings, type, modifier) && + Math.abs(delta) > Math.abs(tilt) + ) { outward ||= delta < 0; cancel ||= delta > 0; } else { diff --git a/src/app/zoomOutGuard.test.mjs b/src/app/zoomOutGuard.test.mjs index 5821a5c87..1b2322cb5 100644 --- a/src/app/zoomOutGuard.test.mjs +++ b/src/app/zoomOutGuard.test.mjs @@ -245,6 +245,33 @@ test('parallel two-finger tilt is not mistaken for pinch zoom', () => { assert.equal(f.camera.views.length, 0); }); +test('two-finger tilt with finger-spacing drift is left to Cesium', () => { + for (const [spacing, tilt] of [ + [-0.01, 3], + [-2, 3], + [-3, -3], + ]) { + const f = guardFixture(); + f.frame(500_000); + f.movement(PINCH, spacing, { tiltDeltaY: tilt }); + // Real Cesium's two-finger tilt climbs and picks its own pitch. + f.camera.pitch = Cesium.Math.toRadians(-26.07746); + f.frame(529_406); + assert.equal(f.camera.views.length, 0, `${spacing}, ${tilt}`); + f.dispose(); + } +}); + +test('a pinch that closes the fingers more than it moves them still zooms out', () => { + // One finger still, the other sliding towards it: the midpoint moves half as far. + const f = guardFixture(); + f.frame(500_000); + f.movement(PINCH, -10, { tiltDeltaY: -5 }); + f.frame(700_000); + assert.equal(f.camera.views.length, 1); + f.dispose(); +}); + test('unconfigured wheel modifiers and one-finger drag do not enable the guard', () => { for (const [type, modifier] of [[WHEEL, SHIFT], [WHEEL, ALT], [LEFT_DRAG]]) { const f = guardFixture();