CinemachinePath.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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, tangent, and roll settings. Bezier interpolation
  8. /// is performed between the waypoints, to get a smooth and continuous path.</summary>
  9. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  10. [AddComponentMenu("Cinemachine/CinemachinePath")]
  11. [SaveDuringPlay]
  12. [DisallowMultipleComponent]
  13. [HelpURL(Documentation.BaseURL + "manual/CinemachinePath.html")]
  14. public class CinemachinePath : CinemachinePathBase
  15. {
  16. /// <summary>A waypoint along the path</summary>
  17. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  18. [Serializable] public struct Waypoint
  19. {
  20. /// <summary>Position in path-local space</summary>
  21. [Tooltip("Position in path-local space")]
  22. public Vector3 position;
  23. /// <summary>Offset from the position, which defines the tangent of the curve at the waypoint.
  24. /// The length of the tangent encodes the strength of the bezier handle.
  25. /// The same handle is used symmetrically on both sides of the waypoint, to ensure smoothness.</summary>
  26. [Tooltip("Offset from the position, which defines the tangent of the curve at the waypoint. "
  27. + "The length of the tangent encodes the strength of the bezier handle. The same handle "
  28. + "is used symmetrically on both sides of the waypoint, to ensure smoothness.")]
  29. public Vector3 tangent;
  30. /// <summary>Defines the roll of the path at this waypoint.
  31. /// The other orientation axes are inferred from the tangent and world up.</summary>
  32. [Tooltip("Defines the roll of the path at this waypoint. The other orientation axes are inferred from the tangent and world up.")]
  33. public float roll;
  34. }
  35. /// <summary>If checked, then the path ends are joined to form a continuous loop</summary>
  36. [Tooltip("If checked, then the path ends are joined to form a continuous loop.")]
  37. public bool m_Looped;
  38. /// <summary>The waypoints that define the path.
  39. /// They will be interpolated using a bezier curve</summary>
  40. [Tooltip("The waypoints that define the path. They will be interpolated using a bezier curve.")]
  41. public Waypoint[] m_Waypoints = Array.Empty<Waypoint>();
  42. /// <summary>The minimum value for the path position</summary>
  43. public override float MinPos => 0;
  44. /// <summary>The maximum value for the path position</summary>
  45. public override float MaxPos
  46. {
  47. get
  48. {
  49. int count = m_Waypoints.Length - 1;
  50. if (count < 1)
  51. return 0;
  52. return m_Looped ? count + 1 : count;
  53. }
  54. }
  55. /// <summary>True if the path ends are joined to form a continuous loop</summary>
  56. public override bool Looped => m_Looped;
  57. private void Reset()
  58. {
  59. m_Looped = false;
  60. m_Waypoints = new Waypoint[2]
  61. {
  62. new Waypoint { position = new Vector3(0, 0, -5), tangent = new Vector3(1, 0, 0) },
  63. new Waypoint { position = new Vector3(0, 0, 5), tangent = new Vector3(1, 0, 0) }
  64. };
  65. m_Appearance = new Appearance();
  66. InvalidateDistanceCache();
  67. }
  68. private void OnValidate() { InvalidateDistanceCache(); }
  69. /// <summary>When calculating the distance cache, sample the path this many
  70. /// times between points</summary>
  71. public override int DistanceCacheSampleStepsPerSegment => m_Resolution;
  72. /// <summary>Returns normalized position</summary>
  73. float GetBoundingIndices(float pos, out int indexA, out int indexB)
  74. {
  75. pos = StandardizePos(pos);
  76. int rounded = Mathf.RoundToInt(pos);
  77. if (Mathf.Abs(pos - rounded) < UnityVectorExtensions.Epsilon)
  78. indexA = indexB = (rounded == m_Waypoints.Length) ? 0 : rounded;
  79. else
  80. {
  81. indexA = Mathf.FloorToInt(pos);
  82. if (indexA >= m_Waypoints.Length)
  83. {
  84. pos -= MaxPos;
  85. indexA = 0;
  86. }
  87. indexB = Mathf.CeilToInt(pos);
  88. if (indexB >= m_Waypoints.Length)
  89. indexB = 0;
  90. }
  91. return pos;
  92. }
  93. /// <summary>Get a worldspace position of a point along the path</summary>
  94. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  95. /// <returns>Local-space position of the point along at path at pos</returns>
  96. public override Vector3 EvaluateLocalPosition(float pos)
  97. {
  98. var result = Vector3.zero;
  99. if (m_Waypoints.Length > 0)
  100. {
  101. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  102. if (indexA == indexB)
  103. result = m_Waypoints[indexA].position;
  104. else
  105. {
  106. // interpolate
  107. var wpA = m_Waypoints[indexA];
  108. var wpB = m_Waypoints[indexB];
  109. result = SplineHelpers.Bezier3(pos - indexA,
  110. m_Waypoints[indexA].position, wpA.position + wpA.tangent,
  111. wpB.position - wpB.tangent, wpB.position);
  112. }
  113. }
  114. return result;
  115. }
  116. /// <summary>Get the tangent of the curve at a point along the path.</summary>
  117. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  118. /// <returns>Local-space direction of the path tangent.
  119. /// Length of the vector represents the tangent strength</returns>
  120. public override Vector3 EvaluateLocalTangent(float pos)
  121. {
  122. var result = Vector3.forward;
  123. if (m_Waypoints.Length > 0)
  124. {
  125. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  126. if (indexA == indexB)
  127. result = m_Waypoints[indexA].tangent;
  128. else
  129. {
  130. var wpA = m_Waypoints[indexA];
  131. var wpB = m_Waypoints[indexB];
  132. result = SplineHelpers.BezierTangent3(pos - indexA,
  133. m_Waypoints[indexA].position, wpA.position + wpA.tangent,
  134. wpB.position - wpB.tangent, wpB.position);
  135. }
  136. }
  137. return result;
  138. }
  139. /// <summary>Get the orientation the curve at a point along the path.</summary>
  140. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  141. /// <returns>Local-space orientation of the path, as defined by tangent, up, and roll.</returns>
  142. public override Quaternion EvaluateLocalOrientation(float pos)
  143. {
  144. var result = Quaternion.identity;
  145. if (m_Waypoints.Length > 0)
  146. {
  147. pos = GetBoundingIndices(pos, out var indexA, out var indexB);
  148. var fwd = EvaluateLocalTangent(pos);
  149. if (!fwd.AlmostZero())
  150. result = Quaternion.LookRotation(fwd) * RollAroundForward(GetRoll(indexA, indexB, pos));
  151. }
  152. return result;
  153. }
  154. internal float GetRoll(int indexA, int indexB, float standardizedPos)
  155. {
  156. if (indexA == indexB)
  157. return m_Waypoints[indexA].roll;
  158. float rollA = m_Waypoints[indexA].roll;
  159. float rollB = m_Waypoints[indexB].roll;
  160. if (indexB == 0)
  161. {
  162. // Special handling at the wraparound - cancel the spins
  163. rollA %= 360;
  164. rollB %= 360;
  165. }
  166. return Mathf.Lerp(rollA, rollB, standardizedPos - indexA);
  167. }
  168. // same as Quaternion.AngleAxis(roll, Vector3.forward), just simplified
  169. static Quaternion RollAroundForward(float angle)
  170. {
  171. float halfAngle = angle * 0.5F * Mathf.Deg2Rad;
  172. return new Quaternion(0, 0, Mathf.Sin(halfAngle), Mathf.Cos(halfAngle));
  173. }
  174. }
  175. }