Writing a user radar

This tutorial will take you through the steps of getting set up, writing your first Hello World app, and creating a User Radar app that uses the sensor data and displays the locations of users in real-time.

Step 1: Getting Started

Make sure you have the following prerequisites:

  • ZigJS Browser Plugin
  • Microsoft Kinect or Asus Wavi Xtion

Step 2: Hello World

We're going to make our first app, which will be useless except as a test to make sure all the right things are talking to each other in the background. This app is a blank page that will simply display an alert when the Zig plugin loads. Create an html file with the following content:
<!DOCTYPE html>
<html>
<head>
<title>Zig Plugin Test</title>

<script src='http://cdn.zigfu.com/zigjs/zig.min.js'></script>

<script>

    function loaded() {
        zig.addEventListener('loaded', function() {
            alert('Hello motion world!');
        });
    }

    document.addEventListener('DOMContentLoaded', loaded, false);

</script>

</head>
<body>

</body>
</html>

The included script from cdn.zigfu.com is zig.js, which defines the zig object and all of the bindings you'll need to interact with the browser plugin. Note that you don't have to have an event handler for the loaded event. It's a good way of testing the basic functionality of the plugin, and for doing something different on your site based on whether or not the user has the plugin installed.

Step 3: User Radar Sample App

Now let's go ahead and make something that's actually useful! This will be the user radar, which will have little dots that move around inside of a box to indicate where the users are in the room. Ok, so maybe it's not the most useful thing, but it's an important component for a number of applications and should be a good introduction to the zigfu object model, which is used throughout the entire ZDK.

First things first, comment out the alert; it's obnoxious and doesn't deserve to be part of this new and improved sample. Now let's start with the box. We'll code up the user radar using a div for the box, and have some JavaScript code that creates a div for each dot. Add the following code inside the head of the HTML:

<style>
    div#radar 
    {
        width: 400px;
        height: 400px;
        border: 1px solid black;
        overflow: hidden;
    }
</style>

and the following code to the body. It doesn't matter whether it goes before or after the div that contains the plugin:

<div id='radar'></div>

This makes the box (the div called 'radar') 400 x 400 pixels (set arbitrarily) and gives it a border so you can see where it is. The overflow: hidden directive ensures that users do not show up outside of the box. There are other ways to solve that problem, but this is the simplest method.

We'll receive events from the zig object, and for the onuserfound callback function, we'll instantiate an object with that class and stick it in the box. Then each time there's new data from the Kinect, there will be an onupdatedata callback function, and in that callback we'll move the user to the appropriate position on the screen. But first things first, let's get set up with receiving callbacks from the zig object and make sure that the callbacks are being run. Edit the ZigPluginLoaded function to look like this:

function loaded() {
    var radardiv = document.getElementById('radar');

    // The radar object will respond to events from the 
    // zig object and move the dots around accordingly.
    // It is also responsible for creating and destroying 
    // the dots when users are added and removed.
    // Functions onnewuser(), onlostuser(), and ondataupdate() 
    // are called by the zig object when those things happen.
    var radar = {
        onuserfound: function (user) {
            console.log('running onnewuser in radar');
        },
        onuserlost: function (user) {
            console.log('running onlostuser in radar');
        },
        ondataupdate: function (zig) {

        }
    };
    // Add the radar object as a listener to the zig object, so that 
    // the zig object will call the radar object's callback functions.
    zig.addListener(radar);
}

At this point, the file in its entirety should look like this:

<!DOCTYPE html>
<html>
<head>
<title>User Radar</title>
<script src='http://cdn.zigfu.com/zigjs/zig.min.js'></script>

<script>

    function loaded() {
        var radardiv = document.getElementById('radar');

        // The radar object will respond to events from the 
        // zig object and move the dots around accordingly.
        // It is also responsible for creating and destroying 
        // the dots when users are added and removed.
        // Functions onuserfound(), onuserlost(), and ondataupdate() 
        // are called by the zig object when those things happen.
        var radar = {
            onuserfound: function (user) {
                console.log('running onuserfound in radar');
            },
            onuserlost: function (user) {
                console.log('running onuserlost in radar');
            },
            ondataupdate: function (zigdata) {

            }
        };  
        // Add the radar object as a listener to the zig object, so that 
        // the zig object will call the radar object's callback functions.
        zig.addListener(radar);
    }
    document.addEventListener('DOMContentLoaded', loaded, false);

</script>

<style>
    div#radar 
    {
        width: 400px;
        height: 400px;
        border: 1px solid black;
        position: relative;
        overflow: hidden;
    }
</style>

</head>
<body>

<div id='radar'></div>

</body>
</html>

To check if this works, open the html file in a browser and open a JavaScript console. We'll use the log messages to see whether the radar object's callbacks are being called when they should be. If you're unfamiliar with the JavaScript console, then see here for more information. Now, with the console open, go to a place where the sensor will be able to see you. The sensor only has about a 45 degree horizontal field of view, so make sure you stay within that. Also, the sensor is far-sighted and doesn't have reading glasses, so you'll have to stay at least three or four feet from the sensor for it to detect you. When the sensor first detects a user, the console should look something like this:

Zig: inited
Zig: New user: 3
running onuserfound in radar

The last line is the log message that you just put into the radar object's onnewuser function. The first two lines are debug messages from the zig object.

Note: You can disable debug messages from the zig object with zig.verbose=false, although it's generally a good idea to leave the messages in until you're ready to release your app.

Now cover the red glowy part of the sensor with your hand for about ten seconds so it can't see you. You should see the following messages in the console:

Zig: Lost user: 3
running onuserlost in radar
Note: If you do see the New User message from the zig object, but not the onuserfound message, then for some reason, onuserfound is not being called. Double-check that the spelling of the functions is correct, because if it's not, the code will simply fail silently. If you don't see anything in the console, then reload the page and try again ... if you still don't see it, then for some reason the Zig plugin isn't running. See our Troubleshooting page for more help.

Now that the callback structure has been set up, the next step is creating the dot that will go inside the box to represent a user. We want to create a new dot each time a user is found, and destroy the dot when the user is lost. We also want the dots to move around correctly, but let's get them creating and destroying correctly first. Since the user dots should all look the same, create a css class called user that we'll add to all the objects that we create in the JavaScript code.

div.user 
{
    position: relative;
    width: 10px;
    height: 10px;
    background-color: blue;
}

Now, let's add the code that creates and destroys the dots. One of the cool things about the zigfu JavaScript API is that each user object supplied by the zig object persists until the user is lost. This means that we can add properties and methods to user objects dynamically. In this case, an elegant way of associating a div with each user is to add a reference to a user div as a property of the respective user object. Here's code that creates and destroys a div for each tracked user. Change the declaration of the radar object to the following:

        var radar = {
            onuserfound: function (user) {
                var userdiv = document.createElement('div');
                userdiv.className = 'user';
                user.radarelement = userdiv; // add radarelement as a property of the user
                radardiv.appendChild(user.radarelement);
            },
            onuserlost: function (user) {
                radardiv.removeChild(user.radarelement);
            },
            ondataupdate: function (zigdata) {

            }
        };

This should display a blue dot in the top left of the box for each user that the sensor finds. Here's an example, with two users detected (don't worry if it thinks your chair is a user, the user detection middlewares often have false positives):

The final step for the user radar is to make the dots move around. For this we'll use the ondataupdate callback, which gets called every time the sensor has new data, approximately 30 times per second. The data passed in the zigdata parameter is a reference to the zig object, which has a number of different pieces of data inside it. We're interested in zig.users, which is a hashtable of all the users currently being tracked. The zig.users hashtable has userids as keys for user objects, which are the values. Replace the definition of the ondataupdate callback with the following:

ondataupdate: function (zigdata) {
    for (var userid in zigdata.users) {
        var user = zigdata.users[userid];
        var pos = user.position;
        var el = user.radarelement;
        var parentElement = el.parentNode;
        var zrange = 4000;
        var xrange = 4000;
        var pixelwidth = parentElement.offsetWidth;
        var pixelheight = parentElement.offsetHeight;
        var heightscale = pixelheight / zrange;
        var widthscale = pixelwidth / xrange;
        el.style.left = (((pos[0] / xrange) + 0.5) * pixelwidth - (el.offsetWidth / 2)) + "px";
        el.style.top = ((pos[2] / zrange) * pixelheight - (el.offsetHeight / 2)) + "px";
    }
}

The first several lines of this function define some local variables to make the code more explicit. The xrange and zrange variables specify the ranges in millimeters in which we expect to find users (x is left-right, z is distance from the sensor). Note the for loop that iterates through the keys of the zigdata.users hashtable. The last two lines are the meat of the function, which move the left and top style properties of the user's radarelement according to the user's real-world position. It should look like this in action:

There it is! Congratulations, you've just made a motion-enabled website! Here's the final html file:

<!DOCTYPE html>
<html>
<head>
<title>User Radar</title>
<script src='http://cdn.zigfu.com/zigjs/zig.min.js'></script>

<script>

    function loaded() {
        var radardiv = document.getElementById('radar');

        // The radar object will respond to events from the 
        // zig object and move the dots around accordingly.
        // It is also responsible for creating and destroying 
        // the dots when users are added and removed.
        // Functions onuserfound(), onuserlost(), and ondataupdate() 
        // are called by the zig object when those things happen.
        var radar = {
            onuserfound: function (user) {
                var userdiv = document.createElement('div');
                userdiv.className = 'user';
                user.radarelement = userdiv; // add the radarelement property to the user object
                radardiv.appendChild(user.radarelement);
            },
            onuserlost: function (user) {
                radardiv.removeChild(user.radarelement);
            },
            ondataupdate: function (zigdata) {
                for (var userid in zigdata.users) {
                    var user = zigdata.users[userid];
                    var pos = user.position;
                    var el = user.radarelement;
                    var parentElement = el.parentNode;
                    var zrange = 4000;
                    var xrange = 4000;
                    var pixelwidth = parentElement.offsetWidth;
                    var pixelheight = parentElement.offsetHeight;
                    var heightscale = pixelheight / zrange;
                    var widthscale = pixelwidth / xrange;
                    el.style.left = (((pos[0] / xrange) + 0.5) * pixelwidth - (el.offsetWidth / 2)) + "px";
                    el.style.top = ((pos[2] / zrange) * pixelheight - (el.offsetHeight / 2)) + "px";
                }
            }
        };
        // By adding the radar object as a listener to the zig object, the zig object will now start calling
        // the callback functions defined in the radar object.
        zig.addListener(radar);

    }

    document.addEventListener('DOMContentLoaded', loaded, false);

</script>

<style>
    div#radar 
    {
        width: 400px;
        height: 400px;
        border: 1px solid black;
        position: relative;
        overflow: hidden;
    }
    div.user 
    {
        position: relative;
        width: 10px;
        height: 10px;
        background-color: blue;
    }
</style>

</head>
<body>

<div id='radar'></div>

</body>
</html>