// Velocity Estimator|Utilities|90180
namespace VRTK
{
using UnityEngine;
using System.Collections;
///
/// The Velocity Estimator is used to calculate an estimated velocity on a moving object that is moving outside of Unity physics.
///
///
/// Objects that have rigidbodies and use Unity physics to move around will automatically provide accurate velocity and angular velocity information.
///
/// Any object that is moved around using absolute positions or moving the transform position will not be able to provide accurate velocity or angular velocity information.
/// When the Velocity Estimator script is applied to any GameObject it will provide a best case estimation of what the object's velocity and angular velocity is based on a given number of position and rotation samples.
/// The more samples used, the higher the precision but the script will be more demanding on processing time.
///
[AddComponentMenu("VRTK/Scripts/Utilities/VRTK_VelocityEstimator")]
public class VRTK_VelocityEstimator : MonoBehaviour
{
[Tooltip("Begin the sampling routine when the script is enabled.")]
public bool autoStartSampling = true;
[Tooltip("The number of frames to average when calculating velocity.")]
public int velocityAverageFrames = 5;
[Tooltip("The number of frames to average when calculating angular velocity.")]
public int angularVelocityAverageFrames = 10;
protected Vector3[] velocitySamples;
protected Vector3[] angularVelocitySamples;
protected int currentSampleCount;
protected Coroutine calculateSamplesRoutine;
///
/// The StartEstimation method begins logging samples of position and rotation for the GameObject.
///
public virtual void StartEstimation()
{
EndEstimation();
calculateSamplesRoutine = StartCoroutine(EstimateVelocity());
}
///
/// The EndEstimation method stops logging samples of position and rotation for the GameObject.
///
public virtual void EndEstimation()
{
if (calculateSamplesRoutine != null)
{
StopCoroutine(calculateSamplesRoutine);
calculateSamplesRoutine = null;
}
}
///
/// The GetVelocityEstimate method returns the current velocity estimate.
///
/// The velocity estimate vector of the GameObject
public virtual Vector3 GetVelocityEstimate()
{
Vector3 velocity = Vector3.zero;
int velocitySampleCount = Mathf.Min(currentSampleCount, velocitySamples.Length);
if (velocitySampleCount != 0)
{
for (int i = 0; i < velocitySampleCount; i++)
{
velocity += velocitySamples[i];
}
velocity *= (1.0f / velocitySampleCount);
}
return velocity;
}
///
/// The GetAngularVelocityEstimate method returns the current angular velocity estimate.
///
/// The angular velocity estimate vector of the GameObject
public virtual Vector3 GetAngularVelocityEstimate()
{
Vector3 angularVelocity = Vector3.zero;
int angularVelocitySampleCount = Mathf.Min(currentSampleCount, angularVelocitySamples.Length);
if (angularVelocitySampleCount != 0)
{
for (int i = 0; i < angularVelocitySampleCount; i++)
{
angularVelocity += angularVelocitySamples[i];
}
angularVelocity *= (1.0f / angularVelocitySampleCount);
}
return angularVelocity;
}
///
/// The GetAccelerationEstimate method returns the current acceleration estimate.
///
/// The acceleration estimate vector of the GameObject
public virtual Vector3 GetAccelerationEstimate()
{
Vector3 average = Vector3.zero;
for (int i = 2 + currentSampleCount - velocitySamples.Length; i < currentSampleCount; i++)
{
if (i < 2)
{
continue;
}
int first = i - 2;
int second = i - 1;
Vector3 v1 = velocitySamples[first % velocitySamples.Length];
Vector3 v2 = velocitySamples[second % velocitySamples.Length];
average += v2 - v1;
}
average *= (1.0f / Time.deltaTime);
return average;
}
protected virtual void OnEnable()
{
InitArrays();
if (autoStartSampling)
{
StartEstimation();
}
}
protected virtual void OnDisable()
{
EndEstimation();
}
protected virtual void InitArrays()
{
velocitySamples = new Vector3[velocityAverageFrames];
angularVelocitySamples = new Vector3[angularVelocityAverageFrames];
}
protected virtual IEnumerator EstimateVelocity()
{
currentSampleCount = 0;
Vector3 previousPosition = transform.localPosition;
Quaternion previousRotation = transform.localRotation;
while (true)
{
yield return new WaitForEndOfFrame();
float velocityFactor = 1.0f / Time.deltaTime;
int v = currentSampleCount % velocitySamples.Length;
int w = currentSampleCount % angularVelocitySamples.Length;
currentSampleCount++;
velocitySamples[v] = velocityFactor * (transform.localPosition - previousPosition);
Quaternion deltaRotation = transform.localRotation * 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 * velocityFactor * angularVelocity.normalized;
}
angularVelocitySamples[w] = angularVelocity;
previousPosition = transform.localPosition;
previousRotation = transform.localRotation;
}
}
}
}