Plask UnityとiOSにおけるアニメーションへのAPI応答について

概要

  • 撮影した動画やiOSで読み込んだ動画をPlask APIに送信し、出力した後
  • Plask API outputをUnityで3Dモデルのアニメーションに変換

手順

3Dモデルの設定

    • Unityシーンへモデルをインポートする前に、Plask API outputのローカル軸と3Dモデルのローカル軸を揃えることが必要です。
    • 使用する3Dモデルの軸を揃えるリギングは、以下のドキュメントを参考に行ってください。

    Plask APIデータ用にモデルのリギングを設定する

     

     

    モデルのインポートとUnity上でのシーン構築

     

  • 3Dモデルをインポートして、UnityからPlask API outputを適用してください。

  • Writing Unity script to apply API outputに則って書かれたスクリプトが、3Dモデルのコンポーネントとして追加されます。

  • Unity SceneをiOSプラットフォームにビルドし、iOS projectに追加してください。

API output 構造

  • API outputは、下記の例のような構造を持つjsonファイルとなります

    {
            "result": [
                    {
                          "motionNumber": 0,
                          "trackData": [
                                {
                                      "boneName": "hips",
                                      "fps": 30,
                                      "property": "position",
                                        "transformKeys": [
                                            {
                                                  "frame": 0,
                                                  "time": 0,
                                                  "value": [
                                                        0.06301826238632202,
                                                        0,
                                                        0.2913147509098053
                                                  ]
                                            }
                                      ]
                                  }
                          ]
                    }
            ],
            "workingtime": 20.022384643554688
    }
    • response.json

      {
              "result": [
                      {
                            "motionNumber": 0,
                            "trackData": [
                                  {
                                        "boneName": "hips",
                                        "fps": 30,
                                        "property": "position",
                                          "transformKeys": [
                                              {
                                                    "frame": 0,
                                                    "time": 0,
                                                    "value": [
                                                          0.06301826238632202,
                                                          0,
                                                          0.2913147509098053
                                                    ]
                                              }
                                        ]
                                    }
                            ]
                      }
              ],
              "workingtime": 20.022384643554688
      }
  • Motion capture dataは、“result”>”trackData”に含まれています。中には、24個のボーンからなるスケルトンをベースにした位置データ [[[(x, y, z)]]] や回転データ (x, y, z, w) が保存されています。ボーンは次のような構造になっています。

    ソースのボーン構造。

  • “result”>”trackData” の配列のうち、hipsは位置とクォータニオン型の値を持ち、hipsを除く23個のボーンはクォータニオン型の値のみを持つため、合計25個の要素が存在することになります。各要素は、boneName・fps・property・transformKeysから構成されます。

    "trackData": [
    {
    "boneName": "hips",
    "fps": 30,
    "property": "position",
    "transformKeys": [
    {
    "frame": 0,
    "time": 0,
    "value": [
    0.0630182 6238632202,
    0,
    0.2913147509098053
    ]
    },
    ...
    ]
    },
    {
    "boneName": "hips",
    "fps": 30,
    "property": "rotationQuaternion",
    "transformKeys": [ ...
    ]
    },
    {
    "boneName": "leftUpLeg",
    "fps": 30,
    "property": "rotationQuaternion",
    "transformKeys": [ ...
    ]
    },
    ...
    {
    "boneName": "rightHandIndex1",
    "fps": 30,
    "property": "rotationQuaternion",
    "transformKeys": [ ...
    ]
    },
    • boneName はモーションキャプチャデータを有するボーンの名称です。
    • fps は、動きを抽出した動画のフレームレート(fps)です。
    • property は、変換値のタイプを表します。
    • transformKeys は、各ポーズに対応した時間と値からなる配列です。各ポーズには、フレーム・時間・値の情報が存在します。
      • frame は、ポーズのフレーム順序を表します。
      • time は、ポーズをする時間を表します。単位は秒です。
      • value はポーズの変換値を表します。プロパティの種類が位置の場合は3つの値 (x, y, z)、rotationQuaternionの場合は4つの値 (x, y, z, w) を持ちます。

API output networking: iOS > Unity

  1. でビルドしたUnity画面を呼び出してください。

    class AppDelegate: UIResponder, UIApplicationDelegate {
    //method to call unity screen
    func initAndShowUnity() -> Void {
    if let framework = self.unityFrameworkLoad() {
    self.unityFramework = framework
    self.unityFramework?.setDataBundleId("com.unity3d.framework")
    self.unityFramework?.runEmbedded(withArgc: CommandLine.argc,
    argv: CommandLine.unsafeArgv,
    appLaunchOpts: [:])
    self.unityFramework?.showUnityWindow()
    print("windows: \\(application?.windows)")
    }
    }
    }

    class VideoPreviewViewController: UIViewController, AVAudioPlayerDelegate {
    private func pushSceneViewController(mocapJsonString: String?,
    duration: Double) {

    if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    appDelegate.initAndShowUnity()

    unityViewController = appDelegate.unityFramework?.appController()?.rootViewController

    viewModel.send(event: VideoPreviewEvent.setUnityNotification)
    viewModel.send(event: VideoPreviewEvent.handleUnityReady)
    }
    }
    }
  2. iOS上でPlask APIとのネットワーク接続が完了したら、応答データをjsonからStringに変換し、Unity script (unityFramework)]に送信します。

    class VideoPreviewViewModel: NSObject {
    private func handleUnityReady() {
    guard let jsonString = self.jsonString else { return } //API output converted to String

    unityManager.makeAnimationClip(jsonString: jsonString)
    }
    }

    class UnityManager {
    private var appDelegate: AppDelegate? {
    return UIApplication.shared.delegate as? AppDelegate
    }

    func makeAnimationClip(jsonString: String?) {
    appDelegate?.unityFramework?.sendMessageToGO(withName: "vangaurd_t_choonyung", //3d model
    functionName: "MakeAnimationClip", //method
    message: jsonString) //API output converted to String
    }
    }

Unity scriptを作成し、API outputを適用する

結果: Source code: Unity script

    1. API outputに含まれる24個のソースボーンと、それに対応する3Dモデルのターゲットボーンに対して、{<ターゲットボーン>:<ソースボーン>}の形式でbonesDictionaryを定義します。
    • ディクショナリーのキーを使用する3Dモデルのボーンに置き換えます。
    public class MocapAnimation : MonoBehaviour {

    Dictionary<string, string> bonesDictionary = new Dictionary<string, string>()
    {
    { "mixamorig:Hips", "hips" },
    { "mixamorig:LeftUpLeg", "leftUpLeg" },
    { "mixamorig:RightUpLeg", "rightUpLeg" },
    { "mixamorig:Spine", "spine" },
    { "mixamorig:LeftLeg", "leftLeg" },
    { "mixamorig:RightLeg", "rightLeg" },
    { "mixamorig:Spine1", "spine1" },
    { "mixamorig:LeftFoot", "leftFoot" },
    { "mixamorig:RightFoot", "rightFoot" },
    { "mixamorig:Spine2", "spine2" },
    { "mixamorig:LeftToeBase", "leftToeBase" },
    { "mixamorig:RightToeBase", "rightToeBase" },
    { "mixamorig:Neck", "neck" },
    { "mixamorig:LeftShoulder", "leftShoulder" },
    { "mixamorig:RightShoulder", "rightShoulder" },
    { "mixamorig:Head", "head" },
    { "mixamorig:LeftArm", "leftArm" },
    { "mixamorig:RightArm", "rightArm" },
    { "mixamorig:RightForeArm", "rightForeArm" },
    { "mixamorig:LeftForeArm", "leftForeArm" },
    { "mixamorig:LeftHand", "leftHand" },
    { "mixamorig:RightHand", "rightHand" },
    { "mixamorig:LeftHandMiddle1", "leftHandIndex1" },
    { "mixamorig:RightHandMiddle1", "rightHandIndex1" }
    };
    ...
    }
  1. JSON stringをデシリアライズした後、<string, trackData>の形式でディクショナリーを作成し、各データをパースして簡単に検索できるようにします。

    • string: “<source bone name>.<transform type>”

      • Source bone: API outputの24個のジョイントを表す名称
      • Transform type: :位置やrotationQuaternionなどのプロパティ
    • trackData : boneName, fps, property, transformKeys (custom defined)を含んだクラス

      • boneName: “<source bone>”
      • fps: 1秒当たりのフレーム数(フレームレート)
      • property: “<transform type>”
      • transformKeys: フレームデータ(任意で定義)
        • frame: フレーム数
        • times: キーフレームを含んだ時間の配列
        • values: 時間と一致する位置座標またはクォータニオン座標の値
    • <string, trackData> 例

      dictionary.txt

      // 1
      "hips.position"
      : trackData(boneName: "hips", fps: 30, property: "position", transformKeys: [(frame: 0, time: 0, values: [0.0, 1.0, 3.0]), ...])

      // 2
      "rightHandIndex1.rotationQuaternion"
      : mocapData(boneName: "rightHandIndex1", fps: 30, property: "rotationQuaternion", transformKeys: [(frame: 3, time: 0.166, values: [0.0, 15.0, 30.0, 45.0]), ...)
    [Serializable]
    public class MocapResult {
    public string id;
    public MocapData[] result;
    public float workingtime;
    }

    [Serializable]
    public class MocapData {
    public int motionNumber;
    public TrackData[] trackData;
    }

    [Serializable]
    public class TrackData {
    public string boneName;
    public int fps;
    public string property;
    public TransformKeys[] transformKeys;
    }

    [Serializable]
    public class TransformKeys {
    public int frame;
    public float time;
    public float[] value;
    }

    public class MocapAnimation : MonoBehaviour {

    // ...

    void MakeAnimationClip(string jsonString) {
    MocapResult mocapResult = JsonUtility.FromJson<MocapResult>(jsonString);

    Dictionary<String, TrackData> mocapDictionary = new Dictionary<string, TrackData>();

    MocapData result = mocapResult.result[0];

    for (int i = 0; i < result.trackData.Length; i++) {
    string key = result.trackData[i].boneName + "." + result.trackData[i].property;
    mocapDictionary.Add(key, result.trackData[i]);
    }

    if (transform.childCount > 0) {
    ApplyAnimationFromTransform(transform, mocapDictionary);
    }
    }

    // ...
    }
  2. <target bone>:<source bone>}に従い、API output内のbonesDictionary, trackDataのペアをターゲットボーンのAnimation componentに適用する関数に引き渡します。

    • アニメーションを実行するには、3DモデルにAnimation componentを含める必要があります。
    • 3Dモデルのすべてのボーンを調べて、ボーンがboneDictionary内に含まれているかを確認してください。
    • ボーンがboneDictionaryに存在する場合、アニメーションコンポーネントにtrackDataを適用するための関数に引き渡されます。
    public class MocapAnimation : MonoBehaviour {
    // ...

    private void ApplyAnimationFromTransform(Transform parent, Dictionary<String, TrackData> trackDataDictionary) {
    if (parent.childCount > 0) {
    foreach (Transform child in parent) {
    child.gameObject.AddComponent(typeof(Animation));

    string childName = child.gameObject.name;

    if (bonesDictionary.ContainsKey(childName) == true) {
    string boneName = bonesDictionary[childName];

    if (boneName == "hips") {
    SetObjectPositionAndQuaternionAnimation(child, trackDataDictionary);
    } else {
    TrackData trackData = trackDataDictionary[boneName + ".rotationQuaternion"];
    SetObjectQuaternionAnimation(child, trackData);
    }
    }

    ApplyAnimationFromTransform(child, trackDataDictionary);
    }
    }
    }

    //...
    }
  3. コンポーネントを作成し、適用するための関数

    • SetObjectPositionAndQuaternionAnimationは、ソースボーンがhipsに対応するターゲットボーンのアニメーションクリップを作成する際に使用する関数です。
    • SetObjectQuaternionAnimationは、ソースボーンがnot hipsである23個のジョイントに対応する、ターゲットボーンのアニメーションコンポーネントを作成するために使用される関数です。
    • 手順
      • 上記で作成した3Dモデルのアニメーションコンポーネントを呼び出します。
      • 位置座標 (x, y, z)、クォータニオン座標 (x, y, z, w) のプロパティごとにキーフレーム配列を作成し、API outputの値を順番に挿入していきます。
      • キーフレーム配列を反映したAnimationCurveを作成します
      • AnimationCurveを反映したAnimationClipを作成します

    • キーワード
      • Keyframe Array: 位置と値の数値を順番に並べた配列
      • AnimationCurve: AnimationClipを作成する際に、時間経過によって評価できるキーフレームの集合を保存
    public class MocapAnimation : MonoBehaviour {

    // ...

    //Create and apply position and quaternion animation of hips bones
    private void SetObjectPositionAndQuaternionAnimation(Transform transform, Dictionary<String, TrackData> trackDataDictionary) {

    TrackData positionData = trackDataDictionary["hips.position"];
    TrackData quaternionData = trackDataDictionary["hips.rotationQuaternion"];

    Animation anim = transform.GetComponent<Animation>();

    Keyframe[] positionKeysX = new Keyframe[positionData.transformKeys.Length];
    Keyframe[] positionKeysY = new Keyframe[positionData.transformKeys.Length];
    Keyframe[] positionKeysZ = new Keyframe[positionData.transformKeys.Length];

    Keyframe[] quaternionKeysX = new Keyframe[quaternionData.transformKeys.Length];
    Keyframe[] quaternionKeysY = new Keyframe[quaternionData.transformKeys.Length];
    Keyframe[] quaternionKeysZ = new Keyframe[quaternionData.transformKeys.Length];
    Keyframe[] quaternionKeysW = new Keyframe[quaternionData.transformKeys.Length];

    for (int i = 0; i < positionData.transformKeys.Length; i++) {
    TransformKeys positionTransformKey = positionData.transformKeys[i];
    float positionTime = positionTransformKey.time;

    positionKeysX[i] = new Keyframe(positionTime, positionTransformKey.value[0]);
    positionKeysY[i] = new Keyframe(positionTime, positionTransformKey.value[1]);
    positionKeysZ[i] = new Keyframe(positionTime, positionTransformKey.value[2]);

    TransformKeys quaternionTransformKey = quaternionData.transformKeys[i];
    float quaternionTime = quaternionTransformKey.time;

    quaternionKeysX[i] = new Keyframe(quaternionTime, quaternionTransformKey.value[0]);
    quaternionKeysY[i] = new Keyframe(quaternionTime, -quaternionTransformKey.value[1]);
    quaternionKeysZ[i] = new Keyframe(quaternionTime, -quaternionTransformKey.value[2]);
    quaternionKeysW[i] = new Keyframe(quaternionTime, quaternionTransformKey.value[3]);
    }

    var positionCurveX = new AnimationCurve(positionKeysX);
    var positionCurveY = new AnimationCurve(positionKeysY);
    var positionCurveZ = new AnimationCurve(positionKeysZ);

    var quaternionCurveX = new AnimationCurve(quaternionKeysX);
    var quaternionCurveY = new AnimationCurve(quaternionKeysY);
    var quaternionCurveZ = new AnimationCurve(quaternionKeysZ);
    var quaternionCurveW = new AnimationCurve(quaternionKeysW);

    AnimationClip animationClip = new AnimationClip();
    animationClip.legacy = true;

    animationClip.SetCurve("", typeof(Transform), "localPosition.x", positionCurveX);
    animationClip.SetCurve("", typeof(Transform), "localPosition.y", positionCurveY);
    animationClip.SetCurve("", typeof(Transform), "localPosition.z", positionCurveZ);

    animationClip.SetCurve("", typeof(Transform), "localRotation.x", quaternionCurveX);
    animationClip.SetCurve("", typeof(Transform), "localRotation.y", quaternionCurveY);
    animationClip.SetCurve("", typeof(Transform), "localRotation.z", quaternionCurveZ);
    animationClip.SetCurve("", typeof(Transform), "localRotation.w", quaternionCurveW);

    anim.AddClip(animationClip, "mocapAnimation");
    anim.Play("mocapAnimation");

    }

    //Create and apply bones quaternion animation except for hips
    private void SetObjectQuaternionAnimation(Transform child, TrackData trackData) {
    Animation anim = child.GetComponent<Animation>();

    int frameLength = trackData.transformKeys.Length;

    Keyframe[] keysX = new Keyframe[frameLength];
    Keyframe[] keysY = new Keyframe[frameLength];
    Keyframe[] keysZ = new Keyframe[frameLength];
    Keyframe[] keysW = new Keyframe[frameLength];

    for (int i = 0; i < frameLength; i++) {

    TransformKeys transformKey = trackData.transformKeys[i];
    float time = transformKey.time;

    keysX[i] = new Keyframe(time, transformKey.value[0]);
    keysY[i] = new Keyframe(time, -transformKey.value[1]);
    keysZ[i] = new Keyframe(time, -transformKey.value[2]);
    keysW[i] = new Keyframe(time, transformKey.value[3]);
    }

    var curveX = new AnimationCurve(keysX);
    var curveY = new AnimationCurve(keysY);
    var curveZ = new AnimationCurve(keysZ);
    var curveW = new AnimationCurve(keysW);

    AnimationClip animationClip = new AnimationClip();
    animationClip.legacy = true;

    animationClip.SetCurve("", typeof(Transform), "localRotation.x", curveX);
    animationClip.SetCurve("", typeof(Transform), "localRotation.y", curveY);
    animationClip.SetCurve("", typeof(Transform), "localRotation.z", curveZ);
    animationClip.SetCurve("", typeof(Transform), "localRotation.w", curveW);

    anim.AddClip(animationClip, "mocapAnimation");
    anim.Play("mocapAnimation");

    }
    }

ソースコード:Unity script

MocapAnimation.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;

[Serializable]
public class MocapResult {
public string id;
public MocapData[] result;
public float workingtime;
}

[Serializable]
public class MocapData {
public int motionNumber;
public TrackData[] trackData;
}

[Serializable]
public class TrackData {
public string boneName;
public int fps;
public string property;
public TransformKeys[] transformKeys;
}

[Serializable]
public class TransformKeys {
public int frame;
public float time;
public float[] value;
}

public class MocapAnimation : MonoBehaviour {

Dictionary<string, string> bonesDictionary = new Dictionary<string, string>()
{
{ "mixamorig:Hips", "hips" },
{ "mixamorig:LeftUpLeg", "leftUpLeg" },
{ "mixamorig:RightUpLeg", "rightUpLeg" },
{ "mixamorig:Spine", "spine" },
{ "mixamorig:LeftLeg", "leftLeg" },
{ "mixamorig:RightLeg", "rightLeg" },
{ "mixamorig:Spine1", "spine1" },
{ "mixamorig:LeftFoot", "leftFoot" },
{ "mixamorig:RightFoot", "rightFoot" },
{ "mixamorig:Spine2", "spine2" },
{ "mixamorig:LeftToeBase", "leftToeBase" },
{ "mixamorig:RightToeBase", "rightToeBase" },
{ "mixamorig:Neck", "neck" },
{ "mixamorig:LeftShoulder", "leftShoulder" },
{ "mixamorig:RightShoulder", "rightShoulder" },
{ "mixamorig:Head", "head" },
{ "mixamorig:LeftArm", "leftArm" },
{ "mixamorig:RightArm", "rightArm" },
{ "mixamorig:RightForeArm", "rightForeArm" },
{ "mixamorig:LeftForeArm", "leftForeArm" },
{ "mixamorig:LeftHand", "leftHand" },
{ "mixamorig:RightHand", "rightHand" },
{ "mixamorig:LeftHandMiddle1", "leftHandIndex1" },
{ "mixamorig:RightHandMiddle1", "rightHandIndex1" }
};

// iOS에서 받아온 jsonString -> deserialize
void MakeAnimationClip(string jsonString) {
MocapResult mocapResult = JsonUtility.FromJson<MocapResult>(jsonString);

Dictionary<String, TrackData> mocapDictionary = new Dictionary<string, TrackData>();

MocapData result = mocapResult.result[0];

for (int i = 0; i < result.trackData.Length; i++) {
string key = result.trackData[i].boneName + "." + result.trackData[i].property;
mocapDictionary.Add(key, result.trackData[i]);
}

if (transform.childCount > 0) {
ApplyAnimationFromTransform(transform, mocapDictionary);
}
}

// bones 비교
private void ApplyAnimationFromTransform(Transform parent, Dictionary<String, TrackData> trackDataDictionary) {
if (parent.childCount > 0) {
foreach (Transform child in parent) {
child.gameObject.AddComponent(typeof(Animation));

string childName = child.gameObject.name;

if (bonesDictionary.ContainsKey(childName) == true) {
string boneName = bonesDictionary[childName];

if (boneName == "hips") {
SetObjectPositionAndQuaternionAnimation(child, trackDataDictionary);
} else {
TrackData trackData = trackDataDictionary[boneName + ".rotationQuaternion"];
SetObjectQuaternionAnimation(child, trackData);
}
}

ApplyAnimationFromTransform(child, trackDataDictionary);
}
}
}

// hips의 position, rotation Animation 생성 및 적용
private void SetObjectPositionAndQuaternionAnimation(Transform transform, Dictionary<String, TrackData> trackDataDictionary) {

TrackData positionData = trackDataDictionary["hips.position"];
TrackData quaternionData = trackDataDictionary["hips.rotationQuaternion"];

Animation anim = transform.GetComponent<Animation>();

Keyframe[] positionKeysX = new Keyframe[positionData.transformKeys.Length];
Keyframe[] positionKeysY = new Keyframe[positionData.transformKeys.Length];
Keyframe[] positionKeysZ = new Keyframe[positionData.transformKeys.Length];

Keyframe[] quaternionKeysX = new Keyframe[quaternionData.transformKeys.Length];
Keyframe[] quaternionKeysY = new Keyframe[quaternionData.transformKeys.Length];
Keyframe[] quaternionKeysZ = new Keyframe[quaternionData.transformKeys.Length];
Keyframe[] quaternionKeysW = new Keyframe[quaternionData.transformKeys.Length];

for (int i = 0; i < positionData.transformKeys.Length; i++) {
TransformKeys positionTransformKey = positionData.transformKeys[i];
float positionTime = positionTransformKey.time;

positionKeysX[i] = new Keyframe(positionTime, positionTransformKey.value[0]);
positionKeysY[i] = new Keyframe(positionTime, positionTransformKey.value[1]);
positionKeysZ[i] = new Keyframe(positionTime, positionTransformKey.value[2]);

TransformKeys quaternionTransformKey = quaternionData.transformKeys[i];
float quaternionTime = quaternionTransformKey.time;

quaternionKeysX[i] = new Keyframe(quaternionTime, quaternionTransformKey.value[0]);
quaternionKeysY[i] = new Keyframe(quaternionTime, -quaternionTransformKey.value[1]);
quaternionKeysZ[i] = new Keyframe(quaternionTime, -quaternionTransformKey.value[2]);
quaternionKeysW[i] = new Keyframe(quaternionTime, quaternionTransformKey.value[3]);
}

var positionCurveX = new AnimationCurve(positionKeysX);
var positionCurveY = new AnimationCurve(positionKeysY);
var positionCurveZ = new AnimationCurve(positionKeysZ);

var quaternionCurveX = new AnimationCurve(quaternionKeysX);
var quaternionCurveY = new AnimationCurve(quaternionKeysY);
var quaternionCurveZ = new AnimationCurve(quaternionKeysZ);
var quaternionCurveW = new AnimationCurve(quaternionKeysW);

AnimationClip animationClip = new AnimationClip();
animationClip.legacy = true;

animationClip.SetCurve("", typeof(Transform), "localPosition.x", positionCurveX);
animationClip.SetCurve("", typeof(Transform), "localPosition.y", positionCurveY);
animationClip.SetCurve("", typeof(Transform), "localPosition.z", positionCurveZ);

animationClip.SetCurve("", typeof(Transform), "localRotation.x", quaternionCurveX);
animationClip.SetCurve("", typeof(Transform), "localRotation.y", quaternionCurveY);
animationClip.SetCurve("", typeof(Transform), "localRotation.z", quaternionCurveZ);
animationClip.SetCurve("", typeof(Transform), "localRotation.w", quaternionCurveW);

anim.AddClip(animationClip, "mocapAnimation");
anim.Play("mocapAnimation");

}

// hips 외의 bones rotation Animation 생성 및 적용
private void SetObjectQuaternionAnimation(Transform child, TrackData trackData) {
Animation anim = child.GetComponent<Animation>();

int frameLength = trackData.transformKeys.Length;

Keyframe[] keysX = new Keyframe[frameLength];
Keyframe[] keysY = new Keyframe[frameLength];
Keyframe[] keysZ = new Keyframe[frameLength];
Keyframe[] keysW = new Keyframe[frameLength];

for (int i = 0; i < frameLength; i++) {

TransformKeys transformKey = trackData.transformKeys[i];
float time = transformKey.time;

keysX[i] = new Keyframe(time, transformKey.value[0]);
keysY[i] = new Keyframe(time, -transformKey.value[1]);
keysZ[i] = new Keyframe(time, -transformKey.value[2]);
keysW[i] = new Keyframe(time, transformKey.value[3]);
}

var curveX = new AnimationCurve(keysX);
var curveY = new AnimationCurve(keysY);
var curveZ = new AnimationCurve(keysZ);
var curveW = new AnimationCurve(keysW);

AnimationClip animationClip = new AnimationClip();
animationClip.legacy = true;

animationClip.SetCurve("", typeof(Transform), "localRotation.x", curveX);
animationClip.SetCurve("", typeof(Transform), "localRotation.y", curveY);
animationClip.SetCurve("", typeof(Transform), "localRotation.z", curveZ);
animationClip.SetCurve("", typeof(Transform), "localRotation.w", curveW);

anim.AddClip(animationClip, "mocapAnimation");
anim.Play("mocapAnimation");

}
}