
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Actor_VelocityInterceptor.h
// Author : Paul Humphries
// Date   : 23-Feb-2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif   // ifdef ASURA_USE_PRAGMA_ONCE

#ifndef __AVP_ACTOR_VELOCITYINTERCEPTOR_H__
#define __AVP_ACTOR_VELOCITYINTERCEPTOR_H__

// Core Asura
#include "Core/Asura_List.h"

#include "Core/AVP_Maths.h"

// Velocity interceptor command class (used for elements in the queue)
template <class T> class AVP_Actor_VelocityInterceptorCommand
{
public:
	float	m_fThreshold;
	T		m_xTargetPosition;
	float	m_fDuration;

	AVP_Actor_VelocityInterceptorCommand(const float fThreshold, const T& xTarget, const float fDuration)
	{
		m_fThreshold		= fThreshold;
		m_xTargetPosition	= xTarget;
		m_fDuration			= fDuration;
	}
};

///////////////////////////////////////////////////////////////////////////////
//  AVP_Actor_VelocityInterceptor template class
//
// Default constructor uses static m_xZeroValue. Or construct one with an initial value/position.
// Call CalculatePosition and CalculateVelocity provided the time is updated with AddTime(fTimeSlice).
// Initially the position will be the initial position and the velocity will be the zero (m_xZeroValue).
// Call SetTarget with a new position and time taken to get there, and the current velocity
// will be used as the initial velocity for the movement. The position will move smoothly to
// the target and arrive there with zero velocity after the time specified.
// Call GetComplete to obtain how complete the current movement is (from 0.0f to 1.0f).
// Targets can be queued by calling AddTarget with a threshold, new position and time.
///////////////////////////////////////////////////////////////////////////////
// Velocity-interpolator uses Newton's of motion, with the following knowns...
// - distance to cover (S)
// - time to cover the distance (T)
// - initial velocity (U)
// - final velocity should be zero
//
//   ^					For this graph:
//  V+    +				->	S = S1 + S2
//   |   /|\			.
//   |  / | \			Using geometry:
//   | /  |  \				     (U + V)   T   U.T   V.T
//   |/   |   \			->	S1 = ------- x - = --- + ---
//  U+    |    \			        2      2    4     4
//   | S1 |  S2 \		.
//   |    |      \			     (V + 0)   T   V.T
//  0+----+-------+->	->	S2 = ------- x - = ---
//   0   T/2      T			        2      2    4
//
//		S = S1 + S2
//
//		    U.T   V.T   V.T
//  ->	S = --- + --- + ---
//		     4     4     4
//
//	->	4.S = U.T + 2.V.T
//
//		    (4.S - U.T)
//	->	V = -----------
//		        2.T
///////////////////////////////////////////////////////////////////////////////

template <class T> class AVP_Actor_VelocityInterceptor
{
public:
	AVP_Actor_VelocityInterceptor(const T& xZeroValue)
	: m_xZeroValue(xZeroValue)
	, m_xTargetPosition(m_xZeroValue)
	, m_fDuration(0.0f)
	{
	}

	AVP_Actor_VelocityInterceptor(const T& xInitialPosition, const T& xZeroValue)
	: m_xZeroValue(xZeroValue)
	, m_xTargetPosition(xInitialPosition)
	, m_fDuration(0.0f)
	{
	}

	// Reset to the position specified (zero velocity)...
	void Reset(const T& xPosition)
	{
		m_xTargetPosition	= xPosition;
		m_fDuration			= 0.0f;

		m_xQueue.ClearList();
	}

	// Set a new target to move to after the time specified...
	void SetTarget(const T& xInitialPosition, const T& xInitialVelocity, const T& xTarget, const float fDuration)
	{
		// Initialise the duration, initial and target data...
		m_xInitialPosition	= xInitialPosition;
		m_xInitialVelocity	= xInitialVelocity;
		m_xTargetPosition	= xTarget;
		m_fDuration			= fDuration;
		m_fDurationOverTwo	= fDuration * 0.5f;
		m_fTime				= 0.0f;

		// Calculate the data for the first-half of the interpolation...
		///////////////////////////////////////////////////////////////////////////////
		// Using the above equations (where V is "m_xMidTimeVelocity" and A! is "m_xAcceleration1"):
		//	     (V - U)
		//	A1 = -------
		//	        T
		///////////////////////////////////////////////////////////////////////////////
		m_xMidTimeVelocity	= (((m_xTargetPosition - m_xInitialPosition) * 4.0f) - (m_xInitialVelocity * m_fDuration)) / (2.0f * m_fDuration);
		m_xAcceleration1	= (m_xMidTimeVelocity - m_xInitialVelocity) / m_fDurationOverTwo;

		// Calculate the data for the second-half of the interpolation...
		///////////////////////////////////////////////////////////////////////////////
		// Using the above equations (where A2 is "m_xAcceleration2"):
		//	           (A1.T.T)
		//	S1 = U.T + --------
		//	              T/2
		//		 (0 - V)
		//	A2 = -------
		//	       T/2
		///////////////////////////////////////////////////////////////////////////////
		m_xMidTimePosition	= m_xInitialPosition + (m_xInitialVelocity * m_fDurationOverTwo) + (m_xAcceleration1 * (m_fDurationOverTwo * m_fDurationOverTwo) * 0.5f);
		m_xAcceleration2	= -m_xMidTimeVelocity / m_fDurationOverTwo;
	}

	// Set a new target to move to after the time specified...
	void SetTarget(const T& xTarget, const float fDuration)
	{
		T	xInitialPosition;
		T	xInitialVelocity;

		// Use the current values as our starting point...
		CalculatePosition(xInitialPosition);
		CalculateVelocity(xInitialVelocity);

		SetTarget(xInitialPosition, xInitialVelocity, xTarget, fDuration);
	}

	// Get the current target-position...
	void GetTarget(T& xTarget) const
	{
		xTarget = m_xTargetPosition;
	}

	// Calculate the current position...
	void CalculatePosition(T& xPosition) const
	{
		if(m_fDuration == 0.0f)
		{
			// We're no longer moving...
			xPosition	= m_xTargetPosition;
		}
		else if(m_fTime >= m_fDuration)
		{
			// We've just finished moving...
			xPosition	= m_xTargetPosition;
		}
		else
		{
			///////////////////////////////////////////////////////////////////////////////
			//	          (a.t.t)
			//	s = u.t + -------
			//	             2
			///////////////////////////////////////////////////////////////////////////////
			if(m_fTime < m_fDurationOverTwo)
			{
				// We're on the first-half of the interpolation...
				xPosition	= m_xInitialPosition + (m_xInitialVelocity * m_fTime) + (m_xAcceleration1 * (m_fTime * m_fTime) * 0.5f);
			}
			else
			{
				const float	fTime	= m_fTime - m_fDurationOverTwo;

				// We're on the second-half of the interpolation...
				xPosition	= m_xMidTimePosition + (m_xMidTimeVelocity * fTime) + (m_xAcceleration2 * (fTime * fTime) * 0.5f);
			}
		}
	}

	// Calculate the current velocity...
	void CalculateVelocity(T& xVelocity) const
	{
		if(m_fDuration == 0.0f)
		{
			// We're no longer moving...
			xVelocity	= m_xZeroValue;
		}
		else if (m_fTime >= m_fDuration)
		{
			// We've just finished moving...
			xVelocity	= m_xZeroValue;
		}
		else
		{
			///////////////////////////////////////////////////////////////////////////////
			//	v = u + a.t
			///////////////////////////////////////////////////////////////////////////////
			if (m_fTime < m_fDurationOverTwo)
			{
				// We're on the first-half of the interpolation...
				xVelocity	= m_xInitialVelocity + (m_xAcceleration1 * m_fTime);
			}
			else
			{
				const float	fTime	= m_fTime - m_fDurationOverTwo;

				// We're on the second-half of the interpolation...
				xVelocity	= m_xMidTimeVelocity + (m_xAcceleration2 * fTime);
			}
		}
	}

	// Returns true if a move is currently taking place and false if not...
	bool IsMoving(void) const
	{
		return(m_fDuration > 0.0f);
	}

	// Returns how complete the current move is (from 0.0f to 1.0f => completed)...
	float GetComplete(void) const
	{
		if((m_fDuration == 0.0f) || (m_fTime > m_fDuration))
		{
			return(1.0f);
		}
		else
		{
			return(m_fTime / m_fDuration);
		}
	}

	// This should be called every frame to update the time value...
	void AddTime(const float fTimeSlice)
	{
		AVP_Actor_VelocityInterceptorCommand <T>* xNext = m_xQueue.GetHeadObject();

		m_fTime += fTimeSlice;

		if(m_fTime > m_fDuration)
		{
			m_fDuration	= 0.0f;
		}

		if (xNext)
		{
			if (GetComplete() > xNext->m_fThreshold)
			{
				SetTarget(xNext->m_xTargetPosition, xNext->m_fDuration);
				m_xQueue.PopHeadObject();
			}
		}
	}

	// Add a target to the queue. The next move in the queue will start when the threshold is reached....
	void AddTarget (const float fThreshold, const T& xTarget, const float fDuration)
	{
		m_xQueue.AddToList(new AVP_Actor_VelocityInterceptorCommand <T>(fThreshold, xTarget, fDuration));
	}


protected:
	const T&												m_xZeroValue;

	T														m_xInitialPosition;
	T														m_xMidTimePosition;
	T														m_xTargetPosition;

	T														m_xInitialVelocity;
	T														m_xMidTimeVelocity;

	T														m_xAcceleration1;
	T														m_xAcceleration2;

	float													m_fDuration;
	float													m_fDurationOverTwo;
	float													m_fTime;

	Asura_List <AVP_Actor_VelocityInterceptorCommand<T> >	m_xQueue;
};

#endif		// ifndef __AVP_ACTOR_VELOCITYINTERCEPTOR_H__

/// End of file. ///

