SplineHelpers.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using UnityEngine;
  2. namespace Cinemachine.Utility
  3. {
  4. /// <summary>
  5. /// A collection of utilities relating to Bezier splines
  6. /// </summary>
  7. public static class SplineHelpers
  8. {
  9. /// <summary>Compute the value of a 4-point 3-dimensional bezier spline</summary>
  10. /// <param name="t">How far along the spline (0...1)</param>
  11. /// <param name="p0">First point</param>
  12. /// <param name="p1">First tangent</param>
  13. /// <param name="p2">Second point</param>
  14. /// <param name="p3">Second tangent</param>
  15. /// <returns>The spline value at t</returns>
  16. public static Vector3 Bezier3(
  17. float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
  18. {
  19. t = Mathf.Clamp01(t);
  20. float d = 1f - t;
  21. return d * d * d * p0 + 3f * d * d * t * p1
  22. + 3f * d * t * t * p2 + t * t * t * p3;
  23. }
  24. /// <summary>Compute the tangent of a 4-point 3-dimensional bezier spline</summary>
  25. /// <param name="t">How far along the spline (0...1)</param>
  26. /// <param name="p0">First point</param>
  27. /// <param name="p1">First tangent</param>
  28. /// <param name="p2">Second point</param>
  29. /// <param name="p3">Second tangent</param>
  30. /// <returns>The spline tangent at t</returns>
  31. public static Vector3 BezierTangent3(
  32. float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
  33. {
  34. t = Mathf.Clamp01(t);
  35. return (-3f * p0 + 9f * p1 - 9f * p2 + 3f * p3) * (t * t)
  36. + (6f * p0 - 12f * p1 + 6f * p2) * t
  37. - 3f * p0 + 3f * p1;
  38. }
  39. /// <summary>Compute the weights for the tangent of a 4-point 3-dimensional bezier spline</summary>
  40. /// <param name="p0">First point</param>
  41. /// <param name="p1">First tangent</param>
  42. /// <param name="p2">Second point</param>
  43. /// <param name="p3">Second tangent</param>
  44. /// <param name="w0">First output weight</param>
  45. /// <param name="w1">Second output weight</param>
  46. /// <param name="w2">Third output weight</param>
  47. public static void BezierTangentWeights3(
  48. Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3,
  49. out Vector3 w0, out Vector3 w1, out Vector3 w2)
  50. {
  51. w0 = -3f * p0 + 9f * p1 - 9f * p2 + 3f * p3;
  52. w1 = 6f * p0 - 12f * p1 + 6f * p2;
  53. w2 = -3f * p0 + 3f * p1;
  54. }
  55. /// <summary>Compute the value of a 4-point 1-dimensional bezier spline</summary>
  56. /// <param name="t">How far along the spline (0...1)</param>
  57. /// <param name="p0">First point</param>
  58. /// <param name="p1">First tangent</param>
  59. /// <param name="p2">Second point</param>
  60. /// <param name="p3">Second tangent</param>
  61. /// <returns>The spline value at t</returns>
  62. public static float Bezier1(float t, float p0, float p1, float p2, float p3)
  63. {
  64. t = Mathf.Clamp01(t);
  65. float d = 1f - t;
  66. return d * d * d * p0 + 3f * d * d * t * p1
  67. + 3f * d * t * t * p2 + t * t * t * p3;
  68. }
  69. /// <summary>Compute the tangent of a 4-point 1-dimensional bezier spline</summary>
  70. /// <param name="t">How far along the spline (0...1)</param>
  71. /// <param name="p0">First point</param>
  72. /// <param name="p1">First tangent</param>
  73. /// <param name="p2">Second point</param>
  74. /// <param name="p3">Second tangent</param>
  75. /// <returns>The spline tangent at t</returns>
  76. public static float BezierTangent1(
  77. float t, float p0, float p1, float p2, float p3)
  78. {
  79. t = Mathf.Clamp01(t);
  80. return (-3f * p0 + 9f * p1 - 9f * p2 + 3f * p3) * t * t
  81. + (6f * p0 - 12f * p1 + 6f * p2) * t
  82. - 3f * p0 + 3f * p1;
  83. }
  84. /// <summary>
  85. /// Use the Thomas algorithm to compute smooth tangent values for a spline.
  86. /// Resultant tangents guarantee second-order smoothness of the curve.
  87. /// </summary>
  88. /// <param name="knot">The knots defining the curve</param>
  89. /// <param name="ctrl1">Output buffer for the first control points (1 per knot)</param>
  90. /// <param name="ctrl2">Output buffer for the second control points (1 per knot)</param>
  91. public static void ComputeSmoothControlPoints(
  92. ref Vector4[] knot, ref Vector4[] ctrl1, ref Vector4[] ctrl2)
  93. {
  94. int numPoints = knot.Length;
  95. if (numPoints <= 2)
  96. {
  97. if (numPoints == 2)
  98. {
  99. ctrl1[0] = Vector4.Lerp(knot[0], knot[1], 0.33333f);
  100. ctrl2[0] = Vector4.Lerp(knot[0], knot[1], 0.66666f);
  101. }
  102. else if (numPoints == 1)
  103. ctrl1[0] = ctrl2[0] = knot[0];
  104. return;
  105. }
  106. var a = new float[numPoints];
  107. var b = new float[numPoints];
  108. var c = new float[numPoints];
  109. var r = new float[numPoints];
  110. for (int axis = 0; axis < 4; ++axis)
  111. {
  112. int n = numPoints - 1;
  113. // Linear into the first segment
  114. a[0] = 0;
  115. b[0] = 2;
  116. c[0] = 1;
  117. r[0] = knot[0][axis] + 2 * knot[1][axis];
  118. // Internal segments
  119. for (int i = 1; i < n - 1; ++i)
  120. {
  121. a[i] = 1;
  122. b[i] = 4;
  123. c[i] = 1;
  124. r[i] = 4 * knot[i][axis] + 2 * knot[i+1][axis];
  125. }
  126. // Linear out of the last segment
  127. a[n - 1] = 2;
  128. b[n - 1] = 7;
  129. c[n - 1] = 0;
  130. r[n - 1] = 8 * knot[n - 1][axis] + knot[n][axis];
  131. // Solve with Thomas algorithm
  132. for (int i = 1; i < n; ++i)
  133. {
  134. float m = a[i] / b[i-1];
  135. b[i] = b[i] - m * c[i-1];
  136. r[i] = r[i] - m * r[i-1];
  137. }
  138. // Compute ctrl1
  139. ctrl1[n-1][axis] = r[n-1] / b[n-1];
  140. for (int i = n - 2; i >= 0; --i)
  141. ctrl1[i][axis] = (r[i] - c[i] * ctrl1[i + 1][axis]) / b[i];
  142. // Compute ctrl2 from ctrl1
  143. for (int i = 0; i < n; i++)
  144. ctrl2[i][axis] = 2 * knot[i + 1][axis] - ctrl1[i + 1][axis];
  145. ctrl2[n - 1][axis] = 0.5f * (knot[n][axis] + ctrl1[n - 1][axis]);
  146. }
  147. }
  148. /// <summary>
  149. /// Use the Thomas algorithm to compute smooth tangent values for a spline.
  150. /// This method will assume that the spline is looped (i.e. that the last knot is followed by the first).
  151. /// Resultant tangents guarantee second-order smoothness of the curve.
  152. /// </summary>
  153. /// <param name="knot">The knots defining the curve</param>
  154. /// <param name="ctrl1">Output buffer for the first control points (1 per knot)</param>
  155. /// <param name="ctrl2">Output buffer for the second control points (1 per knot)</param>
  156. public static void ComputeSmoothControlPointsLooped(
  157. ref Vector4[] knot, ref Vector4[] ctrl1, ref Vector4[] ctrl2)
  158. {
  159. int numPoints = knot.Length;
  160. if (numPoints < 2)
  161. {
  162. if (numPoints == 1)
  163. ctrl1[0] = ctrl2[0] = knot[0];
  164. return;
  165. }
  166. int margin = Mathf.Min(4, numPoints-1);
  167. Vector4[] knotLooped = new Vector4[numPoints + 2 * margin];
  168. Vector4[] ctrl1Looped = new Vector4[numPoints + 2 * margin];
  169. Vector4[] ctrl2Looped = new Vector4[numPoints + 2 * margin];
  170. for (int i = 0; i < margin; ++i)
  171. {
  172. knotLooped[i] = knot[numPoints-(margin-i)];
  173. knotLooped[numPoints+margin+i] = knot[i];
  174. }
  175. for (int i = 0; i < numPoints; ++i)
  176. knotLooped[i + margin] = knot[i];
  177. ComputeSmoothControlPoints(ref knotLooped, ref ctrl1Looped, ref ctrl2Looped);
  178. for (int i = 0; i < numPoints; ++i)
  179. {
  180. ctrl1[i] = ctrl1Looped[i + margin];
  181. ctrl2[i] = ctrl2Looped[i + margin];
  182. }
  183. }
  184. }
  185. }