VRMのエージェントの指の動きをOculus の手の動きでミラーリングする方法

<!---
球状
第一関節より少し先でShift +X +0.8
指の股 > gx-0.004, gx0.004
手の幅を合わせる
yは手首のボーンの高さ
## ポーズモードでメッシュを近づける
Ctrl + A で手のトランスフォームをすべて適応
Hierachyからwristのボーんを選択して、ボーンの関係でペアレントを設定
片方の手の大きさと位置の調節
もう一方の手の手動でのミラー化
メッシュの編集
手のUVとマテリアルの変更
新しいOculusHandとして両方を別々に出力
トランスフォームの適用とアーマチュアの結合
fbxとして、出力
OculusHand Mirrorの作成
--->

## 目的

##### 実際にOculusQuest上で動かした様子
https://youtu.be/na-jAuoCEsA

##### Unity Editorで実行した様子
https://youtu.be/pgNnYip-VMk

[以前の記事](https://qiita.com/KayaTakashiro/items/3de8f0632cb5ad2c720a)で作成した方法ではUnity Editor環境ではかなりうまくいくのですが、実際にデバッグするといろいろとバグが発生することがあるので別の方法を探しました。

# 目次

1. BlenderでOculus HandをVRMモデルにくっつける
2. UnityでOculusCustomHandの指の動きをVRMの指の動きへミラーリングする

## 1. BlenderでOculus HandをVRMモデルにくっつける

1.1. OculusHandをVRMのモデルに合わせる
1.2. OculusHandのメッシュの編集
1.3. OcllusHandのUVの編集
1.4 OculusHandとVRMモデルの結合
1.5 VRMモデルのウェイトの変更


を行っていきます
### 1.1 OculusHandをVRMのモデルに合わせる

まず、OculusHandのfbxが欲しいので、Unityから
Assets > Oculus > VR > Meshes > HandTrackingにあるOculusHandをCtrl+Dで複製し、Exploerに複製したやつをDrag&Dropします。

次にimport したOculusHandのfbxについて、Armaturenの位置を調整して、写真のように配置します。

コツとしては手首の太さと位置が可能な限りVRMに一致するようにします。

![oculusbone.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/5e8fe613-72e0-2f36-1eb2-c2ef59440b63.png)

### 1.2 OculusHandのメッシュの編集
次にOculusHandのメッシュについてVRM風に編集をします

注意点としては
1. 指の太さを変えて調整をすること
2. 指の股を深くして、指を長くすること
2. 絶対に指の長さを指の先から長くしないこと
2. 可能な限り左右対称に編集すること

以上になります。


##### Before
![before.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/f3506056-72b5-71bb-2926-aa699980f1b1.jpeg)

##### After
![after.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/8a44e47a-c59c-a1d4-e1ff-29116594ed45.jpeg)

ちなみに、指の太さを変えるときはプロポーショナル編集の球状を利用し、股を下げるときはスムーズを利用しました。
あとは、手首を可能な限り自然に位置合わせをしてください。

### 1.3.　OcllusHandのUVの編集

OculusHandのマテリアルタブから　N00_000_00_Body_00_SKIN　を選択します。

UV展開を写真のようにすると手首と同様の色が付きます

![uv.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/579108ed-7f21-ba28-9146-5f915d4f2cda.png)

### 1.4.OculusHandとVRMモデルの結合

次に,　OculusHandのarmatureとVRMのアーマチュアを結合します

1. OculusHandのArmatureについて、ctrl+AですべてのTransformを適用します
1. OculusHandのArmatureを選択後に、VRMのArmatureを選択し、Ctrl+Jで結合します。
1. 編集モードでインスペクターから、b_l_wrist, b_r_wristのそれぞれについて、ペアレントをJ_Bip_L_LowerArm, J_Bip_R_LowerArmとして設定します。
1. OculusHandのメッシュについて、VRMのBodyのメッシュにCtrl+Jで結合します

### 1.5 VRMモデルのウェイトの変更

最後にb_l_wrist, b_r_wristなどの新しく追加したボーンについて、ウェイトペイントでウェイトを編集し完成です。

![weight.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/d540d8f8-0011-61bd-cae4-e8f04deb7e86.png)

# 2. UnityでOculusCustomHandの指の動きをVRMの指の動きへミラーリングする

2.1 オブジェクトのセットアップ
2.2 スクリプトの作成
2.3　スクリプトの設定

## 2.1 オブジェクトのセットアップ
まず、1で作成したVRMモデルを**fbx**形式で出力します
UnityにDrag&Dropしたあと、RigをHumanoidに変更します
また、マテリアルが消えているのですが、これは編集前のVRMモデルをimportすると、そのテクスチャ付きのマテリアルが選択できるようになるのでそれで、fbxのマテリアルについても設定します。

また、Tracerという空のオブジェクトを作成し、下にLeftHandTracerとRightHandTracerという空の子オブジェクトを作成します。

あとはOVRCameraRigを追加し、LeftHandControllerAnchor, RightHandControllerAnchorの下に、それぞれOVRCustomHandPrefab_L, OVRCustomHandPrefab_Rを設定します。

![object.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/b94731ca-a4b3-870a-5454-eea84732b4c8.png)

## 2.2 スクリプトの作成

以下の5つのスクリプトを作ってください

```HandType.cs
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;

public class HandType : MonoBehaviour
{
    public enum Hand
    {
        None = -1,
        LeftHand = 0,
        Righthand = 1
    }
}

```

```GetOculusBones.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GetOculusBones : MonoBehaviour
{

    [SerializeField] Transform TargetRootBone;

    private List<Transform> customBones = new List<Transform>(new Transform[(int)OVRSkeleton.BoneId.Hand_MaxSkinnable]);
    // Start is called before the first frame update

    private static readonly string[] _fbxHandSidePrefix = { "l_", "r_" };
	private static readonly string _fbxHandBonePrefix = "b_";

	private static readonly string[] _fbxHandBoneNames =
	{
		"wrist",
		"forearm_stub",
		"thumb0",
		"thumb1",
		"thumb2",
		"thumb3",
		"index1",
		"index2",
		"index3",
		"middle1",
		"middle2",
		"middle3",
		"ring1",
		"ring2",
		"ring3",
		"pinky0",
		"pinky1",
		"pinky2",
		"pinky3"
	};

	private static readonly string[] _fbxHandFingerNames =
	{
		"thumb",
		"index",
		"middle",
		"ring",
		"pinky"
	};

	public List<Transform> CustomBones { get { return customBones; } }

	public void TryAutoMapBonesByName(HandType.Hand handType)
	{
		OVRSkeleton.BoneId start =  OVRSkeleton.BoneId.Hand_Start;
		OVRSkeleton.BoneId end = OVRSkeleton.BoneId.Hand_MaxSkinnable;
		Debug.Log("start " + start + " end " + end);
		if (start != OVRSkeleton.BoneId.Invalid && end != OVRSkeleton.BoneId.Invalid)
		{
			for (int bi = (int)start; bi < (int)end; ++bi)
			{
				string fbxBoneName = FbxBoneNameFromBoneId(handType, (OVRSkeleton.BoneId)bi);
				Transform t = FindChildRecursive(TargetRootBone, fbxBoneName);
                Debug.Log("name : " + fbxBoneName + "  transform : " + t);

				if (t != null)
				{
					customBones[(int)bi] = t;
				}
			}
		}
	}

	private static string FbxBoneNameFromBoneId(HandType.Hand handType, OVRSkeleton.BoneId bi)
	{
		{
			// if (bi >= OVRSkeleton.BoneId.Hand_ThumbTip && bi <= OVRSkeleton.BoneId.Hand_PinkyTip)
			// {
			// 	return _fbxHandSidePrefix[(int)handType-1] + _fbxHandFingerNames[(int)bi - (int)OVRSkeleton.BoneId.Hand_ThumbTip] + "_finger_tip_marker";
			// }
			// else
			// {
				return _fbxHandBonePrefix + _fbxHandSidePrefix[(int)handType] + _fbxHandBoneNames[(int)bi];
			// }
		}
	}
	public Transform FindChildRecursive(Transform parent, string boneName)
	{
		foreach (Transform child in parent)
		{
			if (child.name.Contains(boneName))
				return child;

			var result = FindChildRecursive(child, boneName);
			if (result != null)
				return result;
		}
		return null;
	}
}
```

```AvatarHand.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AvatarHand : GetOculusBones
{
}
```
```AgentHand.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Agent : GetOculusBones
{
}
```

```OculusHandController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OculusHandCotroller : MonoBehaviour
{
  [SerializeField]
    private HandType.Hand HandType;

    private Transform[] AgentBones;
    private Transform[] AvatarBones;


    // Start is called before the first frame update
    public void Start()
    {
        var agentHand = gameObject.GetComponent<AgentHand>();
        agentHand.TryAutoMapBonesByName(HandType);
        AgentBones = agentHand.CustomBones.ToArray();

        var avatarHand = gameObject.GetComponent<AvatarHand>();
        avatarHand.TryAutoMapBonesByName(HandType);
        AvatarBones = avatarHand.CustomBones.ToArray();

        if (AgentBones.Length == AvatarBones.Length){
            Debug.Log("Length is equal with Agent hands and Avatar hands ->" + AgentBones.Length + " and " + AvatarBones.Length);
        }
    }

    // Update is called once per frame
    public void LateUpdate() 
    {
     for(int id = 1; id  < AvatarBones.Length; id++){
            AgentBones[id].localRotation = AvatarBones[id].localRotation;
        }   
    }
}
```

軽く解説をすると、

HandType.csが左右の手のEnum方でこれで左右の手の判別をしています。

GetOculusBones.csが手首から、指までの指の変形に関するすべてのボーンのTransformを返すスクリプトになっています。

OculusHandController.csではエージェントの手とアバターの手の両方の手について別々にTransformを取得し、Update時には、アバターの手首から先のlocalRotationをエージェントの対応するTransformのlocalRotationに反映しています。

### 2.3　スクリプトの設定

まず、LeftHandTracerに以下のように設定をしてください。

![tracer.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/dc18ac71-9214-4094-0810-8c42cb183132.png)

注意点として、AgentHandにはJ_Bip_L_Handではなく、J_Bip_L_LowerArmを設定してください