Predictor.cs 9.9 KB

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