Wednesday, August 29, 2012

Colliding with Relative Physijs Objects

‹prev | My Chain | next›

I ran into a bit of a problem last night with my "simple" Three.js / Physijs game. I have the raft and a twisting river. I even have current in the river pushing the raft. But a game without obstacles is a boring game indeed. And therein lies my problem.

In an attempt to keep things simple, I added obstacles directly to individual segments of the river. Even if the segments were far downstream and rotated, this kept the placement of obstacles a simple matter of placing them somewhere between postion.x = 0 and position.x = 1500 in the river segment's frame of reference. Of course that did not work.

After a brief discussion with Chandler Prall in the Three.js IRC channel, my problem is one of expectations. I had expected the obstacle to remain a separate entity. In fact, there is not really a way to group objects in Physijs—just to combine them into compound shapes. So the obstacle that I added to the various river segments we not obstacles—they were additional parts of the river segment.

Before attempting to solve this, I try to verify the theory. Based on observations last night, the theory does seem correct. The obstacles were more rigid than I expected—similar to the parent object. Still, if I understand correctly, I ought to see collision events from the river segment when my raft impacts the square obstacle. I add two collision event listeners, one to the obstacle and one to the river segment:
function addObstacle(water) {
  var width = 250
    , length = 1500
    , position = Math.random() * length;

  var obstacle = new Physijs.BoxMesh(
    new THREE.CubeGeometry(
      25, 25, 25
    ),
    Physijs.createMaterial(
      new THREE.MeshNormalMaterial()
    )
  );
  obstacle.position.y = 13;
  obstacle.position.x = position;

  obstacle.addEventListener("collision", function(object) {
    console.log("[obstacle] collision!");
  });

  water.addEventListener("collision", function(object) {
    console.log("[water] collision!");
  });

  water.add(obstacle);
}
After reloading, I do see the water collision—but only when the raft is first set on top of the river segment. When the raft finally impacts the "obstacle" part of the compound shape, I see no other events:


(the other debug output is stuff I am logging in Physijs and was too lazy to remove)

The event handler definitely works (as evidenced by the raft-on-water event). It simply does not recognize the compound shape collision as event-worthy.

So it seems that I still do not quite understand compound shapes in Physijs. Darn.

I set the event handler issue aside for now. I still need some way to position the obstacles relative to river segments and actually respond to collision events. Last night I was able to do both—just not at the same time.

I am already placing the obstacle in the desired frame of reference—that of the river segment. And last night, I found that, if I manually added the obstacle directly to the Physijs scene, then collision events were generated and received as desired.

So the challenge then becomes to mark the relative position of the obstacles, grab the "world" coordinates of those obstacle markers, and use the global position to place scene-level obstacles.

So the addObstacle() method now does no more than marking positions, storing the markers in a global:
function addObstacle(water) {
  var width = 250
    , length = 1500
    , x = Math.random() * length;

  var marker = new Physijs.BoxMesh(
    new THREE.CubeGeometry(2, 2, 2)
  );
  marker.position.y = 1;
  marker.position.x = x;
  water.add(marker);

  obstacles.push(marker);
}
After the scene is updated (so that the markers are laid out), I draw the obstacles:
function animate() {
  requestAnimationFrame(animate);
  applyRiverCurrent();
  scene.simulate(); // run physics
  render();
}

function render() {
  camera.position.x = raft.position.x + 0.4 * window.innerHeight ;
  camera.position.z = raft.position.z;

  drawObstacles();

  renderer.render(scene, camera);
}
If the scene level objects have not been rendered, then I do so:
var _scene_obstacles = [];
function drawObstacles() {
  if (_scene_obstacles.length > 0) return;

  obstacles.forEach(function(marker) {
    var position = marker.matrixWorld.multiplyVector3(new THREE.Vector3());
    console.log(position);
    var obstacle = new Physijs.BoxMesh(
      new THREE.CubeGeometry(
        25, 25, 25
      ),
      Physijs.createMaterial(
        new THREE.MeshNormalMaterial()
      )
    );
    obstacle.position.y = 13;
    obstacle.position.x = position.x;
    obstacle.position.z = position.z;
    scene.add(obstacle);

    obstacle.addEventListener('collision', function(object) {
      if (object == raft) console.log("Game Over!!!!");
    });

    _scene_obstacles.push(obstacle);
  });
}
Most of this comes directly from the previous obstacle building code. The main difference is the use of the matrixWorld matrix to calculated the world coordinates of the markers so that the obstacles can be placed in the proper location.

And it works!

When my raft impacts the globally places obstacle, I see expected "Game Over" event:


This is pretty messy, but it's a start.

Day #493

2 comments:

  1. As as explanation for not seeing the collision - collisions are detected when two objects were not touching previously but now are. In your case the only collision which meets this requirement is when the raft is first added to the water. In most situations I believe this is a reasonable requirement, however I suppose a secondary type of collision may need to be added for when there are new contact points between objects. When A touches B fire a collision event, and when there are more collision points then fire the second collision event.

    ReplyDelete
    Replies
    1. That makes perfect sense. Phew! I do understand :)

      I don't think this is a good use case for secondary collision events. I did not actually intend to use the event—just to verify a theory.

      Of course, I *would* like to see secondary collision events when a different object in the compound shape is impacted (and see the new object as event data). But I haven't gotten deep enough into the source to know how hard this would be.

      Regardless, *much* thanks for all your help!

      Delete