Playing around with three.js

iPhone

3D iPhone utilizing three.js

I thought it would be fun to play around with three.js so I created a simple spinning iPhone. Rendering is handled by WebGL so it’s only viewable in browsers that have support (Chrome, Firefox 4+).

View it here

Three.js is a javascript 3d engine.  According to their README:

The aim of the project is to create a lightweight 3D engine with a very low level of complexity — in other words, for dummies. The engine can render using <canvas>, <svg> and WebGL.

The code is pretty simple.   First you’ll need some basic layout markup.

<!DOCTYPE html>
<html>
  <head>
    <title>iScreenShot</title>
  </head>
  <body>
    <header id="page-header">
      <h1>iScreenShot</h1>
    </header>

    <!-- Content -->
    <section id="page-content">
      <div id="iphone"></div>
    </section>

    <script type="text/javascript" src="/js/three/Three.js"></script>
    <script type="text/javascript" src="/js/three/RequestAnimationFrame.js"></script>
    <script type="text/javascript" src="/js/three/Detector.js"></script>
    <script type="text/javascript" src="/js/main.js"></script>
  </body>
<html>

You can find the three.js library javascript files in the three.js repository.  The markup should require little explanation.  The only thing of real note is the empty #iphone div that will be targeted by the code in main.js.

Now we’ll need the javascript to set up the scene and mouse interaction.  The contents of main.js follows:

(function($) {

  var camera, scene, renderer,
  geometry, material, mesh;

  // Set up some variables and add a mousemove handler to the page
  var mouseX = 0,
      mouseY = 0,
      windowCentreX = window.innerWidth / 2,
      windowCentreY = window.innerHeight / 2;

  document.addEventListener( 'mousemove', function( event ) {
    // Update mouseX and mouseY based on the new mouse X and Y positions
    mouseX = ( event.clientX - windowCentreX );
    mouseY = ( event.clientY - windowCentreY );
  }, false );

  init();
  animate();

  function init() {
    var width = $('#iphone').width(),
        height = 400;

    camera = new THREE.Camera( 75, width / height, 1, 10000 );
    camera.position.z = 550;

    scene = new THREE.Scene();

    var materials = [
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xffffff, map: THREE.ImageUtils.loadTexture( '/images/iphone-front.png' ) } ),
      new THREE.MeshLambertMaterial ( { color: 0xffffff, map: THREE.ImageUtils.loadTexture( '/images/iphone-rear.png' ) } )
    ];

    cube = new THREE.CubeGeometry( 254, 493, 30, 4, 4, 2, materials );
    mesh = new THREE.Mesh( cube, new THREE.MeshFaceMaterial() );
    scene.addObject( mesh );

    renderer = new THREE.WebGLRenderer();
    renderer.setSize( width, height );

    $('#iphone').append( renderer.domElement );
  }

  function animate() {
    mesh.rotation.y = mouseX * 0.005;
    mesh.rotation.x = mouseY * 0.005;

    requestAnimationFrame( animate );
    render();
  }

  function render() {
    renderer.render( scene, camera );
  }

})(jQuery);

Let’s break this down to see how it works.

First, we setup up some scoped variables and logic to handle the mouse behavior.

(function($) {

  var camera, scene, renderer,
  geometry, material, mesh;

  // Set up some variables and add a mousemove handler to the page
  var mouseX = 0,
      mouseY = 0,
      windowCentreX = window.innerWidth / 2,
      windowCentreY = window.innerHeight / 2;

  document.addEventListener( 'mousemove', function( event ) {
    // Update mouseX and mouseY based on the new mouse X and Y positions
    mouseX = ( event.clientX - windowCentreX );
    mouseY = ( event.clientY - windowCentreY );
  }, false );

  ...

Next, we’ll execute the two main functions for rendering and animating the graphics.

  init();
  animate();

Now for the good stuff. First we need to set up our three.js scene and camera.

    var width = $('#iphone').width(),
        height = 400;

    camera = new THREE.Camera( 75, width / height, 1, 10000 );
    camera.position.z = 550;

    scene = new THREE.Scene();

Here’s where we set up the materials for the cube. See, we’re not actually rendering a fully realized iPhone 3D model. A reasonably cool visual trick can be achieved by just rendering a stretched cube with an iPhone skin slapped on. The materials below define how each side of the cube will look.

var materials = [
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
      new THREE.MeshLambertMaterial ( {
          color: 0xffffff,
          map: THREE.ImageUtils.loadTexture( '/images/iphone-front.png' )
      } ),
      new THREE.MeshLambertMaterial ( {
          color: 0xffffff,
          map: THREE.ImageUtils.loadTexture( '/images/iphone-rear.png' )
      } )
    ];

Finally, we create the actual cube, add it to the scene, initialize the renderer and append it to our empty div.

    cube = new THREE.CubeGeometry( 254, 493, 30, 4, 4, 2, materials );
    mesh = new THREE.Mesh( cube, new THREE.MeshFaceMaterial() );
    scene.addObject( mesh );

    renderer = new THREE.WebGLRenderer();
    renderer.setSize( width, height );

    $('#iphone').append( renderer.domElement );

For a finishing touch we allow the 3D model to be rotated by defining some animation. It’s a simple matter of translating the cube mesh in the x and y directions.

  function animate() {
    mesh.rotation.y = mouseX * 0.005;
    mesh.rotation.x = mouseY * 0.005;

    requestAnimationFrame( animate );
    render();
  }

  function render() {
    renderer.render( scene, camera );
  }

I’m just barely diving into the library, but working with 3D graphics is definitely a welcome change of pace from the static content I’m working with most of the time.