Tuesday, February 3, 2015

Displaying 3d objects on Bing Maps (using Three.js)

On my previous post I've made a couple of experiments on displaying 2d content on top of Bing Maps using Pixi. Performance was top-notch, particularly if the browser supported WebGL (fallbacking to Canvas otherwise).

This time I'm trying to take the WebGL experiment even further by adding 3d content on top of a map. For that I'm going to use the most popular WebGL 3D lib out there: Three.js

Let me start by showing the end-result.


I've placed some boxes on top of a map. Although the map by itself is 2D the boxes are rendered in 3D using WebGL. Thus, as further away from the screen center (the vanishing point) the more pronounced the depth effect will be.
So, how to do this on top of Bing Maps?

As usual, I've decomposed this into simpler steps.

1. Create a DOM element on top of Bing maps to place the Three.JS renderer

This code is very similar to its Pixi counterpart (from my previous post).
var mapDiv = map.getRootElement();
var width = map.getWidth();
var height = map.getHeight();

renderer = new THREE.WebGLRenderer( { alpha: true } );
renderer.shadowMapEnabled = true;
renderer.setSize(width, height);
mapDiv.parentNode.lastChild.appendChild( renderer.domElement );

renderer.domElement.style.position = "absolute";
renderer.domElement.style.top = "0px";
renderer.domElement.style.left = "0px";
The renderer should be created with "alpha", otherwise the map won't be visible.

With this code a canvas element is generated on top of the map for the renderer.


2. Add boxes, lights and a camera

Nothing really special there. Just added these objects directly without any "spatial" positioning. Used:
  • THREE.PerspectiveCamera for the camera
  • THREE.BoxGeometry for the boxes
  • THREE.AmbientLight/THREE.PointLight for the lights
  • A simple texture for the boxes

3. Mapping boxes position to geo coordinates

Now this was the tricky part. All the magic is done on function "updatePosition". What I do, per box, is:
  • Obtain the geo-coordinate associated with the box
  • Obtain the screen coordinate for that coordinate
    • Using map.tryLocationToPixel
  • Obtain the Three.js world coordinates for that pixel
    • Uses a technique called raycasting, trying to find the interception between a projected ray from the obtained screen coordinate to an invisible plane object that I've created.
    • The interception will be the world coordinate to where the box needs to be moved.
  • As the pivot point of the box is on its center the box needs to be shifted upwards to half of its height (I'm actually doing this just once during the loading phase)
  • During zoom the box needs to be scaled. I've added a parent object for each box (at the bottom) to work out as a new pivot point.
Anyway, here's a video of this experiment in action

And also a working page: http://psousa.net/demos/bingmaps/webgl/three/three3.html where you can take a look at the code. Just remember to open this using Chrome :)

So this is basically it. I believe it has the potential for some interesting use cases and WebGL is here to stay. More and more people are using compatible browsers and Microsoft decision to simplify upgrading to Windows 10 will certainly bump this number even further.

7 comments:

  1. Very cool, I'd considered using three.js for my project, but I couldn't see how to generate an infinitely scrolling grid, such as from the canvas work earlier.

    ReplyDelete
  2. Should be doable as well. You can easily generate/modify the 3d objects according to the current viewport on "viewchange". What exactly did you have mind?

    ReplyDelete
  3. Was distracted working on the project, but finally took the time to get my current test page hosted for viewing. http://chad-autry.github.io/hex-widget/ Some of the code from your example back in 2012 is still buried in there.

    ReplyDelete
    Replies
    1. Of course now I see the project only works in chrome.

      Delete
  4. Pedro have you checked out http://cesiumjs.org , if you havn't you might find it interesting,

    I'm porting a google earth plugin app over the cesium with the demise of the GEarth plugin.

    ReplyDelete
    Replies
    1. Yep, I was already aware of it. Really cool stuff. I was actually planning on using it for a project but the interaction with objects is not ideal, particularly when drag&drop is required.

      Delete
  5. Hello Pedro thanks for your tutorial.
    I have a problem if I need to move on the map an object, like your cube. For example between London and Paris. How can I do? what is your advice? I need something like tween.js, or other stuff??

    ReplyDelete