using UnityEngine; namespace Cinemachine.Utility { /// /// This is a utility class to implement position predicting. /// public class PositionPredictor { Vector3 m_Velocity; Vector3 m_SmoothDampVelocity; Vector3 m_Pos; bool m_HavePos; /// /// How much to smooth the predicted result. Must be >= 0, roughly coresponds to smoothing time. /// public float Smoothing; /// /// Have any positions been logged for smoothing? /// /// True if no positions have yet been logged, in which case smoothing is impossible public bool IsEmpty() { return !m_HavePos; } /// /// Apply a delta to the target's position, which will be ignored for /// smoothing purposes. Use this whent he target's position gets warped. /// /// The position change of the target object public void ApplyTransformDelta(Vector3 positionDelta) { m_Pos += positionDelta; } /// Reset the lookahead data, clear all the buffers. public void Reset() { m_HavePos = false; m_SmoothDampVelocity = Vector3.zero; m_Velocity = Vector3.zero; } /// Add a new target position to the history buffer /// The new target position /// deltaTime since the last target position was added /// Current lookahead time setting (unused) public void AddPosition(Vector3 pos, float deltaTime, float lookaheadTime) { if (deltaTime < 0) Reset(); if (m_HavePos && deltaTime > UnityVectorExtensions.Epsilon) { var vel = (pos - m_Pos) / deltaTime; bool slowing = vel.sqrMagnitude < m_Velocity.sqrMagnitude; m_Velocity = Vector3.SmoothDamp( m_Velocity, vel, ref m_SmoothDampVelocity, Smoothing / (slowing ? 30 : 10), float.PositiveInfinity, deltaTime); } m_Pos = pos; m_HavePos = true; } /// Predict the target's position change over a given time from now /// How far ahead in time to predict /// The predicted position change (current velocity * lokahead time) public Vector3 PredictPositionDelta(float lookaheadTime) { return m_Velocity * lookaheadTime; } /// Predict the target's position a given time from now /// How far ahead in time to predict /// The predicted position public Vector3 PredictPosition(float lookaheadTime) { return m_Pos + PredictPositionDelta(lookaheadTime); } } /// Utility to perform realistic damping of float or Vector3 values. /// The algorithm is based on exponentially decaying the delta until only /// a negligible amount remains. public static class Damper { const float Epsilon = UnityVectorExtensions.Epsilon; // Get the decay constant that would leave a given residual after a given time static float DecayConstant(float time, float residual) { return Mathf.Log(1f / residual) / time; } // Exponential decay: decay a given quantity opver a period of time static float DecayedRemainder(float initial, float decayConstant, float deltaTime) { return initial / Mathf.Exp(decayConstant * deltaTime); } /// Standard residual public const float kNegligibleResidual = 0.01f; const float kLogNegligibleResidual = -4.605170186f; // == math.Log(kNegligibleResidual=0.01f); /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public static float Damp(float initial, float dampTime, float deltaTime) { if (dampTime < Epsilon || Mathf.Abs(initial) < Epsilon) return initial; if (deltaTime < Epsilon) return 0; float k = -kLogNegligibleResidual / dampTime; //DecayConstant(dampTime, kNegligibleResidual); #if CINEMACHINE_EXPERIMENTAL_DAMPING // Try to reduce damage caused by frametime variability float step = Time.fixedDeltaTime; if (deltaTime != step) step /= 5; int numSteps = Mathf.FloorToInt(deltaTime / step); float vel = initial * step / deltaTime; float decayConstant = Mathf.Exp(-k * step); // ==================================== // This code is equivalent to: // float r = 0; // for (int i = 0; i < numSteps; ++i) // r = (r + vel) * decayConstant; // (partial sum of geometric series) float r = vel; if (Mathf.Abs(decayConstant - 1) < Epsilon) r *= decayConstant * numSteps; else { r *= decayConstant - Mathf.Pow(decayConstant, numSteps + 1); r /= 1 - decayConstant; } // ==================================== float d = deltaTime - (step * numSteps); if (d > Epsilon) r = Mathf.Lerp(r, (r + vel) * decayConstant, d / step); return initial - r; #else return initial * (1 - Mathf.Exp(-k * deltaTime)); #endif } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public static Vector3 Damp(Vector3 initial, Vector3 dampTime, float deltaTime) { for (int i = 0; i < 3; ++i) initial[i] = Damp(initial[i], dampTime[i], deltaTime); return initial; } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public static Vector3 Damp(Vector3 initial, float dampTime, float deltaTime) { for (int i = 0; i < 3; ++i) initial[i] = Damp(initial[i], dampTime, deltaTime); return initial; } } /// Tracks an object's velocity with a filter to determine a reasonably /// steady direction for the object's current trajectory. public class HeadingTracker { struct Item { public Vector3 velocity; public float weight; public float time; }; Item[] mHistory; int mTop; int mBottom; int mCount; Vector3 mHeadingSum; float mWeightSum = 0; float mWeightTime = 0; Vector3 mLastGoodHeading = Vector3.zero; /// Construct a heading tracker with a given filter size /// The size of the filter. The larger the filter, the /// more constanct (and laggy) is the heading. 30 is pretty big. public HeadingTracker(int filterSize) { mHistory = new Item[filterSize]; float historyHalfLife = filterSize / 5f; // somewhat arbitrarily mDecayExponent = -Mathf.Log(2f) / historyHalfLife; ClearHistory(); } /// Get the current filter size public int FilterSize { get { return mHistory.Length; } } void ClearHistory() { mTop = mBottom = mCount = 0; mWeightSum = 0; mHeadingSum = Vector3.zero; } static float mDecayExponent; static float Decay(float time) { return Mathf.Exp(time * mDecayExponent); } /// Add a new velocity frame. This should be called once per frame, /// unless the velocity is zero /// The object's velocity this frame public void Add(Vector3 velocity) { if (FilterSize == 0) { mLastGoodHeading = velocity; return; } float weight = velocity.magnitude; if (weight > UnityVectorExtensions.Epsilon) { Item item = new Item(); item.velocity = velocity; item.weight = weight; item.time = CinemachineCore.CurrentTime; if (mCount == FilterSize) PopBottom(); ++mCount; mHistory[mTop] = item; if (++mTop == FilterSize) mTop = 0; mWeightSum *= Decay(item.time - mWeightTime); mWeightTime = item.time; mWeightSum += weight; mHeadingSum += item.velocity; } } void PopBottom() { if (mCount > 0) { float time = CinemachineCore.CurrentTime; Item item = mHistory[mBottom]; if (++mBottom == FilterSize) mBottom = 0; --mCount; float decay = Decay(time - item.time); mWeightSum -= item.weight * decay; mHeadingSum -= item.velocity * decay; if (mWeightSum <= UnityVectorExtensions.Epsilon || mCount == 0) ClearHistory(); } } /// Decay the history. This should be called every frame. public void DecayHistory() { float time = CinemachineCore.CurrentTime; float decay = Decay(time - mWeightTime); mWeightSum *= decay; mWeightTime = time; if (mWeightSum < UnityVectorExtensions.Epsilon) ClearHistory(); else mHeadingSum = mHeadingSum * decay; } /// Get the filtered heading. /// The filtered direction of motion public Vector3 GetReliableHeading() { // Update Last Good Heading if (mWeightSum > UnityVectorExtensions.Epsilon && (mCount == mHistory.Length || mLastGoodHeading.AlmostZero())) { Vector3 h = mHeadingSum / mWeightSum; if (!h.AlmostZero()) mLastGoodHeading = h.normalized; } return mLastGoodHeading; } } }