Sunday, March 10, 2013

Converting Three.js Frame of Reference Coordinates

‹prev | My Chain | next›

I think that I have given up on my current approach to a Three.js / Physijs river rafting game. I still hope to come up with something similar as the last game for 3D Game Programming for Kids, but the current approach is proving to be too complex—even for the last game in a beginner's book.

Most of the trouble comes from trying to place river segments:



Excusing the gaps in the river segments, simply placing the segments is a pain. Three.js shapes are positioned from the center of the objects. To account for this, I have to create a new frame of reference for each segment and shift the segment down by half. Once I am done with that, I somehow have to communicate to the next segment the point at which the previous segment stopped.

So I end up defining my river segments at a high-level like this:
  var offset;
  offset = riverSegment(0);
  offset = riverSegment(Math.PI/8,  offset);
  offset = riverSegment(0,          offset);
  offset = riverSegment(-Math.PI/8, offset);
  // ...
That is almost OK, except that the riverSegment() function needs to end with some non-trivial trigonometry:
function riverSegment(rotation, offset) {
  // ...
  return {
    x: Math.cos(rotation) * 1500 + offset.x,
    z: -Math.sin(rotation) * 1500 + offset.z
  };
}
Actually, that is pretty trivial trigonometry and this is a 3D book—it is hard to avoid all mentions and applications of trigonometry in a 3D book. There are other reasons that the current organization of the game is shaping up to be trouble, but it all starts here.

Although, perhaps I can avoid this...

Rather than accumulating world coordinates like this. I believe that I can ask Three.js what the world coordinates of these river segments are. I need to know the world coordinate of the end of each of these segments. So I create vanilla 3D object a segment's length away from the start:
  var end = new THREE.Object3D();
  end.position.set(1500, 0, 0);
  segment.add(end);
I am adding this point into the segment's frame of reference. If the segment is rotated, this point should rotate with it. To get Three.js to tell me the world coordinate, I have to manually call updateMatrixWorld on the segment. This pushes the "end" matrixWorld into the worl frame of reference so that I can grab a 3D point:
  // ...
  segment.updateMatrixWorld();
  var position = end.matrixWorld.multiplyVector3(new THREE.Vector3());
  console.log("%s, %s, %s", position.x, position.y, position.z)
  console.log(" => %s, %s", Math.cos(rotation) * 1500 + offset.x, -Math.sin(rotation) * 1500 + offset.z)
  // ...
The two console.log() statements compare the 3D coordinates of the end point in the world frame of reference. And they turn out to be identical—at least to within a small margin of error:
1500, 0, 0 
 => 1500, 0 

2885.8193359375, 0, -574.025146484375 
 => 2885.81929876693, -574.0251485476347 

4385.8193359375, 0, -574.025146484375 
 => 4385.81929876693, -574.0251485476347 

5771.638671875, 0, -0.000007271766662597656 
 => 5771.63859753386, 0 
So that would work, but I cannot honestly say that updateMatrixWorld() and matrixWorld are any easier to explain to kids than trigonometry would be.


Day #686

No comments:

Post a Comment