Unity、Oculus(HTC Vive)、VRIKで簡易モーションキャプチャを作った話

# 概要
Oculus TouchとVRIKで動かしたモデルのモーションを保存して再生できる機能を作ってみたので紹介します。自分が試したのはOculusですが、HTCViveでも同じ話だと思います。

# VRIK設定
下記サイト等を参考にして設定してください。
http://qiita.com/halne369/items/daadf0e00330a1019830

# モーションデータ取り扱い
Humanoidタイプのモデルのアニメーションを編集するには、AnimationClipを動的に生成するのではなくてHumanPoseHandlerクラスを使うといいらしい。

https://forum.unity3d.com/threads/is-there-any-method-to-manipulate-a-humanoid-muscle-by-api.354221/#post-2291395

HumanPhoseHandlerの使い方は以下の通り。

```csharp:test.cs
HumanPose m_pose;

//ハンドラー取得
var handler = new HumanPoseHandler(m_animator.avatar, m_animator.transform);

//HumanPose取得
handler.GetHumanPose(ref m_pose)

//HumanPoseセット
handler.SetHumanPose(ref m_pose)
```
これだけ。
# モーションデータ取得→保存

やり方は簡単で、毎フレームHumanPoseを取得してScriptableObjectに保存するだけ。

```csharp:HumanPoses.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class HumanPoses : ScriptableObject {

    [System.SerializableAttribute]
  
    public class SerializeHumanPose
    {
        public Vector3 bodyPosition;
        public Quaternion bodyRotation;
        public float[] muscles;
        public int frameCount;
    }

    public List<SerializeHumanPose> poses = new List<SerializeHumanPose>();

}
```

```csharp:RecordHumanPose.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using System;
using LitJson;
using System.IO;
using UnityEditor;

public class RecordHumanPose : MonoBehaviour {

    /// <summary>
    /// 対象のアニメーター
    /// </summary>
    [SerializeField] Animator m_animator;

    int m_frameCount;

    HumanPose m_pose;

    HumanPoseHandler m_handler;

    Action m_recordEndCB;

    
    /// <summary>
    /// 録画開始
    /// </summary>
    public void RecordStart()
    {
      
        m_frameCount = 0;

        HumanPoses humanposes = ScriptableObject.CreateInstance<HumanPoses>();

        var dispose = this.UpdateAsObservable().Subscribe(p =>
        {
            m_handler.GetHumanPose(ref m_pose);
            var seripose = new HumanPoses.SerializeHumanPose();
            seripose.bodyPosition = m_pose.bodyPosition;
            seripose.bodyRotation = m_pose.bodyRotation;
            seripose.frameCount = m_frameCount;
            seripose.muscles = new float[m_pose.muscles.Length];
            seripose.frameCount = m_frameCount;
            for (int i = 0; i < seripose.muscles.Length; i++)
            {
                seripose.muscles[i] = m_pose.muscles[i];
            }
            humanposes.poses.Add(seripose);
        }
        );

        var timeCountObservable = this.UpdateAsObservable().Subscribe(p => m_frameCount++);

        m_recordEndCB = () =>
        {
            string path = AssetDatabase.GenerateUniqueAssetPath("Assets/Resources/HumanPoses.asset");
            AssetDatabase.CreateAsset(humanposes, path);
            AssetDatabase.Refresh();
            m_frameCount = 0;
            timeCountObservable.Dispose();
            dispose.Dispose();
        };
    }


    /// <summary>
    /// 録画終了
    /// </summary>
    public void RecordEnd()
    {
        if (m_recordEndCB != null)
        {
            m_recordEndCB();
            m_recordEndCB = null;
        }

    }

    private void Awake()
    {
        m_handler = new HumanPoseHandler(m_animator.avatar, transform);
    }
}

```

# モーションデータ再生

ScriptableObjectに保存したHumanPoseを読み取ってセットする。

```csharp:PlayHumanPoses.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class PlayHumanPoses : MonoBehaviour {

    [SerializeField]
    HumanPoses m_poses;

    [SerializeField]
    Animator m_animator;

    HumanPoseHandler m_handler;

    System.Action m_finishPlayCB;

    int m_index = 0;

    public void Play()
    {
        m_index = 0;
        var disposable = this.UpdateAsObservable().Where(p=>m_index < m_poses.poses.Count).Select(p => 1).Scan((now, count) =>
        {
            Debug.Log(now);
            return now + count;
        }).Subscribe(xx=>
        {
            DoSetHumanPose(xx);
        });

        m_finishPlayCB = () =>
        {
            disposable.Dispose();
            m_index = 0;
        };
    }


    public void Stop()
    {
        if (m_finishPlayCB != null)
        {
            m_finishPlayCB();
            m_finishPlayCB = null;
        }
    }


    void DoSetHumanPose(int frame)
    {
        if (m_poses.poses[m_index].frameCount == frame-1)
        {
            var pose = new HumanPose();
            pose.bodyPosition = m_poses.poses[m_index].bodyPosition;
            pose.bodyRotation = m_poses.poses[m_index].bodyRotation;
            pose.muscles = m_poses.poses[m_index].muscles;
            m_handler.SetHumanPose(ref pose);
            m_index++;
        }
    }

	// Use this for initialization
	void Awake () {
        m_handler = new HumanPoseHandler(m_animator.avatar, transform);
    }

}
```
# 最後に
後はOculusかHTCViveでVRIKをセットしたモデルを動かして上記方法でモーションを記録＋再生をすればOKです。