Creating a 3D viewer for our Apollonian service using HTML5 – Part 3

To finish off our look at developing an HTML5-based 3D viewer for our Apollonian web-service, today's post integrates the trackball capability of the Three.js library. Many thanks to Jeff Geer for once again pointing me in the right direction on this. 🙂

The trackball capability allows you not to worry about manual implementation of 3D navigation inside your viewer: you simply set up some basic parameters to indicate the size of your model and the speed with which you want navigation to occur – as well as the keys for rotate, zoom and pan – and then you just let it fly.

Here's the updated file for you to try in either Chrome or Firefox (or elsewhere with WebGL enabled), with the A, S and D keys mapped to rotate, zoom and pan, respectively.

Here's a video of the trackball capability in action:

Unable to display content. Adobe Flash is required.

And here's the updated source code:

<!doctype html>

<html>

  <head>

    <title>Apollonian Viewer</title>

  </head>

  <body>

    <script

      type="text/javascript"

      src="http://code.jquery.com/jquery-1.7.1.js">

    </script>

    <script type="text/javascript" src="js/xdr.js"></script>

    <script type="text/javascript" src="js/Three.js"></script>

    <script type="text/javascript">

      var animateWithWebGL;

      var container, root = null;

      var camera, scene, renderer, trackball;

      var zoomScale, xRotation, yRotation;

      var changingLevel = false;

 

      init();

      animate();

 

      // Feature test for WebGL

 

      function hasWebGL()

      {

        try

        {

          var canvas = document.createElement('canvas');

      
0;  
var ret =

            !!(window.WebGLRenderingContext &&

                (canvas.getContext('webgl') ||

                 canvas.getContext('experimental-webgl'))

              );

          return ret;

        }

        catch(e)

        {

          return false;

        };

      }

 

      function init()

      {

        zoomScale = 1;

        xRotation = 0;

        yRotation = 0;

        var rotInc = 0.05;

 

        animateWithWebGL = hasWebGL();

 

        container = document.createElement('div');

        container.style.background = "#000000";

        document.body.appendChild(container);

 

        // Set the scene size (slightly smaller than the

        // inner screen size, to avoid scrollbars)

 

        var WIDTH = window.innerWidth - 25,

            HEIGHT = window.innerHeight - 25;

 

        // Set some camera attributes

 

        var VIEW_ANGLE = 45,

            ASPECT = WIDTH / HEIGHT,

            NEAR = 0.1,

            FAR = 200;

 

        // Create the renderer, camera, scene and trackball

 

        renderer =

          animateWithWebGL ?

            new THREE.WebGLRenderer() :

            new THREE.CanvasRenderer();

        camera =

          new THREE.PerspectiveCamera(

            VIEW_ANGLE,

            ASPECT,

            NEAR,

            FAR

          );

        scene = new THREE.Scene();

 

        trackball =

          new THREE.TrackballControls(camera, container);

        trackball.rotateSpeed = 1.4;

        trackball.zoomSpeed = 2.0;

        trackball.panSpeed = 0.5;

        trackball.noZoom = false;

        trackball.noPan = false;

        trackball.staticMoving = true;

        trackball.dynamicDampingFactor = 0.3;

        trackball.minDistance = 1;

        trackball.maxDistance = 100;

        trackball.keys = [65, 83, 68]; // [a:rotate, s:zoom, d:pan]

        trackball.addEventListener('change', render);

 

        // The camera starts at 0,0,0 so pull it back

 

        camera.position.z = 4;

 

        // Create a point light

 

        var pointLight = new THREE.PointLight(0xFFFFFF);

 

        // Set its position

 

        pointLight.position.x = 2;

        pointLight.position.y = 10;

        pointLight.position.z = 26;

 

        // Add to the scene

 

        scene.add(pointLight);

 

        // And the camera

 

        scene.add(camera);

 

        // Start the renderer

 

        renderer.setSize(WIDTH, HEIGHT);

 

        // Attach the renderer-supplied DOM element

 

        container.appendChild(renderer.domElement);

 

        $(document).keypress(

          function (event)

          {

            // On Firefox we need event.which rather than keyCode

 

            var code = event.keyCode ? event.keyCode : event.which;

            switch (String.fromCharCode(code))

            {

              case '0':

              case '1':

            
case '2':

              case '3':

              case '4':

              case '5':

              case '6':

              case '7':

              case '8':

              case '9':

                var level = code - '0'.charCodeAt();

                populateWithLevel(level == 0 ? 10 : level);

                break;

            }

          }

        );

 

        populateWithLevel(10);

      }

 

      function populateWithLevel(level)

      {

        // Make sure we're not already changing levels

 

        if (changingLevel)

          return;

 

        changingLevel = true;

 

        // If we already have a level loaded, remove the

        // root from the scene and delete it

 

        if (root != null)

        {

          scene.remove(root);

          delete root;

        }

 

        // Make sure CORS is enabled

 

        jQuery.support.cors = true;

 

        // Call our web-service with the appropriate level

 

        $.ajax(

          {

            url:

              'http://apollonian.cloudapp.net/api/spheres/1/' +

              level,

            crossDomain: true,

            data: {},

            dataType: "json",

            error: function(err)

            {

              alert(err.statusText);

            },

            success: function(data)

            {

              // Create the spheres' materials

 

              var materials =

                [

                  new THREE.MeshLambertMaterial({ color: 0x000000 }),

                  new THREE.MeshLambertMaterial({ color: 0xFF0000 }),

                  new THREE.MeshLambertMaterial({ color: 0xFFFF00 }),

                  new THREE.MeshLambertMaterial({ color: 0x00FF00 }),

                  new THREE.MeshLambertMaterial({ color: 0x00FFFF }),

                  new THREE.MeshLambertMaterial({ color: 0x0000FF }),

                  new THREE.MeshLambertMaterial({ color: 0xFF00FF }),

                  new THREE.MeshLambertMaterial({ color: 0xA9A9A9 }),

                  new THREE.MeshLambertMaterial({ color: 0x808080 }),

                  new THREE.MeshLambertMaterial({ color: 0xD3D3D3 }),

                  new THREE.MeshLambertMaterial({ color: 0xFFFFFF }),

                  new THREE.MeshLambertMaterial({ color: 0xFFFFFF })

                ];

 

              // Set up the sphere vars

 

              var rootRad = 0.01, segs = 9, rings = 9;

 

              // Create our root object

 

              var sphereGeom =

                new THREE.SphereGeometry(rootRad, segs, rings);

 

    
;         
// Create the mesh from the geometry

 

              root =

                 new THREE.Mesh(sphereGeom, materials[0]);

 

              scene.add(root);

 

              // Process each sphere, adding it to the scene

 

              $.each(

                data,

                function (i, item)

                {

                  // Get shortcuts to our JSON data

 

                  var x = item.X, y = item.Y, z = item.Z,

                      rad = item.R, level = item.L;

 

                  var length = Math.sqrt(x * x + y * y + z * z);

 

                  // Only add spheres near the edge of the outer one

                  // (and only the front half if not animating)

 

                  if (

                    length + rad > 0.99 &&

                      (animateWithWebGL || z > 0)

                  )

                  {

                    // Create the mesh from the geometry

 

                    var sphere =

                      new THREE.Mesh(sphereGeom, materials[level]);

 

                    sphere.position.x = x;

                    sphere.position.y = y;

                    sphere.position.z = z;

                    var scaledRad = rad / rootRad;

                    sphere.scale.x = scaledRad;

                    sphere.scale.y = scaledRad;

                    sphere.scale.z = scaledRad;

 

                    root.add(sphere);

                  }

                }

              );

 

       
0;     
// Draw!

 

              renderer.render(scene, camera);

 

              changingLevel = false;

            }

          }

        );

        }

 

      function animate()

      {

        requestAnimationFrame(animate);

        trackball.update();

      }

 

      function render()

      {

        renderer.render(scene, camera);

      }     

    </script>

  </body>

</html>

I was able to strip out all the code enabling zoom/spin, although I left the piece in that lets you change levels with the number keys. Once again Three.js and WebGL have really impressed me: this is very interesting technology, and really feels comparable with a native, GPU-enabled 3D graphics experience.

Over the next few posts, we're going to wrap up this series by implementing a Metro-style 3D viewer using WinRT and DirectX. Fun, fun, fun! 🙂

4 responses to “Creating a 3D viewer for our Apollonian service using HTML5 – Part 3”

  1. Horatio McDowney Avatar
    Horatio McDowney

    The implications here are mind numbing. How long before our friends at Autodesk enable BIM manipulation this way? Perhaps even with an API similar to what was available years ago with Mapguide 6.5? . . . . Well I guess I can dream. . .

  2. Kean Walmsley Avatar

    As you might imagine, there's a lot of interest/activity in just that kind of area. What exactly comes out of it remains to be seen, though.

    Kean

  3. Kean,

    I am interested in knowing if you had considered other web graphics frameworks such as SceneJs, which is pretty good but only WebGl though.

    KR

  4. KR,

    Only fairly briefly. I did take a cursory look at others, but didn't perform an in-depth comparison.

    I was very happy with THREE.js, I have to say, although I'm sure its "competitors" have their merits, too.

    Kean

Leave a Reply

Your email address will not be published. Required fields are marked *