Predictor.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using UnityEngine;
  2. namespace Cinemachine.Utility
  3. {
  4. /// <summary>
  5. /// This is a utility class to implement position predicting.
  6. /// </summary>
  7. public class PositionPredictor
  8. {
  9. Vector3 m_Velocity;
  10. Vector3 m_SmoothDampVelocity;
  11. Vector3 m_Pos;
  12. bool m_HavePos;
  13. /// <summary>
  14. /// How much to smooth the predicted result. Must be >= 0, roughly coresponds to smoothing time.
  15. /// </summary>
  16. public float Smoothing;
  17. /// <summary>
  18. /// Have any positions been logged for smoothing?
  19. /// </summary>
  20. /// <returns>True if no positions have yet been logged, in which case smoothing is impossible</returns>
  21. public bool IsEmpty() { return !m_HavePos; }
  22. /// <summary>
  23. /// Apply a delta to the target's position, which will be ignored for
  24. /// smoothing purposes. Use this whent he target's position gets warped.
  25. /// </summary>
  26. /// <param name="positionDelta">The position change of the target object</param>
  27. public void ApplyTransformDelta(Vector3 positionDelta) { m_Pos += positionDelta; }
  28. /// <summary>Reset the lookahead data, clear all the buffers.</summary>
  29. public void Reset()
  30. {
  31. m_HavePos = false;
  32. m_SmoothDampVelocity = Vector3.zero;
  33. m_Velocity = Vector3.zero;
  34. }
  35. /// <summary>Add a new target position to the history buffer</summary>
  36. /// <param name="pos">The new target position</param>
  37. /// <param name="deltaTime">deltaTime since the last target position was added</param>
  38. /// <param name="lookaheadTime">Current lookahead time setting (unused)</param>
  39. public void AddPosition(Vector3 pos, float deltaTime, float lookaheadTime)
  40. {
  41. if (deltaTime < 0)
  42. Reset();
  43. if (m_HavePos && deltaTime > UnityVectorExtensions.Epsilon)
  44. {
  45. var vel = (pos - m_Pos) / deltaTime;
  46. bool slowing = vel.sqrMagnitude < m_Velocity.sqrMagnitude;
  47. m_Velocity = Vector3.SmoothDamp(
  48. m_Velocity, vel, ref m_SmoothDampVelocity, Smoothing / (slowing ? 30 : 10),
  49. float.PositiveInfinity, deltaTime);
  50. }
  51. m_Pos = pos;
  52. m_HavePos = true;
  53. }
  54. /// <summary>Predict the target's position change over a given time from now</summary>
  55. /// <param name="lookaheadTime">How far ahead in time to predict</param>
  56. /// <returns>The predicted position change (current velocity * lokahead time)</returns>
  57. public Vector3 PredictPositionDelta(float lookaheadTime)
  58. {
  59. return m_Velocity * lookaheadTime;
  60. }
  61. /// <summary>Predict the target's position a given time from now</summary>
  62. /// <param name="lookaheadTime">How far ahead in time to predict</param>
  63. /// <returns>The predicted position</returns>
  64. public Vector3 PredictPosition(float lookaheadTime)
  65. {
  66. return m_Pos + PredictPositionDelta(lookaheadTime);
  67. }
  68. }
  69. /// <summary>Utility to perform realistic damping of float or Vector3 values.
  70. /// The algorithm is based on exponentially decaying the delta until only
  71. /// a negligible amount remains.</summary>
  72. public static class Damper
  73. {
  74. const float Epsilon = UnityVectorExtensions.Epsilon;
  75. // Get the decay constant that would leave a given residual after a given time
  76. static float DecayConstant(float time, float residual)
  77. {
  78. return Mathf.Log(1f / residual) / time;
  79. }
  80. // Exponential decay: decay a given quantity opver a period of time
  81. static float DecayedRemainder(float initial, float decayConstant, float deltaTime)
  82. {
  83. return initial / Mathf.Exp(decayConstant * deltaTime);
  84. }
  85. /// <summary>Standard residual</summary>
  86. public const float kNegligibleResidual = 0.01f;
  87. const float kLogNegligibleResidual = -4.605170186f; // == math.Log(kNegligibleResidual=0.01f);
  88. /// <summary>Get a damped version of a quantity. This is the portion of the
  89. /// quantity that will take effect over the given time.</summary>
  90. /// <param name="initial">The amount that will be damped</param>
  91. /// <param name="dampTime">The rate of damping. This is the time it would
  92. /// take to reduce the original amount to a negligible percentage</param>
  93. /// <param name="deltaTime">The time over which to damp</param>
  94. /// <returns>The damped amount. This will be the original amount scaled by
  95. /// a value between 0 and 1.</returns>
  96. public static float Damp(float initial, float dampTime, float deltaTime)
  97. {
  98. if (dampTime < Epsilon || Mathf.Abs(initial) < Epsilon)
  99. return initial;
  100. if (deltaTime < Epsilon)
  101. return 0;
  102. float k = -kLogNegligibleResidual / dampTime; //DecayConstant(dampTime, kNegligibleResidual);
  103. #if CINEMACHINE_EXPERIMENTAL_DAMPING
  104. // Try to reduce damage caused by frametime variability
  105. float step = Time.fixedDeltaTime;
  106. if (deltaTime != step)
  107. step /= 5;
  108. int numSteps = Mathf.FloorToInt(deltaTime / step);
  109. float vel = initial * step / deltaTime;
  110. float decayConstant = Mathf.Exp(-k * step);
  111. // ====================================
  112. // This code is equivalent to:
  113. // float r = 0;
  114. // for (int i = 0; i < numSteps; ++i)
  115. // r = (r + vel) * decayConstant;
  116. // (partial sum of geometric series)
  117. float r = vel;
  118. if (Mathf.Abs(decayConstant - 1) < Epsilon)
  119. r *= decayConstant * numSteps;
  120. else
  121. {
  122. r *= decayConstant - Mathf.Pow(decayConstant, numSteps + 1);
  123. r /= 1 - decayConstant;
  124. }
  125. // ====================================
  126. float d = deltaTime - (step * numSteps);
  127. if (d > Epsilon)
  128. r = Mathf.Lerp(r, (r + vel) * decayConstant, d / step);
  129. return initial - r;
  130. #else
  131. return initial * (1 - Mathf.Exp(-k * deltaTime));
  132. #endif
  133. }
  134. /// <summary>Get a damped version of a quantity. This is the portion of the
  135. /// quantity that will take effect over the given time.</summary>
  136. /// <param name="initial">The amount that will be damped</param>
  137. /// <param name="dampTime">The rate of damping. This is the time it would
  138. /// take to reduce the original amount to a negligible percentage</param>
  139. /// <param name="deltaTime">The time over which to damp</param>
  140. /// <returns>The damped amount. This will be the original amount scaled by
  141. /// a value between 0 and 1.</returns>
  142. public static Vector3 Damp(Vector3 initial, Vector3 dampTime, float deltaTime)
  143. {
  144. for (int i = 0; i < 3; ++i)
  145. initial[i] = Damp(initial[i], dampTime[i], deltaTime);
  146. return initial;
  147. }
  148. /// <summary>Get a damped version of a quantity. This is the portion of the
  149. /// quantity that will take effect over the given time.</summary>
  150. /// <param name="initial">The amount that will be damped</param>
  151. /// <param name="dampTime">The rate of damping. This is the time it would
  152. /// take to reduce the original amount to a negligible percentage</param>
  153. /// <param name="deltaTime">The time over which to damp</param>
  154. /// <returns>The damped amount. This will be the original amount scaled by
  155. /// a value between 0 and 1.</returns>
  156. public static Vector3 Damp(Vector3 initial, float dampTime, float deltaTime)
  157. {
  158. for (int i = 0; i < 3; ++i)
  159. initial[i] = Damp(initial[i], dampTime, deltaTime);
  160. return initial;
  161. }
  162. }
  163. /// <summary>Tracks an object's velocity with a filter to determine a reasonably
  164. /// steady direction for the object's current trajectory.</summary>
  165. public class HeadingTracker
  166. {
  167. struct Item
  168. {
  169. public Vector3 velocity;
  170. public float weight;
  171. public float time;
  172. };
  173. Item[] mHistory;
  174. int mTop;
  175. int mBottom;
  176. int mCount;
  177. Vector3 mHeadingSum;
  178. float mWeightSum = 0;
  179. float mWeightTime = 0;
  180. Vector3 mLastGoodHeading = Vector3.zero;
  181. /// <summary>Construct a heading tracker with a given filter size</summary>
  182. /// <param name="filterSize">The size of the filter. The larger the filter, the
  183. /// more constanct (and laggy) is the heading. 30 is pretty big.</param>
  184. public HeadingTracker(int filterSize)
  185. {
  186. mHistory = new Item[filterSize];
  187. float historyHalfLife = filterSize / 5f; // somewhat arbitrarily
  188. mDecayExponent = -Mathf.Log(2f) / historyHalfLife;
  189. ClearHistory();
  190. }
  191. /// <summary>Get the current filter size</summary>
  192. public int FilterSize { get { return mHistory.Length; } }
  193. void ClearHistory()
  194. {
  195. mTop = mBottom = mCount = 0;
  196. mWeightSum = 0;
  197. mHeadingSum = Vector3.zero;
  198. }
  199. static float mDecayExponent;
  200. static float Decay(float time) { return Mathf.Exp(time * mDecayExponent); }
  201. /// <summary>Add a new velocity frame. This should be called once per frame,
  202. /// unless the velocity is zero</summary>
  203. /// <param name="velocity">The object's velocity this frame</param>
  204. public void Add(Vector3 velocity)
  205. {
  206. if (FilterSize == 0)
  207. {
  208. mLastGoodHeading = velocity;
  209. return;
  210. }
  211. float weight = velocity.magnitude;
  212. if (weight > UnityVectorExtensions.Epsilon)
  213. {
  214. Item item = new Item();
  215. item.velocity = velocity;
  216. item.weight = weight;
  217. item.time = CinemachineCore.CurrentTime;
  218. if (mCount == FilterSize)
  219. PopBottom();
  220. ++mCount;
  221. mHistory[mTop] = item;
  222. if (++mTop == FilterSize)
  223. mTop = 0;
  224. mWeightSum *= Decay(item.time - mWeightTime);
  225. mWeightTime = item.time;
  226. mWeightSum += weight;
  227. mHeadingSum += item.velocity;
  228. }
  229. }
  230. void PopBottom()
  231. {
  232. if (mCount > 0)
  233. {
  234. float time = CinemachineCore.CurrentTime;
  235. Item item = mHistory[mBottom];
  236. if (++mBottom == FilterSize)
  237. mBottom = 0;
  238. --mCount;
  239. float decay = Decay(time - item.time);
  240. mWeightSum -= item.weight * decay;
  241. mHeadingSum -= item.velocity * decay;
  242. if (mWeightSum <= UnityVectorExtensions.Epsilon || mCount == 0)
  243. ClearHistory();
  244. }
  245. }
  246. /// <summary>Decay the history. This should be called every frame.</summary>
  247. public void DecayHistory()
  248. {
  249. float time = CinemachineCore.CurrentTime;
  250. float decay = Decay(time - mWeightTime);
  251. mWeightSum *= decay;
  252. mWeightTime = time;
  253. if (mWeightSum < UnityVectorExtensions.Epsilon)
  254. ClearHistory();
  255. else
  256. mHeadingSum = mHeadingSum * decay;
  257. }
  258. /// <summary>Get the filtered heading.</summary>
  259. /// <returns>The filtered direction of motion</returns>
  260. public Vector3 GetReliableHeading()
  261. {
  262. // Update Last Good Heading
  263. if (mWeightSum > UnityVectorExtensions.Epsilon
  264. && (mCount == mHistory.Length || mLastGoodHeading.AlmostZero()))
  265. {
  266. Vector3 h = mHeadingSum / mWeightSum;
  267. if (!h.AlmostZero())
  268. mLastGoodHeading = h.normalized;
  269. }
  270. return mLastGoodHeading;
  271. }
  272. }
  273. }