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; 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 remaining margin eases into place, including after zooming stops or reaches
the ceiling. Continuous outward inertia remains guarded without a time cutoff. the ceiling. Continuous outward inertia remains guarded without a time cutoff.
Parallel two-finger tilts, modifier-specific tilts and other camera gestures Two-finger tilts, modifier-specific tilts and other camera gestures cancel the
cancel the correction. Explicit navigation handoffs, including toolbar tilt, 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 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 transforms and flights also cancel it; external pose changes cancel stationary
settling. Oblique views below 300 km are left alone. 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) { for (const { type, modifier } of INPUT_BINDINGS) {
if (!aggregator.isMoving(type, modifier)) continue; if (!aggregator.isMoving(type, modifier)) continue;
const movement = aggregator.getMovement(type, modifier); const movement = aggregator.getMovement(type, modifier);
const distance = const pinch = type === Cesium.CameraEventType.PINCH;
type === Cesium.CameraEventType.PINCH ? movement.distance : movement; const distance = pinch ? movement.distance : movement;
const delta = distance.endPosition.y - distance.startPosition.y; 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; outward ||= delta < 0;
cancel ||= delta > 0; cancel ||= delta > 0;
} else { } 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); 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', () => { test('unconfigured wheel modifiers and one-finger drag do not enable the guard', () => {
for (const [type, modifier] of [[WHEEL, SHIFT], [WHEEL, ALT], [LEFT_DRAG]]) { for (const [type, modifier] of [[WHEEL, SHIFT], [WHEEL, ALT], [LEFT_DRAG]]) {
const f = guardFixture(); const f = guardFixture();