namespace Zinnia.Tracking.Velocity
{
using System;
using UnityEngine;
using Zinnia.Extension;
using Zinnia.Process;
///
/// Takes average samples of a and and uses this cached data to estimate velocity and angular velocity.
///
public class AverageVelocityEstimatorProcess : VelocityTracker, IProcessable
{
[Tooltip("The source to track and estimate velocities for.")]
[SerializeField]
private GameObject source;
///
/// The source to track and estimate velocities for.
///
public GameObject Source
{
get
{
return source;
}
set
{
source = value;
if (this.IsMemberChangeAllowed())
{
OnAfterSourceChange();
}
}
}
[Tooltip("An optional object to consider the source relative to when estimating the velocities.")]
[SerializeField]
private GameObject relativeTo;
///
/// An optional object to consider the source relative to when estimating the velocities.
///
public GameObject RelativeTo
{
get
{
return relativeTo;
}
set
{
relativeTo = value;
if (this.IsMemberChangeAllowed())
{
OnAfterRelativeToChange();
}
}
}
[Tooltip("Whether samples are currently being collected.")]
[SerializeField]
private bool isEstimating = true;
///
/// Whether samples are currently being collected.
///
public bool IsEstimating
{
get
{
return isEstimating;
}
set
{
isEstimating = value;
}
}
[Tooltip("The number of average frames to collect samples for velocity estimation.")]
[SerializeField]
private int velocityAverageFrames = 5;
///
/// The number of average frames to collect samples for velocity estimation.
///
public int VelocityAverageFrames
{
get
{
return velocityAverageFrames;
}
set
{
velocityAverageFrames = value;
if (this.IsMemberChangeAllowed())
{
OnAfterVelocityAverageFramesChange();
}
}
}
[Tooltip("The number of average frames to collect samples for angular velocity estimation.")]
[SerializeField]
private int angularVelocityAverageFrames = 10;
///
/// The number of average frames to collect samples for angular velocity estimation.
///
public int AngularVelocityAverageFrames
{
get
{
return angularVelocityAverageFrames;
}
set
{
angularVelocityAverageFrames = value;
if (this.IsMemberChangeAllowed())
{
OnAfterAngularVelocityAverageFramesChange();
}
}
}
///
/// The current count of samples to calculate the velocity from.
///
protected int currentSampleCount;
///
/// The frame samples of velocity to used to calculate final velocity.
///
protected Vector3[] velocitySamples = Array.Empty();
///
/// The frame samples of angular velocity to used to calculate final angular velocity.
///
protected Vector3[] angularVelocitySamples = Array.Empty();
///
/// The previous position of the .
///
protected Vector3 previousPosition = Vector3.zero;
///
/// The previous rotation of the .
///
protected Quaternion previousRotation = Quaternion.identity;
///
/// The previous position of the .
///
protected Vector3 previousRelativePosition = Vector3.zero;
///
/// The previous rotation of the .
///
protected Quaternion previousRelativeRotation = Quaternion.identity;
///
/// Clears .
///
public virtual void ClearSource()
{
if (!this.IsValidState())
{
return;
}
Source = default;
}
///
/// Clears .
///
public virtual void ClearRelativeTo()
{
if (!this.IsValidState())
{
return;
}
RelativeTo = default;
}
///
public override bool IsActive()
{
return base.IsActive() && Source != null && Source.activeInHierarchy;
}
///
/// The acceleration of the .
///
/// Acceleration of the .
public virtual Vector3 GetAcceleration()
{
if (!this.IsValidState() || !IsActive())
{
return default;
}
Vector3 average = Vector3.zero;
for (int sampleIndex = 2 + currentSampleCount - velocitySamples.Length; sampleIndex < currentSampleCount; sampleIndex++)
{
if (sampleIndex >= 2)
{
int first = sampleIndex - 2;
int second = sampleIndex - 1;
Vector3 v1 = velocitySamples[first % velocitySamples.Length];
Vector3 v2 = velocitySamples[second % velocitySamples.Length];
average += v2 - v1;
}
}
average *= GetFactor();
return average;
}
protected virtual void OnEnable()
{
velocitySamples = new Vector3[VelocityAverageFrames];
angularVelocitySamples = new Vector3[AngularVelocityAverageFrames];
previousPosition = Source.TryGetPosition();
previousRotation = Source.TryGetRotation();
previousRelativePosition = RelativeTo.TryGetPosition();
previousRelativeRotation = RelativeTo.TryGetRotation();
}
///
public virtual void Process()
{
ProcessEstimation();
}
///
protected override Vector3 DoGetVelocity()
{
return GetEstimate(velocitySamples);
}
///
protected override Vector3 DoGetAngularVelocity()
{
return GetEstimate(angularVelocitySamples);
}
///
/// Calculates the multiplication factor for the velocities.
///
/// Multiplication value.
protected virtual float GetFactor()
{
return 1.0f / (Time.inFixedTimeStep ? Time.fixedDeltaTime : Time.deltaTime);
}
///
/// Calculates the average estimate for the given sample set.
///
/// An array of samples to estimate with.
/// The estimated result.
protected virtual Vector3 GetEstimate(Vector3[] samples)
{
Vector3 estimate = Vector3.zero;
int sampleCount = Mathf.Min(currentSampleCount, samples.Length);
if (sampleCount != 0)
{
for (int index = 0; index < sampleCount; index++)
{
estimate += samples[index];
}
estimate *= 1.0f / sampleCount;
}
return estimate;
}
///
/// Collects the appropriate samples for velocities and estimates the results.
///
protected virtual void ProcessEstimation()
{
if (IsEstimating)
{
float factor = GetFactor();
EstimateVelocity(factor);
EstimateAngularVelocity(factor);
currentSampleCount++;
}
else
{
currentSampleCount = 0;
}
}
///
/// Collects samples for velocity.
///
/// The multiplier to apply to the transform difference.
protected virtual void EstimateVelocity(float factor)
{
if (velocitySamples.Length == 0)
{
return;
}
Vector3 currentRelativePosition = RelativeTo.TryGetPosition();
Vector3 relativeDeltaPosition = currentRelativePosition - previousRelativePosition;
Vector3 currentPosition = Source.TryGetPosition();
int sampleIndex = currentSampleCount % velocitySamples.Length;
velocitySamples[sampleIndex] = factor * (currentPosition - previousPosition - relativeDeltaPosition);
previousPosition = currentPosition;
previousRelativePosition = currentRelativePosition;
}
///
/// Collects samples for angular velocity.
///
/// The multiplier to apply to the transform difference.
protected virtual void EstimateAngularVelocity(float factor)
{
if (angularVelocitySamples.Length == 0)
{
return;
}
Quaternion currentRelativeRotation = RelativeTo.TryGetRotation();
Quaternion relativeDeltaRotation = currentRelativeRotation * Quaternion.Inverse(previousRelativeRotation);
Quaternion currentRotation = Source.TryGetRotation();
Quaternion deltaRotation = Quaternion.Inverse(relativeDeltaRotation) * (currentRotation * Quaternion.Inverse(previousRotation));
float theta = 2.0f * Mathf.Acos(Mathf.Clamp(deltaRotation.w, -1.0f, 1.0f));
if (theta > Mathf.PI)
{
theta -= 2.0f * Mathf.PI;
}
Vector3 angularVelocity = new Vector3(deltaRotation.x, deltaRotation.y, deltaRotation.z);
if (angularVelocity.sqrMagnitude > 0.0f)
{
angularVelocity = theta * factor * angularVelocity.normalized;
}
int sampleIndex = currentSampleCount % angularVelocitySamples.Length;
angularVelocitySamples[sampleIndex] = angularVelocity;
previousRotation = currentRotation;
previousRelativeRotation = currentRelativeRotation;
}
///
/// Called after has been changed.
///
protected virtual void OnAfterSourceChange()
{
previousPosition = Source.TryGetPosition();
previousRotation = Source.TryGetRotation();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterRelativeToChange()
{
previousRelativePosition = RelativeTo.TryGetPosition();
previousRelativeRotation = RelativeTo.TryGetRotation();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterVelocityAverageFramesChange()
{
velocitySamples = new Vector3[VelocityAverageFrames];
}
///
/// Called after has been changed.
///
protected virtual void OnAfterAngularVelocityAverageFramesChange()
{
angularVelocitySamples = new Vector3[AngularVelocityAverageFrames];
}
}
}