Expanding your first scene with animations
If we want to animate the scene, the first thing that we need to do is find some way to re-render the scene at a specific interval. Before HTML5 and the related JavaScript APIs came along, the way to do this was using the setInterval(function,interval)
function. With setInterval
, we could specify a function that, for instance, would be called every 100 milliseconds. The problem with this function is that it doesn't take into account what is happening in the browser. If you were browsing another tab, this function would still be fired every couple of milliseconds. Besides that, setInterval
isn't synchronized with the redrawing of the screen. This can lead to higher CPU usage and bad performance.
Introducing requestAnimationFrame
Modern browsers luckily have a solution for that with the requestAnimationFrame
function. With requestAnimationFrame
, you can specify a function that is called at an interval defined by the browser. You do any drawing you need to do in the supplied function, and the browser will make sure it is painted as smoothly and efficiently as possible. Using this is really simple (the complete source can be found in the 04-materials-light-animation.html
file), you just create a function that handles the rendering:
function renderScene() { requestAnimationFrame(renderScene); renderer.render(scene, camera); }
In this renderScene
function, we call requestAnimationFrame
again, to keep the animation going. The only thing we need to change in the code is that instead of calling renderer.render
after we've created the complete scene, we call the renderScene
function once to kick off the animation:
... document.getElementById("WebGL-output") .appendChild(renderer.domElement); renderScene();
If you run this, you won't see any changes yet compared to the previous example because we haven't animated anything yet. Before we add the animation, though, I want to introduce a small helper library that gives us information about the frame rate the animation is running at. This library, from the same author as Three.js, renders a small graph that shows us the frames per second we're getting for this animation.
To add these statistics, we first need to include the library in the <head>
element of the HTML, as follows:
<script src="../libs/stats.js"></script>
And we add a <div>
element that will be used as output for the statistics graph, as follows:
<div id="Stats-output"></div>
The only thing left to do is initialize the statistics and add them to this <div>
element, as follows:
function initStats() { var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output") .appendChild( stats.domElement ); return stats; }
This function initializes the statistics. The interesting part is the setMode
function. If we set it to 0
, we'll measure frames per second (fps), and if we set this to 1
, we can measure rendering time. For this example, we're interested in fps, so 0
it is. At the beginning of our init()
function, we'll call this function, and we've got stats
enabled, as follows:
function init(){ var stats = initStats(); ... }
The only thing left to do is tell the stats
object when we're in a new rendering cycle. We do this by adding a call to the stats.update
function in our renderScene
function, as follows.
function renderScene() { stats.update(); ... requestAnimationFrame(renderScene); renderer.render(scene, camera); }
If you run the code with these additions, you'll see the statistics in the upper-left corner, as shown in the following screenshot:
Animating the cube
With requestAnimationFrame
and the statistics configured, we've got a place to put our animation code. In this section, we'll expand the renderScene
function with code that will rotate our red cube around all of its axes. Let's start by showing you the code:
function renderScene() { ... cube.rotation.x += 0.02; cube.rotation.y += 0.02; cube.rotation.z += 0.02; ... requestAnimationFrame(renderScene); renderer.render(scene, camera); }
That looks simple, right? What we do is that we increase the rotation
property of each of the axes with 0.02 every time the renderScene
function is called, which shows up as a cube smoothly rotating around all if its axes. Bouncing the blue ball isn't much harder.
Bouncing the ball
To bounce the ball, we once again add a couple of lines of code to our renderScene
function, as follows:
var step=0; function renderScene() { ... step+=0.04; sphere.position.x = 20+( 10*(Math.cos(step))); sphere.position.y = 2 +( 10*Math.abs(Math.sin(step))); ... requestAnimationFrame(renderScene); renderer.render(scene, camera); }
With the cube, we changed the rotation
property; for the sphere, we're going to change its position
property in the scene. We want the sphere to bounce from one point in the scene to another with a nice, smooth curve. This is shown in the following figure:
For this, we need to change its position on the x axis and its position on the y axis. The Math.cos
and Math.sin
functions help us in creating a smooth trajectory using the step variable. I won't go into the details of how this works here. For now, all you need to know is that step+=0.04
defines the speed of the bouncing sphere. In Chapter 8, Creating and Loading Advanced Meshes and Geometries, we'll look in much more detail how these functions can be used for animation, and I'll explain everything. Here's how the ball looks in the middle of a bounce:
Before wrapping up this chapter, I want to add one more element to our basic scene. When working with 3D scenes, animations, colors, and properties like that, it often requires a bit of experimenting to get the correct color or speed. It would be very easy if you could just have a simple GUI that allows you to change these kinds of properties on the fly. Luckily, there is!