Wednesday, February 6, 2013

One Too Many Thises

‹prev | My Chain | next›

I have reached that very delicate point in a library's life in which it is complete and utter garbage, but it works exactly as I need it to. I continue to work on a Three.js / Physijs game for 3D Game Programming for Kids (now in beta!) involving a bit of mouse interaction. Specifically, the player will drag ramps around the screen to enable the ball to jump up to the goal:


I will pretty that up later. For now, I have gotten my mouse interaction library to the point that the ramps can be dragged with code like:
  var ramp = {
    startAt: function(location) {
      this.mesh = new Physijs.ConvexMesh( /* ... */ );
      // ...
      this.addMouseHandler();
    },
    addMouseHandler: function() {
      var mesh = this.mesh;
      Mouse.addEventListener('drag', mesh, function(event) {
        mesh.__dirtyPosition = true;
        mesh.position.x = mesh.position.x + event.x_diff;
        mesh.position.y = mesh.position.y + event.y_diff;      
      });
    },
    // ...
  };
I have to do a little lexical scope cheating, but hey, it beats trying to explain the finer details of this to kids. The format of the Mouse.addEventListener() call has the distinct advantage of looking very much like a regular document.addEventListener(). Concepts are precious in a book—all the more so in a book for kids and beginners—so this is a big win.

My Mouse library code is perfectly functional, but is, as I mentioned, a mess. The effort to get it working was a spike so this is expected, but I need to get things more stable. I take some time to DRY up the Mouse library a bit.

Before going much further, I would like to explore the possibility of putting the addEventListener() method directly on the mesh's prototype. This turns out to be fairly straight-forward. Well, maybe not "kid" straight-forward, but simple enough for a library that might be used by beginners. I grab a reference to the Physijs ConvexMesh prototype, which I use to call it as normal (e.g. for collisions), but add a twist for drag events:
  var pjs_ael = Physijs.ConvexMesh.prototype.addEventListener;
  Physijs.ConvexMesh.prototype.addEventListener = function(event_name, callback) {
    if (event_name == 'drag') Mouse.addEventListener('drag', this, callback);
    pjs_ael.call(this, event_name, callback);  
  };
Elsewhere, I can bind the callback to the current mesh:
    document.addEventListener("mousemove", function(e){
      // ...
      active_mouse_object.drag.call(
        active_mouse_object.mesh,
        {
          x:click_pos.x,
          y:click_pos.y,
          x_diff: click_pos.x - drag.x,
          y_diff: click_pos.y - drag.y
        }
      );
      // ..
    });
This allows the ramp to now define the event listener as:
  var ramp = {
    // ...
    addMouseHandler: function() {
      this.mesh.addEventListener('drag', function(event) {
        this.__dirtyPosition = true;
        this.position.x = this.position.x + event.x_diff;
        this.position.y = this.position.y + event.y_diff;      
      });
    },
    // ...
  };
To my trained JavaScript eye, that looks much better. Unfortunately, that is not going to work out in the book. I understand that this on the first line of addMouseHandler refers to the ramp while the other this references in the callback are now bound to the mesh. But I do not relish having to explain to kids how that happens.

So, I opt to stick with a lexical scope hack instead:
  var ramp = {
    // ...
    addMouseHandler: function() {
      var mesh = this.mesh;
      mesh.addEventListener('drag', function(event) {
        mesh.__dirtyPosition = true;
        mesh.position.x = mesh.position.x + event.x_diff;
        mesh.position.y = mesh.position.y + event.y_diff;      
      });
    },
    // ...
  };
I will have to introduce this one way or the other in the book since I am going to show kids the basics of object oriented programming. But this approach allows me to keep the abstraction as "this refers to the current object, but sometimes JavaScript does funny things to it." Not perfect, but good enough for an intro book.

I am still not 100% sold on adding this to the Mesh prototype. I may stick with the Mouse.addEventListener() approach. But I can defer that choice until I write the chapter. Regardless, I think I have this library ready to be extracted. Tomorrow.

(the current state of the code)

Day #653

No comments:

Post a Comment