Leaping into Motion

PeterLeow
936 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

alt text

What's the Fuss?

There has been a real buzz about Virtual reality (VR) and augmented reality (AR) technologies over the past few years. Major VR developments by the big tech companies. such as Sony's PlayStation VR and Google's Daydream, are pushing VR into the mainstream. The unrelenting fever of Pokemon Go in 2016 charted the unwavering course for the future development of AR. Even more excitingly, Microsoft's HoloLens sets to bring the mix of virtual and augmented reality (Mixed Reality) into our real world may not seem so far-fetched after all.

While waiting for the fruits to ripe, aren't you curious how it is possible to control an object in your computer screen using your hands or fingers in the air. What better way to find out the answer then trying your hand at creating one, albeit a simple one.

This article will get you started on writing code to perform simple manipulations of web elements in the browser using your finger in the air via a sensor device called Leap Motion controller by Leap Motion, Inc.

Knowing Your Tool

There is an old Chinese proverb that says "工欲善其事,必先利其器", which I interpret as "one needs to know one's tool well in order to do a good job". So, you will start by getting to know the basics of your tool — the Leap Motion controller.

What's Leap Motion Controller?

It's a sensor device that detects and captures hand and finger motions in the air as input to VR/AR applications.

alt text Figure 1: Leap Motion Controller

System Architecture

Besides the Leap Motion controller hardware, you also need to install the Leap Motion SDK software on the computer that interfaces with the Leap Motion controller.

The Leap Motion SDK runs as a background process on your computer. It receives motion tracking data from your hands and fingers in the real world via the USB connected Leap Motion controller.

The motion tracking data are presented to your application as a series of snapshots called frames. Each frame contains the coordinates, directions, and other information about the hands and fingers detected in that frame. Each frame is represented as a Frame object  in Leap Motion APIs. The Frame object is essentially the root of Leap Motion's tracking model.

alt text Figure 2: Leap Motion Tracking Model

Your software application can then access the Frame object via one of the two APIs provided by the Leap Motion SDK. These two types of APIs are the Native Interface and the WebSocket interface.

alt text Figure 3: Leap Motion SDK

Native Interface

The native interface is a dynamic library that you can use to create Leap-enabled desktop applications in a variety of languages and technologies: C#, C++, Java, Python, Objective-C, Unity, and Unreal.

WebSocket Interface

The WebSocket interface, on the other hand, allows you to create Leap-enabled web applications that work in conventional web browsers out of the box. It provides motion tracking data in the form of JSON formatted messages which are consumed by a JavaScript library, which in turn makes them available to your web applications as regular JavaScript objects for further processing.

Leap Motion Coordinates

The Leap Motion Controller provides right-handed coordinates in units of real world millimeters within the Leap Motion frame of reference, the origin (0, 0, 0) of which is the centre of the Leap Motion controller device.

alt text Figure 4: Leap Motion Coordinates

Interaction Box

The Leap Motion controller can detects and tracks the movement of your hand or finger only if it is within its field of view, an invisible inverted pyramid sitting on the device. To take away much of the guessing work, Leap Motion further provides a virtual Interaction Box to help your hand or finger stays in the range. An Interaction Box in Leap Motion defines a box-shaped region completely within the field of view of the Leap Motion controller. It is Leap Motion's way to assure the users that their hands or fingers will be tracked as long as they stay within this box.

alt text Figure 5: Normalized Interaction Box

Making Things Happen

Having learned the basic mechanism of a Leap Motion controller, you are now ready to get your hands dirty in cooking up code that makes use of the motion tracking data received from the controller. Let's do it...

Setting the Stage

First thing first, you have to set up your computer so that it can interface with the Leap Motion controller. Follow this online setup guide to download and install the desktop developer SDK for your machine. As part of the installation, you may be prompted to upgrade the display driver, just ignore it as it is not required for this exploration trip.

With the SDK installed, plug the Leap Motion controller into your computer via the USB.

Next, get ready an HTML page with the following code and save it as, say, index.html.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Leaping into Motion</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
</head>
<body>
</body>
</html>

Included in the index.html page is the Leap Motion JavaScript library as shown:

<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>

This Leap Motion JavaScript library receives motion tracking data from the WebSocket interface and make the data available to index.html for consumption by JavaScript code.

Getting Connected

With the Leap Motion JavaScript library included, you now have access to the Leap Motion API via the Leap global namespace. To start tracking your hands or fingers, you will call the loop() function of the Leap namespace to mediate the connection between your web application and the WebSocket interface, and to invoke a callback function that receives a Frame object at regular interval. Typically, this interval is set at 60 Frame objects per second. Each Frame object contains motion tracking data of hands or fingers detected by the Leap Motion controller at a particular instance. The code to implement this is as follows:

Leap.loop(function(frame){
  // Add code to process the frame of tracked data 
})

You will add it to index.html as shown:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Leaping into Motion</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
</head>
<body>
<script>
Leap.loop(function(frame){
  // Add code to process the frame of tracked data
})
</script>
</body>
</html>

Creating a Dashboard

In index.html,  create a HTML table, furnished with some CSS, for outputting the Frame object data received from the controller.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Leaping into Motion</title>
<style>
th, td {
  min-width: 300px;
  text-align: left;
  vertical-align: top	
}
</style>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
</head>
<body>

<table>
  <tr>
    <th>Frame Data</th><th>Hand Data</th><th>Finger Data</th>
  </tr>
  <tr>
    <td id="frameData">Frame Data</td><td id="handData">Hand Data</td><td id="fingerData">Finger Data</td>
  </tr>
</table>

<script>
Leap.loop(function(frame){
  // Add code to process the frame of tracked data
})
</script>
</body>
</html>

Deciphering the Frame

Each Frame object passed to the callback function of  Leap.loop()  is identified by an id and may contain Hand objects and Finger objects, among other things. The following code gets the id of a Frame object and the respective numbers of Hand objects and Finger objects contained in that Frame object, and displays them in the browser.

<script>
var frameData = document.getElementById('frameData');

Leap.loop(function(frame){

  // Get and show frame data
  frameData.innerHTML = "Frame ID: " + frame.id  + "<br>"
            + "No of Hands: " + frame.hands.length + "<br>"
            + "No of Fingers: " + frame.fingers.length + "";

})
</script>

Deciphering the Hands

Each Hand object contained in the Frame object possesses a set of properties, such as id, hand type (left or right hand), palm position, grab strength, among other things. The following code gets the id, type, palmPosition, grabStrength, and pinchStrength of each Hand object contained in the Frame object, and displays them in the browser.

// Get and show hand data
handData.innerHTML = "";
for(var i = 0; i < frame.hands.length; i++){
  var hand = frame.hands[i];
  handData.innerHTML += "Hand ID: " + hand.id + "<br>"
			  + "Hand Type: " + hand.type + "<br>"
			  + "Palm Position: " + hand.palmPosition + "<br>"
			  + "Grab Strength: " + hand.grabStrength + "<br>"  
			  + "Pinch Strength: " + hand.pinchStrength + "<br><br>";	  	  
}

The hand.palmPosition returns a 3D vector (x, y, z) indicating the coordinates of the centre position of the palm in millimeters from the Leap origin.

Deciphering the Fingers

Similarly, each Finger object contained in the Frame object possesses a set of properties, such as id of the finger object, the id of the Hand object that it belongs to, finger tip position, finger type, among other things. The following code below gets the id, handId, tipPosition, and type of each Finger object contained in the Frame object, and displays them in the browser.

// Get and show finger data
fingerData.innerHTML = "";
for(var i = 0; i < frame.fingers.length; i++){
  var finger = frame.fingers[i];
  fingerData.innerHTML += "Finger ID: " + finger.id  + "<br>"
			  + "Belong to Hand ID: " + finger.handId + "<br>"
			  + "Finger Tip Position: " + finger.tipPosition + "<br>"
			  + "Finger Type: " + finger.type + "<br>" + "<br>";	  	  
}

The finger.tipPosition returns a 3D vector (x, y, z) indicating the coordinates of the tip position of a finger in millimeters from the Leap origin.

Seeing is Believing

The code discussed above has been created in the Dashboard code section in which the code is split into HTML, CSS, and JavaScript parts named as dashboard.html, dashboard.css, and dashboard.js respectively for ease of cross reference.

Dashboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="dashboard.css">
</head>
<body>
<table>
<tr>
<th>Frame Data</th><th>Hand Data</th><th>Finger Data</th>
</tr>
<tr>
<td id="frameData">Frame Data</td><td id="handData">Hand Data</td><td id="fingerData">Finger Data</td>
</tr>
</table>
<script src="dashboard.js"></script>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Run the code! You should be able to see the constant updating of frame, hands, and fingers data in the browser as you move your hands and fingers above the Leap Motion controller. Have fun!

Animating Hands and Fingers

You have done the code to capture and display the constant update of frame, hands, and fingers data in the browser. Isn’t that a piece of cake? However, trying to make sense of textual data, not to mention ones that are changing constantly, is hard. It will be helpful if the data can be animated using some graphical cues. That sounds interesting, right?

As a lead, let’s create graphical cues for one hand and five finger tips and animate them in the browser based on their position data, i.e. palm position of the hand and tip positions of the respective fingers. For simplicity, each of these hand and fingers is represented graphically by a rounded HTML <div>. Are you ready?

In index.html, add six <div>‘s along with their related CSS as shown:

<div id="palm"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
div {
  background-color: red;
  border-radius: 50px;
  position: absolute;
}
div#palm {
  height: 100px;
  width: 100px;  
}
div.finger {
  height: 20px;
  width: 20px;  
}

In the <script> section, assign these <div>‘s to some JavaScript variables as shown:

var palmDisplay = document.getElementById('palm');
var fingersDisplay = document.getElementsByClassName('finger');

You are ready to write code to animate one of your hand based on its palm position. The palm position is available in 3D vector coordinates in millimeters from the Leap origin available via the palmPosition property of the Hand object. Use the normalizePoint() method of the Leap’s InteractionBox class to convert these coordinates to their normalized coordinates in the range between 0 and 1. The code to do this is as follows:

var normalizedPalmPosition = frame.interactionBox.normalizePoint(hand.palmPosition);

To convert these normalized coordinates to your application's coordinates, simply multiply the normalized coordinate of each axis by the maximum range of the corresponding axis of the browser screen, which ignoring the z axis as it is not required for this exercise.

The following code snippet converts the normalized x coordinate, i.e. normalizedPalmPosition[0], to the browser’s x coordinate which becomes the x coordinate of the centre of <div id="palm"></div> by re-positioning.

var palmX = window.innerWidth * normalizedPalmPosition[0] - palmDisplay.offsetWidth / 2;
palmDisplay.style.left = palmX + "px";

Similarly, The following code snippet converts the normalized y coordinate, i.e. normalizedPalmPosition[1], to the browser's y coordinate which becomes the y coordinate of the centre of <div id="palm"></div> by re-positioning. Note the subtraction of normalized y coordinate from one which is needed to convert the upwards pointing y axis of Leap's coordinate system to its downwards pointing counterpart of browser's coordinate system.

var palmY = window.innerHeight * (1 - normalizedPalmPosition[1]) - palmDisplay.offsetHeight / 2;
palmDisplay.style.top = palmY + "px";

Where do you put these five lines of code? Add them to the for loop for the Hand object.

The code discussed so far has been created in the Animating Hands and Fingers code section in which the code is split into HTML, CSS, and JavaScript parts named as animation.html, animation.css, and animation.js respectively for ease of cross reference.

Animating Hands and Fingers
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Animating Hands and Fingers Motions</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="animation.css">
</head>
<body>
<table>
<tr>
<th>Frame Data</th><th>Hand Data</th><th>Finger Data</th>
</tr>
<tr>
<td id="frameData">Frame Data</td><td id="handData">Hand Data</td><td id="fingerData">Finger Data</td>
</tr>
</table>
<div id="palm"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
<div class="finger"></div>
<script src="animation.js"></script>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Run the code! See that the  <div id="palm"></div>  moves along with one of your hand within the region of the Interaction Box of the Leap Motion controller. Notice that the <div id="palm"></div> can sneak out of your screen. To confine its movement within the bound of your screen, you can either write additional code or simply add a true argument to the normalizePoint() method as shown:

var normalizedPalmPosition = frame.interactionBox.normalizePoint(hand.palmPosition, true);

Wait! What about the code to animate the fingers? The answer is that you have just learned it for the hand, how much different can it be for the fingers? I shall leave it as your homework.

If you have done your homework well, you should be able to see the "fruit of your labour" as shown in this animated gif:

alt text Figure 6: Animating Hand & Fingers Motions

Do not stop here, enhance the code to animate two hands and ten fingers.

Going the Extra Mile

Having written the code to animate your hands and fingers movement in the browser, the next natural thing to look forwards to is to be able to use these animated hands or fingers to interact with your web applications. One of the most common interactions is clicking a web element, e.g. a button, to trigger some event using a mouse. Can this be done using a finger in the air in place of the mouse? You bet!

Clicking with Your Finger in the Air

Imagine there is a virtual vertical touch plane above the Interaction Box. The distance between your finger tip and this touch plane can be obtained via the touchDistance property of the Finger object and is available in the range between -1 and 1.

alt text Figure 7: Touch Plane

As shown in the diagram above, a value between zero and one indicates that the finger tip is in the hovering zone, a value of zero indicates that the finger tip has just touched the touch plane, and a value between zero and minus one indicates that the finger tip is in the touching zone. You can then use the value returned by the  touchDistance property of the Finger object  in your code to emulate the state that a finger is in, i.e. hovering or touching. However,  It is entirely up to you to decide on the threshold value of the touchDistace property that demarcates the hovering state from the touching state. In other words, it isn't set in stone that the threshold value has to be always zero.

Through the touchDistance property, you can emulate a left mouse click using your finger in the air via the Leap Motion controller. Let's extend it to the code in the Open Sesame code section. It is a simple web application that has a door and two buttons — one to open the door to reveal a picture behind it and another to close it. Run it and try out the buttons using your mouse!

Open Sesame
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Open Sesame</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="open_sesame.css">
</head>
<body>
<h2></h2>
<button id="btnOpen">Open</button>
<button id="btnClose">Close</button>
<div style="background-image:url('https://peterleowblog.com/wp-content/uploads/2017/09/Marilyn_Monroe_photo_pose_Seven_Year_Itch-1.jpg');height:500px;width:405px;border:solid 1px">
<div id="door"></div>
</div>
<script src="open_sesame.js"></script>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

You mission is to spare the mouse and use your finger in the air instead to click the respective buttons. Note that the Leap Motion JavaScript library has already been added to this application.

To animate your finger tip on the screen, add a rounded HTML <div> with its related CSS to the Open Sesame code section as shown:

<div id="finger"></div>
div#finger {
  height: 10px;
  width: 10px;
  position: absolute;
  background-color: red;
  border-radius: 5px
}

Next, add the following JavaScript code to animate one of your finger tip on the screen.

var fingerDisplay = document.getElementById("finger");
Leap.loop(function(frame) {
  if (frame.fingers.length > 0) {
    var finger = frame.fingers[0];
    var normalizedFingerPosition = frame.interactionBox.normalizePoint(finger.tipPosition);
 
    var appX = window.innerWidth * normalizedFingerPosition[0] - fingerDisplay.offsetWidth / 2;
    fingerDisplay.style.left = appX + "px";
    var appY = window.innerHeight * (1 - normalizedFingerPosition[1]) - fingerDisplay.offsetHeight / 2;
    fingerDisplay.style.top = appY + "px";
    // add code to emulate left mouse click
  }
});

Run it and you should see a red dot moving along with one of your finger tip. You have just repeated what you have learned in the earlier section.

You are ready to add the code to emulate left mouse click. Follow me:

  • Get the value of the touchDistance property of the finger:
var touchDistance = finger.touchDistance;
  • Assuming an emulated left mouse click occurs if the touchDistance is less than zero:
if (touchDistance < 0) {
  // code to handle click event
}
  • When an emulated left mouse click is detected, you have to identify the HTML element (button or otherwise) beneath the red dot.
fingerDisplay.style.display = "none";
var touchedElement = document.elementFromPoint(appX, appY);
fingerDisplay.style.display = "block";
  • If the HTML element beneath is the Open button, activate the click event for btnOpen.
if (touchedElement == btnOpen) {
  btnOpen.click();
}

With the code that you have added, you can now emulate left mouse click on the Open button using your finger in the air. Check out the action in the Open Sesame 2 code section.

Open Sesame 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Open Sesame 2</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="open_sesame_2.css">
</head>
<body>
<h2></h2>
<button id="btnOpen">Open</button>
<button id="btnClose">Close</button>
<div style="background-image:url('https://peterleowblog.com/wp-content/uploads/2017/09/Marilyn_Monroe_photo_pose_Seven_Year_Itch-1.jpg');height:500px;width:405px;border:solid 1px">
<div id="door"></div>
</div>
<div id="finger"></div>
<script src="open_sesame_2.js"></script>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The code is far from complete. The missing pieces are as follows:

  • The code for activating the click event for btnClose if the HTML element beneath the red dot is the Close button.

  • As it is now, there is no way of telling whether your finger enters or leaves the touching zone. The solution is to introduce different visual cue for each event, such as changing the red dot to blue when a finger enters the touching zone and vice versa when it leaves.

  • Last but not least, you will soon notice that the door opens or closes excessively instead of at a fixed amount at each click, owing to the constant firing of the button event when the finger remains in the touching zone at each frame update. To overcome it manually, your finger has to enter and leave the touching zone at quick succession. This is neither user friendly nor palatable.  One of the solutions I can offer is to use a flag (true or false) to prevent the firing of the same event from subsequent frames if the finger has not left the touching zone after entering it. In other words, there should be only one firing of click event for each cycle of entering and leaving the touching zone. Of course, the actual solution hinges upon your application requirements.

I shall leave them as your homework. Go for it!

Dragging with Your Finger in the Air

Having learned the code to emulate left mouse click using your finger in the air, why not extend it to emulate a mouse drag? Usually, you drag an object on the screen by moving the mouse while holding down its left button, right? So, a mouse drag is effected through a combination of click, hold, and move actions. Translating this into Leap, a drag occurs when your finger enters the touching zone, remains there, while moving. Got it!

Let's add the code to emulate a mouse drag to this partially completed code in the Drag Me Along! code section.

Drag Me Along!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Drag Me Along</title>
<script src="https://js.leapmotion.com/leap-0.6.4.min.js"></script>
<link rel="stylesheet" type="text/css" href="drag_me_along.css">
</head>
<body>
<img id="target" width="200" height="200" src="https://peterleowblog.com/wp-content/uploads/2017/09/aim.png">
<div id="finger"></div>
<script src="drag_me_along.js"></script>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Since drag is an extension of click, start by copying the Leap.loop() part of the JavaScript from the Open Sesame 2 code section to the Drag Me Along! code section.

Next, zoom in to this part of the code as shown:

if (touchedElement == btnOpen) {
  btnOpen.click();
}

This is the only code that you need to modify in order to implement the drag. However, you can only know how to modify it after finding out the answers to these two questions:

  • What is the touchedElement this time?

  • How to make this touchedElement move along with the finger?

I have already explained and demonstrated the code for similar implementations earlier, so I shall not repeat again.

Tips

Interacting with a web application via a Leap Motion controller is inherently a virtual experience. Since it is done without the feel and sensation of a real touch, users can neither control the pace of interactions nor know the state of their interactions with the web application. To alleviate these problems so as to make your web application more usable, consider incorporating the following measures in your implementation:

  • Always provide feedback to the users on the status and progress of their interaction in the form of visual cues on the screen.

  • Need to regulate the response rate of your code vis–à–vis the user's pace of interaction.

Crossing the Finishing Line...

In this article, you have learned the basic mechanism of a Leap Motion controller, gotten started on writing code to implement motion tracking of hands and fingers as well as clicking and dragging of web elements in the browser using your finger in the air via the Leap Motion controller, and picked up some usability tips on using the Leap Motion controller with your web application.

Give yourself a pat on the back!

Every Ending is Another Beginning

As the saying goes,

Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime.

Now that you have been empowered with the fishing skill, it's up to you to use it well to catch a bigger fish — rotating a wheel with your hand in the air via the Leap Motion controller as shown in this animated gif.

alt text Figure 8: Rotating a Wheel with Your Hand in the Air

The end of a journey is the beginning of another. Hope you find this one a fruitful one.

The article Leaping into Motion appeared first on Peter Leow's Code Blog.

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content