NativeAnimationCurveHelper.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. using System.Collections.Generic;
  2. using Unity.Burst;
  3. using Unity.Collections;
  4. using Unity.Collections.LowLevel.Unsafe;
  5. using Unity.Mathematics;
  6. using UnityEngine;
  7. namespace LitMotion.Collections
  8. {
  9. internal readonly struct KeyframeComparer : IComparer<Keyframe>
  10. {
  11. public readonly int Compare(Keyframe keyframe1, Keyframe keyframe2) => keyframe1.time.CompareTo(keyframe2.time);
  12. }
  13. [BurstCompile]
  14. internal unsafe static class NativeAnimationCurveHelper
  15. {
  16. static readonly float4x4 curveMatrix = new
  17. (
  18. 1, 0, 0, 0,
  19. -3, 3, 0, 0,
  20. 3, -6, 3, 0,
  21. -1, 3, -3, 1
  22. );
  23. static readonly float3x3 curveMatrixPrime = new
  24. (
  25. 1, 0, 0,
  26. -2, 2, 0,
  27. 1, -2, 1
  28. );
  29. [BurstCompile]
  30. public static float Evaluate(Keyframe* ptr, int length, WrapMode preWrapMode, WrapMode postWrapMode, float time)
  31. {
  32. time = WrapTime(ptr, length, preWrapMode, postWrapMode, time);
  33. Keyframe keyframe = default;
  34. keyframe.time = time;
  35. int index = GetInsertionIndexForSortedArray(ptr, length, ref keyframe);
  36. if (index == 0)
  37. {
  38. index++;
  39. }
  40. else if (index == length)
  41. {
  42. index = length - 1;
  43. }
  44. keyframe = ptr[index - 1];
  45. Keyframe nextKeyframe = ptr[index];
  46. return Evaluate(time, ref keyframe, ref nextKeyframe);
  47. }
  48. static int GetInsertionIndexForSortedArray(Keyframe* ptr, int length, ref Keyframe keyframe)
  49. {
  50. var list = new UnsafeList<Keyframe>(ptr, length);
  51. var index = list.BinarySearch(keyframe, default(KeyframeComparer));
  52. if (index < 0) index = ~index;
  53. return index;
  54. }
  55. static float WrapTime(Keyframe* ptr, int length, WrapMode preWrapMode, WrapMode postWrapMode, float time)
  56. {
  57. float lastKeyTime = ptr[length - 1].time;
  58. if (time < 0f)
  59. {
  60. switch (preWrapMode)
  61. {
  62. case WrapMode.Default:
  63. case WrapMode.ClampForever:
  64. case WrapMode.Once:
  65. time = 0f;
  66. break;
  67. case WrapMode.Loop:
  68. time = time % lastKeyTime - ptr[0].time;
  69. break;
  70. case WrapMode.PingPong:
  71. time = Mathf.PingPong(time, lastKeyTime - ptr[0].time);
  72. break;
  73. }
  74. }
  75. else if (time > lastKeyTime)
  76. {
  77. switch (postWrapMode)
  78. {
  79. case WrapMode.Default:
  80. case WrapMode.ClampForever:
  81. time = lastKeyTime;
  82. break;
  83. case WrapMode.Once:
  84. time = 0f;
  85. break;
  86. case WrapMode.Loop:
  87. time = time % lastKeyTime - ptr[0].time;
  88. break;
  89. case WrapMode.PingPong:
  90. time = Mathf.PingPong(time, lastKeyTime - ptr[0].time);
  91. break;
  92. }
  93. }
  94. return time;
  95. }
  96. static float Evaluate(float time, ref Keyframe keyframe, ref Keyframe nextKeyframe)
  97. {
  98. if (!math.isfinite(keyframe.outTangent) || !math.isfinite(nextKeyframe.inTangent))
  99. {
  100. return keyframe.value;
  101. }
  102. float timeDiff = nextKeyframe.time - keyframe.time;
  103. float t = (time - keyframe.time) / timeDiff;
  104. float outWeight = (int)keyframe.weightedMode >= (int)WeightedMode.Out ? keyframe.outWeight : 1f / 3f;
  105. float inWeight = (int)nextKeyframe.weightedMode >= (int)WeightedMode.In ? nextKeyframe.inWeight : 1f / 3f;
  106. float tBottom = 0, tTop = 1;
  107. float diff = float.MaxValue;
  108. float4 xCoords = new(keyframe.time, keyframe.time + outWeight * timeDiff, nextKeyframe.time - inWeight * timeDiff, nextKeyframe.time);
  109. float4 curveXCoords = math.mul(curveMatrix, xCoords);
  110. GetTWithNewtonMethod(time, in xCoords, in curveXCoords, ref t, ref tBottom, ref tTop, ref diff);
  111. GetTWithBisectionMethod(time, in curveXCoords, ref t, ref tBottom, ref tTop, ref diff);
  112. float4 yCoords = new(keyframe.value, keyframe.value + outWeight * keyframe.outTangent * timeDiff, nextKeyframe.value - inWeight * nextKeyframe.inTangent * timeDiff, nextKeyframe.value);
  113. float4 curveYCoords = math.mul(curveMatrix, yCoords);
  114. return CubicBezier(in curveYCoords, t);
  115. }
  116. static float CubicBezier(in float4 curveMatrix, float t)
  117. {
  118. float tt = t * t;
  119. float4 powerSeries = new(1, t, tt, tt * t);
  120. return math.dot(powerSeries, curveMatrix);
  121. }
  122. static float CubicBezier(in float3 curveMatrix, float t)
  123. {
  124. float3 powerSeries = new(1, t, t * t);
  125. return math.dot(powerSeries, curveMatrix);
  126. }
  127. static float DeCasteljauBezier(int degree, float4 coords, float t)
  128. {
  129. float one_t = 1 - t;
  130. for (int k = 1; k <= degree; k++)
  131. {
  132. for (int i = 0; i <= (degree - k); i++)
  133. {
  134. coords[i] = one_t * coords[i] + t * coords[i + 1];
  135. }
  136. }
  137. return coords[0];
  138. }
  139. static void GetTWithBisectionMethod(float time, in float4 curveXCoords, ref float t, ref float tBottom, ref float tTop, ref float diff)
  140. {
  141. const float accuracy = 0.0000001f;
  142. const int maxIterationCount = 20;
  143. int iterationCount = 0;
  144. while (diff > accuracy && iterationCount < maxIterationCount)
  145. {
  146. iterationCount++;
  147. t = (tTop + tBottom) * 0.5f;
  148. float x = CubicBezier(in curveXCoords, t);
  149. diff = math.abs(x - time);
  150. UpdateTLimits(x, time, t, ref tBottom, ref tTop);
  151. }
  152. }
  153. static void GetTWithNewtonMethod(float time, in float4 xCoords, in float4 curveXCoords, ref float t, ref float tBottom, ref float tTop, ref float diff)
  154. {
  155. const float accuracy = 0.0000001f;
  156. const int maxIterationCount = 20;
  157. int iterationCount = 0;
  158. float4 primeCoords = default;
  159. for (int i = 0; i < 3; i++)
  160. {
  161. primeCoords[i] = (xCoords[i + 1] - xCoords[i]) * 3;
  162. }
  163. float4 primePrimeCoords = default;
  164. for (int i = 0; i < 2; i++)
  165. {
  166. primePrimeCoords[i] = (primeCoords[i + 1] - primeCoords[i]) * 2;
  167. }
  168. float3 curvePrimeCoords = math.mul(curveMatrixPrime, primeCoords.xyz);
  169. while (diff > accuracy && iterationCount < maxIterationCount)
  170. {
  171. iterationCount++;
  172. float x;
  173. float newT = UseNewtonMethod(curveXCoords, time, t, curvePrimeCoords, primePrimeCoords, out x);
  174. float newDiff = math.abs(x - time);
  175. if (newT < 0 || newT > 1 || newDiff > diff)
  176. {
  177. break;
  178. }
  179. diff = newDiff;
  180. UpdateTLimits(x, time, t, ref tBottom, ref tTop);
  181. t = newT;
  182. }
  183. }
  184. static float UseNewtonMethod(float4 curveCoords, float coord, float t, float3 curvePrimeCoords, float4 primePrimeCoords, out float coordAtT)
  185. {
  186. coordAtT = CubicBezier(in curveCoords, t);
  187. float coordPrimeAtT = CubicBezier(in curvePrimeCoords, t);
  188. float coordPrimePrimeAtT = DeCasteljauBezier(1, primePrimeCoords, t);
  189. float coordAtTMinusCoord = coordAtT - coord;
  190. float fAtT = coordAtTMinusCoord * coordPrimeAtT;
  191. float fPrimeAtT = coordAtTMinusCoord * coordPrimePrimeAtT + coordPrimeAtT * coordPrimeAtT;
  192. return t - (fAtT / fPrimeAtT);
  193. }
  194. static void UpdateTLimits(float x, float time, float t, ref float tBottom, ref float tTop)
  195. {
  196. if (x > time)
  197. {
  198. tTop = math.clamp(t, tBottom, tTop);
  199. }
  200. else
  201. {
  202. tBottom = math.clamp(t, tBottom, tTop);
  203. }
  204. }
  205. }
  206. }