// Actually, the following files might have been made static methods of corresponding
// key structures; but the key structures are declared in a common interface file for now.

#include "stdafx.h"
#include "CryKeyInterpolation.h"


// interpolates the given key linearly out of the given left and right keys, given the time
void InterpolateCryBoneKey (const CryBoneKey& keyLeft, const CryBoneKey& keyRight, int nTime, CryBoneKey& keyOutput)
{
	keyOutput.time = nTime;
	float fTime = float (nTime-keyLeft.time) / (keyRight.time-keyLeft.time);
	keyOutput.relpos = keyLeft.relpos + (keyRight.relpos - keyLeft.relpos) * fTime;
	keyOutput.relquat = Slerp (keyLeft.relquat, keyRight.relquat, fTime);
	keyOutput.relquat.Normalize();
}

// check whether the difference between the two keys is within the specified bounds:
// the Dot product of the quaternions (1==none), and the difference between positions (0=none)
// NOTE: the position delta is defined as a square
bool IsErrorSmall (const CryBoneKey& key1, const CryBoneKey& key2, float fMaxPosDelta2, float fMinQuatDot)
{
	return (key1.relpos - key2.relpos).GetLengthSquared() < fMaxPosDelta2
		&& fabs(key1.relquat|(key2.relquat)) > fMinQuatDot;
}


// Reduces keyframes that can be interpolated by surrounding keys.
// The error metric is max[1-|QDot|] max[]
// Returns the count of optimized keys, and the reduced keys themselves in the passed in array.
unsigned OptimizeKeys (CryBoneKey* pBoneKeys, unsigned nNumKeys, float fPosError, float fQuatError)
{
	// for each starting interval key
	if (nNumKeys > 2)
	for (unsigned i = 0; i < nNumKeys-2; ++i)
  {
    CryBoneKey& keyLeft = pBoneKeys[i];
		unsigned nBest = i + 1; // best interval end
		// for each ending interval key
		for (unsigned j = i+2; j < nNumKeys; ++j)
		{
			CryBoneKey& keyRight = pBoneKeys[j];
			
			nBest = j; // starting with empty interval (no keys to reduce)

			// for each key inside the interval
			for (unsigned k = i+1; k < j; ++k)
			{
				CryBoneKey& keyMiddle = pBoneKeys[k];
				CryBoneKey keyInterp;
				InterpolateCryBoneKey(keyLeft, keyRight, keyMiddle.time, keyInterp);
				if (!IsErrorSmall(keyMiddle, keyInterp, fPosError, fQuatError))
				{
					nBest = j - 1; // this interval is bad, but the previous one was good
					j = nNumKeys; // stop iterating
					break;
				}
			}
		}
		if (nBest > i+1)
		{
      memmove (&pBoneKeys[i+1], &pBoneKeys[nBest], (nNumKeys-nBest)*sizeof(CryBoneKey));
			nNumKeys -= nBest - i - 1;
		}
  }

	while (nNumKeys > 1 && IsErrorSmall(pBoneKeys[nNumKeys-1], pBoneKeys[nNumKeys-2], fPosError, fQuatError))
	{
		nNumKeys--;
	}

	while (nNumKeys > 1 && IsErrorSmall(pBoneKeys[0], pBoneKeys[1], fPosError, fQuatError))
	{
		nNumKeys--;
		memmove (pBoneKeys, pBoneKeys+1, nNumKeys*sizeof(CryBoneKey));
	}

	return nNumKeys;
}
