| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 | using System.Collections.Generic;using Unity.Burst;using Unity.Collections;using Unity.Collections.LowLevel.Unsafe;using Unity.Mathematics;using UnityEngine;namespace LitMotion.Collections{    internal readonly struct KeyframeComparer : IComparer<Keyframe>    {        public readonly int Compare(Keyframe keyframe1, Keyframe keyframe2) => keyframe1.time.CompareTo(keyframe2.time);    }    [BurstCompile]    internal unsafe static class NativeAnimationCurveHelper    {        static readonly float4x4 curveMatrix = new        (            1, 0, 0, 0,            -3, 3, 0, 0,            3, -6, 3, 0,            -1, 3, -3, 1        );        static readonly float3x3 curveMatrixPrime = new        (            1, 0, 0,            -2, 2, 0,            1, -2, 1        );        [BurstCompile]        public static float Evaluate(Keyframe* ptr, int length, WrapMode preWrapMode, WrapMode postWrapMode, float time)        {            time = WrapTime(ptr, length, preWrapMode, postWrapMode, time);            Keyframe keyframe = default;            keyframe.time = time;            int index = GetInsertionIndexForSortedArray(ptr, length, ref keyframe);            if (index == 0)            {                index++;            }            else if (index == length)            {                index = length - 1;            }            keyframe = ptr[index - 1];            Keyframe nextKeyframe = ptr[index];            return Evaluate(time, ref keyframe, ref nextKeyframe);        }        static int GetInsertionIndexForSortedArray(Keyframe* ptr, int length, ref Keyframe keyframe)        {            var list = new UnsafeList<Keyframe>(ptr, length);            var index = list.BinarySearch(keyframe, default(KeyframeComparer));            if (index < 0) index = ~index;            return index;        }        static float WrapTime(Keyframe* ptr, int length, WrapMode preWrapMode, WrapMode postWrapMode, float time)        {            float lastKeyTime = ptr[length - 1].time;            if (time < 0f)            {                switch (preWrapMode)                {                    case WrapMode.Default:                    case WrapMode.ClampForever:                    case WrapMode.Once:                        time = 0f;                        break;                    case WrapMode.Loop:                        time = time % lastKeyTime - ptr[0].time;                        break;                    case WrapMode.PingPong:                        time = Mathf.PingPong(time, lastKeyTime - ptr[0].time);                        break;                }            }            else if (time > lastKeyTime)            {                switch (postWrapMode)                {                    case WrapMode.Default:                    case WrapMode.ClampForever:                        time = lastKeyTime;                        break;                    case WrapMode.Once:                        time = 0f;                        break;                    case WrapMode.Loop:                        time = time % lastKeyTime - ptr[0].time;                        break;                    case WrapMode.PingPong:                        time = Mathf.PingPong(time, lastKeyTime - ptr[0].time);                        break;                }            }            return time;        }        static float Evaluate(float time, ref Keyframe keyframe, ref Keyframe nextKeyframe)        {            if (!math.isfinite(keyframe.outTangent) || !math.isfinite(nextKeyframe.inTangent))            {                return keyframe.value;            }            float timeDiff = nextKeyframe.time - keyframe.time;            float t = (time - keyframe.time) / timeDiff;            float outWeight = (int)keyframe.weightedMode >= (int)WeightedMode.Out ? keyframe.outWeight : 1f / 3f;            float inWeight = (int)nextKeyframe.weightedMode >= (int)WeightedMode.In ? nextKeyframe.inWeight : 1f / 3f;            float tBottom = 0, tTop = 1;            float diff = float.MaxValue;            float4 xCoords = new(keyframe.time, keyframe.time + outWeight * timeDiff, nextKeyframe.time - inWeight * timeDiff, nextKeyframe.time);            float4 curveXCoords = math.mul(curveMatrix, xCoords);            GetTWithNewtonMethod(time, in xCoords, in curveXCoords, ref t, ref tBottom, ref tTop, ref diff);            GetTWithBisectionMethod(time, in curveXCoords, ref t, ref tBottom, ref tTop, ref diff);            float4 yCoords = new(keyframe.value, keyframe.value + outWeight * keyframe.outTangent * timeDiff, nextKeyframe.value - inWeight * nextKeyframe.inTangent * timeDiff, nextKeyframe.value);            float4 curveYCoords = math.mul(curveMatrix, yCoords);            return CubicBezier(in curveYCoords, t);        }        static float CubicBezier(in float4 curveMatrix, float t)        {            float tt = t * t;            float4 powerSeries = new(1, t, tt, tt * t);            return math.dot(powerSeries, curveMatrix);        }        static float CubicBezier(in float3 curveMatrix, float t)        {            float3 powerSeries = new(1, t, t * t);            return math.dot(powerSeries, curveMatrix);        }        static float DeCasteljauBezier(int degree, float4 coords, float t)        {            float one_t = 1 - t;            for (int k = 1; k <= degree; k++)            {                for (int i = 0; i <= (degree - k); i++)                {                    coords[i] = one_t * coords[i] + t * coords[i + 1];                }            }            return coords[0];        }        static void GetTWithBisectionMethod(float time, in float4 curveXCoords, ref float t, ref float tBottom, ref float tTop, ref float diff)        {            const float accuracy = 0.0000001f;            const int maxIterationCount = 20;            int iterationCount = 0;            while (diff > accuracy && iterationCount < maxIterationCount)            {                iterationCount++;                t = (tTop + tBottom) * 0.5f;                float x = CubicBezier(in curveXCoords, t);                diff = math.abs(x - time);                UpdateTLimits(x, time, t, ref tBottom, ref tTop);            }        }        static void GetTWithNewtonMethod(float time, in float4 xCoords, in float4 curveXCoords, ref float t, ref float tBottom, ref float tTop, ref float diff)        {            const float accuracy = 0.0000001f;            const int maxIterationCount = 20;            int iterationCount = 0;            float4 primeCoords = default;            for (int i = 0; i < 3; i++)            {                primeCoords[i] = (xCoords[i + 1] - xCoords[i]) * 3;            }            float4 primePrimeCoords = default;            for (int i = 0; i < 2; i++)            {                primePrimeCoords[i] = (primeCoords[i + 1] - primeCoords[i]) * 2;            }            float3 curvePrimeCoords = math.mul(curveMatrixPrime, primeCoords.xyz);            while (diff > accuracy && iterationCount < maxIterationCount)            {                iterationCount++;                float x;                float newT = UseNewtonMethod(curveXCoords, time, t, curvePrimeCoords, primePrimeCoords, out x);                float newDiff = math.abs(x - time);                if (newT < 0 || newT > 1 || newDiff > diff)                {                    break;                }                diff = newDiff;                UpdateTLimits(x, time, t, ref tBottom, ref tTop);                t = newT;            }        }        static float UseNewtonMethod(float4 curveCoords, float coord, float t, float3 curvePrimeCoords, float4 primePrimeCoords, out float coordAtT)        {            coordAtT = CubicBezier(in curveCoords, t);            float coordPrimeAtT = CubicBezier(in curvePrimeCoords, t);            float coordPrimePrimeAtT = DeCasteljauBezier(1, primePrimeCoords, t);            float coordAtTMinusCoord = coordAtT - coord;            float fAtT = coordAtTMinusCoord * coordPrimeAtT;            float fPrimeAtT = coordAtTMinusCoord * coordPrimePrimeAtT + coordPrimeAtT * coordPrimeAtT;            return t - (fAtT / fPrimeAtT);        }        static void UpdateTLimits(float x, float time, float t, ref float tBottom, ref float tTop)        {            if (x > time)            {                tTop = math.clamp(t, tBottom, tTop);            }            else            {                tBottom = math.clamp(t, tBottom, tTop);            }        }    }}
 |