Wednesday, February 27, 2013

Getting Started with Three.js Plugins for Labels

‹prev | My Chain | next›

I am not one to get all crazy when methods take more than a single argument. Except that I kind of am.

The Label class that I am trying to build for Three.js objects currently requires three arguments:
  function Label(object, camera, content) {
    this.object = object;
    this.camera = camera;
    this.content = content;

    this.el = this.buildElement();
    this.track();
  }
I can live with the object and the content parameters. After all, the entire point of the Label class is to create a relationship between a Three.js object (the object) and a text label (the content). But that camera argument really bugs me.

There is no way to avoid the necessity of the camera. Without it there is no way to project the 3D coordinates of the Three.js object into 2D screen space. And, without that projection, there is no way to accurately place the label. Still, it seems like an awkward thing to require the programmer to supply.

My first inclination is to reach under the renderer covers to redefine the render() method on the THREE.WebGLRender.prototype. Unfortunately, there is no THREE.WebGLRenderer.prototype. Well, there is a prototype—this is, after all JavaScript. But the prototype does not contain any of the methods. All of the THREE.WebGLRenderer methods are defined inside the constructor. In other words, I am out of luck if I want to redefine render() for all instances of WebGLRenderer (not to mention CanvasRenderer).

But I am not entirely out of luck. Three.js does this for a reason. In this case, the reason is that Three.js supports rendering plugins for doing this kind of thing. To convert my Label class into a plugin, I need an init() method, which I make empty. I also need a render() method, which I assign to yesterday's track():
  Label.prototype.track = function() {
    // ...
  };
  Label.prototype.render = Label.prototype.track;
One of the arguments that is supplied to the plugin's render() method just so happens to be the camera, which allows me to ditch the instance variable camera and use this instead:
  Label.prototype.track = function(scene, cam) {
    if (!cam) return;
    // ...
    var projector = new THREE.Projector(),
        pos = projector.projectVector(p3d, cam),
    // ...
  };
  };
  Label.prototype.render = Label.prototype.track;
That is a promising start. I am not sold on requiring the programmer to add each label to the render as a plugin:
  scene.add(mars);
  var mars_label = new Label(mars, camera, "Mars");
  renderer.addPostPlugin(mars_label);
Perhaps this is something that I can improve upon tomorrow. If nothing else, I am glad that Three.js prevented me from trying to reach under the render() covers. I would have been sure to cause problems doing that. Of course, it is even nicer that Three.js supports plugins for this express purpose.

(live code of the plugin)

Day #675

No comments:

Post a Comment