close
Skip to content

feat(core): GlobeView pitch, bearing, and cursor-anchored zoom#10249

Open
charlieforward9 wants to merge 6 commits intomasterfrom
cr/feat/globe-camera
Open

feat(core): GlobeView pitch, bearing, and cursor-anchored zoom#10249
charlieforward9 wants to merge 6 commits intomasterfrom
cr/feat/globe-camera

Conversation

@charlieforward9
Copy link
Copy Markdown
Collaborator

@charlieforward9 charlieforward9 commented Apr 19, 2026

Summary

Brings GlobeView to feature parity with MapView for camera interaction. Three related changes:

  • Pitch and bearing: GlobeView now accepts pitch, bearing, minPitch, maxPitch view-state props. GlobeViewport composes them into a lookAt-based view matrix that keeps the target lng/lat fixed at screen center through tilt and rotation, matching the Google Maps/Earth interaction model. GlobeController wires up dragRotate and touchRotate and enforces the pitch constraints.
  • Cursor-anchored zoom: scroll-wheel zoom targets the mouse cursor when the cursor is over the globe, consistent with MapView. When the cursor is outside the globe, zoom falls back to center zoom instead of anchoring to an inferred ray-miss point.
  • Pan behavior: on-globe drags keep the grabbed lng/lat under the cursor. Drags that start outside the globe preserve the previous delta-based spin behavior, avoiding unintuitive ray-miss anchoring at low zoom.

Includes a ray/sphere-miss fallback in GlobeViewport#unproject for callers that need a surface point, while controller pan/zoom anchoring only uses true globe intersections.

Why

Pitch + bearing + zoom-toward-cursor are the camera affordances users already expect from MapView. Without them, GlobeView-based apps can't offer the same 3D tilt/orbit UX that MapView has supported for years, which makes it hard to build a projection-toggling app where the camera feels the same on both projections.

Test plan

  • New unit tests in test/modules/core/viewports/globe-viewport.spec.ts for pitch/bearing viewport math and globe hit detection
  • New unit tests in test/modules/core/controllers/view-states.spec.ts for pitch/bearing transitions and off-globe delta-spin pan behavior
  • test/apps/globe/app.js updated with sliders to exercise the new view-state props interactively
  • Dev-server smoke test confirmed off-globe drag changes longitude without ray-miss anchoring

Companion PRs

This PR is standalone. Two follow-up PRs build on it to bring terrain rendering to GlobeView:

Add camera tilt (pitch) and rotation (bearing) to GlobeView, matching the
Google Maps/Earth interaction model. Uses a lookAt-based view matrix that
composes pitch and bearing rotations while keeping the target lng/lat at
screen center.

Scroll-wheel zoom now targets the mouse cursor position instead of screen
center (matching MapView). Pan reuses MapState's grabbed-lng/lat logic so
the point under the cursor stays under the cursor through the drag —
previously GlobeState's delta-pan produced wrong on-screen speed and
yanked the center at zoom > 12 where WebMercatorViewport takes over
rendering. Unproject for rays that miss the globe surface returns a
plausible fallback rather than null.

Changes:
- GlobeViewport: lookAt view matrix with pitch/bearing, cursor-anchored
  zoom, ray/sphere miss fallback
- GlobeController: dragRotate/touchRotate, pitch/bearing constraints,
  inherits pan from MapState
- GlobeView: pitch/bearing/minPitch/maxPitch view state props
- Tests for pitch/bearing transitions and viewport math
@charlieforward9
Copy link
Copy Markdown
Collaborator Author

The test-node failure is the test/modules/react/deckgl.spec.ts > DeckGL#uncontrolled view state flake introduced in #10240 — master itself fails the same way (run 24594004664). Not caused by this PR. test-python and test-website pass.

Happy to rebase once #10240's follow-up fix lands.

Comment thread modules/core/src/viewports/globe-viewport.ts
Comment thread modules/core/src/viewports/globe-viewport.ts
coord = vec3.lerp([], coord0, coord1, t);
const discriminant = lt * lt - dSqr;

if (discriminant < 0) {
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.

This is a neat idea, but I'm not sure it leads to intuitive behavior, see other comment about keeping old behavior

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.

Maybe an opt-in?

@charlieforward9
Copy link
Copy Markdown
Collaborator Author

charlieforward9 commented Apr 21, 2026

@felixpalmer thank you for the review - I agree with your reinstatement and pan behavior

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 23, 2026

Coverage Status

Coverage is 83.747%cr/feat/globe-camera into master. No base build found for master.

startPanOnGlobe?: boolean;
};

function unprojectOnGlobe(
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.

maybe create a globe-projection.ts or globe-projection-utils.ts?

class GlobeProjection {
  project()
  unproject()
  getViewMatrix()
   ..
}

or would this be part of GlobeViewport?

coord = vec3.lerp([], coord0, coord1, t);
const discriminant = lt * lt - dSqr;

if (discriminant < 0) {
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.

Maybe an opt-in?

const GlobeViewState = new GlobeController({} as any).ControllerState;

// Bearing should be normalized to [-180, 180]
let viewState = new GlobeViewState({
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.

IIRC, view state is normally a pure object, not a class?

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.

[Bug] Globe example not displaying as expected Tracker: GlobeView graduation

4 participants