ArcGIS API for JavaScript

3D camera intro using the ArcGIS API for JavaScript

When working in 3D, we need to define a point in the 3D space from where we view the scene. This point is also called a camera. In this blog post we’ll explain how to define a camera, how to retrieve it and how to animate the camera to another point in the scene. We’ll also briefly look into creating more complex animations, like rotating a globe or rotating the camera around an object in the scene.

How to define a camera?

The Camera object lives directly on the SceneView and has 3 main components:

This video explains these concepts visually:

 

Interactively position camera

Depending on what you want to focus on in your scene, finding that ideal point-of-view can involve some experimenting. Instead of manually tweaking the above components (position, tilt and heading) it is simpler to interactively place the camera in the scene.

You can do so by zooming zoom out zoom in, panning pan and rotating rotate the camera using the mouse, trackpad and keyboard. The ArcGIS API for JavaScript also supports additional input devices such as touchscreens, gamepads or the 3DConnexion SpaceMouse. Please refer to the SceneView documentation for specific key- and button mappings.

You can navigate in a scene using a gamepad

There are a number of hidden gimmicks that help you place the camera in the 3D space. For example, when you start to pan while the mouse pointer is on top of a facade or far away mountains, the camera will move horizontally. Vice versa when pointing at the flat ground, the camera will also move vertically.

Define camera programmatically

Once you’ve navigated to your favorite position in the scene, you usually want to have your application provide it as a preset to the user. This is where we will look into retrieving the values for the position, tilt and heading and reuse them to initialize a new camera object on the SceneView.

 

Get camera

In the spirit of printf() debugging, you can always print it to the plain camera object to the console using console.log(view.camera). But this will print it as a JavaScript object which is not very useful if you want to copy paste it in your code. This code snippet will print the camera in a nice format, no matter which coordinate system you use in the view:

(function() {
  const view = require("esri/views/View").views.getItemAt(0);
  const p = view.camera.position;

  if (p.spatialReference.isWebMercator || p.spatialReference.isWGS84) {
    console.log(`
{
  position: [
    ${p.longitude.toFixed(8)},
    ${p.latitude.toFixed(8)},
    ${p.z.toFixed(5)}
  ],
  heading: ${view.camera.heading.toFixed(2)},
  tilt: ${view.camera.tilt.toFixed(2)}
}`);
  }
  else {
    console.log(`
{
  position: {
    x: ${p.x.toFixed(5)},
    y: ${p.y.toFixed(5)},
    z: ${p.z.toFixed(3)},
    spatialReference: ${p.spatialReference.wkid}
  },
  heading: ${view.camera.heading.toFixed(2)},
  tilt: ${view.camera.tilt.toFixed(2)}
}`);
  }
})();

You can also use this code snippet with the Scene Viewer. Just copy paste it in the console and it will print out the camera position like this:

Set camera

Thanks to the autocasting capabilities of the JavaScript API, we can directly pass the above JSON object as the camera object to the SceneView.

const view = new SceneView({ ... });

view.camera = {
  position: [
    8.22216751,
    46.48873434,
    13032241.44725,
  ],
  heading: 0.00,
  tilt: 0.16,
};

You can do this again in your application code but also directly in the Scene Viewer through the developer console. This is helpful for example when creating slides that should have a specific camera position.

Using targets

The SceneView provides an additional way to change the camera through a method called goTo(). As an argument it accepts a Camera object as we have seen above. Unlike setting the camera directly on the SceneView, goTo() will create a smooth transition to the new view point.

But goTo() is powerful in that you can also pass it parameters about what you want to see in the scene. We can pass one or multiple targets and goTo() will figure out the new camera position that it needs to navigate to. Targets can be a Geometry (Point, Polyline, Polygon, Mesh, Extent) or a Graphic.
Sometimes we only want to change parts of a camera definition, like the tilt or the heading while preserving the camera zoom and center. In this case, we can again use the goTo() method and only pass the property that we want to change (view center or zoom, camera heading, tilt or position).

Camera animations

As we have seen previously goTo() uses animations to transition the camera to its new position. Animations are a nice way to show off your 3D content and invite users to further explore the scene themselves. Let’s have a look how we can use the JavaScript API to create some custom camera animations.

goTo animation options

When used without additional arguments goTo() will choose default animation properties that best suit the transition. For example, the further away the final position is, the longer the duration of the animation.

We can tweak the duration either relatively using speedFactor or providing an absolute duration in milliseconds, allowing us to speed up or slow down animations.

// Animate twice as fast
view.goTo(target, {
  speedFactor: 2,
});

// Animate for 30 seconds
view.goTo(target, {
  duration: 30000,
  maxDuration: 30000,
});

// Disable animations
view.toTo(target, { duration: 0 });

In addition goTo() will also accelerate the transition at the beginning and slow it down towards the end. In animation terminology this is called easing and goTo() supports a variety of different presets. To make sure the camera always transitions at the same speed, simply call view.goTo(target, { easing 'linear' }). Checkout this sample in the JavaScript API documentation that demonstrates different easing values.

Rotate a globe

If we have an app where data is depicted on a globe, it might be nice to spin the globe when the app starts. This way the user knows that they can interact with the globe. To do that we’ll just change the camera to a new camera with modified longitude which we update on every frame. When the user interacts with the globe we want to stop the animation, so we’ll check in the beginning whether view.interacting is true:

function rotate() {
  if (!view.interacting) {
    const camera = view.camera.clone();
    camera.position.longitude -= 0.2;
    view.goTo(camera, { animate: false });
    requestAnimationFrame(rotate);
  }
}

view.when(function () {
  watchUtils.whenFalseOnce(view, "updating", rotate);
});

You might notice that we’re setting animate to false. That is because we change the camera longitude position by a small amount and we want to change it without interpolating the positions in between. This creates an animation without any lags, as shown in the following CodePen when you click on the play button:

 

An example that makes use of this animation is the Globe of Extremes demo.

Rotate around a point

Similar to rotating the globe, the following CodePen will change the heading while keeping the center always at the same place. This is useful when highlighting a specific feature in your scene from various perspectives.

function rotate() {
  if (!view.interacting) {
    view.goTo({
        heading: view.camera.heading + 0.2,
        center: view.center
    }, {animate: false});
    requestAnimationFrame(rotate);
  }
}

 

We hope this blog post was useful whether you are new to 3D or in that it revealed some camera capabilities you were not aware of. Try changing the above CodePens using different animation properties and check out Raluca’s GitHub repository for other useful code snippets.

Please let us know if you are interested in more advanced animations and we’ll write up a follow up blog post!

Happy mapping 🙂

Raluca and Arno

About the authors

Raluca is a cartographer with a focus on 3D web cartography. She works as a Product Engineer for ArcGIS API for JavaScript #3D, making demos, writing documentation and testing.

Connect:

Developer Evangelist at the Esri R&D Center Zürich, creating 3D web apps using the ArcGIS API for JavaScript.

Connect:

Leave a Reply

Please Login to comment

Next Article

Something in the water: the mythology of Snow’s map of cholera

Read this article