fix(camera): let a two-finger tilt win over finger-spacing drift

Above 300 km the guard treated any shrinking pinch distance as an outward
zoom, so a two-finger tilt whose fingers drifted slightly closer had its
pitch overwritten. A two-finger gesture now counts as a zoom only when the
spacing changes more than the fingers' midpoint moves.
This commit is contained in:
Sameh Khamis committed 2026-11-03 09:10:58 -08:00
1 parent e4b3933166
commit b817d5802b
3 files changed
+44 -5

No files matched your search

+4 -2
View File
@@ -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.
+13 -3
View File
@@ -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 {
+27
View File
@@ -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();