Meta Quest2のハンドトラッキングでハンドを貫通しないようにする

# はじめに

### 概要
本記事では、Unityを用いたMeta Quest2用のVRコンテンツにおいて、ハンドトラッキングを使用する際にハンドモデルがオブジェクトに触れた際に貫通しないようにする手法を記載します。

:::note 
本記事では左手のみの解説ですが、右手も同様にして下さい。
:::



### 本記事のゴール
ハンドトラッキングする際に、他のオブジェクトに触れた際に貫通しないようになります。
下の動画では、貫通する手がハンドトラッキングによる実際の手の位置、貫通しない手が本記事で作成する手です。
![Test18-4_HandTest - SampleScene - Android - Unity 2021.3.25f1 Personal _DX11_ 2023-08-29 23-56-16.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/d9a2f665-b7ec-feb0-135c-aeb7888b3171.gif)



# 動作環境
本記事内容を作成したのは下記環境です。
* Unity 2021.3.25f1
* Oculus Integration 55.0



# 仕組み
### OVRHandsの作り
OVRHandsは、以下のような階層となっています。
その中で、手のメッシュはl_handMeshNode及びr_handMeshNodeが該当しています。
このメッシュはb_l_wristまたはb_r_wrist配下の各オブジェクト（ボーン）と対応しており、これらのTransformがメッシュに反映され、手の位置や形が変化するようです。

各ボーンと手の位置は以下が分かりやすいので参照にしてください。

https://qiita.com/divideby_zero/items/4949fadb2c60f810b3aa

ハンドトラッキングによる手や指のTransform情報は、LeftHandやRightHandにアタッチされているHandコンポーネント等によりボーンに反映されています。

### 手を貫通しないようにするための作戦
OVRHandsをそのまま使用すると実際の手の位置が反映され、手がオブジェクトをすり抜けてしまいます。
そのため、もう一つ手（OVRHandsSlave）を用意し、それをOVRHandsに追随するように動かすとともに、コライダをつけて衝突判定をさせてオブジェクトを貫通しないようにします。
OVRHandsに追随させるためには、Fixed Jointを使用します。



# 作成方法
### 1\. Unityプロジェクトの作成～ハンドトラッキングの設定
下記記事を前提としているためそちらを参照してください。

https://qiita.com/Cohiroto/items/a0e55aa7246a5b937548

### 2\. 新たなOVRHandsの追加
　Projectビューから下記を探し、HierarchyのOVRInteractionにドラッグ&ドロップで追加し、名称を「OVRHandsSlave」に変更する。
　Assets/Oculus/Interaction/OVRIntegration/Runtine/Prefabs/OVRHands.prefab

![01_OVRHandsSlave追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/cfc76b99-1ee1-ea47-1170-0814c1ad68f6.png)


### 3\. OVRHandsSlaveのLeftHand配下の各オブジェクトの非アクティブ化
　OVRHandsSlave/LeftHand配下にある下記オブジェクトを非アクティブ化する。
　　・HandDataLeft
　　・HandFeaturesLeft
　　・HandInteractionsLeft
　　・OVRHandDataSource

![02_OVRHandsSlave非アクティブ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/0de5d01d-cc45-dd57-9eb6-927f464bf7b3.png)


### 4\. OVRHandsSlaveのLeftHandのコンポーネントの無効化
　OVRHandSlave/LeftHandの下記コンポーネントを無効化する。
　　・Hand
　　・Hand Active State

![03_LeftHand非アクティブ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/1ec73f7c-23da-60d5-c4bb-ce9a1720d764.png)


### 5\. OVRHandsSlaveのOVRLeftHandVisualのコンポーネントの無効化
　OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisualの下記コンポーネントを無効化する。
　　・Hand Visual

![04_LeftHandVisual非アクティブ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/ef3a95e4-44c0-f6bb-df25-1c7251c0a95f.png)


### 6\. OVRHandsSlaveのOculusHand_Lのコンポーネントの無効化
　OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_Lの下記コンポーネントを無効化する。
　　・Animator
![05_OculusHand_L非アクティブ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/512df2c4-5d0a-c40c-ed2d-33cf1f6970fa.png)

### 7\. OVRHandsのOculusHand_LにRigidbodyコンポーネント追加
　OVRHands/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_LにRigidbodyコンポーネントを追加する。
　　Is Kinematicにチェックをつける。
![06_OculusHand_L_Rigidbody追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/fc13d332-16fa-a165-fb45-823bc0edb2db.png)


### 8\. OVRHandsSlaveのOculusHand_LにRigidbodyとFixed Jointコンポーネント追加
　OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_LにRigidbodyコンポーネントとFixed Jointコンポーネントを追加する。
　　RigidbodyのFreeze Rotation XYZにチェックをつける。
　　Fixed JointのConnected BodyにOVRHands/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_LのRigidbodyを設定する。

![07_OculusHand_L_Rigidbody,FixedJoint追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/4b830880-1a4a-4825-7f0a-8cd1f0f03c1f.png)


### 9\. OVRHandsSlaveのOculusHand_LにVirturl Handコンポーネント追加
　OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_Lに、下記Virtual Handを追加する。

```C#:VisualHand.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VirtualHand : MonoBehaviour
{
    [SerializeField] Transform sourceOculusHand;
    [SerializeField] Transform OculusHand;
    [SerializeField] private Transform rootSourceBone;
    [SerializeField] private Transform rootBone;
    [SerializeField] private Vector3 adjustRotate;

    [SerializeField] private Rigidbody OculusHandRB;

    private List<Transform> _sourceBones;
    private List<Transform> _bones;

    public bool isSetBoans = true;

    void Start()
    {
        _sourceBones = new List<Transform>();
        _bones = new List<Transform>();
        GetBones(rootSourceBone, _sourceBones);
        GetBones(rootBone, _bones);
    }

    void Update()
    {
        OculusHand.localScale = sourceOculusHand.localScale;
        if (isSetBoans)
        {
            SetBones();
        }
    }

    private void FixedUpdate()
    {
        SetHand();
    }

    private void SetHand()
    {
        OculusHandRB.MoveRotation(sourceOculusHand.rotation * Quaternion.Euler(adjustRotate));
    }

    private void SetBones()
    {
        if (_sourceBones.Count == _bones.Count)
        {
            for (int i = 0; i < _sourceBones.Count; i++)
            {
                _bones[i].localRotation = _sourceBones[i].localRotation;
            }
        }
    }

    private void GetBones(Transform rootBone, List<Transform> bone)
    {
        int length = rootBone.childCount;

        for (int i = 0; i < length; i++)
        {
            Transform b = rootBone.GetChild(i);
            if(b.name.IndexOf("b_") == 0)
            {
                bone.Add(b);

                if (b.childCount > 0)
                {
                    GetBones(b, bone);
                }
            }
        }
    }
}
```

　　Source Oculus HandにOVRHands/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_Lを設定
　　Root Source BoneにOVRHands/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_L/b_l_wristを設定
　　Root BoneにOVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_L/b_l_wristを設定
　　Adjust RotateのYを180に設定
　　Oculus Hand RBに、OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_LのRigidbodyを設定

![08_OculusHand_LのVirtualHand追加_修正.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/0ab8e13b-f3d7-0fd2-0368-a3fa7334d690.png)



### 10\. OVRHandsSlaveのb_l_wrist以下にCapsule Collider追加
　OVRHandsSlave/LeftHand/HandVisualLeft/OVRLeftHandVisual/OculusHand_L/b_l_wrist以下に、Capsule Colliderを追加する。
　ここら辺は実装によりやり方があると思いますが、今回は各ボーンごとに新たに「coll_」で始まるオブジェクトを作成し、それぞれにCapsule Colliderコンポーネントをアタッチ。
　指の関節を意識して位置や大きさを調整。

![09_OculusHand_Lのコライダ追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/1e4aa5f3-9dc0-2a98-6394-3de4946a9f46.png)

![10_OculusHand_Lのコライダ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534594/86e97ca5-01ae-ca1e-1bd1-d03337fc2fcf.png)


# おわりに
以上で、左手において貫通しない手が実装できるようになります。
右手は上記説明の「Left」を「Right」に、「b_l_wrist」を「b_r_wrist」に読み替えて同様にすればOKです。