Leap Motionで操作できるPhoneGap(Cordova)アプリ
こんにちは。今回は田中が書いた英語版アシアルブログの翻訳記事をお届けします。
(原文はこちら)
==============================
こんにちは。
今日は「Leap Motion」という最近手に入れた新しいガジェットを紹介したいと思います。
ガジェット好きな人は知っているかもしれませんが、Leap Motionは小さなスティック状のデバイスで、手のモーションをとらえてホストPCに送る入力装置です。
Kinectに似ていますが、よりシンプルなSDKが用意されていて安価です。
通常、Leap Motionを使う時はPCにつなぎ、Leap Motion対応のアプリケーションを操作します。誰でもLeap Motion対応のアプリケーションを開発することができます。Leap Motionには、Windows、Macアプリの開発者向けにC++のSDKが、ウェブ開発者向けにjavascriptのSDKが含まれています。
JavaScript、ということは、PhoneGapと組み合わせることで、使い慣れたウェブ技術を使ってLeamp Motionにアクセスするモバイルアプリを作ることができます。
■PhoneGapアプリはどのようにLeap Motionとつながるか
Leap Motionは USB経由でホストPCと接続されることになります。つまり、PhoneGapアプリからはPCにアクセスすることになります。PCにLeap Motionのドライバーをインストールすると、ウェブソケットプロトコルを待ち受けるデーモンプログラムが自動的にインストール、設定されます。そして、PhoneGapアプリはHTML5のウェブソケットクライアントを使って、Leap MotionのJavaScript APIを通してPCに接続します。
ウェブソケットクライアントではサーバーからアクセスされる必要があり、Androidデバイスではこの条件を満たすことができません。そこで今回は、サンプルアプリを動かすのにiPadを使っています。
■Leap Motion SDK
Leap MotionのJavaScript SDKはオープンソースで公式ウェブサイトからダウンロードできます。
スタートガイドには、アプリとLeap Motionがどのように通信するの かという重要な概要が記載されていますので、そちらを一読することをおすすめします。
ウェブソケットの接続を通したnutshellでは、特定の頻度(デフォルトでは60fps)で、ハードウェアが計算した生の座標データやジェスチャーコマンドを含むユーザーの動作データが送信されます。
認識されるジェスチャーには、キータップ(キーボードをタップするような動作)、スクリーンタップ、スワイプ、そして円を描くような動きがあります。そして、それぞれの指とその動き(速度など)を検知することもできます。
ここまでの説明で、Leap Motion対応の新しいアプリケーションを作成する準備ができました。この記事では、開発環境にMonacaを使用しています。Monacaとは、PhoneGapアプリを作成するためのブラウザ上で使う開発環境で、クラウドサービスとして提供されています。無料で使うことができますので、まだ使ったことがないという方はぜひ登録して使ってみて下さい。
■Hello Leap Motion!
それでは、最初にシンプルなアプリを作りましょう。最小限のテンプレートから新しいプロジェクトを作成すると、IDEには以下のような画面が表示されます。
index.htmlの内容を、以下のように変更します。
<html>
<head>
<script src="plugins/plugin-loader.js"></script>
<link rel="stylesheet" href="plugins/plugin-loader.css">
<style>
#motion {
box-sizing: border-box;
position: absolute;
top: 30%; left: 20%; width: 60%;
opacity: 0.9;
background-color: #fff;
padding: 30px;
border: 1px solid #ccc;
border-radius: 20px;
text-align: center;
font-size: 30px;
}
</style>
<script src="http://js.leapmotion.com/0.2.0-beta1/leap.min.js"></script>
<script>
monaca.viewport({"width": "640"});
Leap.loop({
enableGestures: true,
host: '10.0.5.130' // CHANGE HERE!!
}, function(obj) {
if (obj.gestures) {
obj.gestures.forEach(function(gesture) {
if (gesture.state == "start") {
$("#motion").text(gesture.type).show();
lastGestureType = gesture.type;
addList(gesture);
} else if (gesture.state == "stop") {
$("#motion").fadeOut();
}
});
}
});
function addList(json) {
var $list = $("#gestures");
$list.prepend("<li class='ui-li ui-li-static'><b>Starting " + json.type + "</b> <div style='font-size: 80%'>" + json + "</div></li>")
}
</script>
</head>
<body>
<div data-role="page">
<div data-role="header" data-position="fixed"><h1>Leap Motion Gestures</h1></div>
<div class="ui-listview" id="gestures"></div>
</div>
<div id="motion" style="display: none"></div>
</body>
</html>
ホストアドレスの部分(ソースコードの中で、// CHANGE HERE!!と記載されている部分)は、お使いのPCのIPアドレスに変更します。Leap Motionのドライバーがインストールされていれば、サーバーは動いているはずです。
プログラムの内容は、コードを見ていただければ何をしているのかわかると思います。「Leap.loop」という関数が最も重要で 、1秒間に最大60回コールバック関数を呼び出し、検出されたジェスチャーを画面に表示しています。
App Storeから「Monaca Debugger」をダウンロードすればこのプログラムを動かすことができるので、Monacaを使うのが初めての方はぜひ試してみて下さい。
プログラムを実行した結果のスクリーンショットが以下になります。モーションを検知しつつ、生データをダンプして検知したモーションを表示します。
■より複雑なプログラム
それでは、より複雑なプログラムを作りましょう。
index.htmlを以下のコードに置き換えます。
<html>
<head>
<title>DOM Visualizer - Leap</title>
<script src="http://js.leapmotion.com/0.2.0-beta1/leap.min.js"></script>
<script>
function moveFinger(Finger, posX, posY, posZ, dirX, dirY, dirZ) {
Finger.style.webkitTransform = "translateX("+posX+"px) translateY("+posY+"px) translateZ("+posZ+"px) rotateX("+dirX+"deg) rotateY(0deg) rotateZ("+dirZ+"deg)";
}
function moveSphere(Sphere, posX, posY, posZ, rotX, rotY, rotZ) {
Sphere.style.webkitTransform = "translateX("+posX+"px) translateY("+posY+"px) translateZ("+posZ+"px) rotateX("+rotX+"deg) rotateY(0deg) rotateZ(0deg)";
}
var fingers = {};
var spheres = {};
Leap.loop({
enableGestures: true,
host: '10.0.5.130' // CHANGE HERE!!
}, function(frame) {
var fingerIds = {};
var handIds = {};
if (frame.hands === undefined ) {
var handsLength = 0
} else {
var handsLength = frame.hands.length;
}
for (var handId = 0, handCount = handsLength; handId != handCount; handId++) {
var hand = frame.hands[handId];
var posX = (hand.palmPosition[0]*3);
var posY = (hand.palmPosition[2]*3)-200;
var posZ = (hand.palmPosition[1]*3)-400;
var rotX = (hand._rotation[2]*90);
var rotY = (hand._rotation[1]*90);
var rotZ = (hand._rotation[0]*90);
var sphere = spheres[hand.id];
if (!sphere) {
var sphereDiv = document.getElementById("sphere").cloneNode(true);
sphereDiv.setAttribute('id',hand.id);
sphereDiv.style.backgroundColor='#'+Math.floor(Math.random()*16777215).toString(16);
document.getElementById('scene').appendChild(sphereDiv);
spheres[hand.id] = hand.id;
} else {
var sphereDiv = document.getElementById(hand.id);
if (typeof(sphereDiv) != 'undefined' & & sphereDiv != null) {
moveSphere(sphereDiv, posX, posY, posZ, rotX, rotY, rotZ);
}
}
handIds[hand.id] = true;
}
for (handId in spheres) {
if (!handIds[handId]) {
var sphereDiv = document.getElementById(spheres[handId]);
sphereDiv.parentNode.removeChild(sphereDiv);
delete spheres[handId];
}
}
for (var pointableId = 0, pointableCount = frame.pointables.length; pointableId != pointableCount; pointableId++) {
var pointable = frame.pointables[pointableId];
var posX = (pointable.tipPosition[0]*3);
var posY = (pointable.tipPosition[2]*3)-200;
var posZ =