WebVRアプリを10分でLeapMotion対応にしたい

#はじめに

本記事では，Three.jsで作成したWebVRページに，LeapMotionを導入する方法を解説したいと思います．

**2016/06/15**
最新のwebvr-boilerplateベースで動くか確認してみました．ソースをgithub.ioに差し替え．
https://github.com/cslroot/webvr-boilerplate-leapjs


## 対象読者

本記事では OculusRift DK2所有＋Leapmotion所有＋WebVRに興味があるよ，という方を対象としています．

Oculus Riftあるんだけれど，Leapmotionは持っていません…という方は，amazonに売っているのですぐ買ってください．Oculus Touch が出るのはまだ先(2016 4Q?)です！

------------

## おしながき
1. webvr-boilerplateで開始
2. Leap.jsを組込む
3. bone-handで手を描画


# 開発のまえに
## そもそもLeap Motionって何ですか？

[Leap Motion](https://www.leapmotion.com/)は，"手"に特化した入力装置です($99.99)．

[![LeapMotion](http://img.youtube.com/vi/_1vBYvEM7_A/0.jpg)](http://www.youtube.com/watch?v=_1vBYvEM7_A)

Kinectが身体全体を対象とするのに対して，指の位置や．キーボード，マウスのようにデスクトップサイズで，仮想現実の世界へ手を持ち込むことが可能です．



## LeapMotionをOculusにくっつける

いろんなジェスチャ入力装置がありますが，LeapMotionは公式でjavascriptライブラリを提供しているのみならず，[Oculus Rift DK2用の専用マウントも販売しています](https://developer.leapmotion.com/downloads/javascript))．

でもOculusに両面テープでくっつけるのと同じなので，[いろいろ工夫の余地があります](https://community.leapmotion.com/t/unofficial-vr-mount-thread/2296)．とりあえず，ガムテとかでOculusにくっつけておけばOKです．

注意事項としては，DK2のUSB端子には接続しない（USB延長ケーブルを使ってPC本体へ）ことと，向きに注意する（電源インジケータが下にくるように），ということくらいです．



# ここから本題

## 環境の準備
### 前提
事前に，WebVR対応ブラウザを用意してください．
また，Leapmotionを接続して，サンプルが動くような状態となっていることを前提とします．

### webvr-boilerplateで開始
簡単のため，webvr-boilerplateを使います．適当に展開してください．

ブラウザで確認すると，立方体がくるくる回っている映像が表示されたはずです．

![screencapture-savethebunny-net-webvr-webvr-leapjs00-1449834463461.png](https://qiita-image-store.s3.amazonaws.com/0/18711/4a0e785d-e513-a873-cb67-8cfef7fc6053.png)
http://cslroot.github.io/webvr-boilerplate-leapjs/index.html

あれ，されませんか？ え，ええと，とりあえず動いている前提で進めます…；

### ローカルで動かす方法

**補足：WebGLもWebVRも初めてな方へ**
作成したローカルファイルのindex.htmlを直接ブラウザ開いてもいろいろダメな感じです．ローカルで動かす場合でもサーバを立てる必要があります．
（本記事の末尾に追記しておきました）

## LeapMotionに対応させる

つづいて，Leapmotionが公式で用意しているjavascriptライブラリ（以下，LeapJSと呼称）を組み込みます．

いつものように`script`タグを追加します：

``` index.html
<script src="//js.leapmotion.com/leap-0.6.4.min.js"></script>
<script src="//js.leapmotion.com/leap-plugins-0.1.11.min.js"></script>
```
（[こちらを参照](https://developer.leapmotion.com/downloads/javascript)）


そしてleapmotionからのデータを受け取るための`Leap.loop()`を記述します．
Leap.loop()は，leapmotionから取得したデータを毎フレーム受け取ることができます．各種データには，`frame`オブジェクトを介してアクセスできます．

`frame`が認識された「右手」「左手」の`hand`オブジェクトの配列を持ち，さらに`hand`の下には各指を表す`finger`オブジェクトの配列を持ちます．
つまり，イメージとしては`frame.hands[i].fingers[j]`のような形で指のデータにアクセスすることになります．


とりあえずは，指先の位置情報を取得するサンプルを書いてみましょう．以下，コード断片をindex.htmlの`</html>`の直前にでもコピペしてみてください．

``` index.html

<p id="leapinfo" style="position:absolute; top:0; left:0; font-family:monospace;"></p>
<script type="text/javascript">
  function concatData(data) {return "" + data + "<br>";}
  function getHandName(hand) {
    switch (hand) {
      case 'right': return "[右手]"; break;
      case 'left': return "[左手]"; break;
    }
  }
  function getFingerName(fingerType) {
    return ["親指　 : ", "人差指: ", "中指　: ", "薬指　: ", "小指　: "][fingerType];
  }
  
  var output = document.getElementById('leapinfo');
  var frameString = "", handString = "", fingerString = "";
  var hand, finger;
  
  // Leap.loop uses browser's requestAnimationFrame
  var options = { enableGestures: true };
  
  // Main Leap Loop
  Leap.loop(options, function(frame) {
    // ここで各フレームで認識した結果をハンドリングします．とりあえず先端位置座標を表示しましょう．
    
    frameString = "";
    
    for (var i = 0, len = frame.hands.length; i < len; i++) {
      // 「手」単位の処理
      hand = frame.hands[i];
      frameString += concatData(getHandName(hand.type));
      
      for (var j = 0; j < hand.fingers.length; j++) {
        // 「指」単位の処理
        finger = hand.fingers[j];
        fingerString = getFingerName(finger.type)
        fingerString += finger.tipPosition; // 指先の位置
        frameString += concatData(fingerString);
      }
    }
    output.innerHTML = frameString;
  });

</script>
```

[これで，Leapmotionが接続されている場合は，指先の位置などが取得できるようになった状態です](http://cslroot.github.io/webvr-boilerplate-leapjs/index01.html)．あら簡単！

![screencapture-savethebunny-net-webvr-webvr-leapjs01-1449837947802.png](https://qiita-image-store.s3.amazonaws.com/0/18711/0e535a40-84a0-61e6-7ce7-c18f5a06860a.png)
http://cslroot.github.io/webvr-boilerplate-leapjs/index01.html

ちなみに，次のような情報が取得できます．もう何でもできちゃいますね＞＜：

> hand データ（frame.hands[i]）
>  * type（右手なら "right"，左手なら "left"）
>  * palmPosition[3]（手のひらの中心位置）
>  * palmVelocity[3]（手のひらの移動速度 [mm/s]）
>  * palmNormal[3]（手のひらの法線方向の単位ベクトル [mm/s]）
>  * stabilizedPalmPosition[3]（安定化された palmPosition）
>  * direction[3]（手のひらから指先への単位ベクトル）
>  * pinchStrength（親指といずれかの指で「つまむ」度合い [0〜1]）
>  * grabStrength（「にぎる」度合い [0〜1]）
>  * sphereCenter[3]（手のひらの丸みを表す曲率中心）
>  * sphereRadius（手のひらの丸みを表す曲率半径）
>  * timeVisible（連続して見えている時間 [s]）
>  * pitch()（手の X 軸まわりの回転角度 [rad]）
>  * yaw()（手の Y 軸まわりの回転角度 [rad]）
>  * roll()（手の Z 軸まわりの回転角度 [rad]）

> fingers[5]（finger データの配列）
>  * fingers.length（fingers の要素数）
>  * finger データ（frame.hands[i].fingers[j]）
>  * type（0 なら親指，1 なら人差し指，...，4 なら小指）
>  * tipPosition[3]（指先の位置）
>  * tipVelocity[3]（指先の速度 [mm/s]）
>  * dipPosition[3]（第１関節の位置）
>  * pipPosition[3]（第２関節の位置）
>  * mcpPosition[3]（指のつけ根の位置；ナックルあたり）
>  * carpPosition[3]（中手骨のつけ根の位置；手首あたり）
>  * stabilizedTipPosition[3]（安定化された tipPosition）
>  * direction[3]（指がさす方向の単位ベクトル）
>  * extended（指が伸びていたら true，そうでなければ false）
>  * timeVisible（連続して見えている時間 [s]）

(引用元：http://www.myu.ac.jp/~xkozima/lab/leapTutorial1.html) 



## 手を表示する

座標がわかっても，見えないと困ります．困るんです！
ということで，仮想世界に手を表示しましょう．

自作のメッシュを，取得した座標を元に表示しても良いのですが，幸いなことに，LeapJSには，Three.jsで手の骨組み（ボーンハンド）を表示する仕組みが用意されています．(http://leapmotion.github.io/leapjs-plugins/main/bone-hand/)

これは，webvr-boilerplateがThree.jsを用いて作成されているからできることですね．ラッキィ．

というわけで，早速組み込んでみましょう．先ほどの`Leap.loop()`の末尾に次のようにコードを追加します．

``` index.html
  Leap.loop(options, function(frame) {
    // ～～略～～
  })
  .use('boneHand', {scene: scene, targetEl: document.body});  // boneHandを指定したシーンに描画する
```

しかし，このままでは，いい感じに表示できません．ここでは2つ注意することがあります：

1つは，ライト設定です．webvr-boilerplateのデフォルトシーンには光源が存在しないため，適当なライトを追加して上げる必要があります．
これは適当にライトを追加して上げてください．

2つめは，スケールと座標系の問題です．
先ほどのサンプルで表示されていた座標を注意深く見てもわかるように，mm単位であること，そして座標系のZが反転しています．
（加えて，leapmotionは標準的な位置（机の上など）に置いた時に，手のひら側を認識することを目的としているため，VR用に「手の甲」側を認識するモードにするなども）


![screencapture-localhost-8000-webvr-leapjs-1449841465579.png](https://qiita-image-store.s3.amazonaws.com/0/18711/e07403ec-2197-4766-a347-4fa1d0b28aae.png)


実は，VR用に良きに計らってくれる便利な機能がありますので，こちらも追加します．
```
.use('transform', {vr: true, effectiveParent: camera})
```

最終的には，次のようなコード断片を追加します（先ほどの指先の座標値表示は消します）：

``` index.html
<script type="text/javascript">
   // boneHandの表示のため，シーンにlightを追加する 
  function initLight(scene) {
    var ambient = new THREE.AmbientLight(0x666666);
    scene.add(ambient);
    
    var directionalLight = new THREE.DirectionalLight(0x887766);
    directionalLight.position.set(-1, 1, 1).normalize();
    scene.add(directionalLight);
  }
  initLight(scene);

  // Leap Loop
  var options = { enableGestures: true };
  Leap.loop(options, function(frame) {
  })
  .use('transform', {vr: true, effectiveParent: camera})  // VR用に座標変換．またLeapmotion本体をOculusにマウントしたときに上手く追従するように設定．
  .use('boneHand', {scene: scene, targetEl: document.body});
  
</script>
```

![screencapture-localhost-8000-webvr-leapjs-1449841808099.png](https://qiita-image-store.s3.amazonaws.com/0/18711/1f515b91-9053-2388-35a7-463fdf118a60.png)
http://cslroot.github.io/webvr-boilerplate-leapjs/index02.html

やったね


## ジェスチャで操作する

とりあえず…ですが，ジェスチャでインタラクションを実現してみましょう．

ジェスチャは，`loop()`内でframe.gesturesから取得可能です．
標準で用意されているジェスチャのタイプ `gesture.type` は，
* "circle"
* "keyTap"
* "screenTap"
* "swipe"
があります．

手の左スワイプ，右スワイプでcubeの回転方向でも変えてみましょう．

``` index.html
  var rotSign = 1;
～～～～
  cube.rotation.y += rotSign * delta * 0.0006;
～～～～
  Leap.loop(options, function(frame) {
    frame.gestures.forEach(function(gesture) {
      switch (gesture.type) {
      case 'swipe':
        rotSign = (gesture.direction[0] > 0) ? -1 : 1;
        break;
      }
    });
  })
```

http://cslroot.github.io/webvr-boilerplate-leapjs/index03.html
スワイプに応じて回転方向が変化するはずです．


# さいごに

かなりLeapJSの入門的な内容になってしまいました．

明日は @yuku_t さんの「webvr-boilerplateを使って入門してみた的なことをする予定」です．本記事でも利用したwebvr-boilerplateの使い方について学べそうです．楽しみデスね！


でわでわ！

* * * 

# Appendix
## A.参考文献
1. [LeapMotion JavaScript Getting Started](https://developer.leapmotion.com/getting-started/javascript)
2. [Leap.jsの組込み](https://developer.leapmotion.com/downloads/javascript)
2. [JavaScript SDK Documentation](https://developer.leapmotion.com/documentation/javascript/index.html)
3. [webvr-boilerplate 0.3.2](https://github.com/borismus/webvr-boilerplate/releases/tag/0.3.2)
4. [Leap Motion と JavaScript でハンドパワー駆動のウェブページ](http://www.myu.ac.jp/~xkozima/lab/leapTutorial1.html)
5. [VR Setup](https://developer.leapmotion.com/vr-setup)

## B.開発環境＝確認環境

次の環境で動作確認しています：
* OS: Windows7
* CPU: Core i7-6700 CPU @ 3.40GHz
* GPU: NVIDIA GeForce GTX 970
* Chromium 
* Oculus Rift DK2 (SDK 0.8) 
* Leap motion (クライアント:2.3.1+31549 / Leap.js 0.6.3)
* webvr-boilerplate (0.3.2)

## C. ローカルで簡単に動かす簡易バッチ(for windows)

PythonのSimpleHTTPServerを使って，簡単にローカルで動かすことができます．
1. pythonのインストール（省略）
2. CORS指定したSimpleHTTPServer用pythonスクリプトを書く
3. 呼出しの簡易化バッチファイル
4. ブラウザで http://localhost:8000/ にアクセス

``` simple-cors-httpserver.py
#! /usr/bin/env python2
from SimpleHTTPServer import SimpleHTTPRequestHandler
import BaseHTTPServer

class CORSRequestHandler (SimpleHTTPRequestHandler):
    def end_headers (self):
        self.send_header('Access-Control-Allow-Origin', '*')
        SimpleHTTPRequestHandler.end_headers(self)

if __name__ == '__main__':
    BaseHTTPServer.test(CORSRequestHandler, BaseHTTPServer.HTTPServer)
```

``` startServer.bat
@echo off
setlocal
echo %~dp0
cd /d %~dp0
python simple-cors-http-server.py
endlocal
```

上のpyファイルとバッチファイルの2つを開発中のフォルダにおいて，バッチファイルを実行してください．このバッチファイルでは，バッチファイル自身の存在するフォルダがルートとなるような仕込みをして，pyファイルを起動しています．

直接，SimpleHTTPServerを起動せずにpyスクリプトを介しているのは，異なるドメインのリソースにアクセスするためです．この記述をしない場合，自動的に「file://」プロトコルになってしまうため，最近流行り(?)のhttp or httpsを省略したsrc指定（例： `<script src="//js.leapmotion.com/leap-0.6.4.min.js"></script>`）をした場合に，正常にアクセスできません．httpを使うように通常通り，明示的に`src="http://～～～"`と書き換えても良いのですが，なるべくアップロード後と同じ状態にしておきたいため，このような仕組みをとっています．