Unity2018.2のIAnimationJob

# 更新履歴

- 20180711 2018.2.0リリース。ちょっと追記
- 20180523 書いた

# 概要

- https://blogs.unity3d.com/jp/2018/05/07/unity-2018-2-beta-whats-new/

で、[Animation Jobs C# API](https://docs.google.com/document/d/12ljfvi8knahdElbRuIRijsx0U1T4CyXRQMdOcq6lXRA/edit#heading=h.asizguiiqte5)と紹介されているものです。

あと、[Unity 2017.1 feature spotlight: Playable API](https://blogs.unity3d.com/jp/2017/08/02/unity-2017-1-feature-spotlight-playable-api/)の後半の**Unity2017Europe**の動画より後ろに書いてある。

# 動かしてみた
まだExperimentalなので変わるだろうけど、とりあえず動かしてみた(Unity2018.2.0b4)。

![animjob.png](https://qiita-image-store.s3.amazonaws.com/0/59664/e2120149-6d46-ac71-8246-75da6bce3538.png)

ドキュメントに書いてあるものを少し改造して、TwoBoneIKの上流にAnimationClipのPlayableを連結したらうまくいった。

```
AnimationClip => TwoBoneIK => Animator
```

# ソース

```csharp

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;
using UnityEngine.Experimental.Animations;
using System;


public struct TwoBoneIKJob : IAnimationJob
{
    public float time;

    public TransformSceneHandle effector;

    public TransformStreamHandle top;
    public TransformStreamHandle mid;
    public TransformStreamHandle low;

    Vector3 topT;
    Vector3 midT;
    Vector3 lowT;

    Quaternion topQ;
    Quaternion midQ;
    Quaternion lowQ;


    public void Setup(Animator animator, Transform topX, Transform midX, Transform lowX, Transform effectorX)
    {
        top = animator.BindStreamTransform(topX);
        mid = animator.BindStreamTransform(midX);
        low = animator.BindStreamTransform(lowX);

        effector = animator.BindSceneTransform(effectorX);
    }

    public void ProcessRootMotion(AnimationStream stream)
    {
    }

    public void ProcessAnimation(AnimationStream stream)
    {
        Solve(stream, top, mid, low, effector.GetPosition(stream));
    }

    /// <summary>
    /// Returns the angle needed between v1 and v2 so that their extremities are
    /// spaced with a specific length.
    /// </summary>
    /// <returns>The angle between v1 and v2.</returns>
    /// <param name="aLen">The desired length between the extremities of v1 and v2.</param>
    /// <param name="v1">First triangle edge.</param>
    /// <param name="v2">Second triangle edge.</param>
    private static float TriangleAngle(float aLen, Vector3 v1, Vector3 v2)
    {
        float aLen1 = v1.magnitude;
        float aLen2 = v2.magnitude;
        float c = Mathf.Clamp((aLen1 * aLen1 + aLen2 * aLen2 - aLen * aLen) / (aLen1 * aLen2) / 2.0f, -1.0f, 1.0f);
        return Mathf.Acos(c);
    }

    private static void Solve(AnimationStream stream, TransformStreamHandle topHandle, TransformStreamHandle midHandle, TransformStreamHandle lowHandle, Vector3 effector)
    {
        Quaternion aRotation = topHandle.GetRotation(stream);
        Quaternion bRotation = midHandle.GetRotation(stream);
        Quaternion cRotation = lowHandle.GetRotation(stream);

        Vector3 aPosition = topHandle.GetPosition(stream);
        Vector3 bPosition = midHandle.GetPosition(stream);
        Vector3 cPosition = lowHandle.GetPosition(stream);

        Vector3 ab = bPosition - aPosition;
        Vector3 bc = cPosition - bPosition;
        Vector3 ac = cPosition - aPosition;
        Vector3 ad = effector - aPosition;

        float oldAbcAngle = TriangleAngle(ac.magnitude, ab, bc);
        float newAbcAngle = TriangleAngle(ad.magnitude, ab, bc);

        Vector3 axis = Vector3.Cross(ab, bc).normalized;
        float a = 0.5f * (oldAbcAngle - newAbcAngle);
        float sin = Mathf.Sin(a);
        float cos = Mathf.Cos(a);
        Quaternion q = new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos);

        Quaternion worldQ = q * bRotation;
        midHandle.SetRotation(stream, worldQ);

        aRotation = topHandle.GetRotation(stream);
        cPosition = lowHandle.GetPosition(stream);
        ac = cPosition - aPosition;
        Quaternion fromTo = Quaternion.FromToRotation(ac, ad);
        topHandle.SetRotation(stream, fromTo * aRotation);

        lowHandle.SetRotation(stream, cRotation);
    }
}


[Serializable]
struct TwoBoneIKPlayableSource
{
    public Transform effector;
    public Transform shoulder;
    public Transform elbow;
    public Transform hand;
    public AnimationScriptPlayable CreateSource(PlayableGraph graph, Animator animator)
    {
        var twoBoneIKJob = new TwoBoneIKJob();
        twoBoneIKJob.Setup(animator, shoulder, elbow, hand, effector);
        return AnimationScriptPlayable.Create(graph, twoBoneIKJob, 
1 // 入力口を1に変更。上流にPlayableを連結する
);
    }
}


public class TwoBoneIK : MonoBehaviour
{
    [SerializeField]
    TwoBoneIKPlayableSource Source;

    [SerializeField]
    AnimationClip m_clip;

    private void BuildGraph(Animator animator)
    {
        var output = AnimationPlayableOutput.Create(m_Graph, "output", animator);
        var source = Source.CreateSource(m_Graph, animator);
        output.SetSourcePlayable(source);

        var clipPlayable = AnimationClipPlayable.Create(m_Graph, m_clip);
        m_Graph.Connect(clipPlayable, 0, source, 0); // TowBoneIKの入力にclipPlayableを連結
    }

    PlayableGraph m_Graph;
    void OnEnable()
    {
        m_Graph = PlayableGraph.Create("TwoBoneIK");
        BuildGraph(GetComponent<Animator>());
        m_Graph.Play();
    }

    void OnDisable()
    {
        m_Graph.Destroy();
    }
}

```

# どんなことができそうか
ドキュメントには以下のような用途が示されている。

* 2-Bone IK
* Full body IK
* Custom mixers
* Tail or Pony Tail

既存のPlayableはグラフを流れてくるデータを操作する方法が無かったのでミキサー操作以外にすることが無いように思えたのだけど、IAnimationJob経由でAnimationStreamを操作してデータ生成・変更がきるようになった。PlayableAPI始まったのではないかと期待。

Googleで検索したらサンプルを発見しました(20180711)。

- https://github.com/Unity-Technologies/animation-jobs-samples