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

No comments:

Post a Comment