Tutorial #11: OrbitCamera
In the last tutorial we created a camera that can be moved and rotated freely in a scene. This type of camera can be very useful in some situations (where you want the user to have that kind of freedom\control over what the camera does), but is rather clumsy when it comes to trying to explore one object in detail. Another type of camera, called an orbit camera, is often used for that kind of capability. While an orbit camera can still travel (within certain limits) around the scene it will always be looking at a particular point, orbiting around it. Think of the point you are orbiting as the center of a sphere and the camera being confined to the surface of that sphere, always looking at the center.
The HTML document
Inside the mydemo folder, start by creating a simple html page.
<html>
<head>
<title>Canvas3D tutorial #11: OrbitCamera</title>
<script type="application/javascript" src="../canvas3dapi/c3dapi.js" ></script>
<script type="application/javascript" src="tutorial11.js"></script>
</head>
<body>
<!-- Add a canvas element to the page. It is scripted by using its id -->
<canvas id="tutorial" style="border: 2px solid blue" width="500" height="500"></canvas>
</body>
</html>
The JavaScript Code
We’ll start with a scene very similar to the one in tutorial 9 and 10, except this time we’ll use an OrbitCamera that moves around the duck based on how the user drags the mouse. Setting up an OrbitCamera is slightly different from a FreeCamera. Both start at [0,0,0] looking directly down the Z-axis by default, but instead of directly setting the position and lookatPoint like we did with FreeCamera, we set the point to orbit and how far away to orbit it using setOrbitPoint and setDistance respectively. Prior to calling setDistance, setFarthestDistance and setClosestDistance must be called to define the acceptable range in which the camera can operate. For this example they are set fairly wide, but we do not modify the distance after initially setting it (that come’s later in this tutorial). After the camera is initialized you add it to the scene the same way you would a FreeCamera.
The next significant change is that this example uses mouse events instead of keyboard events. The setMouseCallback function accepts four arguments (we’ve only used three in this example) which are the functions to call when the user presses a mouse button, releases a mouse button, moves the mouse and scrolls the mouse-wheel (the first part of this tutorial does not use the mouse wheel). Here mouseUp only sets a flag recording that the button is not currently pressed. Even if the user moves the mouse they are not currently ‘dragging’. MouseDown sets that flag to true. They are holding the button down, so if they move the mouse then they are dragging. It also records the current X and Y coordinates of the mouse, translated into coordinates in the scene by xevtpos and yevtpos. Finally, mouseMove checks if the user is currently dragging the mouse (as determined by mouseUp and mouseDown). If the mouse is being dragged it determines how much the mouse has moved on the X and Y axes and moves the camera around the orbit point based on that distance and the variable SENSITIVITY.
//Tutorial #11
c3dl.addMainCallBack(canvasMain, "tutorial");
c3dl.addModel("duck.dae");
var isDragging = false; //tracks or not the user is currently dragging the mouse
var rotationStartCoords = [0,0]; //The mouse coordinates at the beginning of a rotation
var SENSITIVITY = 0.7;
//Called when the user releases the left mouse button.
//Records that the user is no longer dragging the mouse
function mouseUp(evt)
{
if(evt.which == 1)
{
isDragging = false;
}
}
//Called when the user presses the left mouse button.
//Records that the user may start to drag the mouse, along with the current X & Y
// coordinates of the mouse.
function mouseDown(evt)
{
if(evt.which == 1)
{
isDragging = true;
rotationStartCoords[0] = xevtpos(evt);
rotationStartCoords[1] = yevtpos(evt);
}
}
//Called when the mouse moves
//This function will only do anything when the user is currently holding
// the left mouse button. It will determine how far the cursor has moved
// since the last update and will pitch and yaw the camera based on that
// amount and the sensitivity variable.
function mouseMove(evt)
{
if(isDragging == true)
{
var cam = scn.getCamera();
var x = xevtpos(evt);
var y = yevtpos(evt);
// how much was the cursor moved compared to last time
// this function was called?
var deltaX = x - rotationStartCoords[0];
var deltaY = y - rotationStartCoords[1];
cam.yaw(-deltaX * SENSITIVITY);
cam.pitch(deltaY * SENSITIVITY);
// now that the camera was updated, reset where the
// rotation will start for the next time this function is
// called.
rotationStartCoords = [x,y];
}
}
//Calculates the current X coordinate of the mouse in the client window
function xevtpos(evt)
{
return 2 * (evt.clientX / evt.target.width) - 1;
}
//Calculates the current Y coordinate of the mouse in the client window
function yevtpos(evt)
{
return 2 * (evt.clientY / evt.target.height) - 1;
}
function canvasMain(canvasName){
scn = new c3dl.Scene();
scn.setCanvasTag(canvasName);
renderer = new c3dl.WebGL();
renderer.createRenderer(this);
scn.setRenderer(renderer);
scn.init(canvasName);
if(renderer.isReady() )
{
var duck = new c3dl.Collada();
duck.init("duck.dae");
duck.setTexture("duck.png");
duck.yaw(Math.PI * 0.5); //rotate the duck to look up the Z axis
scn.addObjectToScene(duck);
var cam = new c3dl.OrbitCamera();
cam.setFarthestDistance(1000);
cam.setClosestDistance(60);
cam.setOrbitPoint([0.0, 0.0, 0.0]);
cam.setDistance(400);
scn.setCamera(cam);
scn.setMouseCallback(mouseUp,mouseDown, mouseMove);
scn.startScene();
}
}
The second part of this tutorial will deal with the distance to the orbit point and changing the orbit point. Recall that an orbit camera can be described as traveling on the surface of an invisible sphere. SetDistance determines the size of that sphere. When initializing the camera earlier, we called setClosestDistance and setFarthestDistance. These, as their names imply, set the minimum and maximum distance between the camera and the point it orbits. Both of these default to zero and should be set to a reasonable value for your scene before you set the actual distance of the camera. Note that if you try to set the camera to a distance outside this range, it will not move. Likewise if you move one of the boundaries so that the camera would be outside it, the camera will be shifted so that it is inside that boundary.
The setDistance function allows you to set an absolute distance, but there are also functions that allow you to make small changes. GoFarther and goCloser will move the camera towards or away from the orbit point by the amount that is passed to them. In the next stage of this tutorial this will be linked to the mouse’s scroll-wheel so that the user can make small, incremental shifts towards or away from the point being orbitted.
You can also use setOrbitPoint to change what point the camera is orbiting, either because the thing you wanted to orbit has moved, or because you want to orbit something else. We’ll add a second object (a teapot, using the same model and texture from tutorial 8 ) to the scene and use the space-bar to swap the camera’s orbit point between the two (using keyboard callbacks). One thing you may notice about setOrbitPoint is that it attempts to maintain the same angle to the new orbit point that it had to the old one. This means the camera may jump across the scene, but it will stay looking in the same direction.
// Tutorial 11
c3dl.addMainCallBack(canvasMain, "tutorial");
c3dl.addModel("duck.dae");
c3dl.addModel("teapot.dae");
var isDragging = false; //tracks whether or not the user is currently dragging the mouse
var rotationStartCoords = [0,0]; //The mouse coordinates at the beginning of a rotation
var SENSITIVITY = 0.7;
ZOOM_SENSITIVITY = 3;
var duck;
var teapot;
//Called when the user releases the left mouse button.
//Records that the user is no longer dragging the mouse
function mouseUp(evt)
{
if(evt.which == 1)
{
isDragging = false;
}
}
//Called when the user presses the left mouse button.
//Records that the user may start to drag the mouse, along with the current X & Y
// coordinates of the mouse.
function mouseDown(evt)
{
if(evt.which == 1)
{
isDragging = true;
rotationStartCoords[0] = xevtpos(evt);
rotationStartCoords[1] = yevtpos(evt);
}
}
//Called when the mouse moves
//This function will only do anything when the user is currently holding
// the left mouse button. It will determine how far the cursor has moved
// since the last update and will pitch and yaw the camera based on that
// amount and the sensitivity variable.
function mouseMove(evt)
{
if(isDragging == true)
{
var cam = scn.getCamera();
var x = xevtpos(evt);
var y = yevtpos(evt);
// how much was the cursor moved compared to last time
// this function was called?
var deltaX = x - rotationStartCoords[0];
var deltaY = y - rotationStartCoords[1];
cam.yaw(-deltaX * SENSITIVITY);
cam.pitch(deltaY * SENSITIVITY);
// now that the camera was updated, reset where the
// rotation will start for the next time this function is
// called.
rotationStartCoords = [x,y];
}
}
//This function is called when the user moves the scroll-wheel on the mouse
//Depending on the direction of the action, the camera will either get
// closer to or farther from the orbit point.
function mouseScroll(evt) {
var cam = scn.getCamera();
if(-evt.detail*ZOOM_SENSITIVITY < 0)
{
cam.goFarther(-1 * -evt.detail*ZOOM_SENSITIVITY);
}
// towards screen
else
{
cam.goCloser(-evt.detail*ZOOM_SENSITIVITY);
}
}
//This function is called whenever a key is released.
//If the key released was the space-bar, the camera will switch
// which object it is orbiting.
function swapOrbitPoint(evt) {
if(evt.keyCode == 32) {
var cam = scn.getCamera();
//compare the camera's orbit point with the location of the duck
if(c3dl.isVectorEqual(cam.getOrbitPoint(),duck.getPosition())) {
cam.setOrbitPoint(teapot.getPosition());
}
else{
cam.setOrbitPoint(duck.getPosition());
}
}
}
//Calculates the current X coordinate of the mouse in the client window
function xevtpos(evt)
{
return 2 * (evt.clientX / evt.target.width) - 1;
}
//Calculates the current Y coordinate of the mouse in the client window
function yevtpos(evt)
{
return 2 * (evt.clientY / evt.target.height) - 1;
}
function canvasMain(canvasName){
scn = new c3dl.Scene();
scn.setCanvasTag(canvasName);
renderer = new c3dl.WebGL();
renderer.createRenderer(this);
scn.setRenderer(renderer);
scn.init(canvasName);
if(renderer.isReady() )
{
duck = new c3dl.Collada();
duck.init("duck.dae");
duck.setTexture("duck.png");
duck.yaw(Math.PI * 0.5); //rotate the duck to look up the Z axis
scn.addObjectToScene(duck);
teapot = new c3dl.Collada();
teapot.init("teapot.dae");
teapot.setTexture("teapot.png");
teapot.setPosition([-600,0,0]);
teapot.scale([5,5,5]);
scn.addObjectToScene(teapot);
var cam = new c3dl.OrbitCamera();
cam.setFarthestDistance(1000);
cam.setClosestDistance(20);
cam.setOrbitPoint(duck.getPosition());
cam.setDistance(400);
scn.setCamera(cam);
scn.setMouseCallback(mouseUp,mouseDown, mouseMove,mouseScroll);
scn.setKeyboardCallback(swapOrbitPoint);
scn.startScene();
}
}
Two more functions (roll and rotateOnAxis) were recently added to OrbitCamera and will be included in the next release of the library. This tutorial will be updated to feature them when they are released.
