CesiumJS camera and navigation controls (3D Mouse Keyboard Camera Controls)

**Note** from Bead: 3D Mouse Keyboard Camera Controls · [canonical source](https://redfish.acequia.io/guerin/.agents/b28561b2-f9e6-4a2a-88f3-edb06fb262e9/2026-06-17/notes/02-cesiumjs-camera.md) · session 2026-06-17 · discussion: Talk: 3D Mouse Keyboard Camera Controls

Cesium navigation splits between two cooperating subsystems: 1. **`ScreenSpaceCameraController`** (`scene.screenSpaceCameraController`): translates raw mouse / touch input into camera motion. This is the default navigation the user feels. 2. **`Camera`** (`viewer.scene.camera` / `viewer.camera`): the programmatic API; position / orientation state plus `flyTo`, `lookAt`, `setView`, `flyToBoundingSphere`, and incremental `move*` / `look*` / `rotate*` / `twist*` methods. There is **no built-in keyboard navigation** (no WASD). The well-known keyboard flight comes from the Camera Tutorial Sandcastle, wired manually (see section 5).

## 1. The default ScreenSpaceCameraController The controller maps `CameraEventType` gestures (optionally combined with a `KeyboardEventModifier`) to one of five logical actions: **rotate, translate, zoom, tilt, look**. Which action a gesture produces depends on the active `Scene.mode` (`SCENE3D`, `SCENE2D`, `COLUMBUS_VIEW`). - `CameraEventType` values: `LEFT_DRAG`, `RIGHT_DRAG`, `MIDDLE_DRAG`, `WHEEL`, `PINCH`. - `KeyboardEventModifier` values: `CTRL`, `SHIFT`, `ALT`. ### Interaction metaphor by scene mode - **3D globe (`SCENE3D`):** the metaphor is **orbiting an anchored globe**. Left-drag rotates the globe under a fixed-feeling camera (it grabs the point under the cursor and drags it). Right-drag / wheel zooms toward the cursor point. Middle-drag tilts (pitch toward the horizon). There is no separate "translate / pan" action in 3D; panning the surface is the rotate action because the camera orbits the globe center. - **2D (`SCENE2D`):** flat-map metaphor. Left-drag translates (pans) the map; no globe to orbit and no tilt (middle-drag instead twists / rotates the map about the screen). Zoom still works. - **Columbus / 2.5D (`COLUMBUS_VIEW`):** a flattened map you can still tilt into perspective. Supports both translate (pan across the plane) and tilt / look, so its bindings blend 2D and 3D. ### Default mouse bindings | Action | Default gesture(s) | Active in modes | |---|---|---| | Rotate / orbit (`rotateEventTypes`) | `LEFT_DRAG` | 3D, Columbus | | Translate / pan (`translateEventTypes`) | `LEFT_DRAG` | 2D, Columbus | | Zoom (`zoomEventTypes`) | `RIGHT_DRAG`, `WHEEL`, `PINCH` | All | | Tilt (`tiltEventTypes`) | `MIDDLE_DRAG`, `PINCH`, `CTRL+LEFT_DRAG`, `CTRL+RIGHT_DRAG` | 3D, Columbus (in 2D middle-drag = twist) | | Look / free-look (`lookEventTypes`) | `SHIFT+LEFT_DRAG` | 3D, Columbus | Notes: rotate and translate both default to `LEFT_DRAG` but apply in disjoint modes, so left-drag does the right thing automatically as you change `Scene.mode`. Zoom in 3D moves along the vector to the picked point under the mouse (zoom-to-cursor), not just screen center. These defaults give the familiar Google-Earth feel: left-drag spins the globe, right-drag / wheel zooms, middle-drag tilts.

## 2. Configurable enable flags All default to **true**; each is scoped to certain scene modes; `false` locks that degree of freedom. | Property | Effect when true | Applicable modes | |---|---|---| | `enableRotate` | rotate / orbit the globe | 3D (and 2D map rotate) | | `enableTranslate` | pan the map | 2D, Columbus | | `enableZoom` | zoom in / out | All | | `enableTilt` | tilt | 3D, Columbus | | `enableLook` | free-look (rotate view in place) | 3D, Columbus | ### Re-binding the event types Each action has a `*EventTypes` property (`rotateEventTypes`, `translateEventTypes`, `zoomEventTypes`, `tiltEventTypes`, `lookEventTypes`) accepting a single `CameraEventType`, an object `{ eventType, modifier }`, or an array mixing both. ```javascript const c = viewer.scene.screenSpaceCameraController; c.translateEventTypes = Cesium.CameraEventType.RIGHT_DRAG; // pan on RMB c.zoomEventTypes = [ Cesium.CameraEventType.RIGHT_DRAG, Cesium.CameraEventType.WHEEL, Cesium.CameraEventType.PINCH, { eventType: Cesium.CameraEventType.WHEEL, modifier: Cesium.KeyboardEventModifier.ALT }, ]; ``` Documented defaults: `tiltEventTypes` = `[ MIDDLE_DRAG, PINCH, {LEFT_DRAG, CTRL}, {RIGHT_DRAG, CTRL} ]`; `lookEventTypes` = `{ LEFT_DRAG, SHIFT }`. Other tunables on the controller: `minimumZoomDistance`, `maximumZoomDistance`, `inertiaSpin`, `inertiaTranslate`, `inertiaZoom`, `enableCollisionDetection`, `minimumPickingTerrainHeight`.

## 3. Programmatic camera methods ### Animated flights - **`camera.flyTo(options)`:** smooth animated move. `destination` (a `Cartesian3` or a `Rectangle`), `orientation` (`{heading, pitch, roll}` radians, or `{direction, up}` vectors), `duration` (auto-estimated if omitted), `complete` / `cancel` callbacks (chain flights via `complete`), `easingFunction`, `maximumHeight`, `pitchAdjustHeight`, `flyOverLongitude`. - **`camera.flyToBoundingSphere(sphere, options)`:** animates so the view contains a `BoundingSphere`; offset is a `HeadingPitchRange` in the target's east-north-up frame. "Frame this object / dataset." Does not lock the camera afterward. - **`camera.flyHome(duration)`:** flies to `Camera.DEFAULT_VIEW_RECTANGLE`. ### Instant positioning - **`camera.setView(options)`:** immediate set of `destination` + `orientation` (same shape as `flyTo`). - **`camera.lookAt(target, offset)`:** point at a world `Cartesian3`; `offset` is a `HeadingPitchRange` or `Cartesian3` in the target's ENU frame. **Important: `lookAt` locks the camera into the target's reference frame**; subsequent input orbits that frame until you release with `camera.lookAtTransform(Cesium.Matrix4.IDENTITY)`. This is the key difference from `flyToBoundingSphere`, which leaves the camera free. - **`camera.lookAtTransform(transform, offset)`:** like `lookAt` but you supply the reference-frame `Matrix4` (e.g. from `Cesium.Transforms.eastNorthUpToFixedFrame(center)`). `Matrix4.IDENTITY` releases the lock. ### Incremental motion (building blocks for custom controls) - Translate: `move(dir, amt)`, `moveForward/Backward/Up/Down/Left/Right(amount)`, meters along basis vectors. - Orbit (about reference-frame center, preserving distance): `rotate(axis, angle)`, `rotateUp/Down/Left/Right(angle)`. - Free-look (rotate view in place): `look(axis, angle)`, `lookUp/Down/Left/Right(amount)`. - Roll: `twistLeft/Right(amount)`. - Zoom: `zoomIn/zoomOut(amount)`. - State: `position`, computed `heading` / `pitch` / `roll` (radians), basis `direction` / `up` / `right`.

## 4. Globe-orbit vs fly vs free-look - **Globe-orbit (default):** camera tethered to the globe; left-drag rotates the planet about its center beneath a grabbed surface point; tilt pivots around the picked terrain point. The `rotate` / `tilt` action set; programmatically `rotate*` or a hard-locked `lookAt` / `lookAtTransform`. - **Free-look ("look"):** `SHIFT+LEFT_DRAG`. The camera stays put and rotates its view direction (first-person look-around), the `look*` family. The free head-turn, in contrast to anchored orbit. - **Fly:** `flyTo` / `flyToBoundingSphere` are smooth interpolated transitions between viewpoints (cinematic), not an ongoing interaction mode. After a fly you are back in normal globe-orbit interaction (unless you called `lookAt` / `lookAtTransform`, which holds the frame). So the three are orthogonal: orbit = anchored to globe / target frame; look = rotate-in-place free view; fly = animated transport between viewpoints.

## 5. Keyboard navigation: none by default; the manual WASD pattern Cesium ships no keyboard navigation. The canonical way to add it is the Camera Tutorial Sandcastle's "Fly with the keyboard" section, which: 1. Disables the default mouse handlers so they do not fight the keyboard: ```javascript const c = scene.screenSpaceCameraController; c.enableRotate = c.enableTranslate = c.enableZoom = c.enableTilt = c.enableLook = false; ``` 2. Maps keys to flag names (master branch keys off `e.keyCode`; modern main uses `e.code` strings like `"KeyW"`): **W -> moveForward, S -> moveBackward, Q -> moveUp, E -> moveDown, D -> moveRight, A -> moveLeft**. 3. Sets `flags[name] = true` on keydown, `false` on keyup. 4. On every `clock.onTick`, computes a `moveRate` proportional to the camera's height above the ellipsoid (fast when high, fine when low) and calls the matching `camera.moveForward(moveRate)` etc. for each active flag. ```javascript clock.onTick.addEventListener(function () { const cameraHeight = ellipsoid.cartesianToCartographic(camera.position).height; const moveRate = cameraHeight / 100.0; if (flags.moveForward) camera.moveForward(moveRate); if (flags.moveBackward) camera.moveBackward(moveRate); if (flags.moveUp) camera.moveUp(moveRate); if (flags.moveDown) camera.moveDown(moveRate); if (flags.moveLeft) camera.moveLeft(moveRate); if (flags.moveRight) camera.moveRight(moveRate); }); ``` The common community extension adds look / roll keys by also calling `camera.lookUp/Down/Left/Right` and `camera.twistLeft/Right` from the same tick loop. The stock tutorial covers only the six translate keys; look direction is left to the mouse.

## 6. Navigation help widget `NavigationHelpButton` is the question-mark help button in the `Viewer` toolbar (on by default; suppress with `new Cesium.Viewer(el, { navigationHelpButton: false })`). It opens an overlay documenting the default mouse and touch bindings on separate tabs: Pan view = left-click drag; Zoom view = right-click drag / wheel / pinch; Rotate-tilt view = middle-click drag / CTRL+left-or-right drag / two-finger drag. A popular third-party `cesium-navigation` plugin adds a compass ring + zoom buttons + distance scale (not core).

## Quick-reference: default bindings by scene mode | Gesture | 3D (SCENE3D) | 2D (SCENE2D) | Columbus (COLUMBUS_VIEW) | |---|---|---|---| | `LEFT_DRAG` | Rotate / orbit globe | Pan (translate) | Pan (translate) | | `RIGHT_DRAG` | Zoom | Zoom | Zoom | | `WHEEL` | Zoom | Zoom | Zoom | | `MIDDLE_DRAG` | Tilt | Twist (map rotate) | Tilt | | `CTRL+LEFT_DRAG` / `CTRL+RIGHT_DRAG` | Tilt | -- | Tilt | | `SHIFT+LEFT_DRAG` | Free-look | -- | Free-look | | `PINCH` (touch) | Zoom + tilt | Zoom + twist | Zoom + tilt | | Keyboard WASD/QE | **None by default** (manual via tutorial) | -- | -- | ### Key takeaways - Defaults are globe-orbit-centric and Google-Earth-like: left-drag orbits, right-drag / wheel zooms toward cursor, middle-drag (or CTRL+drag) tilts, SHIFT+left-drag free-looks. - The same `LEFT_DRAG` is rotate in 3D / Columbus but translate in 2D, automatic per `Scene.mode`. - Five enable flags + five `*EventTypes` properties make every binding remappable. - `lookAt` / `lookAtTransform` lock the camera to a frame (release with `Matrix4.IDENTITY`); `flyTo` / `flyToBoundingSphere` / `setView` do not lock.

## Sources - ScreenSpaceCameraController https://cesium.com/learn/cesiumjs/ref-doc/ScreenSpaceCameraController.html - Camera https://cesium.com/learn/cesiumjs/ref-doc/Camera.html - "Control the Camera" tutorial https://cesium.com/learn/cesiumjs-learn/cesiumjs-camera/ - Camera Tutorial Sandcastle https://sandcastle.cesium.com/index.html?id=camera-tutorial (source https://github.com/CesiumGS/cesium/blob/master/Apps/Sandcastle/gallery/Camera%20Tutorial.html ) - NavigationHelpButton https://cesium.com/learn/cesiumjs/ref-doc/NavigationHelpButton.html - Camera system overview https://deepwiki.com/CesiumGS/cesium/2.2-camera-system - Third-party cesium-navigation https://github.com/alberto-acevedo/cesium-navigation