using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Cinemachine
{
///
/// This is a utility to implement position predicting.
///
public struct PositionPredictor
{
Vector3 m_Velocity;
Vector3 m_SmoothDampVelocity;
Vector3 m_Pos;
bool m_HavePos;
/// How much to smooth the predicted result. Must be >= 0, roughly corresponds 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 => !m_HavePos;
/// Get the current position of the tracked object, as set by the last call to AddPosition().
/// This is only valid if IsEmpty returns false.
/// The current position of the tracked object, as set by the last call to AddPosition()
public Vector3 CurrentPosition => m_Pos;
///
/// Apply a delta to the target's position, which will be ignored for
/// smoothing purposes. Use this when the 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
public void AddPosition(Vector3 pos, float deltaTime)
{
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 * lookahead time)
public Vector3 PredictPositionDelta(float lookaheadTime) => m_Velocity * 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) => Mathf.Log(1f / residual) / time;
// Exponential decay: decay a given quantity over a period of time
static float DecayedRemainder(float initial, float decayConstant, float deltaTime)
=> 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 CINEMACHINE_EXPERIMENTAL_DAMPING
if (!Time.inFixedTimeStep)
return StableDamp(initial, dampTime, deltaTime);
#endif
return StandardDamp(initial, dampTime, deltaTime);
}
/// 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;
}
/// 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.
// Internal for testing
internal static float StandardDamp(float initial, float dampTime, float deltaTime)
{
if (dampTime < Epsilon || Mathf.Abs(initial) < Epsilon)
return initial;
if (deltaTime < Epsilon)
return 0;
return initial * (1 - Mathf.Exp(kLogNegligibleResidual * deltaTime / dampTime));
}
///
/// Get a damped version of a quantity. This is the portion of the
/// quantity that will take effect over the given time.
///
/// This is a special implementation that attempts to increase visual stability
/// in the context of an unstable framerate.
///
/// It relies on AverageFrameRateTracker to track the average framerate.
///
/// 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.
// Internal for testing
internal static float StableDamp(float initial, float dampTime, float deltaTime)
{
if (dampTime < Epsilon || Mathf.Abs(initial) < Epsilon)
return initial;
if (deltaTime < Epsilon)
return 0;
// Try to reduce damage caused by frametime variability, by pretending
// that the value to decay has accumulated steadily over many constant-time subframes.
// We simulate being called for each subframe. This does result in a longer damping time,
// so we compensate with AverageFrameRateTracker.DampTimeScale, which is calculated
// every frame based on the average framerate over the past number of frames.
float step = Mathf.Min(deltaTime, AverageFrameRateTracker.kSubframeTime);
int numSteps = Mathf.FloorToInt(deltaTime / step);
float vel = initial * step / deltaTime; // the amount that accumulates each subframe
float k = Mathf.Exp(kLogNegligibleResidual * AverageFrameRateTracker.DampTimeScale * step / dampTime);
// ====================================
// This code is equivalent to:
// float r = 0;
// for (int i = 0; i < numSteps; ++i)
// r = (r + vel) * k;
// (partial sum of geometric series)
float r = vel;
if (Mathf.Abs(k - 1) < Epsilon)
r *= k * numSteps;
else
{
r *= k - Mathf.Pow(k, numSteps + 1);
r /= 1 - k;
}
// ====================================
// Handle any remaining quantity after the last step
r = Mathf.Lerp(r, (r + vel) * k, (deltaTime - (step * numSteps)) / step);
return initial - r;
}
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoad]
#endif
// Internal for testing.
// This class keeps a running calculation of the average framerate over a fixed
// time window. Used to smooth out framerate fluctuations for determining the
// correct damping constant.
internal static class AverageFrameRateTracker
{
const int kBufferSize = 100;
static float[] s_Buffer = new float[kBufferSize];
static int s_NumItems = 0;
static int s_Head = 0;
static float s_Sum = 0;
public const float kSubframeTime = 1.0f / 1024.0f; // Do not change this without also changing SetDampTimeScale()
public static float FPS { get; private set; }
public static float DampTimeScale { get; private set; }
#if UNITY_EDITOR
static AverageFrameRateTracker() => Reset();
#endif
[RuntimeInitializeOnLoadMethod]
static void Initialize()
{
#if CINEMACHINE_EXPERIMENTAL_DAMPING
// GML TODO: use a different hook
Application.onBeforeRender -= Append;
Application.onBeforeRender += Append;
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
#endif
Reset();
}
static void OnSceneLoaded(Scene scene, LoadSceneMode mode) => Reset();
// Internal for testing
internal static void Reset()
{
s_NumItems = 0;
s_Head = 0;
s_Sum = 0;
FPS = 60;
SetDampTimeScale(FPS);
}
static void Append()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Reset();
return;
}
#endif
var dt = Time.unscaledDeltaTime;
if (++s_Head == kBufferSize)
s_Head = 0;
if (s_NumItems == kBufferSize)
s_Sum -= s_Buffer[s_Head];
else
++s_NumItems;
s_Sum += dt;
s_Buffer[s_Head] = dt;
FPS = s_NumItems / s_Sum;
SetDampTimeScale(FPS);
}
// Internal for testing
internal static void SetDampTimeScale(float fps)
{
// Approximation computed heuristically, and curve-fitted to sampled data.
// Valid only for kSubframeTime = 1.0f / 1024.0f
DampTimeScale = 2.0f - 1.81e-3f * fps + 7.9e-07f * fps * fps;
}
}
}
}