Monday, February 25, 2013

Labeling Three.js Elements with DOM Elements

‹prev | My Chain | next›

My son continues to be obsessed with the idea of chatting within the games that we write in 3D Game Programming for Kids. I have yet to figure out a means to make a faye service publicly available without incurring problems of a publicly available faye service. Quite aside from that, I realize that I do not know how to draw speech bubbles in Three.js 3D. So, I set aside my concerns over the larger problem for a day to see if I can solve this other problem.

My understanding of text in Three.js is that it is possible, but complex. In my experience, DOM overlays have been much easier. It then becomes an exercise of positioning the two dimensional DOM elements at the proper location for the 3D elements.

To test this out, I switch back to my old Earth-Mars orbit simulator:



I should probably make that an orthographic projection, but I am, in fact, using a perspective camera in there, so this ought to be a realistic experiment.

I add a DOM label for the Earth:
  var label = document.createElement('div');
  label.textContent = 'Earth';
  label.style.backgroundColor = 'white';
  label.style.position = 'absolute';
  label.style.padding = '1px 4px';
  label.style.borderRadius = '2px';
  
  document.body.appendChild(label);
Now, in my animate() function, I add a call to labelPlanets() which will be responsible for updating the label's screen position to mirror the location on the screen:
  function animate() {
    requestAnimationFrame(animate);
    //  update the planets' positions...    

    labelPlanets();
        
    // Now, show what the camera sees on the screen:
    renderer.render(scene, camera);
  }
Thankfully, Three.js includes the Projector for converting between projected and non-projected coordinate systems. I still need to translate between XY with with origin at the center to the DOM's origin at the top left with Y increasing down the screen. But I know how to do that:
  function labelPlanets() {
    var projector = new THREE.Projector();
    var pos = projector.projectVector(earth.position.clone(), camera);
    label.style.top = '' + (heightHalf - heightHalf * pos.y ) + 'px';
    label.style.left = '' + (widthHalf * pos.x + widthHalf) + 'px';    
  }
I find that, if I do not clone() the Earth's position, then the Earth disappears for some reason. That strangeness aside, I have my label:



That works surprisingly well. I still need to ruminate over this Faye question so I may take a day or two to build this out into a speech balloon / label library for the book. Unless inspiration strikes.


Day #673

No comments:

Post a Comment