This package contains the SceneNavigationController API, along with its constituents which may be used to create your own controller.

Overview

The SceneNavigationController lets you pan, rotate, and zoom in 3D. The Gizmos that visualize the current gesture can be customized via the optional gizmos option. If the gizmos option is omitted, defaul gestures are used (rotation: circles GLB, pan: arrows GLB, zoom: octahedron GLB at 40 px).

Use of this controller allows the following interactions:

  • Panning orthogonally to the camera’s forward direction is done with a left-mouse drag by default, or a one-finger drag on touch. The mouse button can be configured.

  • Rotating around an anchor under the mouse is done with a right-mouse drag by default, or a two-finger drag on touch. The mouse button can be configured.

  • Rotating around the camera eye with left + right mouse drags or the normal rotation controls with ctrl pressed, or with normal rotation controls if NavigationKeysMode.CAMERA_FORWARD is set.

  • Zooming in to and away from an anchor under your mouse with the scroll wheel or pinch gestures.

  • Zooming in on a clicked point, when both useZoomAnimations and allowZoomOnClick are enabled.

  • Move horizontally relative to the earth’s surface with the arrow or WASD keys (or corresponding keys if you don’t have a QWERTY keyboard) when using NavigationKeysMode.TANGENT_FORWARD, or relative to the camera’s forward and right vector with NavigationKeysMode.CAMERA_FORWARD.

  • Move vertically relative to the earth’s surface with the Q and E keys (or corresponding keys if you don’t have a QWERTY keyboard) when using NavigationKeysMode.TANGENT_FORWARD, or relative to the camera’s up direction with NavigationKeysMode.CAMERA_FORWARD.

The left/right mouse button roles for pan vs. rotation can be swapped via the swapPanRotateButtons option.

A Bounds object must be passed to create an instance of SceneNavigationController. These bounds define the main navigation area, so it is recommended to provide bounds that are large enough to encompass your entire 3D object, with some margin to "move around in."

By default, navigation is constrained by these bounds. This behavior can be disabled by setting constrainToBounds to false, allowing free camera movement outside the bounds.

For detailed API information, see the SceneNavigationController file in the source code.

Camera bounds state notifications

The controller exposes whether the camera is currently within the configured bounds and allows listening to changes of this state.

Use cameraInBounds to query the current state, or onCameraInBoundsChange to be notified when the camera transitions between in-bounds and out-of-bounds:

const handle = navigateController.onCameraInBoundsChange((inBounds) => {
  console.log("Is camera in bounds:", inBounds);
});

Limitations

The map’s camera must be an instance of type PerspectiveCamera.

Usage

This code shows a typical use of the tool:

// Retrieve a 3D layer from somewhere.
// const layer = ...

map.layerTree.addChild(layer);

// Specify custom gizmos for each navigation type.
const gizmos = {
   [NavigationType.ROTATION]: new NavigationGizmo("url/to/rotation-gizmo.glb"),
   [NavigationType.PAN]: new NavigationGizmo("url/to/pan-gizmo.glb"),
   [NavigationType.ZOOM]: new NavigationGizmo("url/to/zoom-gizmo.glb", { sizeInPixels: 40 })
};

// Create a controller with varying options.
const navigateController = new SceneNavigationController(layer.model.bounds, {
  gizmos, // note: if omitted, the default gizmo setup is applied
  navigationMode: NavigationKeysMode.CAMERA_FORWARD, // navigate along camera paths
  defaultSpeed: 8, // ~28km/h
  allowZoomOnClick: true, // clicking on a spot zooms in on to that location by a set fraction
  useZoomAnimations: false, // don't use smooth animations when zooming or out
  fasterMultiplier: 2, // go two times as fast when shift is pressed
  slowerMultiplier: 0.5, // go only half as fast when space is pressed
  swapPanRotateButtons: true, // rotate: left mouse button, pan: right mouse button
});

map.defaultController = new DefaultController({ navigateController });