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            










« Maintaining per-object XData in AutoCAD using .NET | Main | Blinking lights with a Netduino Plus 2 »

February 15, 2013

An augmented reality view of an Apollonian packing using PointCloud Browser

After my initial (only partially successful) attempt, earlier in the week, to get 3D geometry from the Apollonian web-service into a PointCloud Browser session, I finally managed to get it working properly.

Given the currently fairly light documentation available – especially for the Viper JavaScript namespace which gives access to the 3D rendering capabilities in the browser – I ended up posting a question to the PointCloud forum. The answer was very instructive – I was able not only to get spheres of different radii displayed using the same mesh…

All in white

… but also to apply different colours to the same mesh via tinting. Here’s an intermediate step I hit (I’m not fully sure why the colours came out as they did, but anyway)…

A bit washed out

… before getting much more satisfactory results. With fewer lines of code.

That's better

Here’s the updated HTML page (also available here and at this shortened URL):

<!DOCTYPE html>

<html>

  <head>

    <title>Apollonian</title>

    <meta

      name="viewport"

      content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>

    <meta name="viper-init-options" content="manual"/>

 

    <link

      rel="viper-app-icon" type="images/png"

      href="resources/images/appicon.jpg"/>

    <link

      rel="stylesheet" href="./css/common.css" type="text/css"

      charset="utf-8"/>

 

    <script

      type="text/javascript"

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

    </script>

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

 

    <script type="text/xml" id="library">

      <library>

        <mesh

          id="sphere_mesh" radius="1" primitive="sphere"

          details="2" />

        <node id="sphere_node">

          <model

            texture_src="resources/images/white-4x4.png"

            id="sphere_model" mesh="sphere_mesh"/>

        </node>

      </library>

    </script>

 

    <script type="text/xml" id="scene">

      <scene base="relative-baseplane">

        <light id="main_light"

          intensity="1.0"

          fade="constant"

          ambient="0.2, 0.2, 0.2, 0.2"

          diffuse="1.0, 1.0, 1.0, 1.0"

          specular="1.0, 1.2, 1.2, 1.0"

          position="3, 0.5, 2, 0"/>

      </scene>

    </script>

 

    <script type="text/javascript">

      function startup() {

        viper.requireRealityMap();

      }

 

      /*

      * This function is called when the web app is fully loaded

      * (e.g. sounds, textures, image descriptors)

      * and we're completely ready to go

      */

      function onAppLoaded() {

        startup();

 

        var nodeListener = new viper.NodeListener();

        viper.getCamera().attachListener(

          nodeListener,

          function(node, data) {

            var pos = data.position.getTranslation();

            viper.log(

              "Received camera pos update: " +

              pos.getX() + "," + pos.getY() + "," + pos.getZ()

            );

          }

        );

      }

 

      /*

       * This function is called when the Viper JavaScript API is

       * ready for use

       */

      function onViperReady() {

        viper.setLoggingEnabled(true);

 

        var scene = viper.getScene();

        populateWithLevel(scene, 5);

 

        // Create an observer. This observer contains the callback

        // functions that may be called from the engine layer.

        // We only need to add the functions that we are interested

        // in.

 

        var observer = {

          /*

          * Called when the user clicked cancel in the map creation

          * view

          */

          onMapCreationCancelled: function () {

            startup();

          }

        }

 

        // Attach the observer to viper

 

        viper.setObserver(observer);

      }

 

      function populateWithLevel(scene, level) {

 

        viper.log("Populate with level: " + level);

 

        // 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/0.3/' +

              level,

            crossDomain: true,

            data: {},

            dataType: "json",

            error: function (err) {

              alert(err.statusText);

            },

            success: function (data) {

 

              viper.log("Successfully called web service.");

 

              // Hard-code the colour for each level in an array

 

              var colors =

                [ "0,0,0,1", "1,0,0,1", "1,1,0,1", "0,1,0,1",

                  "0,1,1,1", "0,0,1,1", "1,0,1,1", "0.9,0.9,0.9,1",

                  "0.6,0.6,0.6,1", "0.3,0.3,0.3,1", "1,1,1,1",

                  "1,1,1,1" ]

 

              // Process each sphere, adding it to the scene

 

              $.each(

                data,

                function (i, item) {

 

                  // Get shortcuts to our JSON data

 

                  viper.log("Processing item " + i);

 

                  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

 

                  if (length + rad > 0.29) {

 

                    // Create a spherical node

 

                    var nodeID = "sphere_" + i;

                    var position = new viper.math.Vector(x, y, z);

                    var sphere = new viper.Node(nodeID, position);

                    sphere.setPrototype("sphere_node");

                    sphere.setScale(rad);

                    sphere.setTint(colors[parseInt(level)]);

                    scene.addChild(sphere);

                  }

                }

              );

            }

          }

        );

      }

    </script>

  </head>

  <body/>

</html>

The page depends on an additional texture (which you can get here) but that should be of modest inconvenience).

To really get a sense of the responsiveness of the PointCloud Browser when viewing this 3D geometry, here’s a quick video I recorded this morning to show it in action:




For fun, here’s a level 7 packing (you can tweak the level in the call to populateWithLevel(), above). This change does slow down the load considerably – which stalls the “reality map acquisition” process for several seconds – even though the runtime performance on my iPad 2 remains pretty good. So I’ve left the posted versions at level 5.

At level 7

blog comments powered by Disqus

Feed/Share

10 Random Posts