Oculus Quest, ハンドトラッキングにおけるハンドサイン認識

#目的
Questのハンドトラッキングでなんかしたい

#手法
とりあえず指が曲がってるかどうかでハンドサインを認識してみる.
ボーンの内積掛けて指がどれだけ曲がってるかを閾値判定

以下二つのコードはOVRSkeltonが付いてるオブジェクトに付けてください．
まず, 指の内積計算するコード

```cs:HandBoneDot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class HandBoneDot : MonoBehaviour
{
	public List<BoneDot> _boneDots;
	public List<FingerDot> _fingerDots;
	public enum FingerIndex { Thumb, Index, Middle, Ring, Pinky }
	private OVRSkeleton _ovrSkeleton;
	private bool _isInitialized;
	int[,] BoneDirectionIndex = {
	{ (int)OVRSkeleton.BoneId.Hand_Thumb1,(int)OVRSkeleton.BoneId.Hand_Thumb2, (int)OVRSkeleton.BoneId.Hand_Thumb3 },
	{ (int)OVRSkeleton.BoneId.Hand_Thumb2,(int)OVRSkeleton.BoneId.Hand_Thumb3, (int)OVRSkeleton.BoneId.Hand_ThumbTip },//Thumb
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Index1, (int)OVRSkeleton.BoneId.Hand_Index2 },
	{ (int)OVRSkeleton.BoneId.Hand_Index1,(int)OVRSkeleton.BoneId.Hand_Index2, (int)OVRSkeleton.BoneId.Hand_Index3 },
	{ (int)OVRPlugin.BoneId.Hand_Index2,(int)OVRSkeleton.BoneId.Hand_Index3, (int)OVRSkeleton.BoneId.Hand_IndexTip },//Index
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Middle1, (int)OVRSkeleton.BoneId.Hand_Middle2 },
	{ (int)OVRSkeleton.BoneId.Hand_Middle1,(int)OVRSkeleton.BoneId.Hand_Middle2, (int)OVRSkeleton.BoneId.Hand_Middle3 },
	{ (int)OVRSkeleton.BoneId.Hand_Middle2,(int)OVRSkeleton.BoneId.Hand_Middle3, (int)OVRSkeleton.BoneId.Hand_MiddleTip },//Middle
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Ring1, (int)OVRSkeleton.BoneId.Hand_Ring2 },
	{ (int)OVRSkeleton.BoneId.Hand_Ring1,(int)OVRSkeleton.BoneId.Hand_Ring2, (int)OVRSkeleton.BoneId.Hand_Ring3 },
	{ (int)OVRSkeleton.BoneId.Hand_Ring2,(int)OVRSkeleton.BoneId.Hand_Ring3, (int)OVRSkeleton.BoneId.Hand_RingTip },//Ring
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Pinky1, (int)OVRSkeleton.BoneId.Hand_Pinky2 },
	{ (int)OVRSkeleton.BoneId.Hand_Pinky1,(int)OVRSkeleton.BoneId.Hand_Pinky2, (int)OVRSkeleton.BoneId.Hand_Pinky3 },
	{ (int)OVRSkeleton.BoneId.Hand_Pinky2,(int)OVRSkeleton.BoneId.Hand_Pinky3, (int)OVRSkeleton.BoneId.Hand_PinkyTip }//Pinky
	};
	public class BoneDot
	{
		Transform BoneBegin, BoneMiddle, BoneEnd;
		public float dot;
		public BoneDot(Transform begin, Transform middle, Transform end)
		{
			BoneBegin = begin;
			BoneMiddle = middle;
			BoneEnd = end;

			this.Update();
		}
		public void Update()
		{
			dot = Vector3.Dot((BoneMiddle.position - BoneBegin.position).normalized, (BoneEnd.position - BoneMiddle.position).normalized);
		}
	}
	public class FingerDot
	{
		BoneDot[] bones;
		public float dot;
		public FingerDot(params BoneDot[] bone)
		{
			bones = bone;
		}
		public void Update()
		{
			dot = 1;
			for (int i = 0; i < bones.Length; i++)
				dot *= bones[i].dot;
		}
	}
	private void Awake()
	{
		if (_ovrSkeleton == null)
		{
			_ovrSkeleton = GetComponent<OVRSkeleton>();
		}
	}

	private void Start()
	{
		if (_ovrSkeleton == null)
		{
			this.enabled = false;
			return;
		}

		Initialize();
	}

	private void Initialize()
	{
		_boneDots = new List<BoneDot>();
		_fingerDots = new List<FingerDot>();
		_ovrSkeleton = GetComponent<OVRSkeleton>();

		for (int i = 0; i < BoneDirectionIndex.Length / 3; i++)
		{
			var boneVis = new BoneDot(
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 0]].Transform,
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 1]].Transform,
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 2]].Transform);

			_boneDots.Add(boneVis);
		}

		for (int i = 0; i < Enum.GetNames(typeof(FingerIndex)).Length; i++)
		{
			FingerDot Fing;
			if (i == 0)
				Fing = new FingerDot(_boneDots[0], _boneDots[1]);
			else
				Fing = new FingerDot(_boneDots[2 + 3 * (i - 1)], _boneDots[3 + 3 * (i - 1)], _boneDots[4 + 3 * (i - 1)]);
			_fingerDots.Add(Fing);
		}

		_isInitialized = true;
	}

	public void Update()
	{
		if (_isInitialized)
		{
			for (int i = 0; i < _boneDots.Count; i++)
				_boneDots[i].Update();
			for (int i = 0; i < _fingerDots.Count; i++)
				_fingerDots[i].Update();
		}
	}
}

```
ハンドサイン認識のコード
閾値付近でハンドサインがバタつかないようにsignには多少補正をかけてある.
補正をかけてない値が欲しい場合はHandSignNumを参照する.
HandSignNumは親指を一桁目とした二進数になっている．
例えば親指だけ立っているとき00001(2)=1(10)，ピースサインの場合は人差し指と中指が立ってるので00110(2)=6(10)といった具合．
一つ下のEnumも同様に0~31で表される．

```cs:HandSignDetector.cs
using UnityEngine;
using System.Collections;

public class HandSignDetector : MonoBehaviour
{
	[Range(-1, 1)] public float[] FingerBendThrethold = new float[5] { 0.85f, 0, 0, 0, 0 };
	public int HandSignNum;
	public HandSign_Bend sign;
	public float SignEndWaitTime = 0.2f;
	bool IsPosing = false, PoseChangeWaiting = false;
	HandBoneDot handBone;
	void Start()
	{
		handBone = GetComponent<HandBoneDot>();
		if (handBone == null)
			return;
		HandSignNum = 0;
		for (int i = 0; i < handBone._fingerDots.Count; i++)
			if (handBone._fingerDots[i].dot > FingerBendThrethold[i])
				HandSignNum += (int)Mathf.Pow(2, i);
		UpdateSignInit();
	}
	void Update()
	{
		HandSignNum = 0;
		for (int i = 0; i < handBone._fingerDots.Count; i++)
			if (handBone._fingerDots[i].dot > FingerBendThrethold[i])
				HandSignNum += (int)Mathf.Pow(2, i);

		if (!IsPosing)
			UpdateSignInit();
		else if (HandSignNum != (int)sign && !PoseChangeWaiting && IsPosing)
			StartCoroutine(PoseEndWait(SignEndWaitTime));
	}
	void UpdateSignInit()
	{
		sign = (HandSign_Bend)System.Enum.ToObject(typeof(HandSign_Bend), HandSignNum);
		IsPosing = true;
	}
	IEnumerator PoseEndWait(float waitime)
	{
		float t = Time.time;
		PoseChangeWaiting = true;
		while (Time.time < t + waitime)
		{
			if (HandSignNum == (int)sign)
			{
				PoseChangeWaiting = false;
				yield break;
			}
			yield return null;
		}

		IsPosing = false;
		PoseChangeWaiting = false;
	}
}
```

以下ハンドサイン一覧用のEnum
HandSign_Bend.csの方は思いつくハンドサインの名前を入れたEnum. 思いつかなかったところは立ってる指の名前を羅列してある.
HandSign_BendRaw.csの方は指の名前の羅列のみ.

```cs:HandSign_Bend.cs
public enum HandSign_Bend
{
	Fist,
	ThumbsUp,
	Point,
	Gun,
	Fuck,
	ThumbMiddle,
	Peace,
	ThumbIndexMiddle,
	Ring,
	ThumbRing,
	IndexRing,
	ThumbIndexRing,
	MiddleRing,
	ThumbMiddleRing,
	Three,
	ThumbIndexMiddleRing,
	Lover,
	Phone,
	DevilsHorn,
	Spidey,
	MiddlePinky,
	ThumbMiddlePinky,
	IndexMiddlePinky,
	ThumbIndexMiddlePinky,
	RingPinky,
	ThumbRingPinky,
	IndexRingPinky,
	ThumbIndexRingPinky,
	MiddleRingPinky,
	ThumbMiddleRingPinky,
	Four,
	OpenHand
}
```

```cs:HandSign_BendRaw.cs
public enum HandSign_BendRaw
{
	AllBend,
	Thumbs,
	Index,
	ThumbIndex,
	Middle,
	ThumbMiddle,
	IndexMiddle,
	ThumbIndexMiddle,
	Ring,
	ThumbRing,
	IndexRing,
	ThumbIndexRing,
	MiddleRing,
	ThumbMiddleRing,
	IndexMiddleRing,
	ThumbIndexMiddleRing,
	Pinky,
	ThumbPinky,
	IndexPinky,
	ThumbIndexPinky,
	MiddlePinky,
	ThumbMiddlePinky,
	IndexMiddlePinky,
	ThumbIndexMiddlePinky,
	RingPinky,
	ThumbRingPinky,
	IndexRingPinky,
	ThumbIndexRingPinky,
	MiddleRingPinky,
	ThumbMiddleRingPinky,
	IndexMiddleRingPinky,
	ThumbIndexMiddleRingPinky
}
```

#結果
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">うぃ～～<br>ハンドトラッキング正直なところ結構トラッキング範囲広い癖に複雑な印はすぐ信頼度下がって消えるからちょっと使いづらい <a href="https://t.co/wBe3TMd1ey">pic.twitter.com/wBe3TMd1ey</a></p>&mdash; KEEL_210/人間性2Cell (@jdatmtjp) <a href="https://twitter.com/jdatmtjp/status/1222327381718732803?ref_src=twsrc%5Etfw">January 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#まとめ
割とうまくいってる．