Thursday, October 6, 2011

porting to the newest version of three.js release r45

Three.js recently updated to release r45 which includes major changes in the way cameras are set up. Basically the camera and the "controller" which moves around the users viewpoint have been separated. This post is to walk through the steps of changing from the old style of camera setup to the new.

Old setup

Initialization
function init() {
 container = document.createElement("div");
 document.body.appendChild(container);

 scene = new THREE.Scene();

 addCamera();
 addLights();
 addGrid();
 for (var i = 0; i <= NUM; i++)  //add cubes and projectile to the scene
  createCube(i);

 renderer = new THREE.WebGLRenderer();  //set up for rendering
 renderer.setSize(window.innerWidth, window.innerHeight);
 renderer.setClearColor(new THREE.Color(0x99CCFF), 1);

 container.appendChild(renderer.domElement);

 setEventListeners();  //double click to fire cannon
}
Camera Setup
function addCamera() {
 camera = new THREE.TrackballCamera({
  fov: 60,
  aspect: window.innerWidth / window.innerHeight,
  near: 1,
  far: 1e3,
  rotateSpeed: 1.0,
  zoomSpeed: 1.2,
  panSpeed: 0.8,
  noZoom: false,
  noPan: false,
  staticMoving: true,
  dynamicDampingFactor: 0.3,
  keys: [65, 83, 68]
 });
 camera.position.x = -15;
 camera.position.y = 6;
 camera.position.z = 15;
 camera.target.position.y = 6.0;
}
Render Loop
function render() {
 // Resize client if necessary
 if (w !== window.innerWidth || h !== window.innerHeight) {
  renderer.setSize(window.innerWidth, window.innerHeight);
  // set old sizes for comparison again
  w = window.innerWidth, h = window.innerHeight;
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
 }

 renderer.render(scene, camera);
}

Step 1 - The Init() needs to have an "addControls" function after the renderer is setup and an "onWindowResize" handler is a much better practice than having an "if resized" in the middle of the render loop
function init() {
 container = document.createElement("div");
 document.body.appendChild(container);

 scene = new THREE.Scene();

 addCamera();
 addLights();
 addGrid();
 for (var i = 0; i <= NUM; i++)  //add cubes and projectile to the scene
  createCube(i);

 renderer = new THREE.WebGLRenderer();  //set up for rendering
 renderer.setSize(window.innerWidth, window.innerHeight);
 renderer.setClearColor(new THREE.Color(0x99CCFF), 1);

 container.appendChild(renderer.domElement);

 addControls();  //NEW with R45

 setEventListeners();  //double click to fire cannon
 window.addEventListener( 'resize', onWindowResize, false );  //NEW with R45
 
} 
Step 2 Many of the camera parameters have migrated to the controller so our addCamera() function is smaller - NOTE the removal of all "camera.target" code which will have to migrate to the controller
function addCamera() {
 
 camera = new THREE.PerspectiveCamera(60,window.innerWidth / window.innerHeight,1,1e3); 
 camera.position.x = -15;
 camera.position.y = 6;
 camera.position.z = 15;
 //camera.target.position.y = 6.0;  //camera.target doesn't exist in r45 
}
Step 3 Many of the old camera parameters and a few snazzy new ones are used to make a controller for the camera.
function addControls(){
 controls = new THREE.TrackballControls( camera, renderer.domElement );
 controls.rotateSpeed = 1.0;
 controls.zoomSpeed =1.2;
 controls.panSpeed = 0.8;
 controls.noZoom = false;
 controls.noPan = false;
 controls.staticMoving = true;
 controls.dynamicDampingFactor =  0.3;
 controls.keys = [65, 83, 68];
 controls.minDistance = 1.1;  //new with r45
 controls.maxDistance = 100;  //new with r45
}
Step 4 The new window resize handler changes the all relevant properties including the controller which would otherwise act strangely by miscalculating various mouse clicks and drags in a resized window
function onWindowResize( event ) {

    width = window.innerWidth;
    height = window.innerHeight;

    renderer.setSize( width, height );

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    controls.screen.width = width;  //new in r45
    controls.screen.height = height; //new in r45

    camera.radius = ( width + height ) / 4;

 };
Step 5 Render is cleaner without the old resize branch and a new function call is made each loop to the controller with "controls.update()"
function render() {
 controls.update();  //new in r45

 renderer.clear();
 renderer.render(scene, camera);
}

There are a few more changes in r45 - notably scene.add is now overloaded so various lights and objects can be added with scene.add(something) but you code should now run and in the future it will be easier to make custom controllers which are separate from the camera

strelz