CinemachineSmoothPath.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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 = new Waypoint[0];
  49. /// <summary>The minimum value for the path position</summary>
  50. public override float MinPos { get { return 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 { get { return m_Looped; } }
  64. /// <summary>When calculating the distance cache, sample the path this many
  65. /// times between points</summary>
  66. public override int DistanceCacheSampleStepsPerSegment { get { return 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. Waypoint[] m_ControlPoints1;
  88. Waypoint[] m_ControlPoints2;
  89. bool m_IsLoopedCache;
  90. 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 worldspace position of a point along the path</summary>
  148. /// <param name="pos">Position along the path. Need not be normalized.</param>
  149. /// <returns>World-space position of the point along at path at pos</returns>
  150. public override Vector3 EvaluatePosition(float pos)
  151. {
  152. Vector3 result = Vector3.zero;
  153. if (m_Waypoints.Length > 0)
  154. {
  155. UpdateControlPoints();
  156. int indexA, indexB;
  157. pos = GetBoundingIndices(pos, out indexA, out indexB);
  158. if (indexA == indexB)
  159. result = m_Waypoints[indexA].position;
  160. else
  161. result = SplineHelpers.Bezier3(pos - indexA,
  162. m_Waypoints[indexA].position, m_ControlPoints1[indexA].position,
  163. m_ControlPoints2[indexA].position, m_Waypoints[indexB].position);
  164. }
  165. return transform.TransformPoint(result);
  166. }
  167. /// <summary>Get the tangent of the curve at a point along the path.</summary>
  168. /// <param name="pos">Position along the path. Need not be normalized.</param>
  169. /// <returns>World-space direction of the path tangent.
  170. /// Length of the vector represents the tangent strength</returns>
  171. public override Vector3 EvaluateTangent(float pos)
  172. {
  173. Vector3 result = transform.rotation * Vector3.forward;
  174. if (m_Waypoints.Length > 1)
  175. {
  176. UpdateControlPoints();
  177. int indexA, indexB;
  178. pos = GetBoundingIndices(pos, out indexA, out indexB);
  179. if (!Looped && indexA == m_Waypoints.Length - 1)
  180. --indexA;
  181. result = SplineHelpers.BezierTangent3(pos - indexA,
  182. m_Waypoints[indexA].position, m_ControlPoints1[indexA].position,
  183. m_ControlPoints2[indexA].position, m_Waypoints[indexB].position);
  184. }
  185. return transform.TransformDirection(result);
  186. }
  187. /// <summary>Get the orientation the curve at a point along the path.</summary>
  188. /// <param name="pos">Position along the path. Need not be normalized.</param>
  189. /// <returns>World-space orientation of the path, as defined by tangent, up, and roll.</returns>
  190. public override Quaternion EvaluateOrientation(float pos)
  191. {
  192. Quaternion transformRot = transform.rotation;
  193. Vector3 transformUp = transformRot * Vector3.up;
  194. Quaternion result = transformRot;
  195. if (m_Waypoints.Length > 0)
  196. {
  197. float roll = 0;
  198. int indexA, indexB;
  199. pos = GetBoundingIndices(pos, out indexA, out indexB);
  200. if (indexA == indexB)
  201. roll = m_Waypoints[indexA].roll;
  202. else
  203. {
  204. UpdateControlPoints();
  205. roll = SplineHelpers.Bezier1(pos - indexA,
  206. m_Waypoints[indexA].roll, m_ControlPoints1[indexA].roll,
  207. m_ControlPoints2[indexA].roll, m_Waypoints[indexB].roll);
  208. }
  209. Vector3 fwd = EvaluateTangent(pos);
  210. if (!fwd.AlmostZero())
  211. {
  212. Quaternion q = Quaternion.LookRotation(fwd, transformUp);
  213. result = q * RollAroundForward(roll);
  214. }
  215. }
  216. return result;
  217. }
  218. // same as Quaternion.AngleAxis(roll, Vector3.forward), just simplified
  219. Quaternion RollAroundForward(float angle)
  220. {
  221. float halfAngle = angle * 0.5F * Mathf.Deg2Rad;
  222. return new Quaternion(
  223. 0,
  224. 0,
  225. Mathf.Sin(halfAngle),
  226. Mathf.Cos(halfAngle));
  227. }
  228. }
  229. }