Tuesday, July 31, 2012

Image Materials in Three.js

‹prev | My Chain | next›

It's alpha release date for Gaming JavaScript, so this will (hopefully) be a short post.

In Gladius / CubicVR.js, I got pretty good at applying images to the objects in my animations. I would like understand how to do the same in Three.js. I don't think I need to get into bump maps and the like. But it would be nice to at least put a face on my avatar.

So I create a 256 pixel high "face" image (remember that 256 is a magic number):


Next, I load that image and attempt to create a texture from it:
function buildAvatar() {
  // ...
  var img = new Image();
  img.src = 'face.png';
  var texture = new THREE.Texture(img);

  var head_material = new THREE.MeshPhongMaterial({map: texture});
  var head_geometry = new THREE.SphereGeometry(75);
  var head = new THREE.Mesh(head_geometry, head_material);
  head.position.y = (100 + 35) * .8;
  avatar.add(head);
  // ...
}
With that, I get:


Well, that's not quite what I was hoping for.

Ah! There are no lights in this drawing. I am using nothing but normal materials (they "shine" on their own). So let's add a light to the avatar:
  var light = new THREE.PointLight(0xffffff, 10);
  light.position.set(50, 150, 150);
  avatar.add(light);
Hrm... that does not actually help. Now I just have a shiny black head:


At this point, I am at a bit of a loss, so I start digging through Three.js code. Eventually, I happen across the ImageUtils, which include a loadTexture() function that seems to be doing just about the same thing that I'm trying here. In fact, the code is very close to what I had tried, save for an onload() callback on the image that marks the texture as needing an update.

It seems that Three.js will silently ignore the image without this. So I copy the onload() function into my code:
  var img = new Image();
  var texture = new THREE.Texture(img);
  img.onload = function () { texture.needsUpdate = true; };
  img.src = 'face.png';
  texture.needsUpdate = true;
And behold, I have my avatar with a face:


Sure, that face leaves much to be desired, but it is a functioning start. And a good place to stop tonight. Alpha copies of Gaming JavaScript coming soon!


Day #464

Monday, July 30, 2012

Fallback from WebGL to Canvas in Three.js

‹prev | My Chain | next›

My daughter pointed out that avatar island is not working on her older machine. So tonight, I hope to get a fallback working such that, if the browser does not support WebGL, it will still render just fine with canvas.

Three.js includes a example detector similar to Modernizr. I don't need the full detector, but I will use the canvas and WebGL detectors:
var canvas = !! window.CanvasRenderingContext2D;
var webgl = ( function () { try { return !! window.WebGLRenderingContext ∓& !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )();
Then, for the renderer, I do a simple conditional check:
  if (webgl) renderer = new THREE.WebGLRenderer();
  else if (canvas) renderer = new THREE.CanvasRenderer();
  if (renderer) {
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColorHex(0x87CEEB);
    document.body.appendChild(renderer.domElement);
  }
  else {
    alert("Can't make renderer. What kind of browser are you using?!");
  }
And this seems to work. Kind of.

On my modern browser, I can see avatar island as it is meant to be:


On my daughter's machine, however, I see:


The missing planes actually appear and disappear as the avatar moves about its island. After some Googling, I find that this is related to an old, non-fixable bug in Three.js' CanvasRenderer. As suggested in the bug report, I try subdividing the geometries of the island and the sea:
 // Sea
  var seaGeometry = new THREE.PlaneGeometry(2*ISLAND_WIDTH, 2*ISLAND_WIDTH, 4, 2)
    , seaMaterial = new THREE.MeshBasicMaterial({color: 0x483D8B})
    , seaMesh = new THREE.Mesh(seaGeometry, seaMaterial);
  seaMesh.position.y = -1;
  scene.add(seaMesh);

  // Island
  var islandGeometry = new THREE.PlaneGeometry(ISLAND_WIDTH, ISLAND_WIDTH, 64, 8)
    , islandMaterial = new THREE.MeshNormalMaterial({color: 0x7CFC00});
  island = new THREE.Mesh(islandGeometry, islandMaterial);
  scene.add(island);
This helps slightly:


But in reality, it kills my CPU and the results are not nearly satisfactory. So I think this means that Gaming JavaScript is going to require WebGL capable browsers. And I'm going to need to install a newer browser for my daughter.


Day #463

Sunday, July 29, 2012

Lights and Materials in Three.js

‹prev | My Chain | next›

Up today I am going to play around a bit with Three.js lighting and material. I have my avatar island pretty much as far as I am going to take it. Before switching back to Gladius, however, I would like to take a quick look at something that Gladius does well: materials.

For the most part during my Three.js experiment, I have been using MeshNormalMaterial:
var material = new THREE.MeshNormalMaterial();

// The head
var head_shape = new THREE.SphereGeometry(75);
var head = new THREE.Mesh(head_shape, material);
head.position.y = (100 + 35) * .8;
scene.add(head);

// The body
var body_shape = new THREE.CylinderGeometry(1, 100, 100);
var body = new THREE.Mesh(body_shape, material);
scene.add(body);
The normal material produces a flat, colorful surface on the mesh, even without any lighting:


The normals, the faces pointing out, are given a nice coloring to make them distinctive from each other. It works well especially when first getting started.

I have also been making extensive use of MeshBasicMaterial:
var material = new THREE.MeshBasicMaterial({
  color: 0x0000bb
});
This produces:


Again, I do not have any external lighting. This material has its own intrinsic color emanating from it. It is a bit unrealistic in that sense, but it works well for islands, skyboxes and other scenery.

But, if I add a simple light to the scene:
var light = new THREE.PointLight();
light.position.set(50, 150, 150);
scene.add(light);
I still get the same look:


According to the PointLight documentation, this is to be expected. The PointLight has an effect with the MeshPhongMaterial (named for Phong Shading) and MeshLambertShading (see Lambert Reflectance).

Leaving the PointLight in place, I change the material to MeshPhongMaterial:
var material = new THREE.MeshPhongMaterial({
  color: 0x0000bb
});
This results in a more satisfactory 3D illumination:


Switching to MeshLambertMaterial:
var material = new THREE.MeshLambertMaterial({
  color: 0x0000bb
});

Produces a slightly duller (more matte) version:


For now, I am going to stick with the Phong material, but up the shininess to 50 (the default is 30):
var material = new THREE.MeshPhongMaterial({
  color: 0x0000bb,
  shininess: 50
});
Then I try a series of lights at 10 intensity:
var light = new THREE.PointLight(0xffffff, 10);
light.position.set(50, 150, 150);
scene.add(light);
The point light results in:


Next, a directional light:
var directionalLight = new THREE.DirectionalLight(0xffffff, 10);
directionalLight.position.set(50, 150, 150);
directionalLight.target = head;
scene.add( directionalLight );
Which results in:


The spotlight is defined as:
var spotLight = new THREE.SpotLight( 0xffffff, 10);
spotLight.position.set( 50, 150, 150 );
scene.add( spotLight );
And looks like:


That is a pretty hectic introduction to all of this stuff, but I have a better grasp then when I started. There is also a pretty excellent example app demonstrating the differences between different materials with a flying light source.




Day #462

Saturday, July 28, 2012

River in Three.js

‹prev | My Chain | next›

As of last night, I have my sample avatar island, written in Three.js, all set up with an invisible fence keeping my avatar on the island:


Just to make sure I really understand this stuff, I am going to add a river running through the island that my avatar is also prevented from crossing. To start with, I switch my intersectObject() (singular) collision detection with the plural intersectObjects():
function detectCollision() {
  var x, z;
  if (controls.moveLeft) z = 1;
  if (controls.moveRight) z = -1;
  if (controls.moveBackward) x = 1;
  if (controls.moveForward) x = -1;

  var vector = new THREE.Vector3( x, 0, z );
  var ray = new THREE.Ray(controls.object.position, vector);
  var intersects = ray.intersectObjects(blockers);

  if (intersects.length > 0) {
    if (intersects[0].distance < 75) {
      if (controls.moveLeft) controls.moveLeft = false;
      if (controls.moveRight) controls.moveRight = false;
      if (controls.moveBackward) controls.moveBackward = false;
      if (controls.moveForward) controls.moveForward = false;
    }
  }
}
Instead of checking for the intersection of a single object like last night, I am now checking for an array of blockers.

Now I need a river:
function river() {
  var river = new THREE.Object3D()
    , riverMaterial = new THREE.MeshBasicMaterial({color: 0x483D8B})
    , wallMaterial = new THREE.MeshBasicMaterial()
    , width = 400;

  // actual river here...
}
I am going to build this as two separate pieces: the top and bottom. A small gap between the two segments will serve as a natural bridge over which the avatar can walk. The top segment is then:
  var top = new THREE.Object3D();

  waterShape = new THREE.PlaneGeometry(ISLAND_WIDTH/2, width);
  water = new THREE.Mesh(waterShape, riverMaterial);
  wallShape = new THREE.CubeGeometry(ISLAND_WIDTH/2, 10000, width);
  wall = new THREE.Mesh(wallShape, wallMaterial);
  wall.visible = false;
  top.add(water);
  top.add(wall);
  top.position.x = -ISLAND_WIDTH/3;
  top.position.z = ISLAND_WIDTH/4;

  // list of water that blocks avatar
  blockers.push(wall);

  river.add(top);
Here, I create two shapes, the visible blue water and the invisible wall. Both are half the distance of the entire island long and width (400) units wide. The wall is also 10,000 pixels tall, which should prevent the avatar from getting over without a really tall ladder.

Both the wall and the water are added to the top segment, placed to the left and top of the island and added to the river object. I also add the wall to the list of objects that will block the avatar.

After doing the same for the bottom segment, but shifting the X position accordingly, I have my river:


I am prevent from crossing the river, but I am, indeed, able to cross the "natural bridge":


It would be nice to be able to build the river planar object based on the intersection of the wall and the island (which is also a plane). This would make for an easy way to build arbitrary barrier shapes that project visibly onto the ground. Alas, there does not seem to be an easy way to do this.


Day #461

Friday, July 27, 2012

Fencing My Three.js Avatar in with Collisions

‹prev | My Chain | next›

Up tonight, I hope to put my Three.js collision detection skills to good use. Specifically, I would like to finally replace the simple boundary limit detection that I have keeping my avatar on the island:


The boundary checking that prevents the avatar from taking that next step could not be simpler:
function render() {
  // ...
  if (controls.object.position.z >  ISLAND_HALF) controls.moveLeft = false;
  if (controls.object.position.z < -ISLAND_HALF) controls.moveRight = false;
  if (controls.object.position.x >  ISLAND_HALF) controls.moveBackward = false;
  if (controls.object.position.x < -ISLAND_HALF) controls.moveForward = false;
  // ...
}
The controls for the avatar are prevented from moving left if the avatar is half way from the center of the island. Similarly, the controls are prevented from moving forward if the avatar (controls.object) is already at the topmost half of the island.

As an aside, I am not quite certain why Three.js' FirstPersonControls rotate the coordinate system like this. Normally, the positive z-axis comes out of the screen; here it is to the left. Normally, the positive x-axis is to the right; here it is coming out of the screen. In other words, the orientation is rotated 90° clockwise. No doubt, FirstPersonControls does this for a reason (it seems related to a target), but it proves a bit of an annoyance.

Anyhow, I need an invisible fence around my island. Last night I made a wall with something like:
  var wallGeometry = new THREE.CubeGeometry(ISLAND_WIDTH, 1000, 100)
    , wallMaterial = new THREE.MeshBasicMaterial({wireframe: true})
    , wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
  wallMesh.position.z = ISLAND_HALF;
  scene.add(wallMesh);
With the wireframe option, this renders:


Instead of building four individual walls, it seems easier to place a cube around then entire island. I establish my invisible (well, not-so-invisible now) fence with:
  var fenceGeometry = new THREE.CubeGeometry(ISLAND_WIDTH, 1000, ISLAND_WIDTH, 3, 1, 3)
    , fenceMaterial = new THREE.MeshBasicMaterial({wireframe: true});
  fence = new THREE.Mesh(fenceGeometry, fenceMaterial);
  fence.flipSided = true;
  scene.add(fence);
To detect if the avatar intersects, I call my detectCollisions() function from the render() method:
function render() {
 // ...
 detectCollision();
 // ...
}

function detectCollision() {
  var vector = controls.target.clone().subSelf( controls.object.position ).normalize();
  var ray = new THREE.Ray(controls.object.position, vector);
  var intersects = ray.intersectObject(fence);

  if (intersects.length > 0) {
    if (intersects[0].distance < 5) {
      console.log(intersects);
    }
  }
}
The problem with the current detectCollisions() is that the vector is always pointing in the same direction. This results in collisions only being detected when I reach the maximum X distance.

Instead I need to calculate the vector direction based on the direction in which the avatar is currently travelling:
function detectCollision() {
  var x, z;
  if (controls.moveLeft) z = 1;
  if (controls.moveRight) z = -1;
  if (controls.moveBackward) x = 1;
  if (controls.moveForward) x = -1;

  var vector = new THREE.Vector3( x, 0, z );
  var ray = new THREE.Ray(controls.object.position, vector);
  var intersects = ray.intersectObject(fence);

  if (intersects.length > 0) {
    if (intersects[0].distance < 5) {
      console.log(intersects);
    }
  }
}
It is a bit of a pain to calculate the X's and Z's manually like this, but it does the trick. When I move through any of the walls, I see the desired console.log() output:


The last thing remaining is to prevent the avatar from moving past the wall. For that, I need to do a little more than logging the collision. I need to determine the direction in which I am moving and stop. I just stored the direction information in x and z variable so I reuse those. And, based on the direction, I disable the corresponding controls.move* boolean:
function detectCollision() {
  var x, z;
  if (controls.moveLeft) z = 1;
  if (controls.moveRight) z = -1;
  if (controls.moveBackward) x = 1;
  if (controls.moveForward) x = -1;

  var vector = new THREE.Vector3( x, 0, z );
  var ray = new THREE.Ray(controls.object.position, vector);
  var intersects = ray.intersectObject(fence);

  if (intersects.length > 0) {
    if (intersects[0].distance < 50) {
      if (z ==  1) controls.moveLeft = false;
      if (z == -1) controls.moveRight = false;
      if (x ==  1) controls.moveBackward = false;
      if (x == -1) controls.moveForward = false;
    }
  }
}
And, that actually works! I am now stuck on the island:


I am not 100% sold on that implementation, but it does the trick.

Day #460

Thursday, July 26, 2012

Collision Detection in Three.js

‹prev | My Chain | next›

I was not quite able to get collision detection working in Three.js last night. I don't really need it for my immediate need, which is keeping my avatar on its tropical island:


I can already achieve the necessary boundary limits with simple position checks. Instead, I would like to use collision detection to do this. So I create a wall:
  var wallGeometry = new THREE.CubeGeometry(100, 1000, 5000, 5, 5, 5)
    , wallMaterial = new THREE.MeshBasicMaterial({color: 0xFFD700})
    , wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
//  wallMesh.position.x = -M*0.6;
  scene.add(wallMesh);
And try to detect a collision with this wall on every render():
function render() {
  // ...
  detectCollision();
  // ...
}
Collision detection is a three step process in Three.js. First, I need to know where I am and in which direction I am facing. That is, I need a Ray. Second I need to know if that Ray crosses another object (the wall in this case). Lastly, I need to know the distance from the object. Last night, with the help of a Three.js "bug report", I cobbled together a collision detection algorithm of:
function detectCollision() {
  var vector = controls.target.clone().subSelf( controls.object.position ).normalize();
  var ray = new THREE.Ray(controls.position, vector);
  var intersects = ray.intersectObjects(walls);

  if (intersects.length > 0) console.log(intersects);
}
This failed to work, even when the player was standing in the middle of the wall:


After debugging this some, I realize that my problem is that my Ray has a null position. Earlier in the function, I got the position of my avatar from controls.object.position. For the Ray, I am just trying controls.position, which is undefined. Such is the price of copying code without really understanding it.

In the end, I also have to add a distance check:
function detectCollision() {
  var vector = controls.target.clone().subSelf( controls.object.position ).normalize();
  var ray = new THREE.Ray(controls.object.position, vector);
  var intersects = ray.intersectObjects(walls);

  if (intersects.length > 0) {
    if (intersects[0].distance < 5) {
      console.log(intersects);
    }
  }
}
But, with that, I have my wall collision detection working. I call it a night here. Up tomorrow, I will rework this into a useful island boundary, which should conclude my Three.js work.


Day #459

Wednesday, July 25, 2012

Simple Three.js Bounds, If Not Collisions

‹prev | My Chain | next›

I am nearly done with my Three.js avatar. I have an island for it to walk around. I have the arms and legs animated as it walks. The avatar even spins in the right direction when walking. The only problem left is that I can walk on water:


It is easy enough to bound the avatar. In the render() method, I check the X and Z position of the avatar, stopping motion if necessary:
function render() {
  // ...
  if (controls.object.position.z >  2800) controls.moveLeft = false;
  if (controls.object.position.z < -2800) controls.moveRight = false;
  if (controls.object.position.x >  2800) controls.moveBackward = false;
  if (controls.object.position.x < -2800) controls.moveForward = false;

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

  controls.update(clock.getDelta());
}
The FirstPersonControls are tied to the avatar. So I can access the avatar as controls.object. Then it is a simple matter of disabling the controls before the call to controls.update().

With that, I am prevented from leaving the island:


That is probably sufficient for my needs, but I would like to get collision detection working. My understanding is that rays are needed for collision detected. So I add a wall whose collision that I would like to detect and implement a collision detection function in render():
function detectCollision() {
  var vector = controls.target.clone().subSelf( controls.object.position ).normalize();
  var ray = new THREE.Ray(controls.position, vector);
  var intersects = ray.intersectObjects(walls);

  if (intersects.length > 0) console.log(intersects);
}
Unfortunately, that never works. Even if I place the wall at the origin where the avatar starts, the ray and the walls never intersect.

I am unclear why Rays are needed for collision detection. Position alone ought to be sufficient to determine if two objects overlap. This leads me to believe that I may very well be doing something wrong. But what I am doing wrong eludes me for tonight. I will pick back up with this tomorrow. If I cannot solve it by then, it is probably time to venture back to Gladius land.


Day #458

Tuesday, July 24, 2012

Animating Three.js Rotation

‹prev | My Chain | next›


My Three.js avatar controls are working fairly well. There are a couple of problems still lingering. The first that I hope to fix tonight is that the avatar is facing the wrong direction when moving left or right. I need for it to face left or right, but instead it continues to point forward.

This is not as simple as rotating the avatar about the y-axis (up and down):
function render() {
    // ...
    if (controls.moveLeft) avatar.rotation.y = -Math.PI/2;
    if (controls.moveRight) avatar.rotation.y = Math.PI/2;
    // ...
}
The problem with that approach is that the avatar is added to the entire scene:
  avatar = buildAvatar();
  scene.add(avatar);
By adding to the entire scene, the y-axis for the avatar is at the origin. In other words, if I rotate about the y-axis, I rotate about the origin and end up on the other side of the world.

To make rotation work around the center of the avatar, I need to add a frame of reference for the avatar:
  avatar = buildAvatar();
  var a_frame = new THREE.Object3D();
  a_frame.add(avatar);
  scene.add(a_frame);
Instead of tying the FirstPersonControls to the avatar, I now need to tie them to this frame of reference:
  avatar = buildAvatar();
  var a_frame = new THREE.Object3D();
  a_frame.add(avatar);
  scene.add(a_frame);

  controls = new THREE.FirstPersonControls(a_frame);
  controls.movementSpeed = 10000;
  controls.activeLook = false;
Now, rotating the avatar about the y-axis will rotate around the new a_frame y-axis, so this will work:
function render() {
    // ...
    if (controls.moveLeft) avatar.rotation.y = -Math.PI/2;
    if (controls.moveRight) avatar.rotation.y = Math.PI/2;
    // ...
}
And, indeed it does work as I walk from left to right, I am facing the direction in which I am walking:


Last up tonight, I would like the transition from walking forward to walking to the side to be smoother. Currently, moving to the side immediately renders the avatar facing to the side.

For that, I am going to use Tween, which comes along with Three.js in examples/js/Tween.js. After copying that into my /scripts directory, I update the HTML to source this library in addition to Three.js:
<!DOCTYPE html>
<html>
  <head>
    <script src="/scripts/Three.js"></script>
    <script src="/scripts/Tween.js"></script>
    <script src="/scripts/avatar.js"></script>
    <title>Avatar</title>
  </head>
  <body>
  </body>
</html>
Next, I update the usual Three.js animate() function to update any active "Tweens":
function animate() {
  // note: three.js includes requestAnimationFrame shim
  requestAnimationFrame(animate);
  TWEEN.update();
  render();
}
Without a running "tween", this has no effect. So I add one. The animation that I desire is for the avatar to rotate from its current y-axis frame of reference rotation to some end-angle (e.g. +90° or -90°). In tween-ese, this means starting an object's y property at the avatar's current rotation, describing the end state for the y rotation and the number of milliseconds that the animation should take. Lastly, I need an onUpdate callback that will effect slight change on the avatar:
function spinAvatar(angle) {
  new TWEEN.Tween( { y: avatar.rotation.y } )
      .to( { y: angle }, 100 )
      .onUpdate( function () {
          avatar.rotation.y = this.y;
      } )
      .start();
}
That is a nice, compact DSL for describing changes over time. I like it. It certainly beats trying to manually keep track of the rotation myself in the game's animate() function.

I swap out the immediate angle changes for this spinAvatar() tween:
function render() {
  // ...
  if (controls.moveForward || controls.moveBackward ||
      controls.moveRight || controls.moveLeft) {
    // ...
    if (controls.moveLeft) spinAvatar(-Math.PI/2);
    if (controls.moveRight) spinAvatar(Math.PI/2);
  }
  else {
    spinAvatar(0);
  }
  // ...
}
And it works! I have a nice, smooth rotation immediately when the avatar starts walking, giving the whole thing a nice feel.


Day #457

Monday, July 23, 2012

Three.js First Person Controls

‹prev | My Chain | next›

Today, I hope to make my Three.js avatar move based on human input. I had previously experimented with the FirstPersonControls for the camera movement, but had concluded that it might be better suited for the avatar movement—at least in my "game".

So I start by removing the FlyControls on the camera (it uses many of the was WASD controls) and add FirstPersonControls for the avatar:
  controls = new THREE.FirstPersonControls(avatar);
  controls.movementSpeed = 10000;
The movementSpeed setting was found via trial and error for the camera controls. And indeed, it still seems about right for the avatar, because I am able to maneuver the avatar to the edge of land:


I find it a bit strange that FirstPersonControls rotate the avatar from its normal face-foward direction so that it seems as though it would like to walk from left-to-right:


I can live with that, but I cannot live with what happens if I leave the avatar stationary for a time:


Indeed, it I try walking forward with the "W" key at this point, I walk into the ground:


This may simply be a limitation of applying the FirstPersonControls to an avatar instead of a camera. I set that aside for now, because there are other aspects of avatar motion that bear investigation.

I think I would prefer that the camera follow the avatar as it explores the country, so I no longer add the camera to the Three.js scene. Rather, I add it to the avatar, effectively making the avatar the parent of the camera:
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
  camera.position.z = 1500;
  camera.position.y = 750;
  avatar.add(camera);
And that seems to work:


Again the rotation of the avatar seems to be coming into play here, having a very negative effect on the overall experience.

Eventually, I track this rotation down to the activeLook setting. Specifically, disabling it seems to do the trick:
  controls = new THREE.FirstPersonControls(avatar);
  controls.movementSpeed = 10000;
  controls.activeLook = false;
The activeLook setting appears to tie mouse position with where the object is looking / oriented. Since I want no orientation changes, setting it to false achieves the behavior that I desire.

Last up for tonight is to get the legs of the avatar moving only when it walks. There are no movement events to which I can bind in FirstPersonControls, nor is there a single i-am-moving boolean. So I have to resort to checking the status of a bunch of different movement booleans to get this working:
var w = 500;
function render() {
  var t_float = clock.getElapsedTime()
    , t = t_float * 1000
    , amplitude = (w/2 - Math.abs((t % (2*w)) - w))/w;

  if (controls.moveForward || controls.moveBackward ||
      controls.moveRight || controls.moveLeft) {
    avatar_left_leg.rotation.x  =    amplitude*(Math.PI/6);
    avatar_right_leg.rotation.x = -1*amplitude*(Math.PI/6);

    avatar_left_arm.rotation.x  =    amplitude*(Math.PI/6);
    avatar_right_arm.rotation.x = -1*amplitude*(Math.PI/6);
  }
  renderer.render(scene, camera);

  controls.update(clock.getDelta());
}
With that, I can take a nice seaside stroll:


That seems like a good stopping point for tonight. I think tomorrow I will conclude my Three.js exploration with some collision checking.


Day #456

Sunday, July 22, 2012

WebGL vs Canvas Three.js

‹prev | My Chain | next›

I was eventually able to add a skybox to my Three.js animation yesterday:


The implementation is both slightly disappointing and clunky. Even so, I going to push past that today, starting with something on which my avatar can walk. I start with a sundering sea surrounding a large, very square island:
  // Sea
  var seaGeometry = new THREE.PlaneGeometry(M, M, 3, 3)
    , seaMaterial = new THREE.MeshBasicMaterial({color: 0x483D8B})
    , seaMesh = new THREE.Mesh(seaGeometry, seaMaterial);
  seaMesh.position.y = -1;
  scene.add(seaMesh);

  // Grass
  var grassGeometry = new THREE.PlaneGeometry(M*0.9, M*0.9, 3, 3)
    , grassMaterial = new THREE.MeshBasicMaterial({color: 0x7CFC00})
    , grassMesh = new THREE.Mesh(grassGeometry, grassMaterial);
  scene.add(grassMesh);
The sea is just slightly below (y = -1) the island grass. This seems simple enough so I do not expect any problems, yet I see:


I really do not understand why this renders so poorly. The green grass should completely cover the water and the sky should certainly not protrude out into the water and grass. Perhaps this is simply the limitation of the CanvasRenderer?

To test that theory, I switch to the WebGLRenderer instead:
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);

  document.body.appendChild(renderer.domElement);
When I reload the page, however, I am greeted with a blank screen and WebGL errors in the JavaScript console:
THREE.WebGLRenderer 49                                         Three.js:331
Error creating WebGL context.                                  Three.js:334
Uncaught TypeError: Cannot call method 'getExtension' of null  Three.js:334
I am unsure what caused this, but I have been unable to get any WebGL stuff to work for the past week or so of Chrome canary updates (I am currently using 22.0.1207.1 dev). Lacking any clearer ideas on how to fix this, I start poking around in Chrome's about:flags. There are no obvious webgl-is-disabled settings (the only webgl setting is definitely on), so I start looking at GPU settings. Eventually, I stumble across "Override software rendering list":


So I enable this setting:


And behold, I have WebGL rendering again on Chrome:


I am unsure why my system no longer support GPU-acceleration without an override. For now, I will chalk that up to running a canary build of a browser on Mint Linux.

I am also unsure why the WebGL rendering is so much better than the Canvas version. I would expect WebGL to be more efficient, but not less buggy. But, for whatever reason, using WebGL rendering has certainly improved things. I call it a night here and will get started on moving the avatar about land tomorrow.


Day #455

Saturday, July 21, 2012

Can't Make a Three.js Skybox

‹prev | My Chain | next›

I have a pretty decent Three.js avatar and even have decent animation of the arms and legs as it walks about:


I would still like to add a bit more before switching back to Gladius. First, I would like to switch the controls so that they move the avatar around instead of moving the camera. To see that, I think I need to add some terrain on which the avatar can walk. I would also like to give the avatar a nice sunny day to walk about in. I think the way to accomplish the terrain on which to walk is a simple plane.

The sky seems a little trickier so I start with that first. In CubicVR.js, there is the concept of a SkyBox (though it does not seem to be exposed by Gladius' CubicVR.js plugin yet). There does not appear to be a similar concept in Three.js.

After rooting through some of the Three.js examples, the most common thing seems to be to make a really, really big cube. That seems... less than ideal, but lacking the imagination to devise a better idea, let's see how that works.

Again, there seems to be no documentation for the CubeGemotry class proper, but the code uses expressive variable names. I think something like this ought to suffice:
  var M = 1000 * 1000;
  var skyGeometry = new THREE.CubeGeometry(M, M, M, 1, 1, 1, null, true);
I set the x, y, and z dimension to 1 million units each. The number of segments needed to represent each is one because, really, how many segments does it take to represent a plane? The null option is the materials, which I will set separately in the mesh. The last argument says to use sides.

I then add a plain-old material (the same one that I am using for the avatar) to the sky and use this to build and add the skybox to the scene:
  var skyMaterial = new THREE.MeshNormalMaterial();
  var skyboxMesh  = new THREE.Mesh(skyGeometry, skyMaterial);
  scene.add(skyboxMesh);
Only, I don't see a skybox, just the same white background. I fiddle with various materials, to no end. Frustrated, I call it a night. Hopefully tomorrow I will be able to get this working.

Update: I should be in bed, but couldn't let this go. In the end I did get this working, though the changes seem to beg more questions. The first change seems reasonable: I need to tell the skybox that it is flip-sided. That is, the sides of the box do not face outward, but inward. I can live with that as I have seen similar things in Gladius. The second change that needs to be made is that... I need more segments to render the skybox cube. After making fun of it earlier, I find through trial and error that 4 segments seem to do the trick:
  var M = 1000 * 10;
  var skyGeometry = new THREE.CubeGeometry(M, M, M, 4, 4, 4, null, true);
  var skyMaterial = new THREE.MeshBasicMaterial({color: 0x3030ff});
  var skyboxMesh  = new THREE.Mesh(skyGeometry, skyMaterial);
  skyboxMesh.flipSided = true;
  scene.add(skyboxMesh);
I am also unable to get this to work with really large cubes. A skybox with dimensions in the neighborhood of ten thousands seems to be the maximum. This works:


But seems a little clunky. I think tomorrow I will experiment with images instead of plain filled materials.

Day #454

Friday, July 20, 2012

Frame of Reference in Three.js

‹prev | My Chain | next›

Yesterday I was able to make a first pass at arm motion in my simple Three.js avatar. I then proceded to use FlyControls to move the camera around so that I could view my handiwork. The result was less than satisfactory:


Today, I hope to figure out where I have gone wrong, correct my mistakes and, hopefully, verify that I have the arms moving properly.

And, damn, but it does not take long so see my first mistake. My avatar is an empty Three.js Object3D to which everything else is added. When I added the body (a cylinder with a top of radius 1), I shifted the body's position by half its bottom width:
function buildAvatar() {
  var avatar = new THREE.Object3D();

  var body_geometry = new THREE.CylinderGeometry(1, 300, 300);
  var body = new THREE.Mesh(body_geometry, material);
  body.position.z = -150;
  avatar.add(body);
I did this in an attempt to keep everything else that was added to the avatar centered on the avatar's z=0 plane.

I then proceeded to add the head without specifying a z-offset:
  var head_geometry = new THREE.SphereGeometry(200);
  var head = new THREE.Mesh(head_geometry, material);
  head.position.y = 200;
  avatar.add(head);
Why I though a cylinder needed that offset, but a sphere did not escapes me now. The fix is easy enough:
  var head_geometry = new THREE.SphereGeometry(200);
  var head = new THREE.Mesh(head_geometry, material);
  head.position.y = 200;
  head.position.z = -100;
  avatar.add(head);
In fact, it turns out that Three.js already centers both objects for me. In other words, I was making more work for myself that Three.js was trying to relieve me from. So I clear both position.z settings, reload and am greeted with a saner head and body:


Clearly, I should have sought the different perspective from camera controls sooner.

Everything is not quite right, however. The legs, which I had thought to be done, are not rotating about the leg/body "socket". As can be seen above the body end of the leg is protruding from the body. As the leg rotates, it recesses back into the avatar:


Now that I finally have it through my think skull that Three.js positions its primitives around the origin, I think I can solve this. First, in limb(), which is responsible for generating the extremities, I position the cylinder used for the arm/leg exactly half the height of the cylinder. The sphere used for the hand/foot then needs to travel the entire height of the cylinder:
function limb(material) {
  var limb = new THREE.Object3D();

  var arm_geometry = new THREE.CylinderGeometry(25, 25, 300);
  var arm = new THREE.Mesh(arm_geometry, material);
  arm.position.y = 150;
  limb.add(arm);

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

  return limb;
}
Both of these are relative to an empty Object3D entity that gets added to the avatar. I had been adding those directly to the avatar proper:
  var right_arm = limb(material);
  // ...
  avatar.add(right_arm);
But the rotation that is needed for the walking motion is easier to calculate relative to an already rotated position on the avatar's body. So, for each of the arms and legs, I calculate the necessary offset and rotatation and add a limb to this "socket":
  // Right Armx
  socket = new THREE.Object3D();
  socket.position.x = -125;
  socket.rotation.z = Math.PI/3;

  var right_arm = limb(material);
  right_arm.name = 'right_arm';
  socket.add(right_arm);

  avatar.add(socket);

  //  Right Leg
  socket = new THREE.Object3D();
  socket.position.y = -150;
  socket.position.x = 100;
  socket.rotation.z = Math.PI;

  var right_leg = limb(material);
  right_leg.name = 'right_leg';
  socket.add(right_leg);

  avatar.add(socket);
With that, walking is a simple small oscillation around the x-axis:
  avatar_left_leg.rotation.x  =    amplitude*(Math.PI/8);
  avatar_right_leg.rotation.x = -1*amplitude*(Math.PI/8);

  avatar_left_arm.rotation.x  =    amplitude*(Math.PI/8);
  avatar_right_arm.rotation.x = -1*amplitude*(Math.PI/8);
And now, The avatar looks quite nice:


Even from the side:


Before calling it a day, I apply a bit of learned knownledge. Since both the sphere (radius=200) and the body (height=300) are centered everywhere, including the Z-X plane (horizontal), I can better calculate how high along the y-axis the head should be. To be perfectly balance atop the body, I would need to move the head up the y-axis by half the distance of the body (150) and half the height of the sphere (200), or 350. I do not want the head quite that precariously balanced, so I skootch it down by a percentage:
  var body_geometry = new THREE.CylinderGeometry(1, 300, 300);
  var body = new THREE.Mesh(body_geometry, material);
  avatar.add(body);

  var head_geometry = new THREE.SphereGeometry(200);
  var head = new THREE.Mesh(head_geometry, material);
  head.position.y = (200 + 150) * .9;
  avatar.add(head);
With that, I think I have a pretty solid Three.js avatar.

I do not believe that Gladius has scene capability, so I will likely take pick back up tomorrow exploring that feature in Three.js.


Day #453

Thursday, July 19, 2012

Camera Controls in Three.js

‹prev | My Chain | next›

From yesterday, I have a walking avatar working in Three.js:


Today, I hope to add saunter to the walk. OK, maybe not saunter, but I would like to get the avatar a little more animated by getting the arms to move in rhythm with the legs.

Before worrying about frames of reference and the rotation of the arms, I try this the dumb way. That is, I add a name to the arm objects just as I did the leg objects yesterday:
  var right_arm = limb(material);
  right_arm.name = 'right_arm';
  right_arm.position.x = 150;
  right_arm.position.z = -50;
  right_arm.rotation.z = -Math.PI/3;
Using the name, I can make use of Three.js' getChildByName() to find the arms for later manipulation:
  avatar_left_arm = avatar.getChildByName("left_arm", true);
  avatar_right_arm = avatar.getChildByName("right_arm", true);
  avatar_left_leg = avatar.getChildByName("left_leg", true);
  avatar_right_leg = avatar.getChildByName("right_leg", true);
Lastly, in the render() function that animates my avatar, I add the same exact x-axis rotation to the arms that I did the legs:
var w = 500;
function render() {
  var t_float = clock.getElapsedTime()
    , t = t_float * 1000
    , amplitude = (w/2 - Math.abs((t % (2*w)) - w))/w;

  avatar_left_arm.rotation.x  =    amplitude*(Math.PI/8);
  avatar_right_arm.rotation.x = -1*amplitude*(Math.PI/8);
  avatar_left_leg.rotation.x  =    amplitude*(Math.PI/8);
  avatar_right_leg.rotation.x = -1*amplitude*(Math.PI/8);

  renderer.render(scene, camera);
}
After reloading the page... it actually seems to work:


I am not quite convinced that this is doing what I expect—it may just be a trick of perspective. To find out for certain, I am going to rotate the camera around. I could just hard-code the camera to a different position, refresh the page and look. I think it a better use of my time to figure out how to animate that.

At first I think about doing this manually myself, but stumble across FirstPersonControls. There is no actual documentation, but the code is quite readable. It seems that I need to supply a Camera object, which I have in the standard Three.js init() 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);

  controls = new THREE.FirstPersonControls(camera);
  // ...
}
Then, in the usual Three.js render() function, I update the controls:
function render() {
  // ...
  controls.update(clock.getDelta());
}
(I added the clock yesterday)

With that, I get a completely blank page. After some experimentation, I find that, if I start right on top of the avatar and I tune the controls:
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
//  camera.position.z = 1000;
  scene.add(camera);

  controls = new THREE.FirstPersonControls(camera);
  controls.movementSpeed = 1000;
  controls.lookSpeed = 0.125;
  controls.lookVertical = true;
Then I can eventually navigate back to see that my orientation is wrong. I am now underneath my avatar looking up:


It probably should not surprise me, but the FirstPersonControls start moving forward and I have to stop the motion in order to reverse and re-orient so that I can see things.

It turns out that I want the FlyControls, no the first person controls. I am still unclear why the first person controls seemingly ignored my z-axis orientation, but when I uncomment that setting and enable some reasonable FlyControls settings:
function init() {
  scene = new THREE.Scene();

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

  controls = new THREE.FlyControls(camera);
  controls.movementSpeed = 1000;
  controls.rollSpeed = 1.0;
  // ...
}
Then I can again see the avatar when I start. More importantly, I can use the controls to see that I have a seriously messed up avatar when viewed from anywhere other than straight on:


The controls are nice, but the mouse movement on my trackpad causes wild gyration of the camera. To disable, I find the dragToLook property in the source. Setting it to true seems to disable it:
  controls = new THREE.FlyControls(camera);
  controls.movementSpeed = 1000;
  controls.rollSpeed = 1.0;
  controls.dragToLook = true;
With that setting, I would expect that I could click and drag the page to repeat the wild gyrations, but this has no effect. I am not going to worry (for now) about that since I have no use for the behavior.

Satisfied that I have decent camera control, I call it a night. I am going to revisit the way that I build the avatar tomorrow and hopefully ensure that the arm motion is also satisfactory.


Day #452

Wednesday, July 18, 2012

Animating Three.js Legs

‹prev | My Chain | next›

Today is mostly filled with fun as OSCON, but the gods of my chain must be appeased. So I pick back up with the Three.js avatar that I created yesterday:


Today, I would to add simple animation. To keep things short, I am only going to animate the legs. I will worry about the arms tomorrow as the additional rotation should complicate things slightly.

I start by adding two more global variables at the top of my avatar.js script:
var camera, scene, renderer, clock,
avatar, avatar_left_leg, avatar_right_leg;
In Gladius / CubicVR.js, I have been naming those objects and assigning them to variables on demand. It turns out to be possible to do the same in Three.js:
  var left_leg = limb(material);
  left_leg.name = 'left_leg';
  left_leg.rotation.z = Math.PI;
  left_leg.position.y = -250;
  left_leg.position.x = -100;
  left_leg.position.z = -50;
  avatar.add(left_leg);
The limb() is the simple generator from yesterday that produces a grouped set of 3D objects; in this case it produces a leg and foot. After setting the name and doing the same for the right leg, I can assign the global variables after the avatar is assembled:
function init() {
  scene = new THREE.Scene();
  // ...
  avatar = buildAvatar();
  scene.add(avatar);

  avatar_left_leg = avatar.getChildByName("left_leg", true);
  avatar_right_leg = avatar.getChildByName("right_leg", true);
  // ...
}
The true second argument to getChildByName() instructs the method to look at the top-level avatar object and all children and grandchildren objects.

I will use the algorithm from the gladius avatar to animate this one as well. First off, I need a clock object. I build that in the initialization code:
document.addEventListener( "DOMContentLoaded", function( e ) {
  init();

  clock = new THREE.Clock(true);
  animate();
});
The true argument in this case starts the clock immediately rather than waiting for a manual call to start().

With that, I am ready to apply the walking algorithm to the two legs. This is done in the render() method, which is invoked on each subsequent frame:
var w = 500;
function render() {
  var t_float = clock.getElapsedTime()
    , t = t_float * 1000
    , amplitude = (w/2 - Math.abs((t % (2*w)) - w))/w;

  avatar_left_leg.rotation.x  =    amplitude*(Math.PI/8);
  avatar_right_leg.rotation.x = -1*amplitude*(Math.PI/8);

  renderer.render(scene, camera);
}
The only difference between this animation and the one that I did for Gladius is that the Clock object in Three.js produces a float of seconds when getElapsedTime() is invoked instead of an integer of the number of milliseconds. So, after simply multiplying by 1000, I have the number of milliseconds and... everything works.

I can watch as my avatar steps forward with its right foot:


And then the left:


That will do for a stopping point today. Tomorrow I will get the arms swinging in unison and possibly add some camera panning.


Day #451