CinemachinePathBase.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using UnityEngine;
  2. using Cinemachine.Utility;
  3. using System;
  4. namespace Cinemachine
  5. {
  6. /// <summary>Abstract base class for a world-space path,
  7. /// suitable for a camera dolly track.</summary>
  8. public abstract class CinemachinePathBase : MonoBehaviour
  9. {
  10. /// <summary>Path samples per waypoint</summary>
  11. [Tooltip("Path samples per waypoint. This is used for calculating path distances.")]
  12. [Range(1, 100)]
  13. public int m_Resolution = 20;
  14. /// <summary>This class holds the settings that control how the path
  15. /// will appear in the editor scene view. The path is not visible in the game view</summary>
  16. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  17. [Serializable] public class Appearance
  18. {
  19. [Tooltip("The color of the path itself when it is active in the editor")]
  20. public Color pathColor = Color.green;
  21. [Tooltip("The color of the path itself when it is inactive in the editor")]
  22. public Color inactivePathColor = Color.gray;
  23. [Tooltip("The width of the railroad-tracks that are drawn to represent the path")]
  24. [Range(0f, 10f)]
  25. public float width = 0.2f;
  26. }
  27. /// <summary>The settings that control how the path
  28. /// will appear in the editor scene view.</summary>
  29. [Tooltip("The settings that control how the path will appear in the editor scene view.")]
  30. public Appearance m_Appearance = new Appearance();
  31. /// <summary>The minimum value for the path position</summary>
  32. public abstract float MinPos { get; }
  33. /// <summary>The maximum value for the path position</summary>
  34. public abstract float MaxPos { get; }
  35. /// <summary>True if the path ends are joined to form a continuous loop</summary>
  36. public abstract bool Looped { get; }
  37. /// <summary>Get a standardized path position, taking spins into account if looped</summary>
  38. /// <param name="pos">Position along the path</param>
  39. /// <returns>Standardized position, between MinPos and MaxPos</returns>
  40. public virtual float StandardizePos(float pos)
  41. {
  42. if (Looped && MaxPos > 0)
  43. {
  44. pos = pos % MaxPos;
  45. if (pos < 0)
  46. pos += MaxPos;
  47. return pos;
  48. }
  49. return Mathf.Clamp(pos, 0, MaxPos);
  50. }
  51. /// <summary>Get a worldspace position of a point along the path</summary>
  52. /// <param name="pos">Postion along the path. Need not be standardized.</param>
  53. /// <returns>World-space position of the point along at path at pos</returns>
  54. public abstract Vector3 EvaluatePosition(float pos);
  55. /// <summary>Get the tangent of the curve at a point along the path.</summary>
  56. /// <param name="pos">Postion along the path. Need not be standardized.</param>
  57. /// <returns>World-space direction of the path tangent.
  58. /// Length of the vector represents the tangent strength</returns>
  59. public abstract Vector3 EvaluateTangent(float pos);
  60. /// <summary>Get the orientation the curve at a point along the path.</summary>
  61. /// <param name="pos">Postion along the path. Need not be standardized.</param>
  62. /// <returns>World-space orientation of the path</returns>
  63. public abstract Quaternion EvaluateOrientation(float pos);
  64. /// <summary>Find the closest point on the path to a given worldspace target point.</summary>
  65. /// <remarks>Performance could be improved by checking the bounding polygon of each segment,
  66. /// and only entering the best segment(s)</remarks>
  67. /// <param name="p">Worldspace target that we want to approach</param>
  68. /// <param name="startSegment">In what segment of the path to start the search.
  69. /// A Segment is a section of path between 2 waypoints.</param>
  70. /// <param name="searchRadius">How many segments on either side of the startSegment
  71. /// to search. -1 means no limit, i.e. search the entire path</param>
  72. /// <param name="stepsPerSegment">We search a segment by dividing it into this many
  73. /// straight pieces. The higher the number, the more accurate the result, but performance
  74. /// is proportionally slower for higher numbers</param>
  75. /// <returns>The position along the path that is closest to the target point.
  76. /// The value is in Path Units, not Distance units.</returns>
  77. public virtual float FindClosestPoint(
  78. Vector3 p, int startSegment, int searchRadius, int stepsPerSegment)
  79. {
  80. float start = MinPos;
  81. float end = MaxPos;
  82. if (searchRadius >= 0)
  83. {
  84. int r = Mathf.FloorToInt(Mathf.Min(searchRadius, (end - start) / 2f));
  85. start = startSegment - r;
  86. end = startSegment + r + 1;
  87. if (!Looped)
  88. {
  89. start = Mathf.Max(start, MinPos);
  90. end = Mathf.Min(end, MaxPos);
  91. }
  92. }
  93. stepsPerSegment = Mathf.RoundToInt(Mathf.Clamp(stepsPerSegment, 1f, 100f));
  94. float stepSize = 1f / stepsPerSegment;
  95. float bestPos = startSegment;
  96. float bestDistance = float.MaxValue;
  97. int iterations = (stepsPerSegment == 1) ? 1 : 3;
  98. for (int i = 0; i < iterations; ++i)
  99. {
  100. Vector3 v0 = EvaluatePosition(start);
  101. for (float f = start + stepSize; f <= end; f += stepSize)
  102. {
  103. Vector3 v = EvaluatePosition(f);
  104. float t = p.ClosestPointOnSegment(v0, v);
  105. float d = Vector3.SqrMagnitude(p - Vector3.Lerp(v0, v, t));
  106. if (d < bestDistance)
  107. {
  108. bestDistance = d;
  109. bestPos = f - (1 - t) * stepSize;
  110. }
  111. v0 = v;
  112. }
  113. start = bestPos - stepSize;
  114. end = bestPos + stepSize;
  115. stepSize /= stepsPerSegment;
  116. }
  117. return bestPos;
  118. }
  119. /// <summary>How to interpret the Path Position</summary>
  120. public enum PositionUnits
  121. {
  122. /// <summary>Use PathPosition units, where 0 is first waypoint, 1 is second waypoint, etc</summary>
  123. PathUnits,
  124. /// <summary>Use Distance Along Path. Path will be sampled according to its Resolution
  125. /// setting, and a distance lookup table will be cached internally</summary>
  126. Distance,
  127. /// <summary>Normalized units, where 0 is the start of the path, and 1 is the end.
  128. /// Path will be sampled according to its Resolution
  129. /// setting, and a distance lookup table will be cached internally</summary>
  130. Normalized
  131. }
  132. /// <summary>Get the minimum value, for the given unit type</summary>
  133. /// <param name="units">The unit type</param>
  134. /// <returns>The minimum allowable value for this path</returns>
  135. public float MinUnit(PositionUnits units)
  136. {
  137. if (units == PositionUnits.Normalized)
  138. return 0;
  139. return units == PositionUnits.Distance ? 0 : MinPos;
  140. }
  141. /// <summary>Get the maximum value, for the given unit type</summary>
  142. /// <param name="units">The unit type</param>
  143. /// <returns>The maximum allowable value for this path</returns>
  144. public float MaxUnit(PositionUnits units)
  145. {
  146. if (units == PositionUnits.Normalized)
  147. return 1;
  148. return units == PositionUnits.Distance ? PathLength : MaxPos;
  149. }
  150. /// <summary>Standardize the unit, so that it lies between MinUmit and MaxUnit</summary>
  151. /// <param name="pos">The value to be standardized</param>
  152. /// <param name="units">The unit type</param>
  153. /// <returns>The standardized value of pos, between MinUnit and MaxUnit</returns>
  154. public virtual float StandardizeUnit(float pos, PositionUnits units)
  155. {
  156. if (units == PositionUnits.PathUnits)
  157. return StandardizePos(pos);
  158. if (units == PositionUnits.Distance)
  159. return StandardizePathDistance(pos);
  160. float len = PathLength;
  161. if (len < UnityVectorExtensions.Epsilon)
  162. return 0;
  163. return StandardizePathDistance(pos * len) / len;
  164. }
  165. /// <summary>Get a worldspace position of a point along the path</summary>
  166. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  167. /// <param name="units">The unit to use when interpreting the value of pos.</param>
  168. /// <returns>World-space position of the point along at path at pos</returns>
  169. public Vector3 EvaluatePositionAtUnit(float pos, PositionUnits units)
  170. {
  171. return EvaluatePosition(ToNativePathUnits(pos, units));
  172. }
  173. /// <summary>Get the tangent of the curve at a point along the path.</summary>
  174. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  175. /// <param name="units">The unit to use when interpreting the value of pos.</param>
  176. /// <returns>World-space direction of the path tangent.
  177. /// Length of the vector represents the tangent strength</returns>
  178. public Vector3 EvaluateTangentAtUnit(float pos, PositionUnits units)
  179. {
  180. return EvaluateTangent(ToNativePathUnits(pos, units));
  181. }
  182. /// <summary>Get the orientation the curve at a point along the path.</summary>
  183. /// <param name="pos">Postion along the path. Need not be normalized.</param>
  184. /// <param name="units">The unit to use when interpreting the value of pos.</param>
  185. /// <returns>World-space orientation of the path</returns>
  186. public Quaternion EvaluateOrientationAtUnit(float pos, PositionUnits units)
  187. {
  188. return EvaluateOrientation(ToNativePathUnits(pos, units));
  189. }
  190. /// <summary>When calculating the distance cache, sample the path this many
  191. /// times between points</summary>
  192. public abstract int DistanceCacheSampleStepsPerSegment { get; }
  193. /// <summary>Call this if the path changes in such a way as to affect distances
  194. /// or other cached path elements</summary>
  195. public virtual void InvalidateDistanceCache()
  196. {
  197. m_DistanceToPos = null;
  198. m_PosToDistance = null;
  199. m_CachedSampleSteps = 0;
  200. m_PathLength = 0;
  201. }
  202. /// <summary>See whether the distance cache is valid. If it's not valid,
  203. /// then any call to GetPathLength() or ToNativePathUnits() will
  204. /// trigger a potentially costly regeneration of the path distance cache</summary>
  205. /// <returns>Whether the cache is valid</returns>
  206. public bool DistanceCacheIsValid()
  207. {
  208. return (MaxPos == MinPos)
  209. || (m_DistanceToPos != null && m_PosToDistance != null
  210. && m_CachedSampleSteps == DistanceCacheSampleStepsPerSegment
  211. && m_CachedSampleSteps > 0);
  212. }
  213. /// <summary>Get the length of the path in distance units.
  214. /// If the distance cache is not valid, then calling this will
  215. /// trigger a potentially costly regeneration of the path distance cache</summary>
  216. /// <returns>The length of the path in distance units, when sampled at this rate</returns>
  217. public float PathLength
  218. {
  219. get
  220. {
  221. if (DistanceCacheSampleStepsPerSegment < 1)
  222. return 0;
  223. if (!DistanceCacheIsValid())
  224. ResamplePath(DistanceCacheSampleStepsPerSegment);
  225. return m_PathLength;
  226. }
  227. }
  228. /// <summary>Standardize a distance along the path based on the path length.
  229. /// If the distance cache is not valid, then calling this will
  230. /// trigger a potentially costly regeneration of the path distance cache</summary>
  231. /// <param name="distance">The distance to standardize</param>
  232. /// <returns>The standardized distance, ranging from 0 to path length</returns>
  233. public float StandardizePathDistance(float distance)
  234. {
  235. float length = PathLength;
  236. if (length < Vector3.kEpsilon)
  237. return 0;
  238. if (Looped)
  239. {
  240. distance = distance % length;
  241. if (distance < 0)
  242. distance += length;
  243. }
  244. return Mathf.Clamp(distance, 0, length);
  245. }
  246. /// <summary>Get the path position to native path units.
  247. /// If the distance cache is not valid, then calling this will
  248. /// trigger a potentially costly regeneration of the path distance cache</summary>
  249. /// <param name="pos">The value to convert from</param>
  250. /// <param name="units">The units in which pos is expressed</param>
  251. /// <returns>The path position, in native units</returns>
  252. public float ToNativePathUnits(float pos, PositionUnits units)
  253. {
  254. if (units == PositionUnits.PathUnits)
  255. return pos;
  256. if (DistanceCacheSampleStepsPerSegment < 1 || PathLength < UnityVectorExtensions.Epsilon)
  257. return MinPos;
  258. if (units == PositionUnits.Normalized)
  259. pos *= PathLength;
  260. pos = StandardizePathDistance(pos);
  261. float d = pos / m_cachedDistanceStepSize;
  262. int i = Mathf.FloorToInt(d);
  263. if (i >= m_DistanceToPos.Length-1)
  264. return MaxPos;
  265. float t = d - (float)i;
  266. return MinPos + Mathf.Lerp(m_DistanceToPos[i], m_DistanceToPos[i+1], t);
  267. }
  268. /// <summary>Convert a path position from native path units to the desired units.
  269. /// If the distance cache is not valid, then calling this will
  270. /// trigger a potentially costly regeneration of the path distance cache</summary>
  271. /// <param name="pos">The value to convert from, in native units</param>
  272. /// <param name="units">The units to convert to</param>
  273. /// <returns>The path position, in the requested units</returns>
  274. public float FromPathNativeUnits(float pos, PositionUnits units)
  275. {
  276. if (units == PositionUnits.PathUnits)
  277. return pos;
  278. float length = PathLength;
  279. if (DistanceCacheSampleStepsPerSegment < 1 || length < UnityVectorExtensions.Epsilon)
  280. return 0;
  281. pos = StandardizePos(pos);
  282. float d = pos / m_cachedPosStepSize;
  283. int i = Mathf.FloorToInt(d);
  284. if (i >= m_PosToDistance.Length-1)
  285. pos = m_PathLength;
  286. else
  287. {
  288. float t = d - (float)i;
  289. pos = Mathf.Lerp(m_PosToDistance[i], m_PosToDistance[i+1], t);
  290. }
  291. if (units == PositionUnits.Normalized)
  292. pos /= length;
  293. return pos;
  294. }
  295. private float[] m_DistanceToPos;
  296. private float[] m_PosToDistance;
  297. private int m_CachedSampleSteps;
  298. private float m_PathLength;
  299. private float m_cachedPosStepSize;
  300. private float m_cachedDistanceStepSize;
  301. private void ResamplePath(int stepsPerSegment)
  302. {
  303. InvalidateDistanceCache();
  304. float minPos = MinPos;
  305. float maxPos = MaxPos;
  306. float stepSize = 1f / Mathf.Max(1, stepsPerSegment);
  307. // Sample the positions
  308. int numKeys = Mathf.RoundToInt((maxPos - minPos) / stepSize) + 1;
  309. m_PosToDistance = new float[numKeys];
  310. m_CachedSampleSteps = stepsPerSegment;
  311. m_cachedPosStepSize = stepSize;
  312. Vector3 p0 = EvaluatePosition(0);
  313. m_PosToDistance[0] = 0;
  314. float pos = minPos;
  315. for (int i = 1; i < numKeys; ++i)
  316. {
  317. pos += stepSize;
  318. Vector3 p = EvaluatePosition(pos);
  319. float d = Vector3.Distance(p0, p);
  320. m_PathLength += d;
  321. p0 = p;
  322. m_PosToDistance[i] = m_PathLength;
  323. }
  324. // Resample the distances
  325. m_DistanceToPos = new float[numKeys];
  326. m_DistanceToPos[0] = 0;
  327. if (numKeys > 1)
  328. {
  329. stepSize = m_PathLength / (numKeys - 1);
  330. m_cachedDistanceStepSize = stepSize;
  331. float distance = 0;
  332. int posIndex = 1;
  333. for (int i = 1; i < numKeys; ++i)
  334. {
  335. distance += stepSize;
  336. float d = m_PosToDistance[posIndex];
  337. while (d < distance && posIndex < numKeys-1)
  338. d = m_PosToDistance[++posIndex];
  339. float d0 = m_PosToDistance[posIndex-1];
  340. float delta = d - d0;
  341. float t = (distance - d0) / delta;
  342. m_DistanceToPos[i] = m_cachedPosStepSize * (t + posIndex - 1);
  343. }
  344. }
  345. }
  346. }
  347. }