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