Day 4:HTML5 Animation using Three.js

Three.js is a lightweight JavaScript 3D library with a very low level of complexity. It supports canvas, svg and WebGL renderers. Browser support for WebGL renderers is still a little spotty, particularly on Mobile, so for example if you wanted to run a WebGL 3D project on the IPad or some older Android phones, it wouldn’t work, however canvas based animations in Three.js would work on an iPad.

The process of getting the Three.js lib into your project is the same as it was for JQuery which was covered in Day 3: HTML5 Animation using JQuery.

Getting Started
All 3D libs are fairly similar. If you are familiar with Flash back in 2008 or so there was a popular 3D library called Papervision. Well think of Three.js as Papervision for JavaScript. It provides the same functionality and the ability to import 3D models into your project and move them around or create your own mesh’s using the tools in the lib.

Creating a Scene
In a 3D world, you will always need the same 3 things:

All 3D libs are similar in this way. You’re going to put objects in your scene, view them thru the camera and render it in real-time. Rendering is kind of like an update function that will re-render the scene to show the updates in real time. The camera is necessary to the application so you know what view to render the scene from.

3D rotating cube using WebGL
Whenever I’m using a new 3D library, I always like to start out by making a cube, rotate it and maybe add a texture map. In this particular case, if we wanted to add a texture map, we’ll have to host our project online. Due to security issues we can’t load the texture locally. There is a work around for it, but for the purposes of this tutorial we’ll just host our sample. You can see a sample here.

HTML & JavaScript

  1. <!DOCTYPE html PUBLIC>
  2. <head>
  3. <title>Three.js cube geometry with skin</title>
  4. <meta charset="utf-8">
  5. <style src="css/style.css"></style>
  8. </head>
  10. <body>
  11.  <script src="js/Three.js"></script>
  13.     <script>
  15.  window.requestAnimFrame = (function(callback){
  16.         return window.requestAnimationFrame ||
  17.         window.webkitRequestAnimationFrame ||
  18.         window.mozRequestAnimationFrame ||
  19.         window.oRequestAnimationFrame ||
  20.         window.msRequestAnimationFrame ||
  21.         function(callback){
  22.             window.setTimeout(callback, 1000 / 60);
  23.         };
  24.     })();
  26.  function animate(lastTime, angularSpeed, three) {
  27.   //update
  28.   var date = new Date();
  29.   var time = date.getTime();
  30.   var timeDiff = time - lastTime;
  31.   var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
  32.   three.cube.rotation.y +=angleChange;
  33.   lastTime = time;
  35.   //render
  36.   three.renderer.render(three.scene,;
  38.   // request new frame
  39.   requestAnimFrame(function() {
  40.    animate(lastTime, angularSpeed, three);
  41.   });
  42.  }
  44.  window.onload = function() {
  46.   console.log("window loaded!")
  47.   var angularSpeed = 0.2; //revolutions persecond
  48.   var lastTime = 0;
  50.   //CREATE RENDERER (use WebGL)
  51.   var renderer = new THREE.WebGLRenderer();
  52.   renderer.setSize(window.innerWidth, window.innerHeight);
  53.   document.body.appendChild(renderer.domElement);
  55.   // CREATE CAMERA
  56.   var camera = new THREE.PerspectiveCamera(80, window.innerWidth/window.innerHeight, 1, 1000);
  57.   camera.position.z = 700;
  58.   //camera.position.y = 500;
  60.   // CREATE SCENE
  61.   scene = new THREE.Scene();
  63.   // Mats
  64.   var MLMatz = new THREE.MeshLambertMaterial({
  65.    map: THREE.ImageUtils.loadTexture("images/texture.jpg")
  66.   });
  68.   //Cube
  69.   var cube = new THREE.Mesh(new THREE.CubeGeometry(100,100,100), MLMatz);
  70.   cube.overdraw = true;
  71.   scene.add(cube);
  73.   // add ambient lighting
  74.   var ambientLight = new THREE.AmbientLight(0×555555);
  75.   scene.add(ambientLight);
  77.   // add directional light
  78.   var directionalLight = new THREE.DirectionalLight(0xffffff);
  79.   directionalLight.position.set(1,1,1).normalize();
  80.   scene.add(directionalLight);
  82.   //create wrapper that contains three.js objects
  83.   var three = {
  84.    renderer: renderer, camera: camera, scene: scene, cube: cube
  85.   };
  87.   // wait for texture image to load before starting animation
  88.   var textureImg = new Image();
  89.   textureImg.onload = function() {
  90.     animate(lastTime, angularSpeed, three, this);
  91.   };
  92.   textureImg.src = "images/texture.jpg"
  95.  };
  97.  </script>
  98. </body>
  99. </html>

If you test this in your browser you will likely see a black cube rotating without textures, because of the security issue I spoke about however if you hosted the sample folder, you’d see the texture. It should look like the image below.

I’ll go over it line by line below.

Line 9 we import our Three.js scripts (as described in Day2 of this series)

Line 13 is our requestAnimFrame . using requestAnimFrame instead of a setTimeout because this method is optimized by the browser particularly for animation. The browser doesn’t know what your intentions are with setTimeout, but with requestAnimFrame your browser knows what you are trying to do and will assist you.

Line 46 window.onload = function() {} if you remember from previous days lessons is how we wait for all the HTML to be loaded before we call JavaScript.

Inside the anonymous function we set a couple of vars, angularSpeed and lastTime. angularSpeed is revolutions per second and lastTime is derived from the computer clock and is a way to make sure the animations are smooth and there is no render lag.

Line 52 We create the renderer

Line 57
We create the camera

Line 62 we create the scene

Line 65 we create the materials or texture map

Line 71 we create the cube

Line 75 we rite the ambient light

Line 80 we create a directional light

Line 85 we create a wrapper or container that holds all the objects and inside we set the vars for the renderer, camera, scene and cube.

Line 90 we load a texture by creating a Image class. When the image loads we call an anonymous function and in there we call the animate function, passing in 4 params, lastTime, angular speed, three and this. We’ll cover these params as we go along. The important takeaway from this paragraph is that we don’t start our animation until the texture is loaded.

Line 26 we call the animate function and pass it three params lastTime, angularSpeed and three). basically current time, speed and the wrapper

The most important things we’re doing here is setting up smooth animation with date, time and timeDiff. This is done the same way in Flash and other rendering engines, we use the computer’s internal clock to get the current time and use that to make sure we don’t get any elasticity or rubber banding in our animation. I’m not going to get into the date, time, timeDiff variables too much in this tutorial.

The most important math being done here is angleChange. this is where we use a lot of math to come up with basically a rotation speed.

on Line 33 we rotate our cube using angleChange.

on Line 37 we have our container renderer rener the scene and pass in the scene and the camera as params.

on line 40 we call requestAnimFrame and pass in an anonymous function to call animate again. This creates a continous loop that essentially fires off animate over and over again. Animate calls render and that is how we update our geometry in real time.

Thats it for a simple cube with texture using WebGL. I will hopefully do some more tutorials in the future using Three.js to cover how to import a collada or how to output a model in Blender to JSON Three.js format as well as using Canvas to create 3D animations instead of WebGL.

Posted on July 10, 2012 at 9:21 am by Runtime · Permalink
In: HTML5, JavaScript, Three.js, Tutorial, animation

Leave a Reply

You must be logged in to post a comment.