November 2014

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            










« Manufacturing DevBlog | Main | Creating a 3D viewer for our Apollonian service using WinRT – Part 1 »

May 23, 2012

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');

          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);

                  }

                }

              );

 

              // 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! :-)

blog comments powered by Disqus

Feed/Share

10 Random Posts