OculusTouchで物をつかむ方法(1) OVRInputを用いてTouchの入力を受け取る

# はじめに
UnityでOculus Touchを用いるサンプルプロジェクトを作ったので、とりあえず公開してみました。
物をつかむための素朴な方法を実装しただけの物で、実用には耐えないと思いますが、
仕組みを学習する上で役立てて頂けると嬉しいです。

せっかくなので、何回かに分けてノウハウを共有したいと思っています。
今日はまず、全般的なことをまとめたあと、Touchの入力を受け取る処理について見ていきます。

## とりあえずの完成品
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Oculus Touch のサンプルプロジェクトを作ってみた <a href="https://t.co/m4CHsKkX8a">https://t.co/m4CHsKkX8a</a> <a href="https://twitter.com/YouTube">@YouTube</a>さんから</p>&mdash; 三浦真人 (@miurror) <a href="https://twitter.com/miurror/status/818804295919276033">2017年1月10日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


現時点のソースコードを公開しております。
[github.com/miurror/TouchSample](https://github.com/miurror/TouchSample)

## 参考サイト
以下の動画（英語）で解説されていることと、よく似た内容を説明する予定です。
[Unity - Setting up the Oculus Touch](https://youtu.be/8KKyK3gQktA)
[Unity - Grabbing & throwing objects with Oculus Touch](https://youtu.be/mFFta9OszzA)

Oculusの中の人（mfmfさん）が提供して下さっているOculus Avatar SDKを用いたサンプルプロジェクト [Avatar Grab sample](https://forums.oculus.com/developer/discussion/47204/avatar-grab-sample-available-unity) は、非常に参考になります。（Oculus Avatar SDKとは、Oculusが提供するかっこいいアバターをUnityやC++で扱うためのソフトウェア開発キットのことです。）

以上の参考サイトは、きゅーこんさんの記事 [Devel/OculusRift/OculusTouch](http://cubic9.com/Devel/OculusRift/OculusTouch/) で知りました。
フレームシンセシスさんの記事 [Unity＋Oculus Touch開発メモ](https://framesynthesis.jp/tech/unity/touch/) も、繰り返し参考にしています。


## 開発環境
- Unity 5.5.0f3
- C#
- Oculus Utilities for Unity 5 V1.10.0 （インポートするのはOVR/Scripts のみ）

Oculus Utilitiesは、RiftやGear VR用アプリの開発者向けに、様々なasset, script, sample sceneなどを提供しているOculusの公式パッケージです。今回、明示的に用いるのは、入力方法を統合するAPIであるOVRInputになります（他にも裏で動くscriptが幾つかあります）。最新版のダウンロードはこちらから。
[developer3.oculus.com/downloads](https://developer3.oculus.com/downloads/)

また、OVRInputに関する公式の手引きはこちらです。
[developer3.oculus.com/documentation/game-engines/latest/concepts/unity-ovrinput](https://developer3.oculus.com/documentation/game-engines/latest/concepts/unity-ovrinput/)

# Touchの位置トラッキングでオブジェクトを動かす
## TouchController.cs
TouchにはRift本体と同様に、赤外線LEDとカメラを用いた位置トラッキング機能があります。そこで、コントローラの位置と向きを、OVRInputの関数`GetLocalControllerPosition()` と `GetLocalControllerRotation()`で取得することにします。それぞれUnityEngineにおけるVector3とQuaternionの値を返します。

```TouchController.cs:
using UnityEngine;

public class TouchController : MonoBehaviour {

	public OVRInput.Controller controller;

	void Update () {

		transform.localPosition = OVRInput.GetLocalControllerPosition(controller);	
		transform.localRotation = OVRInput.GetLocalControllerRotation(controller);
	
	}
}
```
controllerの値をインスペクタからRTouch, LTouchに指定することで、Touchの動きに追従するオブジェクト（つまりバーチャル空間におけるプレイヤーの手）を作ることが出来ます。

## tips
1. 手が身体から大きく離れてしまわないために、空のオブジェクト（Player）を作っておいて、Main Cameraや左右の手（RightHand, LeftHand）の親オブジェクトにします。
2. 顔に近づけると手が消えてしまう場合は、Main Cameraの`Camera.nearClipPlane`を小さな値（たとえば0.01）に設定して描画範囲を広げます。（インスペクタから設定できます。）
3. Unity 5.5 以降では、OVRInput を使わずにUnityEngine.VRの関数`InputTracking.GetLocalPosition()`, `InputTracking.GetLocalRotation()`を用いても同じ結果が得られます。その場合、`OVRInput.Controller`の代わりには、同じ列挙型の`VRNode` を用いて、インスペクタからRightHand, LeftHandを指定します。

# Touchのボタン入力でオブジェクトをつかむ（落とす）
## Grab.cs
Grip Buttonからの入力を取得するために、OVRInputの関数`Get()`を用います。

```Grab.cs
using UnityEngine;

public class Grab : MonoBehaviour {

	public OVRInput.Controller controller;

	// オブジェクトをつかむ（落とす）イベントが生じるための Trigger の閾値
	public const float THRESH_GRAB = 0.55f;
	public const float THRESH_DROP = 0.35f;

	// オブジェクトをつかんでいるかどうか
	private bool grabbing;

	void Update () {

		OVRInput.Update();

		if(!grabbing && OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, controller) >= THRESH_GRAB) GrabObject();
		if(grabbing && OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, controller) <= THRESH_DROP) DropObject();
	}
}
```
controllerの値には先ほどと同様、RTouch, LTouchを指定します。
この際、左右のコントローラに対してボタン名`Axis1D.PrimaryHandTrigger`を共通に使用することが出来ます。その他のボタンに対しても以下のように、左右共通の名前を使うことが出来ます。

<img width="664" alt="ovrinput-touch-pair-individ.png" src="https://qiita-image-store.s3.amazonaws.com/0/143542/118dfae8-2bf1-f2b3-11ab-d8d2d1f96380.png">

これは、「様々なコントローラを統合して扱う」というOVRInputの哲学に関係しています。OVRInputでは、実質的な入力操作（Button, Touch, NearTouch, Axis1D, Axis2D）に列挙体が割り当てられていて（**バーチャルマッピング**）、その他の違いを重要視しない作りになっているのです。

## tips
`OVRInput.Update()`は、ボタンのマッピングを正しく受け取るために必ず呼び出す必要があります。ただし、代わりに次のような方法を採用することも出来ます。

- OVRManager.cs をMain Cameraにアタッチする。
- OVR/Prefabsもインポートして、Main Cameraの代わりにプレハブ OVRCameraRig を使う。（この場合、OVRManager.cs は予めアタッチされている。 Avatar Grab sampleで用いられている方法。）
- OVRInputを使わずに、以下のようなUnityのボタンマッピングを用いてUnityEngineの関数`Input.GetAxis()`でHandTriggerの状態を取得する（この場合、左右でボタン名を切り替える処理が必要になる。参考動画で用いられている方法）。


<img width="664" alt="wAVnMEt.png" src="https://qiita-image-store.s3.amazonaws.com/0/143542/20884bac-a250-1f43-946c-754f8566190e.png">

# まとめ
本日は、Oculus UtilitiesのAPI, OVRInputを用いて、Touchの位置やボタン入力を受け取る方法を見ました。肝心の、物をつかむ処理`GrabObject()`, 落とす処理`DropObject()`についても、次回以降の記事で説明したいと思います。