
///////////////////////////////////////////////////////////////////////////////
//
// File   : Tools_FacialAnimation_MuscleGroupAnimation.cpp
// Author : Richard Rice
// Date   : 23-Sep-2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)

// This file's header...
#include "FacialAnimation/Tools_FacialAnimation_MuscleGroupAnimation.h"

// Asura independent header-files...
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "FacialAnimation/Asura_FacialAnimation_MuscleGroupAnimation.h"

// Tools header-files...


////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////
DEBUGVAR float	dbg_fMuscleGroupAnimationIdenticalTimeThreshold	= 0.010f;

////////////////////////////////////////////////////////////////////////////////
// Version	Author	Comment
// ~~~~~~~	~~~~~~	~~~~~~~
// 1		RAR		Created.
////////////////////////////////////////////////////////////////////////////////
const u_int	Tools_FacialAnimation_MuscleGroupAnimation::s_uChunkVersion	= 1;


////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////


Tools_FacialAnimation_MuscleGroupAnimation::Tools_FacialAnimation_MuscleGroupAnimation(void)
{
}


Tools_FacialAnimation_MuscleGroupAnimation::Tools_FacialAnimation_MuscleGroupAnimation(const Asura_Hash_ID uHash,
																					   Asura_Chunk_Stream& xStream)
{
	ReadFromChunkStream(xStream);
}


void Tools_FacialAnimation_MuscleGroupAnimation::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	u_int	uVersion;
	u_int	uNumKeyFrames;

	// Remove any existing data...
	m_xKeyFrameContainer.Clear();

	// Firstly, read the version, then the data itself...
	xStream >> uVersion;

	xStream >> m_xMuscleGroupName;
	xStream >> uNumKeyFrames;

	for(u_int u = 0; u < uNumKeyFrames; u++)
	{
		float	fTime;
		float	fWeight;
		float	fBalance;

		xStream >> fTime;
		xStream >> fWeight;
		xStream >> fBalance;

		{
			Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame xNewKeyFrame(fTime, fWeight, fBalance);

			m_xKeyFrameContainer.Push_Back(xNewKeyFrame);
		}
	}
}


void Tools_FacialAnimation_MuscleGroupAnimation::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	const u_int	uNumKeyFrames	= m_xKeyFrameContainer.Size();

	xStream << s_uChunkVersion;

	xStream << m_xMuscleGroupName;
	xStream << uNumKeyFrames;

	for(KeyFrameConstIterator xIterator(m_xKeyFrameContainer); !xIterator.Done(); xIterator.Next())
	{
		const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame*	pxKeyFrame	= xIterator.GetCurrent();
		const float														fTime		= pxKeyFrame->GetTime();
		const float														fWeight		= pxKeyFrame->GetWeight();
		const float														fBalance	= pxKeyFrame->GetBalance();

		xStream << fTime;
		xStream << fWeight;
		xStream << fBalance;
	}
}

void Tools_FacialAnimation_MuscleGroupAnimation::AddKeyFrame(float fTime, float fWeight, float fBalance)
{
	const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame xNewKeyFrame(fTime, fWeight, fBalance);

	// See whether this key-frame needs adding somewhere within the list...
	for(KeyFrameItem* pxItem = m_xKeyFrameContainer.Back(); (pxItem != NULL); pxItem = pxItem->Prev())
	{
		if(fTime > pxItem->Data().GetTime())
		{
			m_xKeyFrameContainer.Insert_After(pxItem, xNewKeyFrame);
			return;
		}
	}

	// Doesn't need inserting - add to the start of the list...
	m_xKeyFrameContainer.Push_Front(xNewKeyFrame);

}

void Tools_FacialAnimation_MuscleGroupAnimation::MergeSameTimeKeyFrames()
{
	Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxPrevKeyFrame = 0;	

	for(KeyFrameIterator xKeyFrameIt(m_xKeyFrameContainer); !xKeyFrameIt.Done(); )
	{
		Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxThisKeyFrame = xKeyFrameIt.GetCurrent();	
		
		//Is this keyframe close enough to the previous keyframe?
		if(pxPrevKeyFrame && (pxThisKeyFrame->GetTime()-pxPrevKeyFrame->GetTime()) <= dbg_fMuscleGroupAnimationIdenticalTimeThreshold)
		{
			//Merge this keyframe with the previous taking the highest weight and balance values.
			float fWeight,fBalance;

			if(Asura_Maths::Abs(pxPrevKeyFrame->GetWeight()) > Asura_Maths::Abs(pxThisKeyFrame->GetWeight()))
			{
				fWeight = pxPrevKeyFrame->GetWeight();
			}
			else
			{
				fWeight = pxThisKeyFrame->GetWeight();
			}

			if(Asura_Maths::Abs(pxPrevKeyFrame->GetBalance()) > Asura_Maths::Abs(pxThisKeyFrame->GetBalance()))
			{
				fBalance = pxPrevKeyFrame->GetBalance();
			}
			else
			{
				fBalance = pxThisKeyFrame->GetBalance();
			}

			pxPrevKeyFrame->Set(pxPrevKeyFrame->GetTime(), fWeight, fBalance);

			//Delete the keyframe that has been merged in
			xKeyFrameIt.DeleteCurrent();

		}
		else
		{
			//Keep this keyframe and move onto the next
			pxPrevKeyFrame = pxThisKeyFrame;
			xKeyFrameIt.Next();
		}
	}	
}

void Tools_FacialAnimation_MuscleGroupAnimation::CombineBaseFacePose(const float fBaseFacePoseWeight, const float fBaseFacePoseBalance)
{
	for(KeyFrameIterator xKeyFrameIt(m_xKeyFrameContainer); !xKeyFrameIt.Done(); xKeyFrameIt.Next() )
	{
		Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxThisKeyFrame = xKeyFrameIt.GetCurrent();	

		if(pxThisKeyFrame != NULL)
		{
			// Selected method for combining them is to add them and clamp them to maximum values if they are too big.
			const float fTime	= pxThisKeyFrame->GetTime();
			float	fWeight		= pxThisKeyFrame->GetWeight()  + fBaseFacePoseWeight;
			float	fBalance	= pxThisKeyFrame->GetBalance() + fBaseFacePoseBalance;

			fWeight  = Asura_Maths::ClampToRange( fWeight , -1.0f, 1.0f );
			fBalance = Asura_Maths::ClampToRange( fBalance, -1.0f, 1.0f );

			pxThisKeyFrame->Set( fTime, fWeight, fBalance);
		}
	}
}

void Tools_FacialAnimation_MuscleGroupAnimation::RemoveRedundantKeyFrames()
{
	const float fTolerance = 0.001f;

	const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxPrevKeyFrame = 0;	

	for(KeyFrameIterator xKeyFrameIt(m_xKeyFrameContainer); !xKeyFrameIt.Done(); )
	{
		const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxThisKeyFrame = xKeyFrameIt.GetCurrent();
		const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxNextKeyFrame = xKeyFrameIt.GetNext();

		bool bDeleteCurrent = false;
		if(pxPrevKeyFrame && pxThisKeyFrame && pxNextKeyFrame)
		{
			//Work out the weight/balance that would be got by interpolating between the previouos and next keyframes
			float fPrevTime = pxPrevKeyFrame->GetTime();
			float fNextTime = pxNextKeyFrame->GetTime();
			float fThisTime = pxThisKeyFrame->GetTime();

			if(fNextTime != fPrevTime)
			{
				float fLerp = (fThisTime - fPrevTime) / (fNextTime-fPrevTime);

				float fInterpWeight = (1-fLerp)*pxPrevKeyFrame->GetWeight() + fLerp*pxNextKeyFrame->GetWeight();
				float fInterpBalance = (1-fLerp)*pxPrevKeyFrame->GetBalance() + fLerp*pxNextKeyFrame->GetBalance();

				//Are the interpolated weight and balance close enough?
				float fWeightDiff = pxThisKeyFrame->GetWeight()-fInterpWeight;
				float fBalanceDiff = pxThisKeyFrame->GetBalance()-fInterpBalance;
				
				if(Asura_Maths::Abs(fWeightDiff)<fTolerance && Asura_Maths::Abs(fBalanceDiff)<fTolerance)
				{
					//can remove the middle frame
					bDeleteCurrent = true;
				}
			}
		}

		if(bDeleteCurrent)
		{
			xKeyFrameIt.DeleteCurrent();
		}
		else
		{
			pxPrevKeyFrame = pxThisKeyFrame;
			xKeyFrameIt.Next();
		}		
	}

	//If there are only 2 frames left , check to see if they are identical, if so , then remove the second
	if(m_xKeyFrameContainer.Size() == 2)
	{
		KeyFrameIterator xKeyFrameIt(m_xKeyFrameContainer);
		const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxFirstKeyFrame = xKeyFrameIt.GetCurrent();	

		xKeyFrameIt.Next();
		const Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame* pxLastKeyFrame = xKeyFrameIt.GetCurrent();	

		if(pxFirstKeyFrame->GetWeight() == pxLastKeyFrame->GetWeight() && pxFirstKeyFrame->GetBalance() == pxLastKeyFrame->GetBalance())
		{
			xKeyFrameIt.DeleteCurrent();
		}
		
	}
}

float Tools_FacialAnimation_MuscleGroupAnimation::GetDuration() const
{
	if(!m_xKeyFrameContainer.Size()) return 0.0f;

	//get the time from the last frame
	Asura_LinkList_ConstReverseIt<Asura_FacialAnimation_MuscleGroupAnimation_KeyFrame> xIt(m_xKeyFrameContainer);
	return xIt.GetCurrent()->GetTime();
}


#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)


/// End of file. ///
