//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Estimates the velocity of an object based on change in position
//
//=============================================================================

using UnityEngine;
using System.Collections;

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	public class VelocityEstimator : MonoBehaviour
	{
		[Tooltip( "How many frames to average over for computing velocity" )]
		public int velocityAverageFrames = 5;
		[Tooltip( "How many frames to average over for computing angular velocity" )]
		public int angularVelocityAverageFrames = 11;

		public bool estimateOnAwake = false;

		private Coroutine routine;
		private int sampleCount;
		private Vector3[] velocitySamples;
		private Vector3[] angularVelocitySamples;


		//-------------------------------------------------
		public void BeginEstimatingVelocity()
		{
			FinishEstimatingVelocity();

			routine = StartCoroutine( EstimateVelocityCoroutine() );
		}


		//-------------------------------------------------
		public void FinishEstimatingVelocity()
		{
			if ( routine != null )
			{
				StopCoroutine( routine );
				routine = null;
			}
		}


		//-------------------------------------------------
		public Vector3 GetVelocityEstimate()
		{
			// Compute average velocity
			Vector3 velocity = Vector3.zero;
			int velocitySampleCount = Mathf.Min( sampleCount, velocitySamples.Length );
			if ( velocitySampleCount != 0 )
			{
				for ( int i = 0; i < velocitySampleCount; i++ )
				{
					velocity += velocitySamples[i];
				}
				velocity *= ( 1.0f / velocitySampleCount );
			}

			return velocity;
		}


		//-------------------------------------------------
		public Vector3 GetAngularVelocityEstimate()
		{
			// Compute average angular velocity
			Vector3 angularVelocity = Vector3.zero;
			int angularVelocitySampleCount = Mathf.Min( sampleCount, angularVelocitySamples.Length );
			if ( angularVelocitySampleCount != 0 )
			{
				for ( int i = 0; i < angularVelocitySampleCount; i++ )
				{
					angularVelocity += angularVelocitySamples[i];
				}
				angularVelocity *= ( 1.0f / angularVelocitySampleCount );
			}

			return angularVelocity;
		}


		//-------------------------------------------------
		public Vector3 GetAccelerationEstimate()
		{
			Vector3 average = Vector3.zero;
			for ( int i = 2 + sampleCount - velocitySamples.Length; i < sampleCount; 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;
		}


		//-------------------------------------------------
		void Awake()
		{
			velocitySamples = new Vector3[velocityAverageFrames];
			angularVelocitySamples = new Vector3[angularVelocityAverageFrames];

			if ( estimateOnAwake )
			{
				BeginEstimatingVelocity();
			}
		}


		//-------------------------------------------------
		private IEnumerator EstimateVelocityCoroutine()
		{
			sampleCount = 0;

			Vector3 previousPosition = transform.position;
			Quaternion previousRotation = transform.rotation;
			while ( true )
			{
				yield return new WaitForEndOfFrame();

				float velocityFactor = 1.0f / Time.deltaTime;

				int v = sampleCount % velocitySamples.Length;
				int w = sampleCount % angularVelocitySamples.Length;
				sampleCount++;

				// Estimate linear velocity
				velocitySamples[v] = velocityFactor * ( transform.position - previousPosition );

				// Estimate angular velocity
				Quaternion deltaRotation = transform.rotation * 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.position;
				previousRotation = transform.rotation;
			}
		}
	}
}
