最近のOculusQuestさんちょっと攻めすぎですよね。
OculusLinkのβが出たのもつい最近な気がしているのに、さらにハンドトラッキングまで!!!
そして、2019/12/20にはUnityのAssetStoreにハンドトラッキング対応のSDKが配布されました。 高速感!
導入方法なんかについては他の方の記事にお任せして・・・。
肝はOVRSkelton
OVRHand には GetFingerIsPinching()と GetFingerPinchStrength()があり、それぞれ、ピンチ(つまんでいる)状態かどうかと
つまんでいる力(距離?)が取得できるメソッドが入っています。
参考:OculusQuestのハンドトラッキングについて色々調べてみた
しかし、ぱっと見たところそれ以外の情報取得がそんなにありません。
そこで上記記事でも言及していますが、もっと詳しい情報を知りたい場合は OVRSkelton.Bonesにボーン情報が入っているのでそれを使うことにします。
というのも
僕がやりたかったのは以前作った 「空中に図形を描いて物体を生成するアプリを作ってみた」 のハンドトラッキング版だったので、空中に絵を描くイメージです。
みなさんが空中に絵を描く(例えば、誰かに「**って漢字どう書くんだったっけ?」と聞かれた)時に指をどんな形にするかっていうと
十中八九
←これですよね
この「人差し指だけを伸ばして、他曲げている」をGetFingerIsPinching()やGetFingerPinchStrength()でやるのはちょっと無理があります。(できなくはないと思いますが)
そこで、 OVRSkelton.Bonesから情報を抜き出して簡単に分析することにしました。
Bone情報取得
OVRSkelton.Bonesは IList(Readonly)で、何番目にどの情報が入っているかは以下のようにenumで宣言されています。
publicenumBoneId{Invalid=OVRPlugin.BoneId.Invalid,Hand_Start=OVRPlugin.BoneId.Hand_Start,Hand_WristRoot=OVRPlugin.BoneId.Hand_WristRoot,// root frame of the hand, where the wrist is locatedHand_ForearmStub=OVRPlugin.BoneId.Hand_ForearmStub,// frame for user's forearmHand_Thumb0=OVRPlugin.BoneId.Hand_Thumb0,// thumb trapezium boneHand_Thumb1=OVRPlugin.BoneId.Hand_Thumb1,// thumb metacarpal boneHand_Thumb2=OVRPlugin.BoneId.Hand_Thumb2,// thumb proximal phalange boneHand_Thumb3=OVRPlugin.BoneId.Hand_Thumb3,// thumb distal phalange boneHand_Index1=OVRPlugin.BoneId.Hand_Index1,// index proximal phalange boneHand_Index2=OVRPlugin.BoneId.Hand_Index2,// index intermediate phalange boneHand_Index3=OVRPlugin.BoneId.Hand_Index3,// index distal phalange boneHand_Middle1=OVRPlugin.BoneId.Hand_Middle1,// middle proximal phalange boneHand_Middle2=OVRPlugin.BoneId.Hand_Middle2,// middle intermediate phalange boneHand_Middle3=OVRPlugin.BoneId.Hand_Middle3,// middle distal phalange boneHand_Ring1=OVRPlugin.BoneId.Hand_Ring1,// ring proximal phalange boneHand_Ring2=OVRPlugin.BoneId.Hand_Ring2,// ring intermediate phalange boneHand_Ring3=OVRPlugin.BoneId.Hand_Ring3,// ring distal phalange boneHand_Pinky0=OVRPlugin.BoneId.Hand_Pinky0,// pinky metacarpal boneHand_Pinky1=OVRPlugin.BoneId.Hand_Pinky1,// pinky proximal phalange boneHand_Pinky2=OVRPlugin.BoneId.Hand_Pinky2,// pinky intermediate phalange boneHand_Pinky3=OVRPlugin.BoneId.Hand_Pinky3,// pinky distal phalange boneHand_MaxSkinnable=OVRPlugin.BoneId.Hand_MaxSkinnable,// Bone tips are position only. They are not used for skinning but are useful for hit-testing.// NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguousHand_ThumbTip=OVRPlugin.BoneId.Hand_ThumbTip,// tip of the thumbHand_IndexTip=OVRPlugin.BoneId.Hand_IndexTip,// tip of the index fingerHand_MiddleTip=OVRPlugin.BoneId.Hand_MiddleTip,// tip of the middle fingerHand_RingTip=OVRPlugin.BoneId.Hand_RingTip,// tip of the ring fingerHand_PinkyTip=OVRPlugin.BoneId.Hand_PinkyTip,// tip of the pinkyHand_End=OVRPlugin.BoneId.Hand_End,// add new bones hereMax=OVRPlugin.BoneId.Max}親指:Thumb
人差し指:Index
中指:Middle
薬指:Ring
小指:Pinky
で、1,2,3 は関節を表しており、数字が大きくなるほど指先に近づいていくようです。
僕は英語をロクに読まず 「第一関節」= 1 だと思い込んで処理を書いていたらさっぱり正しい値が返ってこなくてハテ? となりました。ご注意を・・・。
そして、 Tipとついているのは指先です。
なお、Pinky と Thumb だけ 0番があります。
そして、この Bone には Transform が入っており、位置や回転が取れそうです。
例えば「人差し指の先端の位置」を取るには
varindexTipPos=skeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;こうなります(注:enumなのでintキャストが必要)
直線判定
以上を踏まえ、
この「人差し指がまっすぐになっていて、中指、薬指、小指は曲がっている」を判定します。(親指は除きました。)
この「人差し指がまっすぐになっている」というのは人差し指の「第三関節→第二関節」の方向(ベクトル)と、「第二関節→第一関節」の方向(ベクトル)と「第一関節→指先」の方向(ベクトル)が大体同じ方向を向いているということです。
この「二つのベクトルが大体同じ方向を向いている」=「どれぐらい似通っているか」を表すのは。そう内積(Vector3.Dot)です。
ベクトルの内積は直角(一番似通っていない)な場合は0
全く同じ場合は+1
全く逆方向の場合は-1です
これを人差し指だけではなく他の指の分もベタ書きするとそこそこ長くなってしまうので、以下のようなメソッドを用意すると便利だと思います。
[SerializeField]privateOVRSkeleton_skeleton;//右手、もしくは左手の Bone情報/// <summary>/// 指定した全てのBoneIDが直線状にあるかどうか調べる/// </summary>/// <param name="threshold">閾値 1に近いほど厳しい</param>/// <param name="boneids"></param>/// <returns></returns>privateboolIsStraight(floatthreshold,paramsOVRSkeleton.BoneId[]boneids){if(boneids.Length<3)returnfalse;//調べようがないVector3?oldVec=null;vardot=1.0f;for(varindex=0;index<boneids.Length-1;index++){varv=(_skeleton.Bones[(int)boneids[index+1]].Transform.position-_skeleton.Bones[(int)boneids[index]].Transform.position).normalized;if(oldVec.HasValue){dot*=Vector3.Dot(v,oldVec.Value);//内積の値を総乗していく}oldVec=v;//ひとつ前の指ベクトル}returndot>=threshold;//指定したBoneIDの内積の総乗が閾値を超えていたら直線とみなす}可変長配列でBoneIDを複数(3個以上)受けとり、一つ前のBoneIDが示す関節から関節のベクトルと、今のBoneIDが示す関節と次の関節のベクトルの内積を計算して、 dotにどんどん乗算していっています。(別に平均でも良い気はしますが)
これを使うと、人差し指がまっすぐかどうかを判定してLogに表示する場合、このようになります。
varisIndexStraight=IsStraight(0.8f,OVRSkeleton.BoneId.Hand_Index1,OVRSkeleton.BoneId.Hand_Index2,OVRSkeleton.BoneId.Hand_Index3,OVRSkeleton.BoneId.Hand_IndexTip);Debug.Log($"人差し指は{isIndexStraight?"まっすぐ":"曲がってる"}");おなじく、他の指も調べていけば、
「人差し指がまっすぐになっていて、中指、薬指、小指は曲がっている」は判定できそうです。
varisIndexStraight=IsStraight(0.8f,OVRSkeleton.BoneId.Hand_Index1,OVRSkeleton.BoneId.Hand_Index2,OVRSkeleton.BoneId.Hand_Index3,OVRSkeleton.BoneId.Hand_IndexTip);varisMiddleStraight=IsStraight(0.8f,OVRSkeleton.BoneId.Hand_Middle1,OVRSkeleton.BoneId.Hand_Middle2,OVRSkeleton.BoneId.Hand_Middle3,OVRSkeleton.BoneId.Hand_MiddleTip);varisRingStraight=IsStraight(0.8f,OVRSkeleton.BoneId.Hand_Ring1,OVRSkeleton.BoneId.Hand_Ring2,OVRSkeleton.BoneId.Hand_Ring3,OVRSkeleton.BoneId.Hand_RingTip);varisPinkyStraight=IsStraight(0.8f,OVRSkeleton.BoneId.Hand_Pinky0,OVRSkeleton.BoneId.Hand_Pinky1,OVRSkeleton.BoneId.Hand_Pinky2,OVRSkeleton.BoneId.Hand_Pinky3,OVRSkeleton.BoneId.Hand_PinkyTip);Debug.Log($"人差し指は{isIndexStraight?"まっすぐ":"曲がってる"}");Debug.Log($"中指は{isMiddleStraight?"まっすぐ":"曲がってる"}");Debug.Log($"薬指は{isRingStraight?"まっすぐ":"曲がってる"}");Debug.Log($"小指は{isPinkyStraight?"まっすぐ":"曲がってる"}");if(isIndexStraight&&!isMiddleStraight&&!isRingStraight&&!isPinkyStraight){//人差し指だけまっすぐで、その他が曲がっているDebug.Log($"お前がナンバーワンだ!");}そして、人差し指の先端の位置 (IndexTip) で線を描くとこうなりました
実家に帰っている間にOculusQuestのハンドトラッキングがUnity対応していたので、色々と試し中。
— すずきかつーき (@divideby_zero) December 22, 2019
左手がボーンだけ表示。
ボーンから関節情報が取れるので、各指が伸びているか曲がっているかを独自に計算してみた。
しかし・・・。座標が安定しないので全然図形は描けない。ぐぬぬ。#OculusQuestpic.twitter.com/6PaNVLBfmh
うーん。 ノイズなのかなんなのか。 まったく直線が描けてないですね。
とりあえずの目標(
で線を描く)はできているので、よしとします。
この問題はまた後日・・・。
まとめ
OVRHandからとれるピンチ情報に加え、この「各指が曲がっているか(false)伸びているか(true)」が加わるだけでもいろんな事が出来るんじゃないかなと思います。
<例>
- 全部falseならグー、人差し指と中指だけtrueならチョキ、全部trueならパー、でじゃんけん
- 中指と小指がfalse、そのほかがtrueで「グワシ!」
- 中指だけtrue そのほかfalse で 「F******!」で、プログラム強制終了。
などなど。
しかし・・。ちゃんとリファレンス見てないので、こんなことしなくても情報は取れるよ!もっといい方法あるよ! などあったらコメント教えてください。
ではでは、よきOculusQuestライフを。
