Bomberman-OverlordEngine-x64 / OverlordEngine / Misc / AnimationBlender.h
AnimationBlender.h
Raw
#pragma once
class AnimationBlender final
{
public:
	AnimationBlender(ModelAnimator* pAnimator)
		: m_pAnimator{ pAnimator }
		, m_BlendDuration{ 0.3f }
		, m_BlendTime{ 0 }
		, m_Blending{ false }
		, m_PreviousTickCount{ 0 }
    {}

    void StartBlend(const AnimationClip& newClip)
    {
        m_PreviousClip = m_pAnimator->m_CurrentClip; // save the last clip
        m_PreviousTickCount = m_PreviousClip.ticksPerSecond; // save the last progress
        m_NewClip = newClip;
        m_Blending = true;
        m_BlendTime = 0.0f;
    }

    void Update(float elapsedSec)
    {
		if (m_Blending)
		{
			m_BlendTime += elapsedSec;
			if (m_BlendTime > m_BlendDuration)
			{
				// end the blend, set the new clip to the animator
				m_pAnimator->SetAnimation(m_NewClip);
				m_Blending = false;
			}
			else
			{
				// calculate blend factor
				float blendFactor = m_BlendTime / m_BlendDuration;

				// Get the end frame of the previous clip
				//AnimationKey endKeyPrevClip = m_PreviousClip.keys[m_PreviousClip.keys.size() - 1];
				AnimationKey keyPrevClip;
				for (const auto& key : m_PreviousClip.keys)
				{
					if (key.tick < m_PreviousTickCount)
					{
						keyPrevClip = key;
						
					}
					else if (key.tick > m_PreviousTickCount)
					{
						break;
					}
				}

				// Get the start frame of the new clip
				//AnimationKey startKeyNewClip = m_NewClip.keys[0];

				float timeInOldClip = m_BlendTime * m_PreviousClip.ticksPerSecond;
				float timeInNewClip = m_BlendTime * m_NewClip.ticksPerSecond;
				// Get the interpolated frame at that time in the new clip
				AnimationKey interpolatedKeyOldClip = GetInterpolatedKey(m_PreviousClip, timeInOldClip);
				AnimationKey interpolatedKeyNewClip = GetInterpolatedKey(m_NewClip, timeInNewClip);

				// Interpolate between frames of m_PreviousClip and m_NewClip
				// This is similar to the interpolation done in ModelAnimator::Update, but here you use the blendFactor for lerp/slerp functions
				// Make sure to use appropriate interpolation method for your specific data structure
				for (size_t i = 0; i < m_pAnimator->m_pMeshFilter->m_BoneCount; i++)
				{
					//auto transformA = endKeyPrevClip.boneTransforms[i];
					//auto transformA = keyPrevClip.boneTransforms[i];
					auto transformA = interpolatedKeyOldClip.boneTransforms[i];

					//auto transformB = startKeyNewClip.boneTransforms[i];
					auto transformB = interpolatedKeyNewClip.boneTransforms[i];

					//	Decompose both transforms
					XMVECTOR scaleA, rotQuatA, transA;
					XMVECTOR scaleB, rotQuatB, transB;

					XMMatrixDecompose(&scaleA, &rotQuatA, &transA, XMLoadFloat4x4(&transformA));
					XMMatrixDecompose(&scaleB, &rotQuatB, &transB, XMLoadFloat4x4(&transformB));

					//	Lerp between all the transformations (Position, Scale, Rotation)
					auto lerpScale = XMVectorLerp(scaleA, scaleB, blendFactor);
					auto lerpRotQuat = XMQuaternionSlerp(rotQuatA, rotQuatB, blendFactor);
					auto lerpTrans = XMVectorLerp(transA, transB, blendFactor);

					//	Compose a transformation matrix with the lerp-results
					XMMATRIX newTransform = XMMatrixAffineTransformation(lerpScale, XMVectorZero(), lerpRotQuat, lerpTrans);

					//	Add the resulting matrix to the m_Transforms vector
					XMFLOAT4X4 result;
					XMStoreFloat4x4(&result, newTransform);

					// Add the resulting matrix to the m_Animator's m_Transforms vector
					m_pAnimator->m_Transforms[i] = result;
				}
			}
		}
    }

    AnimationKey GetInterpolatedKey(const AnimationClip& clip, float time)
    {
        AnimationKey interpolatedKey;
        interpolatedKey.tick = time;

        // Handle looping
        time = fmod(time, clip.duration);

		// Update ModelAnimator
		if (m_pAnimator->m_Reversed)
		{
			m_pAnimator->m_TickCount -= time;
			if (m_pAnimator->m_TickCount < 0)
			{
				m_pAnimator->m_TickCount += clip.duration;
			}
		}
		else
		{
			m_pAnimator->m_TickCount += time;
			if (m_pAnimator->m_TickCount > clip.duration)
			{
				m_pAnimator->m_TickCount -= clip.duration;
			}
		}

        // Find the enclosing keys
        AnimationKey keyA, keyB;
        // Iterate all the keys of the clip and find the following keys:
        // keyA > Closest Key with Tick before/smaller than time
        // keyB > Closest Key with Tick after/bigger than time
        for (const auto& key : clip.keys)
        {
            if (key.tick < time)
            {
                keyA = key;
            }
            else if (key.tick >= time)
            {
                keyB = key;
                break;
            }
        }

        // Figure out the BlendFactor
        float blendFactor = (time - keyA.tick) / (keyB.tick - keyA.tick);

        // Interpolate between keys
        // For every boneTransform in a key (So for every bone)
        for (size_t i = 0; i < m_pAnimator->m_pMeshFilter->m_BoneCount; i++)
        {
            // Retrieve the transform from keyA (transformA)
            auto transformA = keyA.boneTransforms[i];

            // Retrieve the transform from keyB (transformB)
            auto transformB = keyB.boneTransforms[i];

            // Decompose both transforms
            XMVECTOR scaleA, rotQuatA, transA;
            XMVECTOR scaleB, rotQuatB, transB;

            XMMatrixDecompose(&scaleA, &rotQuatA, &transA, XMLoadFloat4x4(&transformA));
            XMMatrixDecompose(&scaleB, &rotQuatB, &transB, XMLoadFloat4x4(&transformB));

            // Lerp between all the transformations (Position, Scale, Rotation)
            auto lerpScale = XMVectorLerp(scaleA, scaleB, blendFactor);
            auto lerpRotQuat = XMQuaternionSlerp(rotQuatA, rotQuatB, blendFactor);
            auto lerpTrans = XMVectorLerp(transA, transB, blendFactor);

            // Compose a transformation matrix with the lerp-results
            XMMATRIX newTransform = XMMatrixAffineTransformation(lerpScale, XMVectorZero(), lerpRotQuat, lerpTrans);

            // Add the resulting matrix to the interpolatedKey boneTransforms vector
            XMFLOAT4X4 result;
            XMStoreFloat4x4(&result, newTransform);
            interpolatedKey.boneTransforms.push_back(result);
        }

        return interpolatedKey;
    }

    bool IsBlending() { return m_Blending; }

private:
	ModelAnimator* m_pAnimator{ nullptr };
    AnimationClip m_PreviousClip;
    AnimationClip m_NewClip;
	float m_PreviousTickCount;
	float m_BlendTime;
	float m_BlendDuration;
	bool m_Blending;
};