[WebVR] Gamepad APIを使ってHTC Vive Controllerを表示する

この記事は[WebGL Advent Calendar 2016](http://qiita.com/advent-calendar/2016/webgl) 18日目の記事です。

とか言いながら、ほぼWebVR、しかもWebGLあんまり関係ないのはご了承ください；
（いちおうWebVRにはWebGL必要だよねってことで( ;´Д`)）


## 概要

Oculus Touchがいよいよ発売され、VR空間で「手」を認識してあれこれやることがスタンダードになってきました。
HTC Viveは当初からコントローラがついていて、HMD同様にトラッキングできていました。

今回の記事は、そのHTC ViveのコントローラをWebVRでも利用するためのセットアップについて書きたいと思います。

ちなみに今回の[サンプルはGithub](https://edom18.github.io/WebVR-HTC_Vive_Sample/)にアップしてあります。（ベースは「[WebVR Boilerplate](https://github.com/borismus/webvr-boilerplate)」です）
そして今回のサンプルは、Three.jsの[Vive paintデモ](https://threejs.org/examples/webvr_vive_paint.html)であったものを参考にさせてもらいました。

### Gamepad API

WebVRに関連するものなので、そのAPIにあるのかなーと思いきや、仕様自体は[Gamepad API](https://w3c.github.io/gamepad/)がそれを担っているようです。
Gamepadの取得は以下のようにします。

```js
// 指定したIDのGamepadを探す
function findGamepad(id)
{
    var gamepads = navigator.getGamepads();
    for (var i = 0, j = 0; i < 4; i++)
    {
        // 取得したゲームパッドの中から「OpenVR Gamepad」を探す
        var gamepad = gamepads[i];
        if (gamepad && gamepad.id === 'OpenVR Gamepad')
        {
            if (j === id)
            {
                return gamepad;
            }
            j++;
        }
    }
}
```

`findGamepad`関数では、`navigator.getGamepad();`を利用して接続されているゲームパッドのリストを取得します。
そして取得したリストの中から`OpenVR Gamepad`のIDが付いているものを探し、さらに関数の引数で指定したインデックスにあるGamepadを返しています。

最初、Gamepadがどうしても取れないなーと思っていたんですが、コントローラのトリガーを引くなどして認識させないとならない点に注意が必要です。
（じゃないといつまでたってもGamepadはnullのまま・・）

## Updateで姿勢制御

さて、上記のようにすることでゲームパッド（Viveコントローラ）が取得できました。
が、VRのコントローラは位置を常にUpdateしないとなりません。
ということで、位置の同期を取るコードは以下のようになります。

```js
this.update = function ()
{
    // 指定したIDのコントローラを探してくる
    // （認識されていなかったり、トラッキングから外れると取得できなくなるため）
    gamepad = findGamepad(id);

    // ゲームパッドが無効な場合は非表示にして更新はしない
    if (gamepad === undefined || gamepad.pose === undefined)
    {
        scope.visible = false;
        return;
    }

    if (gamepad.pose === null) { return; }


    // コントローラの制御
    var pose = gamepad.pose;
    if (pose.position !== null) { scope.position.fromArray(pose.position); }
    if (pose.orientation !== null) { scope.quaternion.fromArray(pose.orientation); }

    // デバイスからの情報を元に姿勢を更新し、ルームスケールのマトリクスを掛けて最終的な姿勢にする
    scope.matrix.compose(scope.position, scope.quaternion, scope.scale);
    scope.matrix.multiplyMatrices(scope.standingMatrix, scope.matrix);
    scope.matrixWorldNeedsUpdate = true;
    scope.visible = true;
}
```

やっていること自体はとてもシンプルです。
ゲームパッドから姿勢を取り出し、それをモデルに伝えているだけ。
ただし、VRのコントローラはトラッキングから外れると`null`になって取得できなくなるので、その確認処理が入ることに注意が必要です。

ゲームパッドが無事に取得できた場合は、`pose`プロパティに必要なデータ（位置と回転）が入っているので、それをモデルに適用してやります。
そしてこれまた注意点ですが、この姿勢制御だけでは位置がおかしなことになります。
なぜなら、VRは空間を認識しているからです。
つまり、コントローラの位置や回転の値は、その空間内を基準としたものになるため、その空間を親としたマトリクスをかけてやる必要があるわけです。

そしてそれを行っているのが以下の一行です。

```js
scope.matrix.multiplyMatrices(scope.standingMatrix, scope.matrix);
```

この`standingMatrix`は、Three.jsの`VRControls`にあるマトリクスです。
（コントローラ生成後に、生成側のスクリプトでそれを設定しています）

```js
controller1.standingMatrix = controls.getStandingMatrix();
```

こうすることによって、無事、手の位置にあるコントローラと同じように動く姿勢が得られます。
今回のサンプルではViveコントローラのモデルを表示していますが、当然、コントローラ以外のモデル（例えば手とか）を利用してもOKです。
その場合は、表示したいモデルを読み込んだあとに、そのモデルを`ViveController`（今回作成したサンプル用のクラス）の子として`add`してやれば大丈夫です。



### ボタンの種類

姿勢に関しては上記の方法で問題なく同期されます。
ただ、コントローラである以上はその状態を取得して、適切に操作できなければなりません。
ゲームパッドには`buttons`というプロパティがあるので、それを参照して、どのボタンがどういう状態なのか、を知ることができます。


ViveのControllerで定義されているボタンは以下の通り。
*※ 参考にさせてもらったThree.jsではbuttons[2]がメニューボタンとしてあったんですが、（途中で変更されたのか）buttons[3]のものが正しかったです。*

- button[0] = タッチパッドボタン
- button[1] = トリガー
- button[2] = グリップ左右のボタン
- button[3] = メニューボタン

また、Vive Controllerにはタッチパッドが付いているため、それを、軸の情報として取得することができます。
その参照は以下になります。

- axes = タッチパッドの触れた位置

これらの情報から、適切に情報を取り出し、画面にフィードバックしてやることで、VR空間内での操作が可能となります。
今まではOculusで映像を見るのが精一杯でしたが、コントローラを使えるようになると、VRでやれることが格段に増えるのでとても楽しいですね。
ぜひ、みなさんも楽しいWebVRライフを送ってください。

それと、ブラウザの（有効化などの）設定については以下のサイトを参考にしました。
http://meta4vr.net/setup/

*※ 現状ではまだ、正式にWebVRに対応したブラウザが出ておらず、どれも実験的なもののため、適切にフラグを有効化してやる必要があります。*