Tuesday, July 17, 2012

Getting Started with Three.js

‹prev | My Chain | next›

I have a pretty cool walking avatar implemented in Gladius. I still have several challenges remaining to me as I get ready for the alpha release of Gaming JavaScript. Perhaps the biggest is the amount of syntax that I want the kids to have to write in order to make an avatar.

So tonight, I take a step back from Gladius and take a look at Three.js. Like CubicVR.js, Three.js is a 3D rendering engine. Unlike CubicVR.js, Three.js aims to make things as simple as possible. That sounds like it might be useful to me.

I start by downloading Three.js into my app:
$ cd scripts
$ wget http://mrdoob.github.com/three.js/build/Three.js
I start with a very simple HTML page that links both Three.js and an avatar.js file that will define my avatar:
<!DOCTYPE html>
<html>
  <head>
    <script src="/scripts/Three.js"></script>
    <script src="/scripts/avatar.js"></script>
    <title>Avatar</title>
  </head>
  <body>
    <h1>Three.js Avatar</h1>
  </body>
</html>
As for the avatar.js file, I mostly stick to the example on the Three.js site. I start by declaring some globals and initializing the view after the DOM has loaded:
var camera, scene, renderer, avatar;

document.addEventListener( "DOMContentLoaded", function( e ) {
  init();
  animate();
});
The latter function called when the DOM is loaded, animate(), is an exact copy from the Three.js example:
function animate() {
  // note: three.js includes requestAnimationFrame shim
  requestAnimationFrame(animate);
  render();
}

function render() {
  renderer.render(scene, camera);
}
The init() function also retains most of the example. It defines a scene, adds a camera, and creates a canvas renderer that is added to the page's DOM. Instead of the cube example, however, I add a call to a new buildAvatar() function:
function init() {
  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
  camera.position.z = 1000;
  scene.add(camera);

  avatar = buildAvatar();
  scene.add(avatar);

  renderer = new THREE.CanvasRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);

  document.body.appendChild(renderer.domElement);
}
That buildAvatar() function is where I get started in earnest. Unlike in CubicVR/Gladius where I set entities as parents of each other, I have to group objects together in Three.js. That is accomplished by creating an empty Object3D object so that I can use its add() method to add the head and the body:
function buildAvatar() {
  var avatar = new THREE.Object3D();

  var material = new THREE.MeshNormalMaterial();

  var body_geometry = new THREE.CylinderGeometry(1, 300, 300);
  var body = new THREE.Mesh(body_geometry, material);
  body.position.z = -150;
  avatar.add(body);

  var head_geometry = new THREE.SphereGeometry(200);
  var head = new THREE.Mesh(head_geometry, material);
  head.position.y = 200;
  avatar.add(head);

  return avatar;
}
The result is a somewhat blocky looking avatar:


I am not too fussed about the blockiness of the avatar; a quick inspection of the source code shows that additional constructor arguments can help with that.

As for the limbs, I start using my favorite thing: frame-of-reference. Specifically, I create a limb, add the arm and the hand to the limb with simple geometry:
  var right_limb = new THREE.Object3D();

  var right_arm_geometry = new THREE.CylinderGeometry(25, 25, 500);
  var right_arm = new THREE.Mesh(right_arm_geometry, material);
  right_limb.add(right_arm);

  var right_hand_geometry = new THREE.SphereGeometry(75);
  var right_hand = new THREE.Mesh(right_hand_geometry, material);
  right_hand.position.y = 250;
  right_limb.add(right_hand);
I can then position the arm and the hand relative to the rest of the avatar by rotating just the limb:
  right_limb.position.x = 150;
  right_limb.position.z = -50;
  right_limb.rotation.z = -Math.PI/3;

  avatar.add(right_limb);
I convert that into a factory function for generating limbs:
function limb(material) {
  var limb = new THREE.Object3D();

  var arm_geometry = new THREE.CylinderGeometry(25, 25, 500);
  var arm = new THREE.Mesh(arm_geometry, material);
  limb.add(arm);

  var hand_geometry = new THREE.SphereGeometry(75);
  var hand = new THREE.Mesh(hand_geometry, material);
  hand.position.y = 250;
  limb.add(hand);

  return limb;
}
And then add the remaining legs and arms. The result is:


That is not bad for a night's work. I am unsure if the Three.js syntax or my familiarity with the topic is more deserving of credit the quickness with which I assembled this. As usual, the answer is probably somewhere in the middle.

I do like the grouping approach to frame of reference. I think that might make more sense to kids. Luckily I know some on which I can experiment. Up tomorrow: animating this Three.js avatar.


Day #450

No comments:

Post a Comment