This post shows how to implement orbital camera controls with only a few lines of code and without a plugin. This enables the user to move the camera with the mouse along the surface of a sphere, whereby the camera always points to the center. At the center of the sphere there is an object that can be intuitively viewed from every angle. By increasing or decreasing the radius, the camera can zoom in or out.
There already exists an arguably much better and more complete plugin for this, but I wanted to understand how it works and make my own minimalistic version of it.
Demo
The following Earth demo uses textures from this great website. The textures are free to use, but there are some copyright limitations. Basically you are not allowed to sell or redistribute them. The higher resolution textures will also cost a small amount of money.
Browser Mouse Events
The following boring piece of code simply listens for browser mouse events and translates them into a little bit higher level events, namely drag
, zoomIn
and zoomOut
.
var Controls = (function(Controls) {
"use strict";
// Check for double inclusion
if (Controls.addMouseHandler)
return Controls;
Controls.addMouseHandler = function (domObject, drag, zoomIn, zoomOut) {
var startDragX = null,
startDragY = null;
function mouseWheelHandler(e) {
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
if (delta < 0 && zoomOut) {
zoomOut(delta);
} else if (zoomIn) {
zoomIn(delta);
}
e.preventDefault();
}
function mouseDownHandler(e) {
startDragX = e.clientX;
startDragY = e.clientY;
e.preventDefault();
}
function mouseMoveHandler(e) {
if (startDragX === null || startDragY === null)
return;
if (drag)
drag(e.clientX - startDragX, e.clientY - startDragY);
startDragX = e.clientX;
startDragY = e.clientY;
e.preventDefault();
}
function mouseUpHandler(e) {
mouseMoveHandler.call(this, e);
startDragX = null;
startDragY = null;
e.preventDefault();
}
domObject.addEventListener("mousewheel", mouseWheelHandler);
domObject.addEventListener("DOMMouseScroll", mouseWheelHandler);
domObject.addEventListener("mousedown", mouseDownHandler);
domObject.addEventListener("mousemove", mouseMoveHandler);
domObject.addEventListener("mouseup", mouseUpHandler);
};
return Controls;
}(Controls || {}));
Initialize the Renderer
The next piece of code simply initializes the most basic components of a THREE.js WebGLRenderer. It is very important, to set the camera.up
vector, because the method camera.lookAt()
uses it. The variable center
contains a vector to the center of the sphere the camera moves on. In this case it is equal to the origin of the coordinate system, but it can be any point.
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(500, 500);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var center = new THREE.Vector3();
var camera = new THREE.PerspectiveCamera(45, 1, 0.01, 300);
camera.up = new THREE.Vector3(0, 0, 1);
camera.position.x = 5;
camera.lookAt(center);
Implement Dragging
This is the crucial part. By dragging the mouse on the canvas element, the camera moves on the surface of a sphere centered around the point center
. It works by simply transforming the Cartesian coordinates (x, y, z) of the camera into Spherical coordinates. The reason for this is, that certain operations are easier to do in Spherical than in Cartesian coordinates and vice versa.
Spherical coordinates can describe any point in three-dimensional space by using the radius \(r\) and the two angles \(\theta\) theta and \(\phi\) phi. By keeping the radius constant and only changing \(\theta\) and \(\phi\) the point moves along the surface of a sphere, which is exactly what we need.
function drag(deltaX, deltaY) {
var radPerPixel = (Math.PI / 450),
deltaPhi = radPerPixel * deltaX,
deltaTheta = radPerPixel * deltaY,
pos = camera.position.sub(center),
radius = pos.length(),
theta = Math.acos(pos.z / radius),
phi = Math.atan2(pos.y, pos.x);
// Subtract deltaTheta and deltaPhi
theta = Math.min(Math.max(theta - deltaTheta, 0), Math.PI);
phi -= deltaPhi;
// Turn back into Cartesian coordinates
pos.x = radius * Math.sin(theta) * Math.cos(phi);
pos.y = radius * Math.sin(theta) * Math.sin(phi);
pos.z = radius * Math.cos(theta);
camera.position.add(center);
camera.lookAt(center);
redraw();
}
A description of the most important variables used in this function:
- deltaX
- The number of pixels the mouse was dragged in the X direction since the last event.
- deltaY
- The number of pixels the mouse was dragged in the Y direction since the last event.
- radPerPixel
- As the name suggests it determines the speed of the rotation. It relates the pixels on the canvas element to the angle the camera should move.
- pos
- This is the vector from the
center
to the camera position. Ifcenter
is at the origin then this is equal to the camera position. - theta
- The angle \(\theta\). Wikipedia contains a section about the conversion of Cartesian to Spherical coordinates. The following formula is taken directly from there: $$\theta = arccos(\frac{z}{r})$$
- phi
- The angle \(\phi\). The following formula is also taken directly from Wikipedia: $$\phi = arctan(\frac{y}{z})$$
The appropriate formulas for turning Spherical back into Cartesian coordinates can also be found on Wikipedia:
$$x = r\:sin\theta\:cos\phi$$ $$y = r\:sin\theta\:sin\phi$$ $$z = r\:cos\theta$$It is almost too easy. That's because the function call camera.lookAt(center);
near the end does most of the real work for us. As the name suggests it changes the viewing angle of the camera, so that it looks at the point center
.
Implement Zooming
The implementation of zooming is pretty trivial.
function zoomIn() {
camera.position.sub(center).multiplyScalar(0.9).add(center);
redraw();
}
function zoomOut() {
camera.position.sub(center).multiplyScalar(1.1).add(center);
redraw();
}
Everything put together
References
- OrbitControls.js Plugin - https://github.com/mrdoob/three.js/blob/master/examples/js/controls/OrbitControls.js
- Textures for Planet Earth - http://planetpixelemporium.com/earth.html
- Cartesian coordinate system - https://en.wikipedia.org/wiki/Cartesian_coordinate_system
- Spherical coordinate system - https://en.wikipedia.org/wiki/Spherical_coordinate_system