Added support for loading uncompressed animations and playing them back

This commit is contained in:
parax0 2016-04-07 12:15:24 -06:00
parent 2376a36f0b
commit ef523c2339
22 changed files with 399 additions and 18 deletions

View File

@ -188,7 +188,9 @@ HEADERS += \
Resource/Script/CLink.h \
Resource/CSkeleton.h \
Resource/Factory/CSkeletonLoader.h \
Scene/CCharacterNode.h
Scene/CCharacterNode.h \
Resource/CAnimation.h \
Resource/Factory/CAnimationLoader.h
# Source Files
SOURCES += \
@ -269,4 +271,6 @@ SOURCES += \
ScriptExtra/CSplinePathExtra.cpp \
Resource/CSkeleton.cpp \
Resource/Factory/CSkeletonLoader.cpp \
Scene/CCharacterNode.cpp
Scene/CCharacterNode.cpp \
Resource/CAnimation.cpp \
Resource/Factory/CAnimationLoader.cpp

View File

@ -2,6 +2,7 @@
#define CANIMSET_H
#include "TResPtr.h"
#include "CAnimation.h"
#include "CResource.h"
#include "CSkeleton.h"
#include "Core/Resource/Model/CModel.h"
@ -26,6 +27,13 @@ class CAnimSet : public CResource
};
std::vector<SNode> mNodes;
struct SAnimation
{
TString Name;
TResPtr<CAnimation> pAnim;
};
std::vector<SAnimation> mAnims;
public:
CAnimSet() : CResource() {}
@ -33,6 +41,10 @@ public:
TString NodeName(u32 Index) { if (Index >= mNodes.size()) Index = 0; return mNodes[Index].Name; }
CModel* NodeModel(u32 Index) { if (Index >= mNodes.size()) Index = 0; return mNodes[Index].pModel; }
CSkeleton* NodeSkeleton(u32 Index) { if (Index >= mNodes.size()) Index = 0; return mNodes[Index].pSkeleton; }
u32 NumAnims() const { return mAnims.size(); }
CAnimation* Animation(u32 Index) { if (Index >= mAnims.size()) Index = 0; return mAnims[Index].pAnim; }
TString AnimName(u32 Index) { if (Index >= mAnims.size()) Index = 0; return mAnims[Index].Name; }
};
#endif // CCHARACTERSET_H

View File

@ -0,0 +1,37 @@
#include "CAnimation.h"
#include <Math/CTransform4f.h>
#include <Math/MathUtil.h>
CAnimation::CAnimation()
: mDuration(0.f)
, mTickInterval(0.0333333f)
, mNumKeys(0)
{
}
void CAnimation::EvaluateTransform(float Time, u32 BoneID, CTransform4f& rOut) const
{
if (mDuration == 0.f) return;
Time = fmodf(Time, mDuration);
if (Time >= FLT_EPSILON) Time -= FLT_EPSILON;
float t = fmodf(Time, mTickInterval) / mTickInterval;
u32 LowKey = (u32) (Time / mTickInterval);
u8 RotChannel = mBoneInfo[BoneID].RotationChannelIdx;
u8 TransChannel = mBoneInfo[BoneID].TranslationChannelIdx;
if (RotChannel != 0xFF)
{
const CQuaternion& rkLow = mRotationChannels[RotChannel][LowKey];
const CQuaternion& rkHigh = mRotationChannels[RotChannel][LowKey + 1];
rOut.Rotate( rkLow.Slerp(rkHigh, t) );
}
if (TransChannel != 0xFF)
{
const CVector3f& rkLow = mTranslationChannels[TransChannel][LowKey];
const CVector3f& rkHigh = mTranslationChannels[TransChannel][LowKey + 1];
rOut.Translate( Math::Lerp<CVector3f>(rkLow, rkHigh, t) );
}
}

View File

@ -0,0 +1,36 @@
#ifndef CANIMATION_H
#define CANIMATION_H
#include "CResource.h"
#include <Math/CQuaternion.h>
#include <Math/CVector3f.h>
#include <vector>
class CAnimation : public CResource
{
DECLARE_RESOURCE_TYPE(eAnimation)
friend class CAnimationLoader;
typedef std::vector<CQuaternion> TRotationChannel;
typedef std::vector<CVector3f> TTranslationChannel;
float mDuration;
float mTickInterval;
u32 mNumKeys;
std::vector<TRotationChannel> mRotationChannels;
std::vector<TTranslationChannel> mTranslationChannels;
struct SBoneChannelInfo
{
u8 RotationChannelIdx;
u8 TranslationChannelIdx;
};
std::vector<SBoneChannelInfo> mBoneInfo;
public:
CAnimation();
void EvaluateTransform(float Time, u32 BoneID, CTransform4f& rOut) const;
};
#endif // CANIMATION_H

View File

@ -1,5 +1,6 @@
#include "CResCache.h"
#include "Core/Resource/Factory/CAreaLoader.h"
#include "Core/Resource/Factory/CAnimationLoader.h"
#include "Core/Resource/Factory/CAnimSetLoader.h"
#include "Core/Resource/Factory/CCollisionLoader.h"
#include "Core/Resource/Factory/CFontLoader.h"
@ -151,6 +152,7 @@ CResource* CResCache::GetResource(CUniqueID ResID, CFourCC Type)
else if (Type == "DCLN") pRes = CCollisionLoader::LoadDCLN(Mem);
else if (Type == "EGMC") pRes = CPoiToWorldLoader::LoadEGMC(Mem);
else if (Type == "CINF") pRes = CSkeletonLoader::LoadCINF(Mem);
else if (Type == "ANIM") pRes = CAnimationLoader::LoadANIM(Mem);
else SupportedFormat = false;
// Log errors
@ -206,6 +208,7 @@ CResource* CResCache::GetResource(const TString& rkResPath)
else if (Type == "DCLN") pRes = CCollisionLoader::LoadDCLN(File);
else if (Type == "EGMC") pRes = CPoiToWorldLoader::LoadEGMC(File);
else if (Type == "CINF") pRes = CSkeletonLoader::LoadCINF(File);
else if (Type == "ANIM") pRes = CAnimationLoader::LoadANIM(File);
else SupportedFormat = false;
if (!pRes) pRes = new CResource(); // Default for unsupported formats

View File

@ -8,6 +8,43 @@ CBone::CBone(CSkeleton *pSkel)
{
}
void CBone::UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot)
{
mAnimTransform = CTransform4f::skIdentity;
if (pAnim)
pAnim->EvaluateTransform(Time, mID, mAnimTransform);
mAnimTransform.Translate(mPosition);
if (mpParent)
mAnimTransform = mpParent->AnimTransform() * mAnimTransform;
if (AnchorRoot && IsRoot())
mAnimTransform.ZeroTranslation();
mAbsPosDirty = true;
for (u32 iChild = 0; iChild < mChildren.size(); iChild++)
mChildren[iChild]->UpdateTransform(pAnim, Time, AnchorRoot);
}
bool CBone::IsRoot() const
{
return (mpParent == nullptr);
}
CVector3f CBone::AbsolutePosition() const
{
if (mAbsPosDirty)
{
mAbsolutePosition = (mAnimTransform * CVector3f::skZero);
mAbsPosDirty = false;
}
return mAbsolutePosition;
}
// ************ CSkeleton ************
CSkeleton::CSkeleton()
: mpRootBone(nullptr)
@ -31,6 +68,11 @@ CBone* CSkeleton::BoneByID(u32 BoneID) const
return nullptr;
}
void CSkeleton::UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot)
{
mpRootBone->UpdateTransform(pAnim, Time, AnchorRoot);
}
void CSkeleton::Draw(FRenderOptions /*Options*/)
{
for (u32 iBone = 0; iBone < mBones.size(); iBone++)
@ -39,8 +81,8 @@ void CSkeleton::Draw(FRenderOptions /*Options*/)
// Draw bone
CTransform4f Transform;
Transform.Scale(0.01f);
Transform.Translate(pBone->Position());
Transform.Scale(0.025f);
Transform.Translate(pBone->AbsolutePosition());
CGraphics::sMVPBlock.ModelMatrix = Transform.ToMatrix4f();
CGraphics::UpdateMVPBlock();
CDrawUtil::DrawSphere(CColor::skWhite);
@ -50,6 +92,6 @@ void CSkeleton::Draw(FRenderOptions /*Options*/)
CGraphics::UpdateMVPBlock();
for (u32 iChild = 0; iChild < pBone->NumChildren(); iChild++)
CDrawUtil::DrawLine(pBone->Position(), pBone->ChildByIndex(iChild)->Position());
CDrawUtil::DrawLine(pBone->AbsolutePosition(), pBone->ChildByIndex(iChild)->AbsolutePosition());
}
}

View File

@ -1,10 +1,12 @@
#ifndef CSKELETON_H
#define CSKELETON_H
#include "CAnimation.h"
#include "CResource.h"
#include "Core/Render/FRenderOptions.h"
#include <Common/TString.h>
#include <Common/types.h>
#include <Math/CTransform4f.h>
#include <Math/CVector3f.h>
class CSkeleton;
@ -20,14 +22,24 @@ class CBone
CVector3f mPosition;
TString mName;
CTransform4f mAnimTransform;
mutable bool mAbsPosDirty;
mutable CVector3f mAbsolutePosition;
public:
CBone(CSkeleton *pSkel);
void UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot);
bool IsRoot() const;
// Accessors
inline u32 ID() const { return mID; }
inline CVector3f Position() const { return mPosition; }
inline u32 NumChildren() const { return mChildren.size(); }
inline CBone* ChildByIndex(u32 Index) const { return mChildren[Index]; }
inline u32 ID() const { return mID; }
inline CVector3f Position() const { return mPosition; }
inline CBone* Parent() const { return mpParent; }
inline u32 NumChildren() const { return mChildren.size(); }
inline CBone* ChildByIndex(u32 Index) const { return mChildren[Index]; }
inline const CTransform4f& AnimTransform() const { return mAnimTransform; }
CVector3f AbsolutePosition() const;
};
class CSkeleton : public CResource
@ -41,7 +53,7 @@ class CSkeleton : public CResource
public:
CSkeleton();
~CSkeleton();
void UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot);
CBone* BoneByID(u32 BoneID) const;
void Draw(FRenderOptions Options);
};

View File

@ -104,11 +104,15 @@ CAnimSet* CAnimSetLoader::LoadANCS(IInputStream& rANCS)
// Unfortunately that's all that's actually supported at the moment. Hope to expand later.
// Since there's no size value I have to actually read the rest of the node to reach the next one
u32 AnimCount = rANCS.ReadLong();
bool ReadAnimNames = Loader.pSet->mAnims.empty();
if (ReadAnimNames) Loader.pSet->mAnims.resize(AnimCount);
for (u32 iAnim = 0; iAnim < AnimCount; iAnim++)
{
rANCS.Seek(0x4, SEEK_CUR);
if (Loader.mVersion == ePrime) rANCS.Seek(0x1, SEEK_CUR);
rANCS.ReadString();
TString AnimName = rANCS.ReadString();
if (ReadAnimNames) Loader.pSet->mAnims[iAnim].Name = AnimName;
}
// PAS Database
@ -165,7 +169,6 @@ CAnimSet* CAnimSetLoader::LoadANCS(IInputStream& rANCS)
u32 UnknownCount2 = rANCS.ReadLong();
rANCS.Seek(UnknownCount2 * 0x1C, SEEK_CUR);
}
// Lots of work for data I'm not even using x.x
}
return Loader.pSet;

View File

@ -0,0 +1,105 @@
#include "CAnimationLoader.h"
#include <Common/Log.h>
void CAnimationLoader::ReadUncompressedANIM()
{
mpAnim->mDuration = mpInput->ReadFloat();
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown
mpAnim->mTickInterval = mpInput->ReadFloat();
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown
mpAnim->mNumKeys = mpInput->ReadLong();
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown
// Read bone channel info
u32 NumRotationChannels = 0;
u32 NumTranslationChannels = 0;
u32 NumRotIndices = mpInput->ReadLong();
std::vector<u8> RotIndices(NumRotIndices);
for (u32 iRot = 0; iRot < NumRotIndices; iRot++)
{
RotIndices[iRot] = mpInput->ReadByte();
if (RotIndices[iRot] != 0xFF)
NumRotationChannels++;
}
u32 NumTransIndices = mpInput->ReadLong();
std::vector<u8> TransIndices(NumTransIndices);
for (u32 iTrans = 0; iTrans < NumTransIndices; iTrans++)
{
TransIndices[iTrans] = mpInput->ReadByte();
if (TransIndices[iTrans] != 0xFF)
NumTranslationChannels++;
}
// Set up bone channel info
mpAnim->mBoneInfo.resize(NumRotIndices);
for (u32 iRot = 0, iTrans = 0; iRot < NumRotIndices; iRot++)
{
u8 RotIdx = RotIndices[iRot];
mpAnim->mBoneInfo[iRot].RotationChannelIdx = RotIdx;
if (RotIdx != 0xFF)
{
mpAnim->mBoneInfo[iRot].TranslationChannelIdx = TransIndices[iTrans];
iTrans++;
}
else
mpAnim->mBoneInfo[iRot].TranslationChannelIdx = 0xFF;
}
// Read bone transforms
mpInput->Seek(0x4, SEEK_CUR); // Skipping quaternion count
mpAnim->mRotationChannels.resize(NumRotationChannels);
for (u32 iRot = 0; iRot < NumRotationChannels; iRot++)
{
mpAnim->mRotationChannels[iRot].resize(mpAnim->mNumKeys);
for (u32 iKey = 0; iKey < mpAnim->mNumKeys; iKey++)
mpAnim->mRotationChannels[iRot][iKey] = CQuaternion(*mpInput);
}
mpInput->Seek(0x4, SEEK_CUR); // Skipping vector3f count
mpAnim->mTranslationChannels.resize(NumTranslationChannels);
for (u32 iTrans = 0; iTrans < NumTranslationChannels; iTrans++)
{
mpAnim->mTranslationChannels[iTrans].resize(mpAnim->mNumKeys);
for (u32 iKey = 0; iKey < mpAnim->mNumKeys; iKey++)
mpAnim->mTranslationChannels[iTrans][iKey] = CVector3f(*mpInput);
}
// Skip EVNT file
}
// ************ STATIC ************
CAnimation* CAnimationLoader::LoadANIM(IInputStream& rANIM)
{
u32 CompressionType = rANIM.ReadLong();
if (CompressionType != 0 && CompressionType != 2)
{
Log::FileError(rANIM.GetSourceString(), "Unknown ANIM compression type: " + TString::HexString(CompressionType, 2));
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();
return Loader.mpAnim;
}

View File

@ -0,0 +1,19 @@
#ifndef CANIMATIONLOADER_H
#define CANIMATIONLOADER_H
#include "Core/Resource/TResPtr.h"
#include "Core/Resource/CAnimation.h"
class CAnimationLoader
{
TResPtr<CAnimation> mpAnim;
IInputStream *mpInput;
CAnimationLoader() {}
void ReadUncompressedANIM();
public:
static CAnimation* LoadANIM(IInputStream& rANIM);
};
#endif // CANIMATIONLOADER_H

View File

@ -3,13 +3,19 @@
#include <vector>
CSkeletonLoader::CSkeletonLoader()
void CSkeletonLoader::SetLocalBoneCoords(CBone *pBone)
{
for (u32 iChild = 0; iChild < pBone->NumChildren(); iChild++)
SetLocalBoneCoords(pBone->ChildByIndex(iChild));
if (pBone->mpParent)
pBone->mPosition -= pBone->mpParent->mPosition;
}
// ************ STATIC ************
CSkeleton* CSkeletonLoader::LoadCINF(IInputStream& rCINF)
{
CSkeletonLoader Loader;
CSkeleton *pSkel = new CSkeleton();
u32 NumBones = rCINF.ReadLong();
@ -71,6 +77,8 @@ CSkeleton* CSkeletonLoader::LoadCINF(IInputStream& rCINF)
}
}
Loader.SetLocalBoneCoords(pSkel->mpRootBone);
// Skip bone ID array
u32 NumBoneIDs = rCINF.ReadLong();
rCINF.Seek(NumBoneIDs * 4, SEEK_CUR);

View File

@ -10,7 +10,8 @@ class CSkeletonLoader
TResPtr<CSkeleton> mpSkeleton;
EGame mVersion;
CSkeletonLoader();
CSkeletonLoader() {}
void SetLocalBoneCoords(CBone *pBone);
public:
static CSkeleton* LoadCINF(IInputStream& rCINF);

View File

@ -1,5 +1,6 @@
#include "CCharacterNode.h"
#include <Core/Render/CRenderer.h>
#include "Core/Render/CRenderer.h"
#include <Common/CTimer.h>
CCharacterNode::CCharacterNode(CScene *pScene, u32 NodeID, CAnimSet *pChar /*= 0*/, CSceneNode *pParent /*= 0*/)
: CSceneNode(pScene, NodeID, pParent)
@ -34,6 +35,8 @@ void CCharacterNode::AddToRenderer(CRenderer *pRenderer, const SViewInfo& /*rkVi
void CCharacterNode::Draw(FRenderOptions Options, int /*ComponentIndex*/, const SViewInfo& /*rkViewInfo*/)
{
CSkeleton *pSkel = mpCharacter->NodeSkeleton(mActiveCharSet);
CAnimation *pAnim = mpCharacter->Animation(mActiveAnim);
pSkel->UpdateTransform(pAnim, (float) CTimer::GlobalTime(), false);
pSkel->Draw(Options);
}
@ -62,3 +65,8 @@ void CCharacterNode::SetActiveCharSet(u32 CharIndex)
MarkTransformChanged();
}
}
void CCharacterNode::SetActiveAnim(u32 AnimIndex)
{
mActiveAnim = AnimIndex;
}

View File

@ -8,6 +8,7 @@ class CCharacterNode : public CSceneNode
{
TResPtr<CAnimSet> mpCharacter;
u32 mActiveCharSet;
u32 mActiveAnim;
public:
explicit CCharacterNode(CScene *pScene, u32 NodeID, CAnimSet *pChar = 0, CSceneNode *pParent = 0);
@ -19,9 +20,11 @@ public:
virtual SRayIntersection RayNodeIntersectTest(const CRay& rkRay, u32 AssetID, const SViewInfo& rkViewInfo);
inline CAnimSet* Character() const { return mpCharacter; }
inline u32 ActiveCharSet() const { return mActiveCharSet; }
inline u32 ActiveAnim() const { return mActiveAnim; }
void SetCharacter(CAnimSet *pChar);
void SetActiveCharSet(u32 CharIndex);
void SetActiveAnim(u32 AnimIndex);
};
#endif // CCHARACTERNODE_H

View File

@ -19,15 +19,21 @@ CCharacterEditor::CCharacterEditor(QWidget *parent)
rCamera.SetMoveSpeed(0.5f);
// Init UI
ui->ToolBar->addSeparator();
mpCharComboBox = new QComboBox(this);
mpCharComboBox->setMinimumWidth(175);
ui->ToolBar->addSeparator();
ui->ToolBar->addWidget(mpCharComboBox);
mpAnimComboBox = new QComboBox(this);
mpAnimComboBox->setMinimumWidth(175);
ui->ToolBar->addWidget(mpAnimComboBox);
connect(&mRefreshTimer, SIGNAL(timeout()), this, SLOT(RefreshViewport()));
mRefreshTimer.start(0);
connect(mpCharComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SetActiveCharacterIndex(int)));
connect(mpAnimComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SetActiveAnimation(int)));
connect(ui->ActionOpen, SIGNAL(triggered()), this, SLOT(Open()));
}
@ -58,6 +64,16 @@ void CCharacterEditor::Open()
SetActiveCharacterIndex(0);
mpCharComboBox->blockSignals(false);
// Set up anim combo box
mpAnimComboBox->blockSignals(true);
mpAnimComboBox->clear();
for (u32 iAnim = 0; iAnim < pSet->NumAnims(); iAnim++)
mpAnimComboBox->addItem( TO_QSTRING(pSet->AnimName(iAnim)) );
SetActiveAnimation(0);
mpAnimComboBox->blockSignals(false);
}
gResCache.Clean();
@ -73,3 +89,8 @@ void CCharacterEditor::SetActiveCharacterIndex(int CharIndex)
{
mpCharNode->SetActiveCharSet((u32) CharIndex);
}
void CCharacterEditor::SetActiveAnimation(int AnimIndex)
{
mpCharNode->SetActiveAnim((u32) AnimIndex);
}

View File

@ -22,6 +22,7 @@ class CCharacterEditor : public QMainWindow
CCharacterNode *mpCharNode;
QComboBox *mpCharComboBox;
QComboBox *mpAnimComboBox;
QTimer mRefreshTimer;
public:
@ -32,6 +33,7 @@ public slots:
void Open();
void RefreshViewport();
void SetActiveCharacterIndex(int CharIndex);
void SetActiveAnimation(int AnimIndex);
};
#endif // CCHARACTEREDITORWINDOW_H

View File

@ -41,7 +41,8 @@ HEADERS += \
FileIO.h \
IOUtil.h \
IInputStream.h \
IOutputStream.h
IOutputStream.h \
CBitStreamInWrapper.h
# Source Files
SOURCES += \
@ -54,4 +55,5 @@ SOURCES += \
CVectorOutStream.cpp \
IOUtil.cpp \
IInputStream.cpp \
IOutputStream.cpp
IOutputStream.cpp \
CBitStreamInWrapper.cpp

View File

@ -20,6 +20,14 @@ CQuaternion::CQuaternion(float _W, float _X, float _Y, float _Z)
{
}
CQuaternion::CQuaternion(IInputStream& rInput)
: W(rInput.ReadFloat())
, X(rInput.ReadFloat())
, Y(rInput.ReadFloat())
, Z(rInput.ReadFloat())
{
}
CVector3f CQuaternion::XAxis() const
{
return (*this * CVector3f::skUnitX);
@ -48,6 +56,36 @@ CQuaternion CQuaternion::Inverse() const
return CQuaternion::skZero;
}
CQuaternion CQuaternion::Slerp(const CQuaternion& rkRight, float t) const
{
// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
float CosHalfTheta = (W * rkRight.W) + (X * rkRight.X) + (Y * rkRight.Y) + (Z * rkRight.Z);
if (Math::Abs(CosHalfTheta) >= 1.f)
return *this;
float ScalarA, ScalarB;
float SinHalfTheta = Math::Sqrt(1.f - (CosHalfTheta * CosHalfTheta));
if (Math::Abs(SinHalfTheta) < 0.001f)
{
ScalarA = 0.5f;
ScalarB = 0.5f;
}
else
{
float HalfTheta = acosf(CosHalfTheta);
ScalarA = sinf((1.f - t) * HalfTheta) / SinHalfTheta;
ScalarB = sinf(t * HalfTheta) / SinHalfTheta;
}
return CQuaternion( (W * ScalarA) + (rkRight.W * ScalarB),
(X * ScalarA) + (rkRight.X * ScalarB),
(Y * ScalarA) + (rkRight.Y * ScalarB),
(Z * ScalarA) + (rkRight.Z * ScalarB) );
}
CVector3f CQuaternion::ToEuler() const
{
// There is more than one way to do this conversion, based on rotation order.

View File

@ -10,11 +10,13 @@ public:
CQuaternion();
CQuaternion(float _W, float _X, float _Y, float _Z);
CQuaternion(IInputStream& rInput);
CVector3f XAxis() const;
CVector3f YAxis() const;
CVector3f ZAxis() const;
CQuaternion Inverse() const;
CQuaternion Slerp(const CQuaternion& rkRight, float t) const;
CVector3f ToEuler() const;
// Operators

View File

@ -100,6 +100,13 @@ void CTransform4f::Scale(float XScale, float YScale, float ZScale)
Scale(CVector3f(XScale, YScale, ZScale));
}
void CTransform4f::ZeroTranslation()
{
m[0][3] = 0.f;
m[1][3] = 0.f;
m[2][3] = 0.f;
}
CTransform4f CTransform4f::MultiplyIgnoreTranslation(const CTransform4f& rkMtx) const
{
CTransform4f Out;
@ -153,6 +160,13 @@ CTransform4f CTransform4f::NoTranslation() const
m[2][0], m[2][1], m[2][2], 0.f);
}
CTransform4f CTransform4f::TranslationOnly() const
{
return CTransform4f(1.f, 0.f, 0.f, m[0][3],
0.f, 1.f, 0.f, m[1][3],
0.f, 0.f, 1.f, m[2][3]);
}
CTransform4f CTransform4f::RotationOnly() const
{
return CTransform4f::FromMatrix4f(Inverse().ToMatrix4f().Transpose());

View File

@ -37,10 +37,12 @@ public:
void Rotate(float XRot, float YRot, float ZRot);
void Scale(CVector3f Scale);
void Scale(float XScale, float YScale, float ZScale);
void ZeroTranslation();
CTransform4f MultiplyIgnoreTranslation(const CTransform4f& rkMtx) const;
CTransform4f Inverse() const;
CTransform4f QuickInverse() const;
CTransform4f NoTranslation() const;
CTransform4f TranslationOnly() const;
CTransform4f RotationOnly() const;
// Conversion

View File

@ -22,6 +22,13 @@ float DegreesToRadians(float Deg);
float RadiansToDegrees(float Rad);
template<typename Type>
Type Lerp(const Type& rkA, const Type& rkB, float t)
{
Type Diff = rkB - rkA;
return rkA + (Diff * t);
}
std::pair<bool,float> RayPlaneIntersecton(const CRay& rkRay, const CPlane& rkPlane);
std::pair<bool,float> RayBoxIntersection(const CRay& rkRay, const CAABox& rkBox);