Skip to content

feat(core): GlobeView pointer-anchored zoom (wheel + transitions)#10307

Open
charlieforward9 wants to merge 3 commits into
visgl:masterfrom
NEW-HEAT:codex/globe-anchored-zoom
Open

feat(core): GlobeView pointer-anchored zoom (wheel + transitions)#10307
charlieforward9 wants to merge 3 commits into
visgl:masterfrom
NEW-HEAT:codex/globe-anchored-zoom

Conversation

@charlieforward9
Copy link
Copy Markdown
Collaborator

@charlieforward9 charlieforward9 commented May 16, 2026

Summary

Adds an opt-in zoomAround: 'pointer' controller option that keeps the geographic point under the cursor/touch fixed during zoom — for wheel/pinch and for the dblclick zoom transition.

  • controller.ts: new zoomAround?: 'center' | 'pointer' option on ControllerOptions.
  • globe-controller.ts: GlobeState gains zoomStart / zoomEnd and a refactored zoom that uses GlobeViewport.panByGlobeAnchor when zoomAround === 'pointer'. Off-globe taps fall through to a plain zoom.
  • globe-viewport.ts:
    • Shared private _getRayToGlobe helper builds the ray/sphere math used by unproject, isPointOnGlobe, and panByGlobeAnchor. JSDoc on the helper explains the contract.
    • New public isPointOnGlobe(xy, { maxDistanceRatio }).
    • New public panByGlobeAnchor(anchorLngLat, pixel) with documented edge-damping (anchor starts loosening at 0.75 × limb radius and never goes below 0.35 × strength) so near-edge anchors don't snap the camera.
  • linear-interpolator.ts: replaces the prior log.warn('around not supported in GlobeView') no-op with real spherical anchoring — initializeProps stashes aroundLngLat, interpolateProps runs panByGlobeAnchor each frame. Off-globe anchors fall through to a plain LERP. This is what makes the dblclick zoom transition land on the tap point instead of the camera center.

Why

Pointer-anchored zoom matches Google Earth-style interaction. Keeping the option opt-in (default remains 'center') avoids changing existing GlobeView behavior.

Tests

  • GlobeController supports pointer anchored zoom option — wheel on a globe with {zoomAround: 'pointer'} shifts longitude; default does not.
  • GlobeViewport#isPointOnGlobe — center hits the globe, corner misses.
  • LinearInterpolator anchors transitions on GlobeViewportinitializeProps records aroundLngLat, mid-transition longitude shifts.
  • LinearInterpolator falls back to a plain LERP when the GlobeView anchor is off-globe — corner anchor leaves aroundLngLat undefined.

CI: vitest run --project browser test/modules/core/controllers/controllers.spec.ts test/modules/core/viewports/globe-viewport.spec.ts test/modules/core/transitions/linear-interpolator.spec.ts.

@coveralls
Copy link
Copy Markdown

coveralls commented May 16, 2026

Coverage Status

coverage: 83.381% (-0.05%) from 83.434% — NEW-HEAT:codex/globe-anchored-zoom into visgl:master

@charlieforward9 charlieforward9 self-assigned this May 16, 2026
Replace the `log.warn('around not supported in GlobeView')` no-op
with real spherical anchoring, mirroring the existing planar branch:
- `initializeProps`: when the start viewport is a GlobeViewport and the
  screen anchor falls on the globe (`isPointOnGlobe`), unproject it to
  lng/lat and stash it as `aroundLngLat`.
- `interpolateProps`: each frame, call `panByGlobeAnchor(aroundLngLat,
  lerp(start.around, end.around, t))` so the geographic point stays
  pinned under the anchor screen point during the transition.

This makes the `_onDoubleClick` zoom transition (`_getTransitionProps
({around: pos})`) actually anchor on GlobeView. Previously the warn
fired and the LERP ran without anchor maintenance, which read as a
center-anchored zoom-in regardless of where the user tapped.

Tests cover the on-globe anchored path and the off-globe fall-through.
@charlieforward9
Copy link
Copy Markdown
Collaborator Author

Added a follow-up commit (e60090a75) that extends this PR with the GlobeView transition-anchor path.

Background

The existing LinearInterpolator checks for around + makeViewport, but its globe branch is a no-op (log.warn('around not supported in GlobeView')). That meant _onDoubleClick on a globe — which already passes _getTransitionProps({around: pos}) — degraded to a plain LERP between start and end view states. Visually the camera read as zooming toward center even though the controller's own end state (from this PR's GlobeState.zoom + panByGlobeAnchor) was correctly anchored.

Change

  • initializeProps: for GlobeViewport, when the anchor screen point lands on the globe (isPointOnGlobe), unproject it to lng/lat and store as aroundLngLat. Off-globe falls through to a plain LERP (matches the previous behavior).
  • interpolateProps: when aroundLngLat is set and the intermediate viewport is a GlobeViewport, call panByGlobeAnchor(aroundLngLat, lerp(start.around, end.around, t)) to keep the geographic point pinned under the anchor screen point each frame. The non-globe branch is unchanged (panByPosition).
  • Removed the now-unused log import.

Tests

  • New LinearInterpolator anchors transitions on GlobeViewport — verifies aroundLngLat is set, aroundPosition is not set (so we don't dual-anchor), and longitude shifts mid-transition.
  • New LinearInterpolator falls back to a plain LERP when the GlobeView anchor is off-globe — verifies a corner-of-canvas anchor leaves aroundLngLat undefined.

Happy to split this into a separate PR stacked on top of #10307 if you'd rather review it independently — let me know.

- Consolidate stray imports at the top of the file.
- Document the two GLOBE_ZOOM_ANCHOR_* constants so the empirical
  damping behavior (start damping at 0.75 of the limb, never below
  35% strength) is self-explanatory.
- Add a JSDoc to _getRayToGlobe explaining it as the shared ray/sphere
  math helper for unproject + isPointOnGlobe + panByGlobeAnchor.
@charlieforward9 charlieforward9 changed the title feat(core): add GlobeView pointer zoom option feat(core): GlobeView pointer-anchored zoom (wheel + transitions) May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants