
///////////////////////////////////////////////////////////////////////////////
//
// File   : Tools_FacialAnimation_FaceAnimation.cpp
// Author : Andy Tate
// Date   : 9-May-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_FaceAnimation.h"

// Asura independent header-files...
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "FacialAnimation/Asura_FacialAnimation_EyeParamAnimation.h"
#include "FacialAnimation/Asura_FacialAnimation_Face.h"
#include "FacialAnimation/Asura_FacialAnimation_FaceAnimation.h"
#include "FacialAnimation/Asura_FacialAnimation_System.h"

// Tools header-files...
#include "FacialAnimation/Tools_FacialAnimation_FaceType.h"
#include "FacialAnimation/Tools_FacialAnimation_System.h"


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

////////////////////////////////////////////////////////////////////////////////
// Version	Author	Comment
// ~~~~~~~	~~~~~~	~~~~~~~
// 1		ART		Created.
// 2		RAR		Changed to store muscle group animations instead of face pose animations. 23/09/2008
// 3		RAR		Added eye animation. 15/10/2008
// 4        EP      Added m_xBaseFacePoseName  26/11/2008
////////////////////////////////////////////////////////////////////////////////
const u_int	Tools_FacialAnimation_FaceAnimation::s_uChunkVersion	= 4;


////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////


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


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


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

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation(void)
: m_eStatus(CONVERTED_FROM_WAV)
, m_pxEyeParamAnimations(NULL)
{
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation
// Author       : Andy Tate
// Date         : 9-May-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation(const Asura_Hash_ID uHash,
																		 Asura_Chunk_Stream& xStream)
:m_pxEyeParamAnimations(NULL)
{
	
	ReadFromChunkStream(xStream);
	CreateAsuraFaceAnimation();
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::~Tools_FacialAnimation_FaceAnimation
// Author       : Richard Rice
// Date         : 15-Oct-2008
////////////////////////////////////////////////////////////////////////////////

Tools_FacialAnimation_FaceAnimation::~Tools_FacialAnimation_FaceAnimation()
{
	delete [] m_pxEyeParamAnimations;
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::Tools_FacialAnimation_FaceAnimation'. ///




////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::ReadFromChunkStream
// Author       : Andy Tate
// Date         : 9-May-2008
////////////////////////////////////////////////////////////////////////////////

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

	// First, read (and verify) the chunk-version.
	xStream >> uVersion;

	switch(uVersion)
	{
		case 1:
		{
			u_int	uStatus;
			u_int	uNumFacePoses;

			// Read the data...
			xStream >> m_xName;
			xStream >> m_xFaceTypeName;
			xStream >> m_xWAVFilename;
			xStream >> m_xDefaultExpressionName;
			xStream >> uStatus;
			xStream >> uNumFacePoses;

			// Update our variables...
			m_eStatus	= static_cast<Status>(uStatus);

			for(u_int u = 0; u < uNumFacePoses; u++)
			{
				Asura_Hash_ID	uFacePoseHash;

				xStream >> uFacePoseHash;

				if(uFacePoseHash != ASURA_HASH_ID_UNSET)
				{
					m_xFacePoseAnimationContainer.AddNode(uFacePoseHash, xStream);
				}
			}
			break;
		}
		case 2:
		{
			u_int	uStatus;
			u_int	uNumMuscleGroups;

			// Read the data...
			xStream >> m_xName;
			xStream >> m_xFaceTypeName;
			xStream >> m_xWAVFilename;
			xStream >> m_xDefaultExpressionName;
			xStream >> uStatus;
			xStream >> uNumMuscleGroups;

			// Update our variables...
			m_eStatus	= static_cast<Status>(uStatus);

			for(u_int u = 0; u < uNumMuscleGroups; u++)
			{
				Asura_Hash_ID	uMuscleGroupHash;

				xStream >> uMuscleGroupHash;

				if(uMuscleGroupHash != ASURA_HASH_ID_UNSET)
				{
					m_xMuscleGroupAnimationContainer.AddNode(uMuscleGroupHash, xStream);
				}
			}
			break;
		}

		case 3:
		{
			u_int	uStatus;
			u_int	uNumMuscleGroups;

			// Read the data...
			xStream >> m_xName;
			xStream >> m_xFaceTypeName;
			m_xBaseFacePoseName.MakeNull();
			xStream >> m_xWAVFilename;
			xStream >> m_xDefaultExpressionName;
			xStream >> uStatus;
			xStream >> uNumMuscleGroups;

			// Update our variables...
			m_eStatus	= static_cast<Status>(uStatus);

			for(u_int u = 0; u < uNumMuscleGroups; u++)
			{
				Asura_Hash_ID	uMuscleGroupHash;

				xStream >> uMuscleGroupHash;

				if(uMuscleGroupHash != ASURA_HASH_ID_UNSET)
				{
					m_xMuscleGroupAnimationContainer.AddNode(uMuscleGroupHash, xStream);
				}
			}
			
			//Any eye animation saved?
			int iNumEyeParam;
			xStream >> iNumEyeParam;
			if(iNumEyeParam)
			{
				//should always be the same number of values being animated for this
				Asura_Assert(iNumEyeParam == ASURA_FACIALANIMATION_EYE_NUMPARAM, "Wrong number of animations for eye parameters");
				m_pxEyeParamAnimations = new Asura_FacialAnimation_EyeParamAnimation[ASURA_FACIALANIMATION_EYE_NUMPARAM];
				
				for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM;i++)
				{
					m_pxEyeParamAnimations[i].ReadFromChunkStream(xStream);
				}
			}

			break;
		}

		case 4:
		{
			u_int	uStatus;
			u_int	uNumMuscleGroups;

			// Read the data...
			xStream >> m_xName;
			xStream >> m_xFaceTypeName;
			xStream >> m_xBaseFacePoseName;
			xStream >> m_xWAVFilename;
			xStream >> m_xDefaultExpressionName;
			xStream >> uStatus;
			xStream >> uNumMuscleGroups;

			// Update our variables...
			m_eStatus	= static_cast<Status>(uStatus);

			for(u_int u = 0; u < uNumMuscleGroups; u++)
			{
				Asura_Hash_ID	uMuscleGroupHash;

				xStream >> uMuscleGroupHash;

				if(uMuscleGroupHash != ASURA_HASH_ID_UNSET)
				{
					m_xMuscleGroupAnimationContainer.AddNode(uMuscleGroupHash, xStream);
				}
			}
			
			//Any eye animation saved?
			int iNumEyeParam;
			xStream >> iNumEyeParam;
			if(iNumEyeParam)
			{
				//should always be the same number of values being animated for this
				Asura_Assert(iNumEyeParam == ASURA_FACIALANIMATION_EYE_NUMPARAM, "Wrong number of animations for eye parameters");
				m_pxEyeParamAnimations = new Asura_FacialAnimation_EyeParamAnimation[ASURA_FACIALANIMATION_EYE_NUMPARAM];
				
				for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM;i++)
				{
					m_pxEyeParamAnimations[i].ReadFromChunkStream(xStream);
				}
			}

			break;
		}


		default:
		{
			Asura_Assertf(false, ("Unexpected chunk-version %d", uVersion));
			break;
		}
	}		// End of switch.
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::ReadFromChunkStream'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::WriteToChunkStream
// Author       : Andy Tate
// Date         : 9-May-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_FaceAnimation::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	const u_int	uStatus			= m_eStatus;
	const u_int	uNumMuscleGroups	= m_xMuscleGroupAnimationContainer.GetNumberOfNodes();

	// First write the chunk-version...
	xStream << s_uChunkVersion;

	xStream << m_xName;
	xStream << m_xFaceTypeName;
	xStream << m_xBaseFacePoseName;
	xStream << m_xWAVFilename;
	xStream << m_xDefaultExpressionName;
	xStream << uStatus;
	xStream << uNumMuscleGroups;
	
	for(MuscleGroupAnimationConstIterator xIterator(m_xMuscleGroupAnimationContainer); !xIterator.Done(); xIterator.Next())
	{
		const Tools_FacialAnimation_MuscleGroupAnimation*	pxMuscleGroupAnimation	= xIterator.GetCurrent();
		const Asura_Hash_ID									uMuscleGroupHash		= xIterator.GetCurrentHash();

		xStream << uMuscleGroupHash;

		if(pxMuscleGroupAnimation != NULL)
		{
			pxMuscleGroupAnimation->WriteToChunkStream(xStream);
		}
	}
	
	if(m_pxEyeParamAnimations)
	{
		xStream << (int) ASURA_FACIALANIMATION_EYE_NUMPARAM;
		for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM; i++)
		{
			m_pxEyeParamAnimations[i].WriteToChunkStream(xStream);
		}
	}
	else
	{
		xStream << 0;
	}

}

/// End of function 'Tools_FacialAnimation_FaceAnimation::WriteToChunkStream'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::AddPhoneme
// Author       : Andy Tate
// Date         : 5-Aug-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_FaceAnimation::AddPhoneme(const char* szPhoneme,
													 const u_int uStartMS, 
													 const u_int uEndMS,
													 const float fWeight)
{
	const Tools_FacialAnimation_FaceType*	pxFaceType	= Tools_FacialAnimation_System::FindOrLoadType(m_xFaceTypeName);

	if(pxFaceType == NULL)
	{
		Asura_Assertf(false, ("Failed to find face-type \"%s\" - cannot add ANY phonemes.", m_xFaceTypeName.GetString()));
	}
	else
	{
		const Asura_String*	pxFacePoseName	= pxFaceType->FindFacePoseNameForPhonemeName(szPhoneme);

		if(pxFacePoseName != NULL)
		{
			const Asura_Hash_ID								uFacePoseHash		= pxFacePoseName->GetHash();
			Tools_FacialAnimation_FaceAnimation_FacePose*	pxFacePoseAnimation	= m_xFacePoseAnimationContainer.GetDataByHash(uFacePoseHash);

			// Decide whether we need to create a new muscle-group animation...
			if(pxFacePoseAnimation == NULL)
			{
				pxFacePoseAnimation	= m_xFacePoseAnimationContainer.AddNode(uFacePoseHash);
			}

			// Add a new key-frame to the appropriate animation...
			if(pxFacePoseAnimation != NULL)
			{
				pxFacePoseAnimation->SetFacePoseName(*pxFacePoseName);
				pxFacePoseAnimation->AddWindow(uStartMS, uEndMS, fWeight);
			}
		}
	}
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::AddPhoneme'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::AddMuscleGroupKeyFrame
// Author       : Richard Rice
// Date         : 24-Sep-2008
////////////////////////////////////////////////////////////////////////////////

void Tools_FacialAnimation_FaceAnimation::AddMuscleGroupKeyFrame(const char* szMuscleGroup,  float fTime, float fWeight, float fBalance)
{
	const Asura_Hash_ID	uMuscleGroupHash = Asura_GetHashID(szMuscleGroup);

	//find/create the muscle group animation
	Tools_FacialAnimation_MuscleGroupAnimation*	pxMuscleGroupAnimation = m_xMuscleGroupAnimationContainer.GetDataByHash(uMuscleGroupHash);

	// Decide whether we need to create a new muscle-group animation...
	if(pxMuscleGroupAnimation == NULL)
	{
		pxMuscleGroupAnimation = m_xMuscleGroupAnimationContainer.AddNode(uMuscleGroupHash);
		pxMuscleGroupAnimation->SetMuscleGroupName(szMuscleGroup);
	}

	pxMuscleGroupAnimation->AddKeyFrame(fTime, fWeight, fBalance);
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::AddMuscleGroupKeyFrame'. ///




////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_FacialAnimation_FaceAnimation::CreateAsuraFaceAnimation
// Author       : Andy Tate
// Date         : 7-Aug-2008
////////////////////////////////////////////////////////////////////////////////

Asura_FacialAnimation_FaceAnimation* Tools_FacialAnimation_FaceAnimation::CreateAsuraFaceAnimation(void) const
{
	const Tools_FacialAnimation_FaceType*	pxToolsFaceType	= Tools_FacialAnimation_System::FindOrLoadType(m_xFaceTypeName);

	if(pxToolsFaceType == NULL)
	{
		Asura_Assertf(false, ("Cannot find the face-type \"%s\" to use when creating this face-animation (\"%s\").", m_xFaceTypeName.GetString(), m_xName.GetString()));
		return(NULL);
	}
	else
	{
		const Asura_Hash_ID						uAnimationHash			= m_xName.GetHash();
		Asura_FacialAnimation_FaceAnimation*	pxAsuraFaceAnimation	= Asura_FacialAnimation_System::FindFaceAnimation(uAnimationHash);

		if(pxAsuraFaceAnimation != NULL)
		{
			Asura_FacialAnimation_System::DestroyFaceAnimation(uAnimationHash);
		}

		pxAsuraFaceAnimation	= Asura_FacialAnimation_System::CreateFaceAnimation(uAnimationHash);

		if(pxAsuraFaceAnimation == NULL)
		{
			Asura_Assertf(false, ("Failed to create face-animation \"%s\" - something's gone badly wrong.", m_xName.GetString()));
			return(NULL);
		}
		else
		{
			// Initialise the necessary data for this face-type...
			pxAsuraFaceAnimation->Tools_InitialiseFromFaceType(m_xFaceTypeName);

			// Create the animation for each muscle group...
			for(MuscleGroupAnimationConstIterator xMuscleGroupIterator(m_xMuscleGroupAnimationContainer); !xMuscleGroupIterator.Done(); xMuscleGroupIterator.Next())
			{
				const Tools_FacialAnimation_MuscleGroupAnimation*	pxToolsMuscleGroupAnimation	= xMuscleGroupIterator.GetCurrent();

				if(pxToolsMuscleGroupAnimation != NULL)
				{
					pxAsuraFaceAnimation->Tools_CreateFromMuscleGroupAnimation(*pxToolsMuscleGroupAnimation, *pxToolsFaceType);
				}
			}

			if(m_pxEyeParamAnimations)
			{
				pxAsuraFaceAnimation->Tools_CopyEyeAnimation(m_pxEyeParamAnimations);
			}

			return(pxAsuraFaceAnimation);
		}
	}
}

/// End of function 'Tools_FacialAnimation_FaceAnimation::CreateAsuraFaceAnimation'. ///



bool Tools_FacialAnimation_FaceAnimation::ConvertToMuscleGroupAnimation(void)
{
	const Tools_FacialAnimation_FaceType*	pxToolsFaceType	= Tools_FacialAnimation_System::FindOrLoadType(m_xFaceTypeName);

	if(!m_xFacePoseAnimationContainer.GetNumberOfNodes()) return false;
	Asura_Assert(!m_xMuscleGroupAnimationContainer.GetNumberOfNodes(), "Not expecting to have muscle group animation data before the conversion");

	if(pxToolsFaceType == NULL)
	{
		Asura_Assertf(false, ("Cannot find the face-type to use while converting %s face-animation.", m_xFaceTypeName.GetString() ) );
		return false;
	}

	// Create the animation for each face-pose...
	for(FacePoseAnimationConstIterator xFacePoseIterator(m_xFacePoseAnimationContainer); !xFacePoseIterator.Done(); xFacePoseIterator.Next())
	{
		const Tools_FacialAnimation_FaceAnimation_FacePose*	pxToolsFaceAnimationFacePose	= xFacePoseIterator.GetCurrent();

		if(pxToolsFaceAnimationFacePose != NULL)
		{
			const Asura_String&						xFacePoseName	= pxToolsFaceAnimationFacePose->GetFacePoseName();
			const Tools_FacialAnimation_FacePose*	pxToolsFacePose	= Tools_FacialAnimation_System::FindOrLoadPose(xFacePoseName);

			Asura_Assertf(pxToolsFacePose, ("Failed to get face pose '%s' while converting animation '%s'",xFacePoseName.GetString(), m_xName.GetString()));

			if(pxToolsFacePose != NULL)
			{
				AddMuscleGroupAnimationFromFacePoseAnimation(*pxToolsFaceAnimationFacePose, *pxToolsFacePose, *pxToolsFaceType);
			}
		}
	}

	//The face pose animations can now be discarded
	m_xFacePoseAnimationContainer.Reset();

	// This function will remove redundant keys and also apply base face pose to it.
	OptimiseMuscleGroupAnimation();

	// It will create eye group if necessary and it will apply base face pose to it.
	CreateAndOptimiseEyesGroups();

	return true;
}

void Tools_FacialAnimation_FaceAnimation::CreateAndOptimiseEyesGroups()
{
	const Tools_FacialAnimation_FacePose* pxToolsBaseFacePose = Tools_FacialAnimation_System::FindOrLoadPose(m_xBaseFacePoseName);

	// If eye param doesn't exist, then create one for us. and fill it with zero parameters at the beginning.
	if( m_pxEyeParamAnimations==NULL )
	{
		InitialiseEyeAnimation( 1 );	// By default, set only default keyframes with time 0.0f

		// Clear them to zero.
		for( u_int u=0; u<ASURA_FACIALANIMATION_EYE_NUMPARAM; u++ )
		{
			m_pxEyeParamAnimations[u].Tools_SetKeyFrame( 0, 0.0f, 0.0f );
		}
	}

	Asura_Assert( m_pxEyeParamAnimations, "Function: Tools_FacialAnimation_FaceAnimation::CreateAndOptimiseEyesGroups can't find eye param animations." );

	// Ok, do test again. This time it should be certain that m_pxEyeParamAnimations does exist. But double check is always handy.
	if( m_pxEyeParamAnimations )
	{
		for( u_int u=0; u<ASURA_FACIALANIMATION_EYE_NUMPARAM; u++ )
		{
			m_pxEyeParamAnimations[u].Tools_RemoveRedundantKeyFrames();

			m_pxEyeParamAnimations[u].Tools_CombineBaseFacePose( pxToolsBaseFacePose->GetEyeValue(u) );
		}
	}
}


void Tools_FacialAnimation_FaceAnimation::OptimiseMuscleGroupAnimation()
{
	// Retrieve info, if the sentence is "Sad", "Happy", "Scared", etc... Later on this info will be added to animation to make the animation really proper one.
	const Tools_FacialAnimation_FacePose*									pxToolsBaseFacePose						= Tools_FacialAnimation_System::FindOrLoadPose(m_xBaseFacePoseName);
	const Tools_FacialAnimation_FacePose::MuscleGroupWeightingContainer*	pxToolsBaseFacePoseMuscleGroupContainer	= (pxToolsBaseFacePose == NULL)
		? NULL
		: &pxToolsBaseFacePose->GetMuscleGroupWeightingContainer();

	Asura_Assertf( (!m_xBaseFacePoseName.IsNull() && pxToolsBaseFacePose ), ( "Can't find this pose: %s  (%s , %s)", m_xBaseFacePoseName.GetString(), m_xName.GetString(), m_xFaceTypeName.GetString() ) );

	//Iterate over the muscle group animations and merge together keyframes as needed
	for(MuscleGroupAnimationIterator xMuscleGroupIterator(m_xMuscleGroupAnimationContainer); !xMuscleGroupIterator.Done(); xMuscleGroupIterator.Next())
	{
		Tools_FacialAnimation_MuscleGroupAnimation* pxMuscleGroupAnimation = xMuscleGroupIterator.GetCurrent();

		if(pxMuscleGroupAnimation != NULL)
		{
			pxMuscleGroupAnimation->MergeSameTimeKeyFrames();
			pxMuscleGroupAnimation->RemoveRedundantKeyFrames();

			if(pxToolsBaseFacePoseMuscleGroupContainer != NULL)
			{
				const Asura_Hash_ID											uMuscleGroupHash		= xMuscleGroupIterator.GetCurrentHash();
				Tools_FacialAnimation_MuscleGroupWeighting*					xMuscleGroupWeighting	= pxToolsBaseFacePoseMuscleGroupContainer->GetDataByHash(uMuscleGroupHash);

				Asura_Assert( xMuscleGroupWeighting, "Can't match base face pose muscle group animation" );

				if( xMuscleGroupWeighting )
				{
					const float	fBaseFacePoseWeight		= xMuscleGroupWeighting->GetWeight();
					const float	fBaseFacePoseBalance	= xMuscleGroupWeighting->GetBalance();

					pxMuscleGroupAnimation->CombineBaseFacePose(fBaseFacePoseWeight, fBaseFacePoseBalance);
				}
			}
		}
	}

}

bool Tools_FacialAnimation_FaceAnimation::NeedsConversionToMuscleGroupAnimation()
{
	if(m_xMuscleGroupAnimationContainer.GetNumberOfNodes()) return false;
	return m_xFacePoseAnimationContainer.GetNumberOfNodes() != 0;
}

void Tools_FacialAnimation_FaceAnimation::AddMuscleGroupAnimationFromFacePoseAnimation(const Tools_FacialAnimation_FaceAnimation_FacePose& xToolsFacePoseAnimation, const Tools_FacialAnimation_FacePose& xToolsFacePose, const Tools_FacialAnimation_FaceType& xToolsFaceType)
{
	const Tools_FacialAnimation_FacePose::MuscleGroupWeightingContainer&	xMuscleGroupWeightingContainer	= xToolsFacePose.GetMuscleGroupWeightingContainer();

	//Create muscle group animation for each muscle group weighting that the face pose has.
	for(Tools_FacialAnimation_FacePose::MuscleGroupWeightingConstIterator xMuscleGroupIterator(xMuscleGroupWeightingContainer); !xMuscleGroupIterator.Done(); xMuscleGroupIterator.Next())
	{
		const Tools_FacialAnimation_MuscleGroupWeighting*	pxToolsMuscleGroupWeighting	= xMuscleGroupIterator.GetCurrent();

		if(pxToolsMuscleGroupWeighting != NULL)
		{
			const Asura_Hash_ID	uMuscleGroupHash = xMuscleGroupIterator.GetCurrentHash();

			//find/create the muscle group animation
			Tools_FacialAnimation_MuscleGroupAnimation*	pxMuscleGroupAnimation = m_xMuscleGroupAnimationContainer.GetDataByHash(uMuscleGroupHash);

			// Decide whether we need to create a new muscle-group animation...
			if(pxMuscleGroupAnimation == NULL)
			{
				const Asura_String&	xName	= pxToolsMuscleGroupWeighting->GetName();

				pxMuscleGroupAnimation = m_xMuscleGroupAnimationContainer.AddNode(uMuscleGroupHash);
				pxMuscleGroupAnimation->SetMuscleGroupName(xName.GetString());
			}


			const Tools_FacialAnimation_FaceAnimation_FacePose::KeyFrameContainer&	xToolsKeyFrameContainer	= xToolsFacePoseAnimation.GetKeyFrameContainer();
			const float																fMuscleGroupWeight		= pxToolsMuscleGroupWeighting->GetWeight();
			const float																fMuscleGroupBalance		= pxToolsMuscleGroupWeighting->GetBalance();

			// Take each key-frame on this face pose animation and add a key-frame to the muscle group animation...
			for(Tools_FacialAnimation_FaceAnimation_FacePose::KeyFrameConstIterator xKeyFrameIterator(xToolsKeyFrameContainer); !xKeyFrameIterator.Done(); xKeyFrameIterator.Next())
			{
				const Tools_FacialAnimation_FaceAnimation_FacePose_KeyFrame*	pxToolsKeyFrame	= xKeyFrameIterator.GetCurrent();

				if(pxToolsKeyFrame != NULL)
				{
					const float	fTime	= pxToolsKeyFrame->GetTime();
					const float	fWeight	= pxToolsKeyFrame->GetWeight();

					pxMuscleGroupAnimation->AddKeyFrame(fTime, fWeight * fMuscleGroupWeight, fMuscleGroupBalance);
				}
			}
			
		}
	}
}


// Not used at all for the moment. But it is functional. EP 2/Dec/2008 
// This is needed by the max plugins. RAR 04/12/2008
void Tools_FacialAnimation_FaceAnimation::RemoveRedundantKeyFrames()
{
	Asura_Assert(!m_xFacePoseAnimationContainer.GetNumberOfNodes(), "RemoveRedundanyKeyFrames() not implemented for face pose animations (only for muscle group animation).");

	//Remove the redundant keyframes from each muscle group animation
	for(MuscleGroupAnimationIterator xMuscleGroupIterator(m_xMuscleGroupAnimationContainer); !xMuscleGroupIterator.Done(); xMuscleGroupIterator.Next())
	{
		Tools_FacialAnimation_MuscleGroupAnimation* pxMuscleGroupAnimation = xMuscleGroupIterator.GetCurrent();
			
		if(pxMuscleGroupAnimation != NULL)
		{
			pxMuscleGroupAnimation->RemoveRedundantKeyFrames();
		}
	}

	if(m_pxEyeParamAnimations)
	{
		for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM; i++)
		{
			m_pxEyeParamAnimations[i].Tools_RemoveRedundantKeyFrames();
		}
	}
	
}



float Tools_FacialAnimation_FaceAnimation::GetDuration() const
{
	Asura_Assert(!m_xFacePoseAnimationContainer.GetNumberOfNodes(), "GetDuration() not implemented for face pose animations (only for muscle group animation).");

	float fDuration = 0.0f;
	for(MuscleGroupAnimationConstIterator xMuscleGroupIterator(m_xMuscleGroupAnimationContainer); !xMuscleGroupIterator.Done(); xMuscleGroupIterator.Next())
	{
		const Tools_FacialAnimation_MuscleGroupAnimation* pxMuscleGroupAnimation = xMuscleGroupIterator.GetCurrent();
			
		if(pxMuscleGroupAnimation != NULL)
		{
			float fMGDuration = pxMuscleGroupAnimation->GetDuration();
			fDuration = Asura_Maths::Max(fDuration, fMGDuration);
		}
	}
	if(m_pxEyeParamAnimations)
	{
		for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM; i++)
		{
			float fEyeDuration = m_pxEyeParamAnimations[i].Tools_GetLastKeyFrameTime();
			fDuration = Asura_Maths::Max(fDuration, fEyeDuration);
		}
	}

	return fDuration;
}


void Tools_FacialAnimation_FaceAnimation::InitialiseEyeAnimation(u_int uNumKeyFrames)
{
	delete [] m_pxEyeParamAnimations;
	m_pxEyeParamAnimations = new Asura_FacialAnimation_EyeParamAnimation[ASURA_FACIALANIMATION_EYE_NUMPARAM];
	for(int i=0; i<ASURA_FACIALANIMATION_EYE_NUMPARAM; i++)
	{
		m_pxEyeParamAnimations[i].Tools_AllocateKeyFrames(uNumKeyFrames);
	}
}

void Tools_FacialAnimation_FaceAnimation::SetEyeKeyFrame(u_int uParam, u_int uFrame, float fTime, float fValue)
{
	Asura_Assert(m_pxEyeParamAnimations, "");
	Asura_Assert(uParam < ASURA_FACIALANIMATION_EYE_NUMPARAM, "");

	m_pxEyeParamAnimations[uParam].Tools_SetKeyFrame(uFrame, fTime, fValue);
	
}

const Asura_FacialAnimation_EyeParamAnimation* Tools_FacialAnimation_FaceAnimation::GetEyeAnimation(u_int uParam) const
{
	if(!m_pxEyeParamAnimations || uParam>=ASURA_FACIALANIMATION_EYE_NUMPARAM)
	{
		Asura_Assert(m_pxEyeParamAnimations,"Animation hasn't got any eye animation");
		Asura_Assert(uParam < ASURA_FACIALANIMATION_EYE_NUMPARAM, "Invalid eye parameter index");
		return 0;
	}

	return &m_pxEyeParamAnimations[uParam];
}

#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)


/// End of file. ///
