CinemachineSmoothPath.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using UnityEngine;
  2. using System;
  3. using Cinemachine.Utility;
  4. namespace Cinemachine
  5. {
  6. /// <summary>Defines a world-space path, consisting of an array of waypoints,
  7. /// each of which has position and roll settings. Bezier interpolation
  8. /// is performed between the waypoints, to get a smooth and continuous path.
  9. /// The path will pass through all waypoints, and (unlike CinemachinePath) first
  10. /// and second order continuity is guaranteed</summary>
  11. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  12. [AddComponentMenu("Cinemachine/CinemachineSmoothPath")]
  13. [SaveDuringPlay]
  14. [DisallowMultipleComponent]
  15. [HelpURL(Documentation.BaseURL + "manual/CinemachineSmoothPath.html")]
  16. public class CinemachineSmoothPath : CinemachinePathBase
  17. {
  18. /// <summary>If checked, then the path ends are joined to form a continuous loop</summary>
  19. [Tooltip("If checked, then the path ends are joined to form a continuous loop.")]
  20. public bool m_Looped;
  21. /// <summary>A waypoint along the path</summary>
  22. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  23. [Serializable] public struct Waypoint
  24. {
  25. /// <summary>Position in path-local space</summary>
  26. [Tooltip("Position in path-local space")]
  27. public Vector3 position;
  28. /// <summary>Defines the roll of the path at this waypoint.
  29. /// The other orientation axes are inferred from the tangent and world up.</summary>
  30. [Tooltip("Defines the roll of the path at this waypoint. The other orientation axes are inferred from the tangent and world up.")]
  31. public float roll;
  32. /// <summary>Representation as Vector4</summary>
  33. internal Vector4 AsVector4
  34. {
  35. get { return new Vector4(position.x, position.y, position.z, roll); }
  36. }
  37. internal static Waypoint FromVector4(Vector4 v)
  38. {
  39. Waypoint wp = new Waypoint();
  40. wp.position = new Vector3(v[0], v[1], v[2]);
  41. wp.roll = v[3];
  42. return wp;
  43. }
  44. }
  45. /// <summary>The waypoints that define the path.
  46. /// They will be interpolated using a bezier curve</summary>
  47. [Tooltip("The waypoints that define the path. They will be interpolated using a bezier curve.")]
  48. public Waypoint[] m_Waypoints = Array.Empty<Waypoint>();
  49. /// <summary>The minimum value for the path position</summary>
  50. public override float MinPos => 0;
  51. /// <summary>The maximum value for the path position</summary>
  52. public override float MaxPos
  53. {
  54. get
  55. {
  56. int count = m_Waypoints.Length - 1;
  57. if (count < 1)
  58. return 0;
  59. return m_Looped ? count + 1 : count;
  60. }
  61. }
  62. /// <summary>True if the path ends are joined to form a continuous loop</summary>
  63. public override bool Looped => m_Looped;
  64. /// <summary>When calculating the distance cache, sample the path this many
  65. /// times between points</summary>
  66. public override int DistanceCacheSampleStepsPerSegment => m_Resolution;
  67. private void OnValidate() { InvalidateDistanceCache(); }
  68. private void Reset()
  69. {
  70. m_Looped = false;
  71. m_Waypoints = new Waypoint[2]
  72. {
  73. new Waypoint { position = new Vector3(0, 0, -5) },
  74. new Waypoint { position = new Vector3(0, 0, 5) }
  75. };
  76. m_Appearance = new Appearance();
  77. InvalidateDistanceCache();
  78. }
  79. /// <summary>Call this if the path changes in such a way as to affect distances
  80. /// or other cached path elements</summary>
  81. public override void InvalidateDistanceCache()
  82. {
  83. base.InvalidateDistanceCache();
  84. m_ControlPoints1 = null;
  85. m_ControlPoints2 = null;
  86. }
  87. internal Waypoint[] m_ControlPoints1;
  88. internal Waypoint[] m_ControlPoints2;
  89. bool m_IsLoopedCache;
  90. internal void UpdateControlPoints()
  91. {
  92. int numPoints = (m_Waypoints == null) ? 0 : m_Waypoints.Length;
  93. if (numPoints > 1
  94. && (Looped != m_IsLoopedCache
  95. || m_ControlPoints1 == null || m_ControlPoints1.Length != numPoints
  96. || m_ControlPoints2 == null || m_ControlPoints2.Length != numPoints))
  97. {
  98. Vector4[] p1 = new Vector4[numPoints];
  99. Vector4[] p2 = new Vector4[numPoints];
  100. Vector4[] K = new Vector4[numPoints];
  101. for (int i = 0; i < numPoints; ++i)
  102. K[i] = m_Waypoints[i].AsVector4;
  103. if (Looped)
  104. SplineHelpers.ComputeSmoothControlPointsLooped(ref K, ref p1, ref p2);
  105. else
  106. SplineHelpers.ComputeSmoothControlPoints(ref K, ref p1, ref p2);
  107. m_ControlPoints1 = new Waypoint[numPoints];
  108. m_ControlPoints2 = new Waypoint[numPoints];
  109. for (int i = 0; i < numPoints; ++i)
  110. {
  111. m_ControlPoints1[i] = Waypoint.FromVector4(p1[i]);
  112. m_ControlPoints2[i] = Waypoint.FromVector4(p2[i]);
  113. }
  114. m_IsLoopedCache = Looped;
  115. }
  116. }
  117. /// <summary>Returns standardized position</summary>
  118. float GetBoundingIndices(float pos, out int indexA, out int indexB)
  119. {
  120. pos = StandardizePos(pos);
  121. int numWaypoints = m_Waypoints.Length;
  122. if (numWaypoints < 2)
  123. indexA = indexB = 0;
  124. else
  125. {
  126. indexA = Mathf.FloorToInt(pos);
  127. if (indexA >= numWaypoints)
  128. {
  129. // Only true if looped
  130. pos -= MaxPos;
  131. indexA = 0;
  132. }
  133. indexB = indexA + 1;
  134. if (indexB == numWaypoints)
  135. {
  136. if (Looped)
  137. indexB = 0;
  138. else
  139. {
  140. --indexB;
  141. --indexA;
  142. }
  143. }
  144. }
  145. return pos;
  146. }
  147. /// <summary>Get a space position of a point along the path</summary>
  148. /// <param name="pos">Position along the path. Need not be normalized.</param>
  149. /// <returns>Local position of the point along at path at pos</returns>
  150. public override Vector3 EvaluateLocalPosition(float pos)
  151. {
  152. var result = Vector3.zero;
  153. if (m_Waypoints.Length > 0)
  154. {
  155. UpdateControlPoints();
  156. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  157. if (indexA == indexB)
  158. result = m_Waypoints[indexA].position;
  159. else
  160. result = SplineHelpers.Bezier3(pos - indexA,
  161. m_Waypoints[indexA].position, m_ControlPoints1[indexA].position,
  162. m_ControlPoints2[indexA].position, m_Waypoints[indexB].position);
  163. }
  164. return result;
  165. }
  166. /// <summary>Get the tangent of the curve at a point along the path.</summary>
  167. /// <param name="pos">Position along the path. Need not be normalized.</param>
  168. /// <returns>Local direction of the path tangent.
  169. /// Length of the vector represents the tangent strength</returns>
  170. public override Vector3 EvaluateLocalTangent(float pos)
  171. {
  172. var result = Vector3.forward;
  173. if (m_Waypoints.Length > 1)
  174. {
  175. UpdateControlPoints();
  176. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  177. if (!Looped && indexA == m_Waypoints.Length - 1)
  178. --indexA;
  179. result = SplineHelpers.BezierTangent3(pos - indexA,
  180. m_Waypoints[indexA].position, m_ControlPoints1[indexA].position,
  181. m_ControlPoints2[indexA].position, m_Waypoints[indexB].position);
  182. }
  183. return result;
  184. }
  185. /// <summary>Get the orientation the curve at a point along the path.</summary>
  186. /// <param name="pos">Position along the path. Need not be normalized.</param>
  187. /// <returns>Local orientation of the path, as defined by tangent, up, and roll.</returns>
  188. public override Quaternion EvaluateLocalOrientation(float pos)
  189. {
  190. var result = Quaternion.identity;
  191. if (m_Waypoints.Length > 0)
  192. {
  193. float roll;
  194. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  195. if (indexA == indexB)
  196. roll = m_Waypoints[indexA].roll;
  197. else
  198. {
  199. UpdateControlPoints();
  200. roll = SplineHelpers.Bezier1(pos - indexA,
  201. m_Waypoints[indexA].roll, m_ControlPoints1[indexA].roll,
  202. m_ControlPoints2[indexA].roll, m_Waypoints[indexB].roll);
  203. }
  204. Vector3 fwd = EvaluateLocalTangent(pos);
  205. if (!fwd.AlmostZero())
  206. result = Quaternion.LookRotation(fwd) * RollAroundForward(roll);
  207. }
  208. return result;
  209. }
  210. // same as Quaternion.AngleAxis(roll, Vector3.forward), just simplified
  211. static Quaternion RollAroundForward(float angle)
  212. {
  213. float halfAngle = angle * 0.5F * Mathf.Deg2Rad;
  214. return new Quaternion(0, 0, Mathf.Sin(halfAngle), Mathf.Cos(halfAngle));
  215. }
  216. }
  217. }