Creating an UBox ADVANCE App
ADVANCE APP
In this part of the tutorial, we’ll show how to add an interactive area so you can trigger a function depending if the user is in or out a certain zone. Test the application here then hit the Enter key and start moving your mouse to mimic the player’s position.
Using the keyboard or mouse to test the app online is highly recommended to speed up the testing process without the need of using the UBox Player.
You would like to understand the vision range of the sensor you are using for this app, in this case the app is meant to work with a Kinect V1 or a Kinect V2.
There will be three main steps to have this working:
- Adding the number of players and their joints.
- Setting the interactive area.
- Customizing the getSkeletons function.
Adding players
On the HTML side, you can add up to 6 players and their joints (parts of the body to be read by the sensor), in this case are using the spine center to measure the distance from the sensor to the body, but any of joints in the following list is available:
- handright
- elbowright
- shulderright
- handleft
- elbowleft
- shoulderleft
- head
- spineneck
- spinecenter
- hipcenter
- kneeleft
- kneeright
- ankleleft
- ankleright
We have moved the skeletonsCanvas div to the higher index so the skeleton joints are visible once you test the app with the UBox Player. Besides that, we have added some new parameters: showSkeletons to either show or hide the skeletons from the screen, and showUserPosition to display the coordinates on the screen, this will be very helpful at the moment of setting the distance of a specific interactive area.
HTML
<html>
<head>
</head>
<body>
<div id="main-container">
<img id="face1" src="{resources:images/face1.png}">
<img id="face2" src="{resources:images/face2.png}">
<img id="face3" src="{resources:images/face3.png}">
<img id="face4" src="{resources:images/face4.png}">
<img id="face5" src="{resources:images/face5.png}">
<img id="face6" src="{resources:images/face6.png}">
<img id="face7" src="{resources:images/face7.png}">
<img id="face8" src="{resources:images/face8.png}">
<img id="face9" src="{resources:images/face9.png}">
<div id="position-viewer">
<div id="interactive-area"></div>
<div id="player1" class="player"></div>
<div id="player2" class="player"></div>
<div id="spinecenter1"></div>
<div id="spinecenter2"></div>
</div>
<canvas id="skeletonsCanvas" width="640" height="480" ></canvas>
<div id="user-position"></div>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.0/TweenMax.min.js"></script>
</body>
</html>
CSS
/* application css */
body{
width: 100%;
height: 100%;
}
#main-container{
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: #efc31a;
overflow: hidden;
}
img{
position: absolute;
width: 500px;
height: 500px;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
opacity: 0;
transform: scale({parameter:imageScale}, {parameter:imageScale});
-ms-transform: scale({parameter:imageScale}, {parameter:imageScale}); /* IE 9 */
-webkit-transform: scale({parameter:imageScale}, {parameter:imageScale}); /* Safari */
}
#face9{
opacity: 1;
}
/* ---- Position Viewer ---- */
#position-viewer{
position: absolute;
bottom: 100px;
right: 100px;
width: 180px;
height: 180px;
background-color: rgba(255,255,255,0.1);
border: 1px solid #fff;
}
#interactive-area{
position: absolute;
top: 0px;
left: 0px;
border: 1px solid #fff;
background-color: rgba(255, 255, 255, 0.65);
}
.player{
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
-ms-transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: none;
}
#user-position{
position:absolute;
color: #3399ff;
background-color: #000;
font-size: 50px;
padding: 20px;
top: 0;
right: 0;
opacity: {parameter:showUserPosition};
font-family: sans-serif;
}
#skeletonsCanvas{
opacity: {parameter:showSkeletons};
}
Creating and interactive area.
You can think of two square areas, a first one that will represent the sensor range of vision and an second one that will represent the area of action.
Both areas will need 4 limits which we have named:
xPosMin, xPosMax, zPosMin and zPosMax for the bigger area, and xPosMin_i, xPosMax_i, zPosMin_i and zPosMax_i for the interactive area that will be inside the first one.
These areas will display as a top view of the experience where users will be represented by small circles. We have named this “position-viewer”. Some other variables and functions would be added to handle the desired behavior.
UBOX EVENTS
/* Vision Range Area */ var xPosMin = 0; var xPosMax = 100; var zPosMin = 0; var zPosMax = 100; /* Interactive Area */ var xPosMin_i = 35; var xPosMax_i = 65; var zPosMin_i = 35; var zPosMax_i = 65; var xDistance = (xPosMax + (xPosMin * -1)); var zDistance = (zPosMax + (zPosMin * -1)); /* Variables handle the users position */ var mainXPos, xPos1, xPos2 = 0; var mainZPos, zPos1, zPos2 = 0; /* Other Variables */ var currentZone = 0; var selectedZone = -1; var webTest = false; // POSITION VIEWER function createPositionViewer(){ $('#interactive-area').css('height', zPosMax_i - zPosMin_i + '%').css('top', zPosMin_i + '%'); $('#interactive-area').css('width', xPosMax_i - xPosMin_i + '%').css('left', xPosMin_i + '%'); } createPositionViewer(); // X AND Z MOVEMENT function updatePos(sklxz){ var currentPlayer; if(sklxz != null){ mainZPos = window['zPos' + sklxz]; mainXPos = window['xPos' + sklxz]; currentPlayer = window['player' + sklxz]; }else{ currentPlayer = window['player1']; sklxz = 1; } $('#user-position').html('X = ' + mainXPos + ', Z = ' + mainZPos); if( mainZPos <= zPosMin || mainZPos >= zPosMax || mainXPos <= xPosMin || mainXPos >= xPosMax){ $(window['player' + sklxz]).css('background-color', '#fff'); currentZone = 0; }else if( mainZPos > zPosMin_i && mainZPos <= zPosMax_i && mainXPos > xPosMin_i && mainXPos <= xPosMax_i){ $(window['player' + sklxz]).css('background-color', '#f00'); currentZone = 2; }else{ $(window['player' + sklxz]).css('background-color', '#000'); currentZone = 1; } if(currentZone != selectedZone){ selectedZone = currentZone; changeContent(selectedZone); } } function changeContent(num){ if(num >= 2){ changeFace(4); }else{ changeFace(9); } } // CHANGE THE BACKGROUND COLOR var prevFace = 9; function changeFace(faceNumber){ var bgColor; /*#E74D3D #EFC319 #41B19C #874B9C #35495E #E58025 #41B19C #2780B9 #EFC319*/ if(faceNumber == 1){ bgColor = '#E74D3D'; }else if(faceNumber == 2 || faceNumber == 9){ bgColor = '#EFC319'; }else if(faceNumber == 3 || faceNumber == 7){ bgColor = '#41B19C'; }else if(faceNumber == 4){ bgColor = '#874B9C'; }else if(faceNumber == 5){ bgColor = '#35495E'; }else if(faceNumber == 6){ bgColor = '#E58025'; }else if(faceNumber == 8){ bgColor = '#2780B9'; } if(faceNumber != prevFace){ TweenMax.to('#main-container', 0.5, {css:{backgroundColor: bgColor}}); TweenMax.fromTo('#face' + faceNumber, 0.5, {y: 500, alpha:0}, {y: 0, alpha: 1, ease:Expo.easeOut}); TweenMax.fromTo('#face' + prevFace, 0.5, {y:0, alpha:1}, {y: -500, alpha:0, ease:Expo.easeOut, onComplete: function(){ prevFace = faceNumber; }}); } } // UBOX NATIVE SCRIPT var canvas = document.getElementById("skeletonsCanvas"); var context = canvas.getContext("2d"); function getSkeletons(jsonObject) { if (context == null) { return; } var skl = 0; context.clearRect(0, 0, canvas.width, canvas.height); try { for (var i = 0; i < jsonObject.skeletons.length; i++) { skl++; for (var j = 0; j < jsonObject.skeletons[i].joints.length; j++) { var joint = jsonObject.skeletons[i].joints[j]; // Draw!!! context.fillStyle = "#FF0000"; context.beginPath(); context.arc(joint.x, joint.y, 10, 0, Math.PI * 2, true); context.closePath(); context.fill(); var cordx = (joint.x * 100) / 640; var cordy = 100 - ((joint.y * 100) / 480); var cordz = (joint.z * 100) / 4; if(joint.name == 'spinecenter') { document.getElementById('spinecenter' + skl).style.left = cordx + 'px'; document.getElementById('spinecenter' + skl).style.top = cordy + 'px'; document.getElementById('spinecenter' + skl).style.zIndex = Math.floor(cordz); window['xPos' + skl] = Math.round (document.getElementById('spinecenter' + skl).style.left.replace("px", "")); window['yPos' + skl] = Math.round (document.getElementById('spinecenter' + skl).style.top.replace("px", "")); window['zPos' + skl] = Number(document.getElementById('spinecenter' + skl).style.zIndex); $('#player' + skl).css('top', ((window['zPos' + skl]-zPosMin)*100)/zDistance + '%'); $('#player' + skl).css('left', ((window['xPos' + skl]-xPosMin)*100)/xDistance + '%'); $('#player' + skl).show(); } updatePos(1); } } } catch(err) { //do nothing } } function msgFromArduino(value){ console.debug('From Arduino: ' + value); } function next() { console.debug('Next!'); } function prior() { console.debug('Prior'); } function grip() { console.debug('Grip!'); changeFace(7); } function release() { console.debug('Release'); changeFace(2); } function noUser(){ console.debug('No User :('); //sendToArduino('0'); changeFace(9); } function newUser() { console.debug('New User :)'); //sendToArduino('1'); changeFace(4); } function changeHandNone() { onsole.debug('Hand None'); } function changeHandRight() { console.debug('Hand Right'); } function changeHandLeft() { console.debug('Hand Left'); }
As mentioned previously, it’s important to have ways to easily test the interactions online. For this app, pressing the Enter key will let you activate the function that mimics the users position moving the cursor across the screen.
// JUST FOR WEB TESTING
function follow(evt) {
if(webTest){
mainXPos = Math.round((evt.pageX/window.innerWidth) * 100);
mainZPos = Math.round((evt.pageY/window.innerHeight) * 100);
$('#player1').css('top', ((mainZPos-zPosMin)*100)/zDistance + '%').css('left', ((mainXPos-xPosMin)*100)/xDistance + '%').show();
updatePos(null);
}
}
function checkKeyUp(e) {
e = e || window.event;
if (e.keyCode == '13') {
document.onmousemove = follow;
webTest = true;
$('#player1, #position-viewer').css('opacity', '1').show();
}
}
window.onkeyup = checkKeyUp;