From a931e2aec6e2c1d862c5d82116e24af604a7a72b Mon Sep 17 00:00:00 2001 From: parax0 Date: Fri, 8 Apr 2016 09:02:15 -0600 Subject: [PATCH] Added support for loading compressed animations --- src/Core/Resource/CAnimation.h | 2 +- .../Resource/Factory/CAnimationLoader.cpp | 213 +++++++++++++++++- src/Core/Resource/Factory/CAnimationLoader.h | 20 ++ src/FileIO/CBitStreamInWrapper.cpp | 67 ++++++ src/FileIO/CBitStreamInWrapper.h | 30 +++ src/FileIO/FileIO.h | 2 + 6 files changed, 324 insertions(+), 10 deletions(-) create mode 100644 src/FileIO/CBitStreamInWrapper.cpp create mode 100644 src/FileIO/CBitStreamInWrapper.h diff --git a/src/Core/Resource/CAnimation.h b/src/Core/Resource/CAnimation.h index f461b3c8..946bd7f2 100644 --- a/src/Core/Resource/CAnimation.h +++ b/src/Core/Resource/CAnimation.h @@ -26,7 +26,7 @@ class CAnimation : public CResource u8 RotationChannelIdx; u8 TranslationChannelIdx; }; - std::vector mBoneInfo; + SBoneChannelInfo mBoneInfo[100]; public: CAnimation(); diff --git a/src/Core/Resource/Factory/CAnimationLoader.cpp b/src/Core/Resource/Factory/CAnimationLoader.cpp index 9aaa2d71..8e64e29c 100644 --- a/src/Core/Resource/Factory/CAnimationLoader.cpp +++ b/src/Core/Resource/Factory/CAnimationLoader.cpp @@ -1,5 +1,6 @@ #include "CAnimationLoader.h" #include +#include void CAnimationLoader::ReadUncompressedANIM() { @@ -38,8 +39,6 @@ void CAnimationLoader::ReadUncompressedANIM() } // Set up bone channel info - mpAnim->mBoneInfo.resize(NumRotIndices); - for (u32 iRot = 0, iTrans = 0; iRot < NumRotIndices; iRot++) { u8 RotIdx = RotIndices[iRot]; @@ -80,6 +79,203 @@ void CAnimationLoader::ReadUncompressedANIM() // Skip EVNT file } +void CAnimationLoader::ReadCompressedANIM() +{ + // Header + mpInput->Seek(0xC, SEEK_CUR); // Skip alloc size, EVNT ID, unknown value + mpAnim->mDuration = mpInput->ReadFloat(); + mpAnim->mTickInterval = mpInput->ReadFloat(); + mpInput->Seek(0x8, SEEK_CUR); // Skip two unknown values + + mRotationDivisor = mpInput->ReadLong(); + mTranslationMultiplier = mpInput->ReadFloat(); + u32 NumBoneChannels = mpInput->ReadLong(); + mpInput->Seek(0x4, SEEK_CUR); // Skip unknown value + + // Read key flags + u32 NumKeys = mpInput->ReadLong(); + mpAnim->mNumKeys = NumKeys - 1; + mKeyFlags.resize(NumKeys); + { + CBitStreamInWrapper BitStream(mpInput); + + for (u32 iBit = 0; iBit < NumKeys; iBit++) + mKeyFlags[iBit] = BitStream.ReadBit(); + } + mpInput->Seek(0x8, SEEK_CUR); + + // Read bone channel descriptors + mCompressedChannels.resize(NumBoneChannels); + mpAnim->mRotationChannels.resize(NumBoneChannels); + mpAnim->mTranslationChannels.resize(NumBoneChannels); + + for (u32 iChan = 0; iChan < NumBoneChannels; iChan++) + { + SCompressedChannel& rChan = mCompressedChannels[iChan]; + rChan.BoneID = mpInput->ReadLong(); + + // Read rotation parameters + rChan.NumRotationKeys = mpInput->ReadShort(); + + if (rChan.NumRotationKeys > 0) + { + for (u32 iComp = 0; iComp < 3; iComp++) + { + rChan.Rotation[iComp] = mpInput->ReadShort(); + rChan.RotationBits[iComp] = mpInput->ReadByte(); + } + + mpAnim->mBoneInfo[rChan.BoneID].RotationChannelIdx = (u8) iChan; + } + else mpAnim->mBoneInfo[rChan.BoneID].RotationChannelIdx = 0xFF; + + // Read translation parameters + rChan.NumTranslationKeys = mpInput->ReadShort(); + + if (rChan.NumTranslationKeys > 0) + { + for (u32 iComp = 0; iComp < 3; iComp++) + { + rChan.Translation[iComp] = mpInput->ReadShort(); + rChan.TranslationBits[iComp] = mpInput->ReadByte(); + } + + mpAnim->mBoneInfo[rChan.BoneID].TranslationChannelIdx = (u8) iChan; + } + else + mpAnim->mBoneInfo[rChan.BoneID].TranslationChannelIdx = 0xFF; + } + + // Read animation data + ReadCompressedAnimationData(); +} + +void CAnimationLoader::ReadCompressedAnimationData() +{ + CBitStreamInWrapper BitStream(mpInput); + + // Initialize + for (u32 iChan = 0; iChan < mCompressedChannels.size(); iChan++) + { + SCompressedChannel& rChan = mCompressedChannels[iChan]; + + // Reserve memory for all keys + + // Set initial rotation/translation + if (rChan.NumRotationKeys > 0) + { + mpAnim->mRotationChannels[iChan].reserve(rChan.NumRotationKeys + 1); + CQuaternion Rotation = DequantizeRotation(false, rChan.Rotation[0], rChan.Rotation[1], rChan.Rotation[2]); + mpAnim->mRotationChannels[iChan].push_back(Rotation); + } + + if (rChan.NumTranslationKeys > 0) + { + mpAnim->mTranslationChannels[iChan].reserve(rChan.NumTranslationKeys + 1); + CVector3f Translate = CVector3f(rChan.Translation[0], rChan.Translation[1], rChan.Translation[2]) * mTranslationMultiplier; + mpAnim->mTranslationChannels[iChan].push_back(Translate); + } + } + + // Read keys + for (u32 iKey = 0; iKey < mpAnim->mNumKeys; iKey++) + { + bool KeyPresent = mKeyFlags[iKey]; + + for (u32 iChan = 0; iChan < mCompressedChannels.size(); iChan++) + { + SCompressedChannel& rChan = mCompressedChannels[iChan]; + + // Read rotation + if (rChan.NumRotationKeys > 0) + { + bool WSign = (KeyPresent ? BitStream.ReadBit() : false); + + if (KeyPresent) + { + rChan.Rotation[0] += (s16) BitStream.ReadBits(rChan.RotationBits[0]); + rChan.Rotation[1] += (s16) BitStream.ReadBits(rChan.RotationBits[1]); + rChan.Rotation[2] += (s16) BitStream.ReadBits(rChan.RotationBits[2]); + } + + CQuaternion Rotation = DequantizeRotation(WSign, rChan.Rotation[0], rChan.Rotation[1], rChan.Rotation[2]); + mpAnim->mRotationChannels[iChan].push_back(Rotation); + } + + // Read translation + if (rChan.NumTranslationKeys > 0) + { + if (KeyPresent) + { + rChan.Translation[0] += (s16) BitStream.ReadBits(rChan.TranslationBits[0]); + rChan.Translation[1] += (s16) BitStream.ReadBits(rChan.TranslationBits[1]); + rChan.Translation[2] += (s16) BitStream.ReadBits(rChan.TranslationBits[2]); + } + + CVector3f Translate = CVector3f(rChan.Translation[0], rChan.Translation[1], rChan.Translation[2]) * mTranslationMultiplier; + mpAnim->mTranslationChannels[iChan].push_back(Translate); + } + } + } + + // Fill in missing keys + u32 NumMissedKeys = 0; + + for (u32 iKey = 0; iKey < mpAnim->mNumKeys; iKey++) + { + if (!mKeyFlags[iKey]) + NumMissedKeys++; + + else if (NumMissedKeys > 0) + { + u32 FirstIndex = iKey - NumMissedKeys - 1; + u32 LastIndex = iKey; + u32 RelLastIndex = LastIndex - FirstIndex; + + for (u32 iMissed = 0; iMissed < NumMissedKeys; iMissed++) + { + u32 KeyIndex = FirstIndex + iMissed + 1; + u32 RelKeyIndex = (KeyIndex - FirstIndex); + + for (u32 iChan = 0; iChan < mCompressedChannels.size(); iChan++) + { + bool HasTranslationKeys = mCompressedChannels[iChan].NumTranslationKeys > 0; + bool HasRotationKeys = mCompressedChannels[iChan].NumRotationKeys > 0; + float Interp = (float) RelKeyIndex / (float) RelLastIndex; + + if (HasRotationKeys) + { + CQuaternion Left = mpAnim->mRotationChannels[iChan][FirstIndex]; + CQuaternion Right = mpAnim->mRotationChannels[iChan][LastIndex]; + mpAnim->mRotationChannels[iChan][KeyIndex] = Left.Slerp(Right, Interp); + } + + if (HasTranslationKeys) + { + CVector3f Left = mpAnim->mTranslationChannels[iChan][FirstIndex]; + CVector3f Right = mpAnim->mTranslationChannels[iChan][LastIndex]; + mpAnim->mTranslationChannels[iChan][KeyIndex] = Math::Lerp(Left, Right, Interp); + } + } + } + + NumMissedKeys = 0; + } + } +} + +CQuaternion CAnimationLoader::DequantizeRotation(bool Sign, s16 X, s16 Y, s16 Z) +{ + CQuaternion Out; + float Multiplier = Math::skHalfPi / (float) mRotationDivisor; + Out.X = sinf(X * Multiplier); + Out.Y = sinf(Y * Multiplier); + Out.Z = sinf(Z * Multiplier); + Out.W = Math::Sqrt( fmax(1.f - ((Out.X * Out.X) + (Out.Y * Out.Y) + (Out.Z * Out.Z)), 0.f) ); + if (Sign) Out.W = -Out.W; + return Out; +} + // ************ STATIC ************ CAnimation* CAnimationLoader::LoadANIM(IInputStream& rANIM) { @@ -91,15 +287,14 @@ CAnimation* CAnimationLoader::LoadANIM(IInputStream& rANIM) return nullptr; } - if (CompressionType == 2) - { - Log::FileError(rANIM.GetSourceString(), "Compressed ANIMs not supported yet"); - return nullptr; - } - CAnimationLoader Loader; Loader.mpAnim = new CAnimation(); Loader.mpInput = &rANIM; - Loader.ReadUncompressedANIM(); + + if (CompressionType == 0) + Loader.ReadUncompressedANIM(); + else + Loader.ReadCompressedANIM(); + return Loader.mpAnim; } diff --git a/src/Core/Resource/Factory/CAnimationLoader.h b/src/Core/Resource/Factory/CAnimationLoader.h index 96c53c07..5660ee46 100644 --- a/src/Core/Resource/Factory/CAnimationLoader.h +++ b/src/Core/Resource/Factory/CAnimationLoader.h @@ -9,8 +9,28 @@ class CAnimationLoader TResPtr mpAnim; IInputStream *mpInput; + // Compression data + std::vector mKeyFlags; + float mTranslationMultiplier; + u32 mRotationDivisor; + + struct SCompressedChannel + { + u32 BoneID; + u16 NumRotationKeys; + s16 Rotation[3]; + u8 RotationBits[3]; + u16 NumTranslationKeys; + s16 Translation[3]; + u8 TranslationBits[3]; + }; + std::vector mCompressedChannels; + CAnimationLoader() {} void ReadUncompressedANIM(); + void ReadCompressedANIM(); + void ReadCompressedAnimationData(); + CQuaternion DequantizeRotation(bool Sign, s16 X, s16 Y, s16 Z); public: static CAnimation* LoadANIM(IInputStream& rANIM); diff --git a/src/FileIO/CBitStreamInWrapper.cpp b/src/FileIO/CBitStreamInWrapper.cpp new file mode 100644 index 00000000..7886f4e5 --- /dev/null +++ b/src/FileIO/CBitStreamInWrapper.cpp @@ -0,0 +1,67 @@ +#include "CBitStreamInWrapper.h" + +CBitStreamInWrapper::CBitStreamInWrapper(IInputStream *pStream, EChunkSize ChunkSize /*= e32Bit*/) + : mpSourceStream(pStream) + , mChunkSize(ChunkSize) + , mBitPool(0) + , mBitsRemaining(0) +{ +} + +void CBitStreamInWrapper::SetChunkSize(EChunkSize Size) +{ + mChunkSize = Size; +} + +long CBitStreamInWrapper::ReadBits(long NumBits, bool ExtendSignBit /*= true*/) +{ + long BitsRemaining = NumBits; + long Out = 0; + long Shift = 0; + + while (BitsRemaining > 0) + { + if (mBitsRemaining <= BitsRemaining) + { + BitsRemaining -= mBitsRemaining; + Out |= (mBitPool << Shift); + Shift += mBitsRemaining; + ReplenishPool(); + } + + else + { + long Mask = (1 << BitsRemaining) - 1; + Out |= (mBitPool & Mask) << Shift; + mBitPool >>= BitsRemaining; + mBitsRemaining -= BitsRemaining; + BitsRemaining = 0; + } + } + + if (ExtendSignBit) + { + bool Sign = ((Out >> (NumBits - 1) & 0x1) == 1); + if (Sign) Out |= (-1 << NumBits); + } + + return Out; +} + +bool CBitStreamInWrapper::ReadBit() +{ + return (ReadBits(1, false) != 0); +} + +// ************ PRIVATE ************ +void CBitStreamInWrapper::ReplenishPool() +{ + if (mChunkSize == e8Bit) + mBitPool = mpSourceStream->ReadByte(); + else if (mChunkSize == e16Bit) + mBitPool = mpSourceStream->ReadShort(); + else if (mChunkSize == e32Bit) + mBitPool = mpSourceStream->ReadLong(); + + mBitsRemaining = mChunkSize; +} diff --git a/src/FileIO/CBitStreamInWrapper.h b/src/FileIO/CBitStreamInWrapper.h new file mode 100644 index 00000000..132cf73e --- /dev/null +++ b/src/FileIO/CBitStreamInWrapper.h @@ -0,0 +1,30 @@ +#ifndef CBITSTREAMINWRAPPER_H +#define CBITSTREAMINWRAPPER_H + +#include "IInputStream.h" + +class CBitStreamInWrapper +{ +public: + enum EChunkSize + { + e8Bit = 8, e16Bit = 16, e32Bit = 32 + }; + +private: + IInputStream *mpSourceStream; + EChunkSize mChunkSize; + unsigned long mBitPool; + long mBitsRemaining; + +public: + CBitStreamInWrapper(IInputStream *pStream, EChunkSize ChunkSize = e32Bit); + void SetChunkSize(EChunkSize Size); + long ReadBits(long NumBits, bool ExtendSignBit = true); + bool ReadBit(); + +private: + void ReplenishPool(); +}; + +#endif // CBITSTREAMINWRAPPER_H diff --git a/src/FileIO/FileIO.h b/src/FileIO/FileIO.h index a9671619..a5f98384 100644 --- a/src/FileIO/FileIO.h +++ b/src/FileIO/FileIO.h @@ -14,4 +14,6 @@ #include "CVectorOutStream.h" #include "CTextOutStream.h" +#include "CBitStreamInWrapper.h" + #endif // FILEIO