HoloLensで始めるMRDesignLabs - 両手ジェスチャー入力の実装

# MRDesignLabsの入力系では両手ジェスチャー入力ができる。
MRDesignLabsのライブラリ自体はFluent Design Systemの要素を取り入れられたライブラリ群で、それらのサンプルとして追加されたものがゲームとしても楽しいLunaModuleがあります。
このゲームは両手でジェスチャー操作ができます。今回はLunaModuleで実現しているジェスチャー部分を抜き出したサンプルを作成しました。

LunaModuleをデプロイして遊びたい方は[こちら](http://qiita.com/miyaura/items/2ecbebe005dcb070c1db)を参考にしてください。

## 環境などなど
### 開発環境
 * Windows 10 Pro
 * Unity 5.6.1f1
 * Visual Studio 2015 Community Edition Update 3 

### ソースコード

* サンプルコードについては以下のGitで公開しています。
 * [https://github.com/TakahiroMiyaura/HoloMultiHnadInputSamples](https://github.com/TakahiroMiyaura/HoloMultiHnadInputSamples)
* MRDesignLabs_Unity_LunarModule
 * [https://github.com/Microsoft/MRDesignLabs_Unity_LunarModule](https://github.com/Microsoft/MRDesignLabs_Unity_LunarModule)

## LunarModuleに追加されている両手ジェスチャー機能

LunarModuleで追加されている機能は３つあります。このうち今回はジェスチャーに関するLocalHandInputの説明と使い方を紹介します。

### プロパティ
Local Hnad InputはMRDesignLabsのハンドジェスチャーの機能を利用してTap&Holdの移動量を検出するコンポーネントです。Tap地点を起点に指の移動量や速度の取得ができます。プロパティは以下のものを持っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/2432dd77-c23f-7188-b9d0-a7d21868b86d.png)


| プロパティ名 | 説明 | 設定値など |
|:-:|:--|:--|
| Filter Tag |  | Default  |
| Handedness  | ジェスチャーを検出する手を設定します。  | <ul><li>Niether</li><li>Left</li><li>Right</li></ul>    |
| Min Confidence  | 最小の信頼度。両手のジェスチャー検出はカメラ位置（HoloLens）を中心に左右の手を判別します。この際、両手をどこまで近づけて検出できるようにするかがこの設定値です。この設定を小さくするとHoloLensに近い位置で手が検出されます。一方、両手操作の場合、手同士が近くなるためその分精度が落ちます。  | 0.0(近い) ～ 1.0(遠い)  |
| Require Focus  | フォーカスを要求するか | <ul><li>True</li><li>False</li></ul>  |
| Use Dead Zone | ジェスチャでの操作時に移動量として検出しない領域を使うかどうか設定します。ジェスチャーでの移動量はセンサーで検出しているため多少のブレが常に発生します。このため、デッドゾーンを設けることで操作を安定化します。 | <ul><li>True</li><li>False</li></ul> |
| Dead Zone | デッドゾーンの範囲を指定します。移動量(速度)がデッドゾーン内の場合、移動していないとみなします。 | Vector3  |
| Clamp Position  | 座標の移動量の制限を行います。この設定をチェックすると移動量が-1.0f～1.0fの範囲で変化するようになります。  | <ul><li>True</li><li>False</li></ul> |
| Clamp Velocity  | 座標の移動量の制限を行います。この設定をチェックすると速度が-1.0f～1.0fの範囲で変化するようになります。  | <ul><li>True</li><li>False</li></ul> |
| Accelerate Position |  座標の変化量のカーブを調整するか設定します。 | <ul><li>True</li><li>False</li></ul>  |
| Accelerate Velocity |　速度の変化量のカーブを調整するか設定します。  | <ul><li>True</li><li>False</li></ul>  |
| Position Accelerattion | 座標の変化量のカーブを調整します。ジェスチャーの操作量に対してどの程度数値を変更するかを定義します。  |   |
| Velocity Accelerattion | 速度の変化量のカーブを調整します。ジェスチャーの操作量に対してどの程度数値を変更するかを定義します。  |   |
| Smooth Time  | ジェスチャー操作の滑らかさを調整します。内部ではVector3.SmoothDampの到達時間として定義されます。  | float  |
| Input Scale | 入力値の倍率を設定します。 | float  |

### 両手ジェスチャーの仕組み

両手ジェスチャを実現するためにはMRDesignLabsのいくつかの機能を使う必要があります。

#### Input Source Hands
HoloLensの操作で使うジェスチャーはMRDesignLabsのInputSourceHandsを使うと取得可能です。
LocalHandInputの前に少しInputSourceHandsの両手検出の仕組みについて説明します。

#### Input Source Hands
Hierarchy上は「InputMapping」-「InputSource」-「Hands」のコンポーネントで定義されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/fd42434f-ba1f-f66f-bef0-b94b54c6f9a6.png)

開発者がジェスチャー操作についての情報を取得するためには以下のようにInputSourceクラスから取得します。

```csharp
        // get Input Source Hands instance.
        var hands = InputSources.Instance.hands;

        // try get current hand state og right hand.
        var handStateR = hands.GetHandState(InputSourceHands.HandednessEnum.Right, 0.15f);
```

### どうやって両手を判断して検出しているのか。

考え方は非常にシンプルです。顔位置（＝カメラ）を中心に手は左右にあるでしょうということを利用しています。
つまり「顔よりも右側のジェスチャーは右手操作」、「顔よりも左側のジェスチャーは左手操作」として検出します。
MrDesignlLabsのInput Suorce Handsはこの考え方をもとにして両手ジェスチャーの検出を可能にしています。
次に実際の判別方法ですが、下図のようにカメラの正面方向のベクトルと手の方向ベクトルからそれらの法線を計算しカメラの上方向のベクトルとの内積（距離）を計算します。この数値がLocalHandInputコンポーネントにもあった「Min Confidence」プロパティに関係します。図の場合、顔の位置から見て右側に0.2mの位置でジェスチャーを検出している状態です。この時のConfidenceの値を計算すると0.2になります。よってLocalHandInputでは右手は検出できている状態になります。例えば、右側0.1mのまで手を近づけるとConfidenceは0.1になるので、「Min Confidence」を下回ります。この場合、右手としては検出されない状態になります。
左手の場合も考え方は一緒です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/2c171e5c-60bd-3937-1eb1-ec24b755e20c.png)

上から見た図。真ん中が顔、球体が手を模式化。Z軸の緑色の線は顔の正面方向のベクトル(Forward)。赤線は手の方向を指すベクトル(Right Hand)で黄線が(1)-(2)の平面に対する法線ベクトル(Cross)を指します。

## 両手ジェスチャーのサンプル
 
次に両手ジェスチャーのサンプルコードの実装を行ってみたいと思います。サンプルでは右手、左手のホールド操作でcubeを動かすサンプルを作ります。
まずは、[HoloLensで試すMRDesignLabs - 空セットアップとBounding Boxの利用](http://qiita.com/miyaura/items/cf0ebc41733a4b31e137)を参考にMRDesignLabsのAsset設定までを完了してください。

### オブジェクトの配置

MRDesignLabsのアセットを設定後、シーンを作成します。サンプルはLocalHnadInput.unityで作成しました。
デフォルトのカメラの代わりにMRDesignLabsアセットのHoloLens.Prefabを配置します。次に空のGameObjectを設定し名前を変更します（サンプルではrootに設定）。次にFitBoxhierarchyに追加します。最後にFitboxコンポーネントのStartup Objectに先ほど作った空のGameObjectを追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/c30c4950-c41a-c12b-ba46-14f877f2759c.png)

次に右手、左手の操作で操作するcubeをRootの下に作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/0dd65874-5687-4890-1309-2f8795094627.png)

次にそれぞれのcubeにLocalHandInputコンポーネントを追加します。LocalHandInput自体はHierarchyのどこのオブジェクトに設置していても動作します。今回はわかりやすく操作するコンポーネントに対して割り当てています。

### LocalHandInputの設定
プロパティ等の設定は上記に記載しています。ここでは変更が必要な個所についてのみ記載します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/95991b7f-8ee8-674f-0890-1b1c217edeb0.png)

変更箇所は以下の３つです。左右別々に検出するので「Handedness」を「Right(左手用にはLeft)」、「Min Confidence」は「0.1」、「Input Scale」を「7」としました。
これで、顔中心から0.1m以上右のセンサー認識範囲が右手操作として認識される範囲です。

### ジェスチャー操作でcubeを動かす

最後にジェスチャーでCubeを動かすロジックを実装します。
Projectの任意の場所にC# Scriptを作成してください。今回は「HoloMultiHandInputSamples\Scripts\ManipilateCubde」として作成しました。
作成したらVisual Studioでロジックを実装します。
実装コードは以下の通りです。
LocalHandInputからホールド中の座標を取得する場合は、LocalPositionプロパティを用います。今回はこの値をcubeに渡して操作しています。また、センサーがそれぞれの手を検出している場合はcubeを青に、センサーからロストしている場合は赤色になるように実装しています。まずは両方のCubeが青い状態からホールドして操作をしてみてください。

```csharp:ManupilateCube.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using HUX.Utility;
using UnityEngine;

[RequireComponent(typeof(LocalHandInput))]
public class ManupilateCube : MonoBehaviour
{
    /// <summary>
    /// Hand Gesture Component.
    /// </summary>
    private LocalHandInput input;

    /// <summary>
    /// Initial world position of GameObject field.
    /// </summary>
    private Vector3 _initializePos;
    
    /// <summary>
    /// material of GameObject field.
    /// </summary>
    private Material material;

    // Use this for initialization
    void Start ()
	{
	    input = gameObject.GetComponent<LocalHandInput>();
        _initializePos = gameObject.transform.localPosition;
        material = new Material(Shader.Find("Diffuse"));
	    gameObject.GetComponent<Renderer>().material = material;
	}
	
	// Update is called once per frame
	void Update ()
	{

        //if Hand is lost,this gameobject's color is become Red.
        material.color = InputSources.Instance.hands.GetHandState(input.Handedness, input.MinConfidence) == null ? Color.red : new Color(.4f,.4f,1f);

        gameObject.transform.position = _initializePos + input.LocalPosition;
	}
}
```

最後に、作成したManupilateCubeを先ほど作った左右のCubeに追加してHoloLens用のビルド設定でビルドを実施し実機へデプロイすれば完了です。起動するとMRDesignLabsのロゴ表示がされますのでタップをするとcubeが２個表示されます。あとは色々操作して両手ジェスチャーの感覚をつかんてみてください。

[![サンプルアプリの動作例](http://img.youtube.com/vi/hDBu0z1i0dY/0.jpg)](http://www.youtube.com/watch?v=hDBu0z1i0dY)

## 最後に
この機能は記述時点ではLunarModuleに同梱されているMRDesignLabsの機能となっています。よって使うためには [https://github.com/Microsoft/MRDesignLabs_Unity_LunarModule](https://github.com/Microsoft/MRDesignLabs_Unity_LunarModule)からとるようにしてください。便利そうなのでそのうちMRDesignLabsに入ってほしいなとおもいつつ。
次回はLunarModuleのチュートリアルで使用されている「Hand Coach」の解説をしたいと思います。