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

Leave a Reply

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