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]; } } }