Thursday, March 28, 2013

Simple World Coordinates in Three.js

‹prev | My Chain | next›

I may have painted myself into the proverbial corner with my approach to the most recent game in 3D Game Programming for Kids. I am taking readers through the building of a river scene:



Since this is the last game in the book, it is more involved than the rest. As such, I use this as a teaching moment to suggest that readers start to think about code organization. Specifically, I build the game with a series of functions:
  addSunlight(scene);
  var scoreboard = addScoreboard();
  var river = addRiver(scene);
  var raft = addRaft(scene);
  resetGame(raft, river, scoreboard);
Each of these functions has the same abstraction level—either adding or working with Three.js / Physijs objects in the game.

My original thought had been to add in-game items at the same level:
  addSunlight(scene);
  var scoreboard = addScoreboard();
  var river = addRiver(scene);
  var raft = addRaft(scene);
  resetGame(raft, river, scoreboard);
  addInGameItems(scene, river, scoreboard);
As with the other addXXX() functions, I need a reference to the scene so that the in-game items can be added to the scene. I need a reference to the river so that I know where to place the objects. I need access to the scoreboard so that I can add and subtract points as the player's raft runs into these items.

As I found last night, there are problems with this approach. The most glaring of these problems is that these in-game items really ought to be reset whenever the game resets. The second problem is that determining the world coordinates of the bonus items given river frame-of-reference coordinates may be too complex for the book. There are other approaches that don't fit as nicely with the Clean Code outline format that I might explore if I am unable to distill this down to simple code. But hopefully that will not be necessary.

But what am I thinking? Three.js has a localToWorld() method that ought to do just what I want:
  function addFruitPowerUp(location, ground) {
    var mesh = new Physijs.ConvexMesh(
      new THREE.SphereGeometry(10),
      new THREE.MeshPhongMaterial({emissive: 0xbbcc00}),
      0
    );
    mesh.receiveShadow = true;
    var p = ground.localToWorld(
      new THREE.Vector3(location.x, location.y, -20)
    );
    console.log(p);
    mesh.position.copy(p);
    scene.add(mesh);
    
    return mesh;
  }
But instead of getting the expected transform, the console.log() statement is showing:
THREE.Vector3 {x: -45, y: -170, z: -20, ...}
That ends up being below the ground since the ground is tilted. Regardless, the lack of decimal points makes it pretty obvious that no translation to world coordinates actually happened.

If I try this directly in the console, I get:
ground.localToWorld(new f.THREE.Vector3(-45, 0, -170))
THREE.Vector3 {x: -33.77378091216087, y: -166.61132156848907, z: -45.00000000000001, constructor: function, set: function…}
The difference is that, by the time I try it from the console, the scene has been rendered, the side-effect being that the ground's world coordinates have been calculated.

I could call my animate() function before adding bonus items, but it is cleaner to use Three.js's updateMatrixWorld() method directly in my current function:
  function addFruitPowerUp(location, ground) {
    var mesh = new Physijs.ConvexMesh(
      new THREE.SphereGeometry(10),
      new THREE.MeshPhongMaterial({emissive: 0xbbcc00}),
      0
    );
    mesh.receiveShadow = true;
    
    ground.updateMatrixWorld();
    var p = new THREE.Vector3(location.x, location.y, -20);
    ground.localToWorld(p);
    mesh.position.copy(p);
    scene.add(mesh);
    
    return mesh;
  }
With that, I have delicious fruit floating along the river, ready to give players bonus points:



That is actually not too bad. I had to use only three additional lines to get this to work. Unfortunately, it comes at the expense of yet another concept that I need to convey in a chapter already overloaded with concepts. Granted it could end up being a very useful one for budding 3D programmers, but I need to consider this carefully.

There no fewer than 6 dozen concepts that I am not including in 3D Game Programming for Kids. For most, someone with 3D programming experience could legitimately question my abilities as an author for not including them. Converting from local to world coordinates is just one of those. I will take a day to two before deciding if it's worth the risk of overloading readers on new concepts. But at least I know how to do it now.



Day #704

1 comment:

  1. Hi Chris! Been browsing through your posts from 2012. Keep up the good work!

    ReplyDelete