Sunday, March 17, 2013

Keeping it Simple… By Cheating Physics

‹prev | My Chain | next›

Writing a book for kids has made me acutely aware of the need to limit both code and concepts when writing. Limiting code and concepts is one of those things that I have always unconsciously tried to do, but now I see it has to be a priority—something that I always have to keep in the forefront. In fact, I have gotten quite obsessive about it, to the point that I will completely delete and restart chapters if need be.

In the case of the last chapter in 3D Game Programming for Kids, I have restrained from even starting the chapter for several months because I was unable to limit either code or concepts with the various approaches that I was trying. That was until Chandler Prall happened to mention Physijs height fields in response to one of my failed attempts at building a river.

After mucking with height fields for a few nights, I have a very satisfactory looking river:



Even better, I made that with 40 lines of code (river trench, water, shading and shadows). Best of all, the concept of a height field is ridiculously simple—make parts of it lower than the rest.

I am still left with the challenge of explaining the nice sine curve that is the river, but that is not hopeless. I need only state that sines and cosine make winding graphs—without actually mentioning their geometric origin. Or I could opt for an even simpler zig-zag river. Regardless, I think that I am ready to proceed with the game.

For the game, I need to solve three more (hopefully) smaller issues. The first is that the camera needs to point well in front of the player's raft so that the player can see what is coming next. Second, the river has to push the raft downstream. Last, I need a way to keep the raft right-side-up—the controls get tricky when the raft flips.

Positioning the camera turns out to be trickier than I had expected. I am using an older version of Three.js (r52) in which the Vector3 class does not support the add(vector) method. Back then, add() added two vectors together and set the current vector to the result. More recent versions of Three.js have made the switch to add(v) producing a new vector from the sums of the current object and v. I am stuck with the old addSelf():
  camera.lookAt(
    donut.position.addSelf(new THREE.Vector3(0.67*height, 0, 0))
  );
The value of 0.67*height (67% the height of the viewport) was chosen through trial and error after positioning the camera with:
  function updateCamera() {
    camera.position.set(
      donut.position.x + 0.75 * height,
      0.1*height,
      donut.position.z
    );
  }
  updateCamera();
This gives the player a reasonable view of what is coming downstream of the raft's current position:



The original idea that I have for the current is to make the water zero friction (the zero in the Phsyijs.createMaterial function):
  var water = new Physijs.ConvexMesh(
    new THREE.CubeGeometry(size, size, 10),
    Physijs.createMaterial(
      new THREE.MeshBasicMaterial({color: 0x0000bb}),
      0,
      0.9
    ),
    0
  );
And then apply a force downstream whenever there is a collision:
  water.addEventListener('collision', function(event) {
    donut.applyCentralForce(
      new THREE.Vector3(1e7, 0, 1e7)
    );
  });
This does not quite work, however. The raft is pushed into the river bank, bounces backward, and eventually falls off the edge of the “world”. I need the initial push to be off to the right a bit. I also need subsequent water force to continue pushing as long as the raft and river are in contact.

The initial motion is easy enough:
  donut.setLinearVelocity(
    new THREE.Vector3(50, 0, -10)
  );
As a side-note, I really need to stop calling this a “donut”. Anyhow...

Whether or not an object is currently colliding with another object is not an easy thing to do in Physijs. In fact, I need to reach under the covers to ask how many objects are currently “touching”:
  setInterval(function() {  
    if (water._physijs.touches.length > 0) {
      donut.applyCentralForce(
        new THREE.Vector3(1e6, 0, 10)
      );
    }
  }, 1000);
That works—the donut/raft is pushed downstream—but it probably violates my concept rule.

It is probably easier to fake current by tilting the ground ever so slightly:
  ground.rotation.y = 0.1;
That involves motion down an incline plane, which is fun for every first year physics student. But I would not need to explain the trigonometry behind the forces. Rather, I can state that things slide down a ramp, which is a concept that kids understand.

Last up, I need to stop the raft from wobbling when to bumps into the water or the sides. Happily, by this point in the book, readers are well familiar with setAngularFactor(). This Physijs method restricts or limits rotational motion around one or all axes. In this case, the easiest thing to do is to prevent rotation entirely:
  var donut = new Physijs.ConvexMesh(shape, cover);
  donut.rotation.x = -Math.PI/2;
  donut.position.set(-2500, 200, 0);
  scene.add(donut);
  donut.castShadow = true;
  donut.setLinearVelocity(
    new THREE.Vector3(50, 0, -10)
  );
  donut.setAngularFactor(new THREE.Vector3(0, 0, 0));
It is a little odd to see the raft/donut bounce completely level, but the simplification in game play makes this a good option.

With that, I can navigate to the end of the river (and the world):



Clearly, the end of the world can use some gussying up. I also need to add a timer and some river obstacles. I think I have a handle on most of that. Hopefully this means that I can finish off this game tomorrow.


Day #693

No comments:

Post a Comment