Thursday, July 12, 2012

Simple Meshes and Materials in Gladius

‹prev | My Chain | next›

For as long as I have been working with Gladius, the loading of the meshes and materials has bugged me. Don't get me wrong, they are definitely handy for handling large systems, but most of the things that I will do in Gaming JavaScript will be of the simpler variety.

My simple avatar requires three meshes (shapes) and two colors (materials):


Currently, this requires external mesh and materials to be loaded via a Gladius engine.get():
      // Mesh and material resources
      var resources = {};
      engine.get(
        [
          {
            type: engine["gladius-cubicvr"].Mesh,
            url: 'assets/procedural-primitive-mesh.js?type=sphere&radius=1',
            load: engine.loaders.procedural,
            onsuccess: function(mesh) {
              resources.sphere_mesh = mesh;
            },
            onfailure: function(error) {console.log(error);}
          },
          // Other meshes ...
          {
            type: engine["gladius-cubicvr"].MaterialDefinition,
            url: 'assets/rgb-material.js?r=255',
            load: engine.loaders.procedural,
            onsuccess: function(material) {
              resources.red_material = material;
            },
            onfailure: function(error) {}
          },
          // Other materials ...
        ],
        {
          oncomplete: game.bind(null, engine, resources)
        }
      );
There is very little actual mesh and material definition going on in that code. The type specifies Mesh or MaterialDefinition and the query string some options. Everything else in there is ceremony built around loading these external "procedural" scripts.

The procedural-primitive-mesh.js is responsible for no more than taking the query parameter strings and returning an appropriate options hash:
function proc(options) {
  return {
    primitive: options,
    compile: true
  };
}
It turns out that the returned options hash is "appropriate" as an argument for an object constructor. Specifically these options hashes are used for Mesh and MaterialDefinition objects.

So, instead of all of the procedural loader ceremony, I can create a new sphere like:
new engine["gladius-cubicvr"].Mesh({
  primitive: {
    type: 'sphere'
  },
  compile: true
})
Looking at that constructor, I really only need one argument: the "sphere" type. So I create a factory function that will generate primitive types with an optional second options hash argument:
      function primitiveMesh(type, options) {
        var Mesh = engine["gladius-cubicvr"].Mesh;

        options = options || {};
        options.type = type;

        return new Mesh({primitives: options, compile: true});
      }
Using that, I can reduce all of the meshes that are used to build my avatar down to:
      var resources = {
        sphere_mesh: primitiveMesh('sphere'),
        cone_mesh: primitiveMesh('cone', {height: 2, base: 2}),
        cylinder_mesh: primitiveMesh('cylinder', {radius: 0.2, height: 2}),
        // material definitions here ...
      };
That is a huge improvement. I have replaced 27 lines of code with three that express my intent far better than before.

I can see a similar improvement with a simple color material definition:
      function colorMaterial(r, g, b) {
        var Material = engine["gladius-cubicvr"].MaterialDefinition;
        return new Material({color: [r, g, b]});
      }
So that my entire resources hash, which had been nearly 45 lines of code, becomes:
      var resources = {
        sphere_mesh: primitiveMesh('sphere'),
        cone_mesh: primitiveMesh('cone', {height: 2, base: 2}),
        cylinder_mesh: primitiveMesh('cylinder', {radius: 0.2, height: 2}),
        red_material: colorMaterial(255, 0, 0),
        blue_material: colorMaterial(0, 0, 255)
      };
The last thing that I need to do is replace the onComplete callback from the old engine.get() call:
      // Mesh and material resources
      var resources = {};
      engine.get(
        [
          // Load meshes and materials ...
        ],
        {
          oncomplete: game.bind(null, engine, resources)
        }
      );
That game.bind() call simply returns a function that, when invoked, will send engine and resources as the first two arguments to
      function game(engine, resources) {
        // Game definition here...
      }
In other words, onComplete() would be the same thing as simply calling game(engine, resources), which is exactly what I do now:
      var resources = {
        sphere_mesh: primitiveMesh('sphere'),
        cone_mesh: primitiveMesh('cone', {height: 2, base: 2}),
        cylinder_mesh: primitiveMesh('cylinder', {radius: 0.2, height: 2}),
        red_material: colorMaterial(255, 0, 0),
        blue_material: colorMaterial(0, 0, 255)
      };

      game(engine, resources);
That is a big win for clarity and, more importantly for a kids programming book, typing. This seems a good stopping point for tonight. Tomorrow I will get back to the topic of adding some wiggle to my avatar.


Day #445

No comments:

Post a Comment