Wednesday, March 13, 2013

Adpaters to Chain Adding of Three.js Objects

‹prev | My Chain | next›

I return tonight to a Three.js problem that has vexed me for several months now. I have what I think is a fairly solid game idea for 3D Game Programming for Kids, but cannot seem to pull it off in a way that makes for a decent narrative. The game has a river rapids setting in which the player has to navigate the river and obstacles to reach the end:

The turns in the river have proven to be the most difficult aspect of this game. Attempts in the past have involved too much geometry or arcane Three.js conversions.

I think it probably best to give up the ghost of this approach. In fact, I may have a better game idea in mind already. But I hate admitting defeat...

Most of my previous attempts have kept the river segments in a global coordinate system. Each subsequent segment then needs some way to know where the previous segment leaves off:
  offset = riverSegment(0);
  offset = riverSegment(Math.PI/8,  offset);
  offset = riverSegment(0,          offset);
  offset = riverSegment(-Math.PI/8, offset);
  // ...
This kinda/sorta works—except when I try to put it into chapter format. Even as the last game in the book, wherein kids and beginners have some pretty good skills, calculating that offset is ugly (see previous posts for the ugly).

But what if I do not use a global coordinate system? What if each segment creates its own frame of reference into which the next segment is placed? That is actually a technique that I try to teach multiple times in the book. Maybe it can work here as well.

In this scenario, I would like to do something like the following when building up the river:
  var river = riverSegment(0).
For this to work, I would replace the offset calculations at the end of riverSegment():
function riverSegment(rotation) {
  // ...
  return {
    x: Math.cos(rotation) * 1500 + offset.x,
    z: -Math.sin(rotation) * 1500 + offset.z
With a frame of reference centered on the end of the river segment:
function riverSegment(rotation) {
  // ...
  var end = new Physijs.ConvexMesh(
    new THREE.PlaneGeometry(1,1),
    new THREE.MeshBasicMaterial()
  end.position.x = length;

  return end; 
The add() method in
  var river = riverSegment(0).
Would come from the end frame-of-reference return value. Brilliant. Except that it will not work.

First, the river variable would be assigned to the return value of that last add(), not the combination of all of those segments. The second problem is that add() in Three.js does not return anything. Of course this second problem causes everything to break—by the time I call add() on the return value of the first add(), I am calling add() on undefined.

This means that I need a proxy object to wrap my Three.js objects and to expose a useful add() method. I start by converting that riverSegment() function to create a RiverSegment object:
function riverSegment(rotation) {
  return new RiverSegment(rotation);
Now I can create a the RiverSegment object:
function RiverSegment(rotation) {
  this.rotation = rotation;

RiverSegment.prototype.init = function() {
  // Three.js & Physijs initialization here...
  this.mesh = segment;
  this.end = end;

RiverSegment.prototype.add = function(segment) {
  return segment;
By virtue of that add() method that returns the next object, I can chain add() calls. And this actually works. But is this appropriate for a book for kids and beginners?

Crazy as it might seem... maybe. By this point in the book, I have supplied several frame of reference examples and we will have three chapters of JavaScript object programming under our collection belts. So it is not completely insane to think kids would not be able to keep up.

But really, that is pretty insane. An adapter for the built-in Three.js add() method is not something to expect beginners to appreciate. For my own edification, I am glad to have gotten this to work, but I think it best to rework the game completely.

(a complete mess of demo code)

Day #689

No comments:

Post a Comment