Chapter 5. 3D Animation

Animation means making changes to the image on the screen over time. With animation, an otherwise static 3D scene comes to life. While there are many techniques for animating, and many ways to model the problem conceptually, at the end of the day, animation is all about one thing: making the pixels move.

WebGL doesn’t have built-in animation capability per se. However, the power and speed of the API allow us to render amazing graphics and change them at up to sixty frames per second, providing us with several options for animating 3D content. Combined with improvements to the runtime architecture of modern browsers, this enables animations that blend seamlessly with the other elements on the page, without tearing or other unwanted artifacts.

Animation can be used to change anything in a WebGL scene: transforms, geometry, textures, materials, lights and cameras. Objects can move, rotate and scale, or follow paths; geometry can bend, twist and change into other shapes; textures can be moved, scaled, rotated, and scrolled, and have their pixels modified every frame; material colors, specular highlights, transparency values and more can change over time; lights can blink, move and change color; and cameras can be moved and rotated to create cinematic effects. The possibilities are essentially limitless.

In this chapter we will look at a variety of animation techniques, and the tools and libraries to implement them. These techniques are grounded in years of film and video game industry practice, backed by rigorous mathematics. Animation with WebGL is an evolving area, so our exploration involves cobbling together various solutions. Three.js comes with animation utilities that handle certain situations well. We will also look at another open source library, Tween.js. Tween.js is a small, easy to use library for creating simple transitions. But these are far from complete packages. If your application is sufficiently complex, you may find the need to create your own animation engine.

Animating WebGL content involves employing one or more of the following concepts:

Often an application will make use of more than one, sometimes all of the above approaches. There are no hard and fast rules about which techniques apply in which situations, though some are better suited for implementing particular effects. Often the choice of technique is driven by production concerns; for example, if you don’t have a needed artist on staff it may be easier to have a programmer generate the animations in code. Other times it may simply come down to personal preference. 3D animation is equal parts art and science, a mix of production and engineering.

Driving Animation with requestAnimationFrame()

In previous chapters we saw how to power our application’s run loop using requestAnimationFrame(), a relatively recent arrival to web browser APIs.

requestAnimationFrame() was designed to allow web applications to provide consistent, reliable presentation of visual content driven by JavaScript code. The content might be changing the page DOM, adjusting layouts, modifying styles using CSS, or creating arbitrary graphics with one of the drawing APIs such as WebGL and Canvas. The feature was first introduced in Firefox version 4 and eventually adopted by all the other browsers. Robert O’Callahan of Mozilla was looking for a way to ensure that animations handled by the browser for built-in features like CSS transitions and SVG could be synchronized with user code written in JavaScript.

Historically, web applications used timers to animate page content, via either setTimeout() or setInterval(). As applications began to incorporate more complex animations and interactivity, it became clear that this approach suffered from several key problems:

  • The timer functions call callbacks at a specific interval (or as close to it as possible), regardless of whether it is a good time to draw or not.

  • JavaScript executed in a timer callback has no reliable way to synchronize with the timing of other browser-generated animation on the page, e.g. SVG or CSS transitions.

  • Timers execute regardless of whether a page or tab is visible or the browser window has been minimized, potentially resulting in wasted drawing calls.

  • JavaScript application code has no idea of the display’s refresh rate and so has to make an arbitrary choice for the interval value: make it 1/24th of a second and you deprive the user of resolution on a 60hz display; make it 1/60th a second and on slow-refresh displays, you waste CPU cycles drawing content that is never seen.

requestAnimationFrame() was designed to solve all of the above problems. Recalling examples from previous chapters, our run loop takes a form similar to the following:

    function run() {

         // Request the next animation frame
          requestAnimationFrame(run);

         // Run animations
         animate();

         // Render the scene
         renderer.render( scene, camera );

Note the absence of a time value in the call to requestAnimationFrame(). We are not asking the browser to call our animation and drawing code at any specific time or interval; rather we are asking it to call it when it is ready to present the page again. This is a key distinction. With this scheme in place, the browser can call user drawing code during its internal repaint cycle. This has several benefits. First, the browser can do this as frequently— or equally important, as infrequently— as needed. When the browser has sufficient idle cycles it can try to ensure the highest frame rate possible to match the displays refresh rate. Conversely, if a page or tab is hidden, or the entire browser is minimized, it can throttle the amount of times it calls such callbacks, optimizing use of the computer or device’s resources. Second, the browser can invoke batch user drawing, which ultimately results in fewer repaints of the screen, also a resource saver. Third, any user drawing code executed from requestAnimationFrame() will be blended, or composited, with all other drawing calls, including internal ones. The net result of all this is smoother, faster, more efficient page drawing and animation.

Using requestAnimationFrame() In Your Application

Like many recent developments in the HTML5 suite of features, requestAnimationFrame() is not necessarily supported in all versions of all browsers— though that is rapidly changing. Also, given its evolution from an experimental feature in one browser through to W3C recommendations, the function has been implemented with different, prefixed names in each of the browsers. Thankfully, we can make use of a great polyfill created by Paul Irish at Google. The code for it, listed in Example 5-1, can be found in the book example file libs/requestAnimationFrame/ RequestAnimationFrame.js. It attempts to find the correctly named version of the function for the current browser or, failing that, falls back to setTimeout(), going for it with a 60fps interval.

Example 5-1. RequestAnimationFrame Polyfill by Paul Irish
/**
 * Provides requestAnimationFrame in a cross browser way.
 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 */

if ( !window.requestAnimationFrame ) {

    window.requestAnimationFrame = ( function() {

        return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {

            window.setTimeout( callback, 1000 / 60 );

        };

    } )();

}

Do I need to define polyfill here with a callout? Does everybody know what one is at this point?

One key to successful use of requestAnimationFrame() is to make sure you request the next frame before calling any other user code, as was done in the run loop fragment shown earlier. This is important for dealing with exceptions. If you are driving your entire 3D application from the animation callback, and code somewhere generates an exception before requesting the next frame, your application is dead. However, if you request the next frame before doing anything else, at least you are guaranteed to continue running. This allows parts of your application to function and repaint elements, even if something is wrong elsewhere.

requestAnimationFrame() and Performance

While requestAnimationFrame() is a boon for animation performance, it comes with a certain responsibility. If the browser is calling your callback every 60th of a second, the onus is on you to write callbacks that take 16 milliseconds or less. That is not a lot of time. So care must be taken to do the minimum amount of work required to make the necessary drawing changes and no more. In the second half of the book we will explore techniques that separate animation, rendering, background processing and other activities from the simple all-in-one run loops we are showing here for illustration purposes. Industrial-strength 3D applications need to consider using timers, workers, and other animation techniques like CSS transforms and transitions in conjunction with requestAnimationFrame() to deliver the most responsive, powerful, resource-efficient experiences possible.

requestAnimationFrame() is arguably one of the most important features introduced for HTML5. This section merely scratched the surface on the topic. There are several excellent online resources for learning more about it. Do a web search on the name and you will discover a trove of articles, backgrounders, how-to’s, tips and tricks and explanations of what is under the hood.

Frame-Based vs. Time-Based Animation

Early computer animation systems emulated predecessor film animation techniques by presenting a succession of still images on the display, or, in vector-based graphics, a series of vector-based images generated by the program. Each such image is known as a frame. Historically, film was shot and played back at a rate of twenty-four images every second, known as a frame rate of 24 frames per second (FPS). This speed was adequate for large projection screens in low light settings. However, in the world of computer-generated animation and 3D games, our senses are actually able to perceive and appreciate changes that occur at higher frame rates, upwards of 30 and up to 60 or more FPS. Despite this, many animation systems, such as Adobe Flash, originally adopted the 24FPS convention due to its familiarity for traditional animators. These days, the frame rates have changed—Flash supports 60FPS if the developer requests it—but the concept of discrete frames remains. This technique of organizing animation into a series of discrete frames is known as frame-based animation.

Frame-based animation has one serious drawback: by tying it to a specific frame rate, the animator has ensured that animation will never be able to be presented at a higher frame rate, even if the computer can support it. This was not an issue for film, where the hardware was fairly uniform throughout the industry. However, in computer animation, performance can vary wildly from device to device. If you create your animations at 24FPS, but your computer can refresh the screen at 60hz, you effectively deprive the user of resolution and smoothness.

A different technique, known as time-based animation, solves this problem. In time-based animation, a series of vector graphics images is connected to particular points in time, not specific frames in a sequence with known frame rates. In this way, the computer can present those images, and the interpolated frames in between them, as frequently as possible and deliver the best images and smoothest transitions. In the examples in the previous chapters, we used time-based animation. Each time through the run loop, the animate() function calculated a time delta between the current and previous frame and used that to compute an angular rotation. All of the examples developed for this and subsequent chapters use time-based animation. So, even though the word frame is right in the name requestAnimationFrame(), rest assured that it can be used equally well for time-based animations.

Animating by Programmatically Updating Properties

By far, the simplest way to get started animating a WebGL scene is to write code that updates an object’s properties each time through the run loop. We have seen examples of this already in previous chapters. To rotate the Three.js cube in Chapter 3, we simply updated the cube’s rotation.y property, that is, the angle of its rotation about the Y-axis, each frame. Here is the code again:

    var duration = 5000; // ms
    var currentTime = Date.now();
    function animate() {

        var now = Date.now();
        var deltat = now - currentTime;
        currentTime = now;
        var fract = deltat / duration;
        var angle = Math.PI * 2 * fract;
        cube.rotation.y += angle;
    }

The variables duration, currentTime, now and deltat are used to compute a time-based animation value for the rotation. In this example, we want a full rotation about the Y-axis over the course of five seconds. The computed angle is a fraction of one complete rotation, the amount that must be added to the cube’s current rotation.y property. Recall that rotations are represented in Three.js as radians, the distance around a unit circle; i.e. Math.PI * 2 is equal to a full (360 degree) rotation.

This concept can be applied to animate anything in a scene: position, rotation, scale, material colors and transparency, and so on. Moreover it is completely general: by using JavaScript code to update properties, arbitrary computation can be applied. Animations can be driven by mathematical formulae, Boolean logic, statistical values, data streams, real-time sensor input and so on. So this is a great technique for scientific illustration and data visualization: depicting solar systems, physical processes and natural phenomena; presenting time series information, statistical analyses, geographic data, web site traffic and other dynamic, database-driven information. It is also excellent for creating really lively and entertaining applications like music visualizers.

Figure 5-1 depicts the wild world of Ellie Goulding’s Lights, a WebGL music visualization developed by UK-based interactive agency Hello Enjoy (http://helloenjoy.com/). This piece has been around for a while, but it still packs a punch. Glowing globes blink on and off, comet trails wind a curvy path through the scene, colored balls fade in and out and change color, spotlights twirl madly, and teardrop-shaped balloons blossom out of the multi-colored, undulating terrain — all in time to the music of the hit song. This is eye candy at its best, with all effects being generated programmatically.

Ellie Goulding’s Lights: A Music Visualizer Built Using Programmatic Animation ()
Figure 5-1. Ellie Goulding’s Lights: A Music Visualizer Built Using Programmatic Animation (http://lights.elliegoulding.com/)

Example 5-1 shows a portion of the code that animates the visuals. The application’s update() method is called each time through the run loop. It in turn calls update() on all the objects in the scene. The excerpt below is from LIGHTS.StarManager.update(), which animates the background stars. The stars are rendered as Three.js particles belonging to a THREE.ParticleSystem object. The lines highlighted in bold show how the RGB color for each star is updated based on elapsed time, a decay factor, and the mod operator (%) to create a blink effect.

Example 5-2. Animating to the Beat: Code Fragment from Ellie Goulding’s Lights
    update: function() {

        var stars = this.stars,
            deltaTime = LIGHTS.deltaTime,
            star, brightness, i, il;

        for( i = 0, il = stars.length; i < il; i++ ) {

            star = this.stars[ i ];

            star.life += deltaTime;

            brightness = (star.life * 2) % 2;

            if( brightness > 1 )
                brightness = 1 - (brightness - 1);

            star.color.r =
            star.color.g =
            star.color.b = (Math.sin( brightness * rad90 - rad90 ) + 1) * 4;
        }

        this.particles.__dirtyColors = true;
    },

As flexible and powerful as programmatic animation is, it has its limitations. It requires hand coding for each effect; as a consequence that makes it hard to scale up to animate many different kinds of objects. It also tends to be more verbose than other data-driven methods such as tweening and key frames, which we will cover shortly. Finally, it puts the programmer at the center of the action, instead of the artist, who may be much better suited to creating the desired visual effect. Still, programmatic animation is an excellent way to quickly and easily add some life to a scene, and the effects can be truly stunning, as in the case of Lights.

Animating Transitions Using Tweens

Many animation effects are better represented as data structures, rather than programmatically generating values each time through the run loop. The application supplies a set of values and a time series, and a general-purpose engine calculates the per-frame values used to update properties. One such data-driven approach is known as tweening.

Tweening is the process of generating values that lie in between a pair of other values. With tweening, the animator supplies only the values at the beginning and end points of the animation, and the engine calculates the intermediate values (tweens) for the intervening times. Tweening is perfect for simple one-time transitions from one state to another, such as moving an object in reaction to a mouse click.

Interpolation

Tweening is accomplished using a mathematical technique called interpolation. Interpolation refers to the generation of a value that lies between two values, based on a scalar input such as a time or fraction value. Interpolation is illustrated in Figure 5-2. For any values A and B, and a fraction u between 0 and 1, the interpolated value P can be calculated by the formula A + u * (B-A). For the example depicted in Figure 5-2, we can see the interpolated value P(u) = 0.4. This is the simplest form of interpolation, known as linear interpolation because the mathematical function used to calculate the result could be graphed with a straight line. Other, more complex interpolation functions, such as splines (a type of curve) and polynomials are also commonly used in animation systems. We will look at spline-based animation shortly.

Interpolation is used to calculate tweens of 3D positions, rotations, colors, scalar values (such as transparency), and more. With a multi-component value such as a 3D vector, a linearly interpolated tween simply interpolates each component piecewise. For example, the interpolated value P at u=0.5 for the 3D vector AB from (0, 0, 0) to (1, 2, 3) would be (0.5, 1, 1.5).

The Tween.js Library

It is pretty straightforward to implement simple tweening on your own. However, if you want to have non-linear interpolation functions, and other bells and whistles such as ease in/ease out, where the animation appears to accelerate to its main speed and decelerate out of it, then the problem becomes more complex. Rather than build your own tweening system, you may want to use an existing library. Tween.js (https://github.com/sole/tween.js) is a popular open source tweening utility created by Soledad Penadés. It has been used in conjunction with Three.js on popular WebGL projects, including RO.ME (http://www.ro.me/), the WebGL Globe (http://workshop.chromeexperiments.com/globe/), and Mine3D (http://egraether.com/mine3d/), a web version of the classic single-player game Minesweeper.

The example in file Chapter 5/tweenjstweens.html contains a sandbox for testing out various Tween.js options. See Figure 5-3 for a screen shot. The sandbox uses Tween.js to apply various transitions to a textured cube: position, rotation, material color and opacity. There are sliders for adjusting the tween duration and delay time (time before the tween starts); check boxes to enable and disable the specific tweens, and an option to loop the tween (repeat it continuously). There is also an option to control easing functions, but we will talk about that in the next section. Play with the different options to see how they modify the effect.

Animating Transitions with Tween.js
Figure 5-3. Animating Transitions with Tween.js

Tween.js is very easy to work with. The syntax is simple, and thanks to the polymorphism of JavaScript, we can target any property using the exact same method calls. It also uses chained method syntax similar to jQuery, allowing for a very concise expression. Let’s have a look. Example 5-2 shows a portion of function playAnimations(), called to trigger the Tweens each time a property is changed.

Example 5-3. Tween.js Code to Animate Position
            positionTween =

                new TWEEN.Tween( group.position )
                 .to({ x: 2, y: 2, z:-3 }, duration * 1000)
                .interpolation(interpolationType)
                .delay( delayTime * 1000 )
                .easing(easingFunction)
                .repeat(repeatCount)
                .start();

The position tween is set up with a single chained set of methods:

  • The constructor, new TWEEN.Tween. It takes a single argument, the target object whose properties it will tween;

  • to(), which takes a JavaScript object defining the properties to tween, and a duration in milliseconds;

  • interpolation(), an optional method that specifies the type of interpolation. This can be omitted for linear interpolation, as this is the default (TWEEN.Interpolation.Linear);

  • delay(), an optional method for inserting a delay before the tween starts;

  • easing(), an optional method for applying an easing function (covered in the next section);

  • repeat(), an optional method for specifying the number of times the tween repeats (default is zero);

  • and finally, start(), which starts the tween.

Note that these methods can also be called separately. Each of the tweens—position, rotation, material color and opacity— is set up in a similar fashion. A great thing about Tween.js is that you do not have to supply all values of the object to the to() method, only those that will be changed. For example, the rotation tween only changes the rotation about the y-axis, so it is created as follows:

            rotationTween =

                new TWEEN.Tween( group.rotation )
                 .to( { y: Math.PI * 2 }, duration * 1000)
                .interpolation(interpolationType)
                .delay( delayTime * 1000 )
                .easing(easingFunction)
                .repeat(repeatCount)
                .start();

Once the tween is set up and started, it is now a matter of making sure that Tween.js updates it every animation frame. It is up to the application to do this, so we add the following line to our run() function:

            TWEEN.update();

Under the hood, Tween.js keeps a list of all its running tween objects and calls their update() methods in turn. update() calculates how much time has elapsed, applies easing functions, delay and repeat options, and ultimately sets the properties of the target object as specified in the to() method. This is a beautifully elegant, yet simple scheme for making object properties change over time without having to hand-code the changes each frame.

Easing

Basic tweens with linear interpolation can result in a stiff, unnatural effect, because the objects change at a constant rate. This is unlike objects in the real world, which behave with inertia, momentum, acceleration and so on. With Tween.js, we can create more natural-feeling tweens by incorporating easing — non-linear functions applied to the start and end of the tween. Easing is a great tool for adding more realism to your tweens. It can even do a fair job approximating physics without requiring the hard work of integrating a physics engine into your application.

Try out the various easing functions in the tweening sandbox and note their effects. Some simply create a gradual speedup and slowdown of the tween; others provide bouncy and springy effects. The polynomial easing functions Quadratic, Cubic, Quartic and Quintic ease the tween just as their names imply: via 2nd, 3rd, 4th and 5th degree functions. Other easing functions provide sine wave, bounce and spring effects. Each easing function can be used to ease in (at the beginning of the tween), ease out (at the end of the tween), or do both.

What the easing functions are actually doing is modifying time. Example 5-3 shows the code for the easing function TWEEN.Easing.Cubic. Inputs to the easing functions are in the range [0..1], i.e. a fraction of the tween’s full duration. The input, k, is cubed by the easing function; therefore, small input values of k return even smaller output values; however, as k approaches 1, so does the return value.

Example 5-4. The Tween.js Cubic Easing Function
     Cubic: {

        In: function ( k ) {

            return k * k * k;

        },

        Out: function ( k ) {

            return --k * k * k + 1;

        },

        InOut: function ( k ) {

            if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k;
            return 0.5 * ( ( k -= 2 ) * k * k + 2 );

        }

    },

The Tween.js easing functions are based on the seminal animation work of Robert Penner (http://www.robertpenner.com/index2.html). They offer a wide range of powerful easing equations, including linear, quadratic, quartic, sinusoidal and exponential. Penner’s work has been ported from the original ActionScript to several languages, including JavaScript, Java, CSS, C++, and C#, and has been incorporated into jQuery’s animation utilities.

As we have just seen, tweens are great for easily creating simple, natural-looking effects. Tween.js even lets you chain animations together into a sequence so that you can compose simple effects into more powerful ones. However, as you begin building complex animation sequences you are going to want a more general solution. That’s where key frames come in.

Using Key Frames for Complex Animations

Tweens are perfect for simple transition effects. More complex animations take the tween concept to the next level by using key frames. Rather than specifying a single pair of values to tween, a key frame animation consists of a list of values, with potentially different durations in between each successive value. Note that the term key frame animation is used in both frame-based and time-based systems — a holdover from frame-based nomenclature.

Key frame data consists of two components: a list of time values, or keys; and a list of values. The listed values represent the property values to be applied at the time of the corresponding key; the animation system computes tweens for time values lying between any pair of keys.

The following code fragment (from a hypothetical animation engine) shows sample key frame values for an animation that moves an object from the origin up and away from the camera. Over the course of a second, the object moves upward in the first quarter of a second, then up some more and away from the camera in the remaining three-quarters of a second. The animation system will calculate tweens for the points (0, 0, 0) to (0, 1, 0) over the first quarter second, then tweens for (0, 1, 0) to (0, 2, 5) over the remaining three quarters of a second.

var keys = [0, 0.25, 1];
var values = [[0, 0, 0],
               [0, 1, 0),
          [0, 2, 5]
              ];

Key frame animations can work with linear interpolation, or more complex interpolation such as spline-based; in other words, the data points representing the keys can be thought of as points in a line graph or as the graph of a more complicated function such as a cubic spline. While both tweening and key framing employ interpolation, there are two main aspects that differentiate key frame animations from simple tweens: 1) key frame animations can contain more than two values and 2) the time interval can vary between successive keys. This enables more powerful effects and gives the animator more control.

Keyframe.js – A Simple Key Frame Animation Utility

Before we can look at a key-framing example, we need to identify an animation library that supports the technique. Tween.js has taken baby steps toward supporting key framing, by allowing lists of property values instead of just pairs. However, in my opinion the syntax for these is a bit cumbersome. Also, there is no way to vary the interval between successive keys. Three.js actually provides built-in animation classes for animating with key frames, but these are not easy to use for hand-coding quick and dirty effects; they were built primarily to support the file loading utilities for loading JSON, COLLADA and other formats. Fair enough: in general, key frame content is meant to be generated by authoring tools such as 3ds Max, Maya or Blender, not written by hand. Still, it would nice to have an easy way for programmers to put together simple key frames. My frustration with the lack of an easy key framing solution for WebGL led me to write my own utility, keyframe.js.

Keyframe.js is very simple. It implements two classes, a KeyFrameAnimator class that controls the animation state (start, stop, looping logic and so on), and an Interpolator class that calculates the tweens for each key pair. At the moment, the library only supports linear interpolation. However, Keyframe.js does allow the programmer to supply easing functions, and for those we can borrow the excellent Penner equations implemented in Tween.js— no need to reinvent the wheel. To see Keyframe.js in action, open the file Chapter 5/keyframeanimation.html. You will see a page that looks like the screen shot in Figure 5-4. Here we a high seas adventure in progress: a wooden crate bobs in turbulent water, while the sky occasionally brightens and darkens, signaling an impending storm. The controls on the right allow you to play with the duration, turn individual animations on and off, and toggle looping.

Complex Animations Using Key Frames
Figure 5-4. Complex Animations Using Key Frames

Example 5-4 shows the code to animate the wooden crate. First, we create a new KF.KeyFrameAnimator and initialize it with parameters: looping, a time duration (in milliseconds), an easing function (borrowed from Tween.js), and a set of key frame interpolation data in the parameter interps. Most notably in contrast with Tween.js, the keys and values are lists, not just pairs; moreover the intervals between successive keys are different. Following the details of the position interpolator (target:group.position), the crate moves left and forward from time t = 0 to t = 0.2; then back to the origin quickly (t = 0.2 to 0.25), then quickly dips into the water (t = 0.25 to 0.375), back up to the surface at t = 0.5, then slowly sinks (t = 0.5 to 0.9) and finally bobs back up at t = 1.0. Note that in Keyframe.js, keys are specified as a fraction of the duration, that is, they always range from 0 to 1; so the actual time of a frame is equal to

time = t x duration

There is a second interpolator for rotation, to tilt the crate about the x-axis. Note that this interpolator has a different number of keys; that is valid and in fact a feature. The position and rotation animations were created intentionally to be a little out of sync, to make the effect more chaotic. The final flourish is the incorporation of the easing function TWEEN.Easing.Bounce.InOut. The combination of independent, uncoordinated translation and rotation with the bouncy math of the easing function does the trick: the crate does a fair job of appearing to bounce around in the water. The only thing left to do is play the animation, by calling its start() method.

Example 5-5. Key Frame Animation for the Crate
        if (animateCrate)
        {
            crateAnimator = new KF.KeyFrameAnimator;
            crateAnimator.init({
                interps:
                    [
                        {
                            keys:[0, .2, .25, .375, .5, .9, 1],
                            values:[
                                    { x : 0, y:0, z: 0 },
                                    { x : .5, y:0, z: .5 },
                                    { x : 0, y:0, z: 0 },
                                    { x : .5, y:-.25, z: .5 },
                                    { x : 0, y:0, z: 0 },
                                    { x : .5, y:-.25, z: .5 },
                                    { x : 0, y:0, z: 0 },
                                    ],
                            target:group.position
                        },
                        {
                            keys:[0, .25, .5, .75, 1],
                            values:[
                                    { x : 0, z : 0 },
                                    { x : Math.PI / 12, z : Math.PI / 12 },
                                    { x : 0, z : Math.PI / 12 },
                                    { x : -Math.PI / 12, z : -Math.PI / 12 },
                                    { x : 0, z : 0 },
                                    ],
                            target:group.rotation
                        },
                    ],
                loop: loopAnimation,
                duration:duration * 1000,
                easing:TWEEN.Easing.Bounce.InOut,
            });
            crateAnimator.start();

        }

The animations for the water and storm are handled similarly, though none of the other animations use an easing function. There is an animation to make the water surface move up and down (simple rotation of the water plane about the x axis), one for creating the appearance of waves, essentially “scrolling” the texture map by interpolating its offset property, and one to make the light flash by interpolating its RGB color values.

This example is a simple illustration of how key frames can create more interesting effects than the basic transitions supported in Tween.js. Key frames can be expressed easily as arrays of keys and values, allowing the animator to sequence of tweens of different durations. In practice, programmers rarely create these kinds of animations by hand; rather, artists do it using professional tools. This is the preferred way to go for developing complex effects, especially those involving multiple objects— the subject of the next section.

Articulated Animation with Key Frames

The animation strategies we have discussed so far can be used to move single objects in place (i.e. with rotation) or around the within the scene, but they can also be used to create complex motions in composite objects using a transform hierarchy.

Let’s say we want to create a robot that walks and waves its arms. We would model the robot as a hierarchical structure: the robot body contains an upper body and lower body, the upper body contains arms and a torso, the arms contain upper arms and lower arms, and so on. By properly constructing the hierarchy and animating the right parts, we can get the robot to moves its arms and legs. The technique of constructing bodies by combining a hierarchy of discrete parts and animating them in combinations is known as articulated animation.

The Three.js examples come with a nice demonstration of articulated animation. Load the Three.js example file examples/webgl_loader_collada_keyframe.html. You will see an animated model of a pump that shows its inner workings. As the pump rotates, it opens up to assemble and disassemble itself, exposing various parts such as valves, gaskets, gears, housings and the bolts that hold the pump together. Each of the parts animates individually; however, thanks to the Three.js transform hierarchy, each part also moves with its ancestors as they go through their paces, opening and closing, inserting one part into another, and so on. Figure 5-5 depicts the pump in action.

Articulated Animation: the Inner Workings of a Pump Using Key Frames with a Transform Hierarchy. COLLADA model created with the Kuda open source authoring system
Figure 5-5. Articulated Animation: the Inner Workings of a Pump Using Key Frames with a Transform Hierarchy. COLLADA model created with the Kuda open source authoring system https://code.google.com/p/kuda/

This pump model is loaded using the COLLADA file format (.dae file extension), an XML-based text format for describing 3D content. COLLADA can represent individual models or entire scenes, and supports materials, lights, cameras, and animations. Without getting deep into the details, key frame data in COLLADA looks similar to the following excerpt from the pump model (file examples/models/collada/pump/pump.dae).

    <animation id="camTrick_G.translate_camTrick_G">
      <source id="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-input" name="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-input">
        <float_array id="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-input-array" count="3">0.04166662 6.5 7.083333 </float_array>
        <source id="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-output" name="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-output">
        <float_array id="camTrick_G.translate_camTrick_G_camTrick_G.translateX_camTrick_G-output-array" count="3">8.637086 7.794443 0 </float_array>

The COLLADA <animation> element defines an animation. The two <float_array> child elements shown here define the keys and values, respectively, required to animate the x component of the transform for an object named camTrick_G. The keys are specified in seconds. Over the course of 7.08333 seconds, camTrick_G will translate in x from 8.637086 to 0. There is an additional key in between at 6.5 seconds that specifies an x translation of 7.794443. So for this animation, there is a rather slow x translation over the first 6.5 seconds, followed by a rapid one over the remaining 0.58333 seconds. There are dozens of such animation elements defined in this COLLADA file (74 in all), for the various objects that comprise the pump model.

Example 5-5 shows an excerpt from the code that sets up the animations for this example. The example makes use of the built-in Three.js classes THREE.KeyFrameAnimation and THREE.AnimationHandler. THREE.KeyFrameAnimation implements general-purpose key frame animation for use with COLLADA and other animation-capable formats. THREE.AnimationHandler is a singleton that manages a list of the animations in the scene and maintains responsibility for updating them each time through the application’s run loop. (The code for these classes can be found in the Three.js project in the folder src/extras/animation.)

Example 5-6. Example Caption Text Goes Here
                 var animHandler = THREE.AnimationHandler;

                for ( var i = 0; i < kfAnimationsLength; ++i ) {

                    var animation = animations[ i ];
                    animHandler.add( animation );

                    var kfAnimation = new THREE.KeyFrameAnimation(
                          animation.node, animation.name );
                    kfAnimation.timeScale = 1;
                    kfAnimations.push( kfAnimation );

                }

The example does a little more setup before eventually calling each animation’s play() method to get it running. play() takes two arguments: a loop flag and an optional start time (with zero, the default, meaning play immediately).

                    animation.play( false, 0 );

This example shows how key frame animation can combine with a transform hierarchy to create complex, articulated effects. Articulated animation is typically used as the basis for animating mechanical objects; however as we will see later on this chapter, it is also essential for driving the skeletons underlying skinned animation.

As is the case with many of the file format loaders that come with Three.js, the COLLADA loader is not part of the core package but rather included with the samples. The source code for the Three.js COLLADA loader can be found in examples/js/loaders/ColladaLoader.js. The COLLADA format will be discussed in detail in Chapter X, The 3D Content Creation Pipeline.

Using Curves and Path Following to Create Smooth, Natural Motion

Key frames are the perfect way to specify a sequence of transitions with varying time intervals. Combined with hierarchy, we can create complex interactions through articulated animation. However, the samples we have looked at so far look mechanical and artificial because they use linear functions to interpolate. The real world has curves: cars hug curved roads, planes travel in curved paths, projectiles fall in an arc, and so on. Attempting to simulate those effects using linear interpolation produces unsettling, unnatural results. We could use a physics engine, but for many uses that is overkill. Sometimes we just want to create a predefined animation that looks natural, without having to pay the costs of computing a physics simulation.

Key frame data is not just limited to describing linear animations. It can be treated as points on a curve, too. The most common type of curve used in animation is a spline curve. Define spline curve here. Figure 5-6 depicts a spline curve, with its control points circled in red.

A Spline Curve. Describe control points and tangents.
Figure 5-6. A Spline Curve. Describe control points and tangents.

Spline interpolation is more complex than simple linear interpolation, incorporating polynomial formulas akin to those found in the Tween.js easing functions, and an additional value on either side of the key values to compute the smooth curve. A full explanation of spline interpolation mathematics is beyond the scope of this book. However, Figure 5-7 shows an intuitive view of how it works: to compute an interpolated value along the curve between points P1 and P2, control points P0 and P3 are also used in order to generate a value that lies on the spline curve.

Spline Interpolation
Figure 5-7. Spline Interpolation

Splines come in several varieties, including cubic Bezier splines, and Catmull-Rom splines, named after animation genius and Pixar founder Ed Catmull. Catmull-Rom has become popular because it easier to construct and compute than Bezier curves. Three.js comes with a built-in animation class that uses Catmull-Rom interpolation. See the Three.js source file src/extras/animations/animation.js.

There are several good online Catmull-Rom tutorials, including http://flashcove.net/795/cubic-spline-generation-in-as3-catmull-rom-curves/ and http://www.mvps.org/directx/articles/catmull/

Spline animation often needs to take into account orientation as well as position. If, for example, you want to animate an object following a curved path, you need to have it turn, tilt and roll in order to make it appear natural. That involves computing a new orientation at each point. Figure 5-8 depicts that process. At each point on the curve, a tangent, normal and binormal are computed. Informally, the tangent is the straight line following the direction of the curve, intersecting it at one point only. The normal is the line perpendicular to the direction of the curve. The binormal is the cross product of the other two lines. Together, these three vectors define a frame of reference known as the TNB frame, which defines the orientation for an object following the path.

Coordinate Frames for Spline Animation. Tangents, normals and binormals are represented by blue, green and red arrows, respectively.
Figure 5-8. Coordinate Frames for Spline Animation. Tangents, normals and binormals are represented by blue, green and red arrows, respectively.

There is a nice example of path following animation in the Three.js samples. Open the file examples/webgl_geometry_extrude_splines.html. Be sure to press the button labeled Camera Spline Animation View to see the animation depicted in Figure 5-9. The camera follows the spline curve from a short distance away, continually adjusting its position and orientation. This particular example was animated programmatically, computing the spline interpolation and TNB frame in code. But it could conceivably be packaged into a reusable path-following animation class.

A Camera Animating Along a Path
Figure 5-9. A Camera Animating Along a Path

Using Morph Targets for Character and Facial Animation

Key frames and articulated animation are great for moving objects around within the scene, but many animation effects require changing the geometry of the object itself. A common way to do this is via morph target animation, or simply, morphing. Morphing uses vertex-based interpolations to change the vertices of a mesh. Typically, a subset of the vertices of a mesh is stored, along with their indices, as a set of morph targets to be used in a tween. The tween interpolates between each of the vertex values in the morph targets, and the animation uses the interpolated values to deform the vertices in the mesh.

Morph targets are excellent for facial expressions and other fine details that are not so easy to implement in a skinned animation (see next section); they are compact and don’t require a highly detailed skeleton with numerous facial bones. In addition, they allow the animator to create very specific expressions by tweaking the mesh right down the vertex level. Figure 5-6 illustrates the use of morphing to create facial expressions. Each different expression, such as the pursed lips or the smile, is represented by a set of vertices including the mouth and surrounding areas.

Facial Morphs.Creative Commons Attribution-Share Alike 3.0 Unported license
Figure 5-10. Facial Morphshttp://en.wikipedia.org/wiki/File:Sintel-face-morph.png.Creative Commons Attribution-Share Alike 3.0 Unported license

Morphs can be used for more than just faces. Several examples in the Three.js project use morph targets to animate entire characters. Figure 5-7 depicts characters animated using morphs. These characters were originally modeled in the id Software MD2 file format, a popular morph-based format used for animating player characters in id games such as Quake II. The MD2 files were converted to the Three.js JSON file format using [converter details here]. The Three.js JSON format will be discussed in detail in a later chapter.

Animating Characters with Morph Targets. Models in MD2 format. (Planet quake credits here)
Figure 5-11. Animating Characters with Morph Targets. Models in MD2 format. (Planet quake credits here)

To see these animations, load the file examples/webgl_morphtargets_md2_control.htm. You will see several ogre characters lumbering, turning and looking over their shoulders. The arrow and WASD keys on the keyboard will make the characters move around the scene, transitioning from their idle animations to walking and turning animation sequences. The effect is quite convincing.

To get a feel for what morph target data looks like, open the converted MD2 file located in examples/models/animated/ogro/ogro-light.js. At around line 18, you will see a JSON property that begins

"morphTargets": [
{ "name": "stand001", "vertices": [0.6,-2.7,1.5,-5.5,-3.3,-0.6 ...

... and continues for several lines. Each element of the morphTargets array is a single morph target; each morph target contains the complete set of vertices for the ogre mesh, but with different position values. Three.js animates the morph by cycling through the set of targets for the model, interpolating vertex values to blend from one target to the next. The code for loading, setting up and animating MD2 characters, implemented in the class THREE.MD2CharacterComplex, can be found in the Three.js example source file examples/js/MD2CharacterComplex.js.

Animating Characters with Skinning

Articulated animation works very well for inorganic objects —robots, cars, machines and so on. It breaks down badly for organic objects. Plants swaying in the breeze, animals bounding, and people dancing all involve changes to the geometry of a mesh: branches twist; skin ripples; muscles bulge. It is nearly impossible to do this well with the tinker toy approach that is articulated animation. So we turn to another technique called skinned animation, also known as skeletal animation or single mesh animation.

Skinned animation involves deforming the actual vertices of a mesh, or skin, over time. Animation is driven by an articulated object hierarchy known as a skeleton (sometimes called a rig). The skeleton is used only as the underlying mechanism for animating; we don’t see it on the screen. Changes to the skeleton, combined with additional data describing how the skeleton influences changes to the skin in various regions of the mesh, drive the skinned animation.

A Character Mesh with Underlying Skeleton, Suitable for Skinned Animation
Figure 5-12. A Character Mesh with Underlying Skeleton, Suitable for Skinned Animation

A skeleton is composed, not surprisingly, of bones. Bones are organized in a hierarchy, in the intuitive way you would expect. Like the old song goes: foot bone connected to the leg bone, leg bone connected to the knee bone... and so on. Just as with articulated animation, transforming a bone moves all its child bones. However, unlike articulated animation, the skeleton is not visible; it is merely a mechanism that exerts an influence over parts of the skin.

Each bone in the skeleton is associated with a set of vertices of the mesh, along with a blend weight (also known as a vertex weight) for each associated vertex. The blend weight specifies how much that particular bone influences its associated vertices. Vertices can be associated with multiple bones, so the ultimate position and orientation of a vertex is determined by the combined transformations of all associated bones, scaled by the respective weights. If this sounds complicated, it is. Skinned animations are almost always produced by authoring tools rather than created by hand. It is also algorithmically complex; these days, most runtime engines animate skins using the GPU if possible. This includes Three.js.

To see an example of skinned animation in action, open the file under the Three.js project located in examples/webgl_animation_skinning.html. You will see many instances of a buffalo model. Click to start the animation; the buffalo will run in place with natural-looking movement. See Figure 5-9.

Meshes Animated Using Skinning in Three.js. Buffalo model from RO.ME
Figure 5-13. Meshes Animated Using Skinning in Three.js. Buffalo model from RO.ME

Let’s walk through a portion of the code for this sample to see how Three.js implements skinning. First, the buffalo model is loaded by creating a new THREE.JSONLoader object and calling its load() method. This class loads files in the Three.js JSON file format. The format contains skinning information as well as the geometry.

                var loader = new THREE.JSONLoader();
                loader.load( "obj/buffalo/buffalo.js", createScene );

load() takes as its second argument a callback function that will be invoked once the file has been downloaded and parsed. Example 5-2 shows an excerpt from the callback function createScene(), with the relevant lines highlighted in bold.

Example 5-7. Callback to Set Up Skinned Animation After File Load
            function createScene( geometry, materials ) {

                buffalos = [];
                animations = [];

                var x, y,
                    buffalo, animation,
                    gridx = 25, gridz = 15,
                    sepx  = 150, sepz = 300;

                var material = new THREE.MeshFaceMaterial( materials );

                var originalMaterial = materials[ 0 ];

                originalMaterial.skinning = true;
                originalMaterial.transparent = true;
                originalMaterial.alphaTest = 0.75;

                THREE.AnimationHandler.add( geometry.animation );

                for( x = 0; x < gridx; x ++ ) {

                    for( z = 0; z < gridz; z ++ ) {

                        buffalo = new THREE.SkinnedMesh( geometry,
                                material, false );

                        buffalo.position.x = - ( gridx - 1 ) * sepx * 0.5 + x * sepx + Math.random() * 0.5 * sepx;
                        buffalo.position.z = - ( gridz - 1 ) * sepz * 0.5 + z * sepz + Math.random() * 0.5 * sepz - 500;

                        buffalo.position.y = buffalo.geometry.boundingSphere.radius * 0.5;
                        buffalo.rotation.y = 0.2 - Math.random() * 0.4;

                        scene.add( buffalo );

                        buffalos.push( buffalo );

                        animation = new THREE.Animation( buffalo, "take_001" );
                        animations.push( animation );

                        offset.push( Math.random() );

                    }

createScene() runs a loop to create many instances of a buffalo mesh from the one loaded geometry. Note the type of mesh created: instead of the THREE.Mesh type we are familiar with from previous examples, this uses a different kind of mesh: THREE.SkinnedMesh. This particular Three.js type will be rendered using a special vertex shader that performs skinned animation on the GPU for performance.

createScene() also makes use of the built-in Three.js animation classes THREE.Animation and THREE.AnimationHandler. THREE.Animation is a class that implements general-purpose key frame animation, which in the case of skinning is used to drive the skeleton animation. THREE.AnimationHandler is a singleton object that stores all animations for a scene, and maintains responsibility for updating them each time through the application’s run loop. Our callback first adds the animation data to the animation handler’s list by calling THREE.AnimationHandler.add(), passing it the geometry’s animation data, which was loaded automatically by the Three.js JSON loader. A little later on, the code creates a new THREE.Animation for each buffalo instance, associating the instance stored in variable buffalo with the animation named “take_001” from the JSON file.

After the animations are set up, we are ready to play them. The application does this by calling the function startAnimation() when the mouse is clicked. See Example 5-3. startAnimation() loops through the array of animations, calling play() on each. Each animation is also given a different, random time offset, to keep the animals from being perfectly synchronized.

Example 5-8. Playing the Skinned Animations
             function startAnimation() {

                for( var i = 0; i < animations.length; i ++ ) {

                    animations[ i ].offset = 0.05 * Math.random();
                    animations[ i ].play();

                }

                dz = dstep;
                playback = true;

            }

If you are interested in the details of the JSON animation format, have a look at the file examples/obj/buffalo.js. Search through the file for the properties bones, skinWeights, and skinIndices to see how the skeleton data is laid out; also look for the property animation, which contains the hierarchy of key frames used to animate the skeleton. There is a lot going on under the covers, and Three.js adds a lot of value, not the least of which is a shader-based implementation of skinning that relies on the GPU for computation.

Animating Using Shaders

This section TBD.

It will show one new example. It will also discuss how shaders are used to accelerate morphs and skins using the built-in objects THREE.MorphAnimMesh and THREE.SkinnedMesh and show code from the shaders.

Possible example: http://localhost/three.js/examples/webgl_morphnormals.html

Chapter Summary

As we have seen, there are many ways to animate 3D content in WebGL. At its core, animation is driven by the new browser function requestAnimationFrame(), the workhorse that ensures user drawing happens in a timely and consistent manner throughout the page. Beyond that, we have several choices for animating, ranging from simple to complex, depending on the desired effect. Content can be animated programmatically each frame, or we can use data-driven methods that include tweening, key framing, morphs and skinning. Naturalistic motion can be achieved by combining key frames with path following. We can also use shaders to animate content in the GPU, enabling even more possibilities. The tools and libraries for animating WebGL are still evolving, with no one clear choice. But there are many possibilities and, thanks to JavaScript and open source, few barriers to get going.