Skip to content

feat(core): support pointer-anchored zoom in GlobeView - #10385

Open
charlieforward9 wants to merge 17 commits into
codex/shared-controller-zoom-anchorfrom
codex/globe-anchored-zoom
Open

feat(core): support pointer-anchored zoom in GlobeView#10385
charlieforward9 wants to merge 17 commits into
codex/shared-controller-zoom-anchorfrom
codex/globe-anchored-zoom

Conversation

@charlieforward9

@charlieforward9 charlieforward9 commented Jun 16, 2026

Copy link
Copy Markdown
Member

Goal

Add stable pointer-anchored zoom to GlobeView through the same controller and transition contracts used by the other views, without Globe-specific branches in shared controller code.

This PR is the Globe-specific half of the maintainer-requested stack:

Supersedes the fork-based #10307.

Changes

  • teaches GlobeViewport.panByPosition to preserve a geographic anchor during zoom using spherical ray math
  • damps correction near the globe limb and falls back to center behavior for far off-globe pointers
  • resolves off-globe anchors before they enter the shared wheel, pinch, double-click, or double-click-drag paths, so zoom still changes scale without pulling the camera toward a phantom point
  • follows the shortest wrapped longitude delta across the antimeridian
  • routes GlobeState constraints and LinearInterpolator anchoring through the generic viewport contract
  • keeps the existing globe drag form of panByPosition intact
  • splits demo UI into settings-control.js, with the same pointer/center control operating in both Globe and Map views
  • removes the old localStorage path and updates the point radius accessor

Validation

  • focused stacked Vitest: 92/92 across controllers, LinearInterpolator, and GlobeViewport
  • regression proof: the new off-globe controller test fails on the previous head with a 1.25738° longitude shift and passes on this head
  • yarn
  • yarn build
  • yarn lint
  • pre-commit checks: 15/15
  • yarn test-headless: 216/217 files and 985 tests passed; the only failure is the unrelated loading-widget.spec.ts spinner assertion, reproduced identically on the untouched feat(core): configure zoom anchors across controllers #10595 base
  • Greptile green on 8d771b0cdb; GitHub Actions had not created a run for the synchronized SHA at the latest check
  • no unresolved review threads before the push
  • exact-head dev server: http://127.0.0.1:41085/
  • live Globe stress QA with no console errors:
    • pointer anchoring at the interior, damped near-limb behavior, and center fallback outside the visible sphere
    • center anchoring with asymmetric padding, preserving longitude/latitude to floating-point precision
    • shortest-path anchoring across the antimeridian
    • finite behavior at latitude 84° and zoom 7
    • wheel, pinch, double-click, and enabled double-click-drag off-globe fallback
    • 100 alternating wheel events at one anchor return to the initial view state within floating-point precision
    • Globe → Map → Globe switching preserves the shared pointer policy; Map anchor error was 2.6e-12px and Globe anchor error was 0.41px

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.
- 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 force-pushed the codex/globe-anchored-zoom branch from c29d36e to 4b98253 Compare June 16, 2026 17:36
…pout)

Per #10307 review: zoomAround is a config option that always needs a value,
unlike the transient gesture anchors guarded the same way. The !== undefined
guard let a partial-props / HMR reconstruction drop the key, so
_shouldZoomAroundPointer() saw undefined and silently reverted to center zoom.
@coveralls

coveralls commented Jun 16, 2026

Copy link
Copy Markdown

Coverage Status

Coverage is 82.265%codex/globe-anchored-zoom into codex/shared-controller-zoom-anchor. No base build found for codex/shared-controller-zoom-anchor.

@chrisgervang chrisgervang added this to the v9.4 milestone Jun 28, 2026

@ibgreen-openai ibgreen-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this could be split into stacked PR, one that adds general things like zoomStart, zoomEnd and other plumbing, and then the globe specific feature could be a much smaller, easier review on top of that?

Comment thread test/apps/globe/app.js Outdated

@chrisgervang chrisgervang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @ibgreen, since this changes a lot of core functionality I'd prefer to split the PRs up and test each of them in the stack

Comment thread test/apps/globe/app.js Outdated
Comment thread docs/api-reference/core/controller.md Outdated
…ared-zoom

# Conflicts:
#	docs/api-reference/core/globe-controller.md
#	modules/core/src/controllers/controller.ts
#	test/modules/core/controllers/controllers.spec.ts
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds pointer-anchored zoom support to GlobeView through the generic controller, viewport, constraint, and transition contracts.

  • Adds spherical anchor correction with limb damping, off-globe fallback, and wrapped-longitude handling.
  • Enables anchored Globe transitions through LinearInterpolator.
  • Expands controller, transition, and viewport coverage and updates the Globe demonstration controls.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
modules/core/src/controllers/globe-controller.ts Integrates Globe zoom anchoring with generic constraint handling and falls back to center anchoring for off-globe pointers.
modules/core/src/viewports/globe-viewport.ts Adds shared ray geometry, anchor-strength damping, wrapped longitude correction, and descriptive parameter names while preserving drag behavior.
modules/core/src/transitions/linear-interpolator.ts Removes the Globe-specific exclusion so transition anchoring uses the common viewport contract.
test/modules/core/viewports/globe-viewport.spec.ts Covers on-globe stability, limb behavior, off-globe fallback, and antimeridian wrapping.
test/apps/globe/app.js Updates the Globe demo with shared Map/Globe and pointer/center controls and corrects the point-radius accessor.

Sequence Diagram

sequenceDiagram
  participant Input as Pointer input
  participant Controller as GlobeController
  participant State as GlobeState
  participant Viewport as GlobeViewport
  Input->>Controller: wheel/pinch/double-click at screen position
  Controller->>Viewport: getZoomAnchorStrength(position)
  alt Pointer can anchor globe
    Controller->>State: zoom around pointer
    State->>Viewport: panByPosition(anchor, screenPosition)
    Viewport-->>State: corrected longitude and latitude
  else Pointer is far off globe
    Controller->>State: zoom around projected center
    State->>Viewport: panByPosition(center, centerPixel)
    Viewport-->>State: unchanged longitude and latitude
  end
Loading

Reviews (6): Last reviewed commit: "fix(core): center globe zoom outside vis..." | Re-trigger Greptile

Comment thread modules/core/src/controllers/controller.ts
Comment thread modules/core/src/viewports/globe-viewport.ts
# Conflicts:
#	test/modules/core/controllers/controllers.spec.ts
@charlieforward9 charlieforward9 changed the title feat(core): GlobeView pointer-anchored zoom (wheel + transitions) feat(core): support pointer-anchored zoom in GlobeView Aug 24, 2026
@charlieforward9
charlieforward9 changed the base branch from master to codex/shared-controller-zoom-anchor August 24, 2026 22:55
@charlieforward9

Copy link
Copy Markdown
Member Author

@ibgreen-openai @chrisgervang I made the physical split you asked for.

The stacked #10385 diff is now 8 files. Fresh post-stack focused tests are 90/90; the unchanged source tree also passed build, lint, test-website, and live Globe pointer/center plus Map pointer verification.

@charlieforward9

Copy link
Copy Markdown
Member Author

I stress-tested this against the exact stacked head today and found one real Globe edge case before asking for review: an off-globe pixel could still be unprojected into a phantom anchor once the target zoom made the sphere larger.

Fixed in 8d771b0cdb. The boundary stays the one we agreed on:

The regression test fails on the previous head with a 1.25738° longitude shift and passes here. I also exercised interior/limb/off-globe pointers, asymmetric padding, antimeridian wrapping, latitude 84° at zoom 7, pinch, double-click, enabled double-click-drag, 100 alternating wheel events, and Globe → Map → Globe switching.

Local gates are green: 92/92 focused tests, yarn, yarn build, yarn lint, and 15/15 pre-commit checks. Greptile is green on the new SHA. @ibgreen-openai @chrisgervang @felixpalmer this should now be the small Globe-specific layer on top of the shared controller PR that you asked for.

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.

5 participants