diff --git a/src/Common/Flags.h b/src/Common/Flags.h index 48222ce3..5a117a19 100644 --- a/src/Common/Flags.h +++ b/src/Common/Flags.h @@ -18,16 +18,18 @@ public: inline bool operator!() const { return !mValue; } inline TFlags operator~() const { return TFlags(FlagEnum(~mValue)); } - inline void operator&=(int Mask) { mValue &= Mask; } - inline void operator&=(u32 Mask) { mValue &= Mask; } - inline void operator|=(TFlags Flags) { mValue |= Flags.mValue; } - inline void operator|=(FlagEnum Flag) { mValue |= Flag; } + inline void operator&=(int Mask) { mValue &= Mask; } + inline void operator&=(u32 Mask) { mValue &= Mask; } + inline void operator|=(TFlags Flags) { mValue |= Flags.mValue; } + inline void operator|=(FlagEnum Flag) { mValue |= Flag; } - inline TFlags operator|(TFlags Flags) const { return TFlags(FlagEnum(mValue | Flags.mValue)); } - inline TFlags operator|(FlagEnum Flag) const { return TFlags(FlagEnum(mValue | Flag)); } - inline TFlags operator&(int Mask) const { return TFlags(FlagEnum(mValue & Mask)); } - inline TFlags operator&(u32 Mask) const { return TFlags(FlagEnum(mValue & Mask)); } - inline TFlags operator&(FlagEnum Flag) const { return TFlags(FlagEnum(mValue & Flag)); } + inline TFlags operator|(TFlags Flags) const { return TFlags(FlagEnum(mValue | Flags.mValue)); } + inline TFlags operator|(FlagEnum Flag) const { return TFlags(FlagEnum(mValue | Flag)); } + inline TFlags operator&(int Mask) const { return TFlags(FlagEnum(mValue & Mask)); } + inline TFlags operator&(u32 Mask) const { return TFlags(FlagEnum(mValue & Mask)); } + inline TFlags operator&(FlagEnum Flag) const { return TFlags(FlagEnum(mValue & Flag)); } + + inline bool HasAnyFlags(TFlags Flags) const { return ((mValue & Flags) != 0); } }; #define DECLARE_FLAGS(Enum, FlagTypeName) typedef TFlags FlagTypeName; diff --git a/src/Core/Core.pro b/src/Core/Core.pro index 602679b8..16889514 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -190,7 +190,8 @@ HEADERS += \ Resource/Factory/CSkeletonLoader.h \ Scene/CCharacterNode.h \ Resource/CAnimation.h \ - Resource/Factory/CAnimationLoader.h + Resource/Factory/CAnimationLoader.h \ + Render/CBoneTransformData.h # Source Files SOURCES += \ diff --git a/src/Core/Render/CBoneTransformData.h b/src/Core/Render/CBoneTransformData.h new file mode 100644 index 00000000..f873832d --- /dev/null +++ b/src/Core/Render/CBoneTransformData.h @@ -0,0 +1,26 @@ +#ifndef CBONETRANSFORMDATA +#define CBONETRANSFORMDATA + +#include "Core/Resource/CSkeleton.h" +#include +#include +#include + +class CBoneTransformData +{ + std::vector mBoneMatrices; + +public: + CBoneTransformData() { } + CBoneTransformData(CSkeleton *pSkel) { ResizeToSkeleton(pSkel); } + inline void ResizeToSkeleton(CSkeleton *pSkel) { mBoneMatrices.resize(pSkel ? pSkel->MaxBoneID() + 1 : 0); } + inline CTransform4f& BoneMatrix(u32 BoneID) { return mBoneMatrices[BoneID]; } + inline const CTransform4f& BoneMatrix(u32 BoneID) const { return mBoneMatrices[BoneID]; } + inline void* Data() { return mBoneMatrices.data(); } + inline u32 DataSize() const { return mBoneMatrices.size() * sizeof(CTransform4f); } + inline CTransform4f& operator[](u32 BoneIndex) { return BoneMatrix(BoneIndex); } + inline const CTransform4f& operator[](u32 BoneIndex) const { return BoneMatrix(BoneIndex); } +}; + +#endif // CBONETRANSFORMDATA + diff --git a/src/Core/Resource/CAnimation.cpp b/src/Core/Resource/CAnimation.cpp index 19e8d576..fbdd4e2d 100644 --- a/src/Core/Resource/CAnimation.cpp +++ b/src/Core/Resource/CAnimation.cpp @@ -18,10 +18,11 @@ void CAnimation::EvaluateTransform(float Time, u32 BoneID, CTransform4f& rOut) c { if (mDuration == 0.f) return; - Time = fmodf(Time, mDuration); + if (Time >= mDuration) Time = mDuration; if (Time >= FLT_EPSILON) Time -= FLT_EPSILON; float t = fmodf(Time, mTickInterval) / mTickInterval; u32 LowKey = (u32) (Time / mTickInterval); + if (LowKey == (mNumKeys - 1)) LowKey = mNumKeys - 2; u8 RotChannel = mBoneInfo[BoneID].RotationChannelIdx; u8 TransChannel = mBoneInfo[BoneID].TranslationChannelIdx; diff --git a/src/Core/Resource/CAnimation.h b/src/Core/Resource/CAnimation.h index 16d451b0..196f0d27 100644 --- a/src/Core/Resource/CAnimation.h +++ b/src/Core/Resource/CAnimation.h @@ -32,6 +32,10 @@ public: CAnimation(); void EvaluateTransform(float Time, u32 BoneID, CTransform4f& rOut) const; bool HasTranslation(u32 BoneID) const; + + inline float Duration() const { return mDuration; } + inline u32 NumKeys() const { return mNumKeys; } + inline float TickInterval() const { return mTickInterval; } }; #endif // CANIMATION_H diff --git a/src/Core/Resource/CSkeleton.cpp b/src/Core/Resource/CSkeleton.cpp index 6fe11f5e..32e96e63 100644 --- a/src/Core/Resource/CSkeleton.cpp +++ b/src/Core/Resource/CSkeleton.cpp @@ -1,6 +1,8 @@ #include "CSkeleton.h" +#include "Core/Render/CBoneTransformData.h" #include "Core/Render/CDrawUtil.h" #include "Core/Render/CGraphics.h" +#include // ************ CBone ************ CBone::CBone(CSkeleton *pSkel) @@ -8,26 +10,25 @@ CBone::CBone(CSkeleton *pSkel) { } -void CBone::UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot) +void CBone::UpdateTransform(CBoneTransformData& rData, CAnimation *pAnim, float Time, bool AnchorRoot) { - mAnimTransform = CTransform4f::skIdentity; + CTransform4f& rTransform = rData[mID]; + rTransform.SetIdentity(); if (pAnim) - pAnim->EvaluateTransform(Time, mID, mAnimTransform); + pAnim->EvaluateTransform(Time, mID, rTransform); if (!pAnim || !pAnim->HasTranslation(mID)) - mAnimTransform.Translate(mPosition); + rTransform.Translate(mPosition); if (mpParent) - mAnimTransform = mpParent->AnimTransform() * mAnimTransform; + rTransform = rData[mpParent->ID()] * rTransform; if (AnchorRoot && IsRoot()) - mAnimTransform.ZeroTranslation(); - - mAbsPosDirty = true; + rTransform.ZeroTranslation(); for (u32 iChild = 0; iChild < mChildren.size(); iChild++) - mChildren[iChild]->UpdateTransform(pAnim, Time, AnchorRoot); + mChildren[iChild]->UpdateTransform(rData, pAnim, Time, AnchorRoot); } bool CBone::IsRoot() const @@ -35,18 +36,9 @@ bool CBone::IsRoot() const return (mpParent == nullptr); } -CVector3f CBone::AbsolutePosition() const -{ - if (mAbsPosDirty) - { - mAbsolutePosition = (mAnimTransform * CVector3f::skZero); - mAbsPosDirty = false; - } - - return mAbsolutePosition; -} - // ************ CSkeleton ************ +const float CSkeleton::skSphereRadius = 0.025f; + CSkeleton::CSkeleton() : mpRootBone(nullptr) { @@ -69,21 +61,35 @@ CBone* CSkeleton::BoneByID(u32 BoneID) const return nullptr; } -void CSkeleton::UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot) +u32 CSkeleton::MaxBoneID() const { - mpRootBone->UpdateTransform(pAnim, Time, AnchorRoot); + u32 ID = 0; + + for (u32 iBone = 0; iBone < mBones.size(); iBone++) + { + if (mBones[iBone]->ID() > ID) + ID = mBones[iBone]->ID(); + } + + return ID; } -void CSkeleton::Draw(FRenderOptions /*Options*/) +void CSkeleton::UpdateTransform(CBoneTransformData& rData, CAnimation *pAnim, float Time, bool AnchorRoot) +{ + mpRootBone->UpdateTransform(rData, pAnim, Time, AnchorRoot); +} + +void CSkeleton::Draw(FRenderOptions /*Options*/, const CBoneTransformData& rkData) { for (u32 iBone = 0; iBone < mBones.size(); iBone++) { CBone *pBone = mBones[iBone]; + const CTransform4f& rkBoneTransform = rkData[pBone->ID()]; // Draw bone CTransform4f Transform; - Transform.Scale(0.025f); - Transform.Translate(pBone->AbsolutePosition()); + Transform.Scale(skSphereRadius); + Transform.Translate(rkBoneTransform.ExtractTranslation()); CGraphics::sMVPBlock.ModelMatrix = Transform; CGraphics::UpdateMVPBlock(); CDrawUtil::DrawSphere(CColor::skWhite); @@ -93,6 +99,29 @@ void CSkeleton::Draw(FRenderOptions /*Options*/) CGraphics::UpdateMVPBlock(); for (u32 iChild = 0; iChild < pBone->NumChildren(); iChild++) - CDrawUtil::DrawLine(pBone->AbsolutePosition(), pBone->ChildByIndex(iChild)->AbsolutePosition()); + { + const CTransform4f& rkChildTransform = rkData[pBone->ChildByIndex(iChild)->ID()]; + CDrawUtil::DrawLine(rkBoneTransform.ExtractTranslation(), rkChildTransform.ExtractTranslation()); + } } } + +std::pair CSkeleton::RayIntersect(const CRay& rkRay, const CBoneTransformData& rkData) +{ + std::pair Out(-1, FLT_MAX); + + for (u32 iBone = 0; iBone < mBones.size(); iBone++) + { + CBone *pBone = mBones[iBone]; + CVector3f BonePos = rkData[pBone->ID()].ExtractTranslation(); + std::pair Intersect = Math::RaySphereIntersection(rkRay, BonePos, skSphereRadius); + + if (Intersect.first && Intersect.second < Out.second) + { + Out.first = pBone->ID(); + Out.second = Intersect.second; + } + } + + return Out; +} diff --git a/src/Core/Resource/CSkeleton.h b/src/Core/Resource/CSkeleton.h index 8fbbca0e..b0ca4057 100644 --- a/src/Core/Resource/CSkeleton.h +++ b/src/Core/Resource/CSkeleton.h @@ -6,9 +6,10 @@ #include "Core/Render/FRenderOptions.h" #include #include -#include +#include #include +class CBoneTransformData; class CSkeleton; class CBone @@ -22,24 +23,19 @@ 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); + void UpdateTransform(CBoneTransformData& rData, CAnimation *pAnim, float Time, bool AnchorRoot); bool IsRoot() const; // Accessors - 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; + inline CSkeleton* Skeleton() const { return mpSkeleton; } + inline CBone* Parent() const { return mpParent; } + 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 TString Name() const { return mName; } }; class CSkeleton : public CResource @@ -50,12 +46,19 @@ class CSkeleton : public CResource CBone *mpRootBone; std::vector mBones; + static const float skSphereRadius; + public: CSkeleton(); ~CSkeleton(); - void UpdateTransform(CAnimation *pAnim, float Time, bool AnchorRoot); + void UpdateTransform(CBoneTransformData& rData, CAnimation *pAnim, float Time, bool AnchorRoot); CBone* BoneByID(u32 BoneID) const; - void Draw(FRenderOptions Options); + u32 MaxBoneID() const; + + void Draw(FRenderOptions Options, const CBoneTransformData& rkData); + std::pair RayIntersect(const CRay& rkRay, const CBoneTransformData& rkData); + + inline u32 NumBones() const { return mBones.size(); } }; #endif // CSKELETON_H diff --git a/src/Core/Resource/Factory/CAnimationLoader.cpp b/src/Core/Resource/Factory/CAnimationLoader.cpp index ffe01157..d8e3e98e 100644 --- a/src/Core/Resource/Factory/CAnimationLoader.cpp +++ b/src/Core/Resource/Factory/CAnimationLoader.cpp @@ -94,7 +94,7 @@ void CAnimationLoader::ReadCompressedANIM() // Read key flags u32 NumKeys = mpInput->ReadLong(); - mpAnim->mNumKeys = NumKeys - 1; + mpAnim->mNumKeys = NumKeys; mKeyFlags.resize(NumKeys); { CBitStreamInWrapper BitStream(mpInput); @@ -176,9 +176,9 @@ void CAnimationLoader::ReadCompressedAnimationData() } // Read keys - for (u32 iKey = 0; iKey < mpAnim->mNumKeys; iKey++) + for (u32 iKey = 0; iKey < mpAnim->mNumKeys - 1; iKey++) { - bool KeyPresent = mKeyFlags[iKey]; + bool KeyPresent = mKeyFlags[iKey+1]; for (u32 iChan = 0; iChan < mCompressedChannels.size(); iChan++) { @@ -187,6 +187,8 @@ void CAnimationLoader::ReadCompressedAnimationData() // Read rotation if (rChan.NumRotationKeys > 0) { + // Note if KeyPresent is false, this isn't the correct value of WSign. + // However, we're going to recreate this key later via interpolation, so it doesn't matter what value we use here. bool WSign = (KeyPresent ? BitStream.ReadBit() : false); if (KeyPresent) @@ -234,12 +236,12 @@ void CAnimationLoader::ReadCompressedAnimationData() { u32 KeyIndex = FirstIndex + iMissed + 1; u32 RelKeyIndex = (KeyIndex - FirstIndex); + float Interp = (float) RelKeyIndex / (float) RelLastIndex; 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) { diff --git a/src/Core/Scene/CCharacterNode.cpp b/src/Core/Scene/CCharacterNode.cpp index a8b1e6e1..5c09a08b 100644 --- a/src/Core/Scene/CCharacterNode.cpp +++ b/src/Core/Scene/CCharacterNode.cpp @@ -4,8 +4,9 @@ CCharacterNode::CCharacterNode(CScene *pScene, u32 NodeID, CAnimSet *pChar /*= 0*/, CSceneNode *pParent /*= 0*/) : CSceneNode(pScene, NodeID, pParent) + , mAnimTime(0.f) { - SetCharacter(pChar); + SetCharSet(pChar); } ENodeType CCharacterNode::NodeType() @@ -36,31 +37,53 @@ void CCharacterNode::Draw(FRenderOptions Options, int /*ComponentIndex*/, const { CSkeleton *pSkel = mpCharacter->NodeSkeleton(mActiveCharSet); CAnimation *pAnim = mpCharacter->Animation(mActiveAnim); - pSkel->UpdateTransform(pAnim, (float) CTimer::GlobalTime(), false); - pSkel->Draw(Options); + pSkel->UpdateTransform(mTransformData, pAnim, mAnimTime, false); + pSkel->Draw(Options, mTransformData); } -SRayIntersection CCharacterNode::RayNodeIntersectTest(const CRay& /*rkRay*/, u32 /*AssetID*/, const SViewInfo& /*rkViewInfo*/) +SRayIntersection CCharacterNode::RayNodeIntersectTest(const CRay& rkRay, u32 /*AssetID*/, const SViewInfo& /*rkViewInfo*/) { - // Not currently doing any ray checks on character nodes so don't care about this right now. + // Check for bone under ray. Doesn't check for model intersections atm + if (mpCharacter) + { + CSkeleton *pSkel = mpCharacter->NodeSkeleton(mActiveCharSet); + + if (pSkel) + { + std::pair Hit = pSkel->RayIntersect(rkRay, mTransformData); + + if (Hit.first != -1) + { + SRayIntersection Intersect; + Intersect.Hit = true; + Intersect.ComponentIndex = Hit.first; + Intersect.Distance = Hit.second; + Intersect.HitPoint = rkRay.PointOnRay(Hit.second); + Intersect.pNode = this; + return Intersect; + } + } + } + return SRayIntersection(); } -void CCharacterNode::SetCharacter(CAnimSet *pChar) +void CCharacterNode::SetCharSet(CAnimSet *pChar) { mpCharacter = pChar; - SetActiveCharSet(0); + SetActiveChar(0); if (!mpCharacter) mLocalAABox = CAABox::skOne; } -void CCharacterNode::SetActiveCharSet(u32 CharIndex) +void CCharacterNode::SetActiveChar(u32 CharIndex) { mActiveCharSet = CharIndex; if (mpCharacter) { + mTransformData.ResizeToSkeleton(mpCharacter->NodeSkeleton(CharIndex)); mLocalAABox = mpCharacter->NodeModel(CharIndex)->AABox(); MarkTransformChanged(); } @@ -70,3 +93,8 @@ void CCharacterNode::SetActiveAnim(u32 AnimIndex) { mActiveAnim = AnimIndex; } + +void CCharacterNode::SetAnimTime(float Time) +{ + mAnimTime = Time; +} diff --git a/src/Core/Scene/CCharacterNode.h b/src/Core/Scene/CCharacterNode.h index c98e3140..79b9e74a 100644 --- a/src/Core/Scene/CCharacterNode.h +++ b/src/Core/Scene/CCharacterNode.h @@ -2,13 +2,16 @@ #define CCHARACTERNODE_H #include "CSceneNode.h" +#include "Core/Render/CBoneTransformData.h" #include "Core/Resource/CAnimSet.h" class CCharacterNode : public CSceneNode { TResPtr mpCharacter; + CBoneTransformData mTransformData; u32 mActiveCharSet; u32 mActiveAnim; + float mAnimTime; public: explicit CCharacterNode(CScene *pScene, u32 NodeID, CAnimSet *pChar = 0, CSceneNode *pParent = 0); @@ -22,9 +25,10 @@ public: inline u32 ActiveCharSet() const { return mActiveCharSet; } inline u32 ActiveAnim() const { return mActiveAnim; } - void SetCharacter(CAnimSet *pChar); - void SetActiveCharSet(u32 CharIndex); + void SetCharSet(CAnimSet *pChar); + void SetActiveChar(u32 CharIndex); void SetActiveAnim(u32 AnimIndex); + void SetAnimTime(float Time); }; #endif // CCHARACTERNODE_H diff --git a/src/Core/Scene/CLightNode.cpp b/src/Core/Scene/CLightNode.cpp index 0294da0f..c62dc7bf 100644 --- a/src/Core/Scene/CLightNode.cpp +++ b/src/Core/Scene/CLightNode.cpp @@ -80,7 +80,7 @@ SRayIntersection CLightNode::RayNodeIntersectTest(const CRay& rkRay, u32 AssetID // Step 1: check whether the ray intersects with the plane the billboard is on CPlane BillboardPlane(-rkViewInfo.pCamera->Direction(), mPosition); - std::pair PlaneTest = Math::RayPlaneIntersecton(rkRay, BillboardPlane); + std::pair PlaneTest = Math::RayPlaneIntersection(rkRay, BillboardPlane); if (PlaneTest.first) { diff --git a/src/Core/Scene/CScriptNode.cpp b/src/Core/Scene/CScriptNode.cpp index 49cffb93..7f13e600 100644 --- a/src/Core/Scene/CScriptNode.cpp +++ b/src/Core/Scene/CScriptNode.cpp @@ -361,7 +361,7 @@ SRayIntersection CScriptNode::RayNodeIntersectTest(const CRay& rkRay, u32 AssetI { // Step 1: check whether the ray intersects with the plane the billboard is on CPlane BillboardPlane(-rkViewInfo.pCamera->Direction(), mPosition); - std::pair PlaneTest = Math::RayPlaneIntersecton(rkRay, BillboardPlane); + std::pair PlaneTest = Math::RayPlaneIntersection(rkRay, BillboardPlane); if (PlaneTest.first) { diff --git a/src/Editor/CBasicViewport.cpp b/src/Editor/CBasicViewport.cpp index 3ed1f0c2..ea47d828 100644 --- a/src/Editor/CBasicViewport.cpp +++ b/src/Editor/CBasicViewport.cpp @@ -231,9 +231,7 @@ CRay CBasicViewport::CastRay() CVector2f CBasicViewport::MouseDeviceCoordinates() { - QPoint MousePos = QCursor::pos(); - QPoint ThisPos = this->mapToGlobal(pos()); - MousePos -= ThisPos; + QPoint MousePos = mapFromGlobal(QCursor::pos()); CVector2f Device( (((2.f * MousePos.x()) / width()) - 1.f), diff --git a/src/Editor/CGizmo.cpp b/src/Editor/CGizmo.cpp index ac5bde75..db038178 100644 --- a/src/Editor/CGizmo.cpp +++ b/src/Editor/CGizmo.cpp @@ -317,7 +317,7 @@ bool CGizmo::TransformFromInput(const CRay& rkRay, CCamera& rCamera) mTranslatePlane.Redefine(PlaneNormal, mPosition); // Do translate - std::pair Result = Math::RayPlaneIntersecton(rkRay, mTranslatePlane); + std::pair Result = Math::RayPlaneIntersection(rkRay, mTranslatePlane); if (Result.first) { diff --git a/src/Editor/CharacterEditor/CCharacterEditor.cpp b/src/Editor/CharacterEditor/CCharacterEditor.cpp index d388a40b..c74d82df 100644 --- a/src/Editor/CharacterEditor/CCharacterEditor.cpp +++ b/src/Editor/CharacterEditor/CCharacterEditor.cpp @@ -1,6 +1,7 @@ #include "CCharacterEditor.h" #include "ui_CCharacterEditor.h" #include "Editor/UICommon.h" +#include #include CCharacterEditor::CCharacterEditor(QWidget *parent) @@ -8,6 +9,9 @@ CCharacterEditor::CCharacterEditor(QWidget *parent) , ui(new Ui::CCharacterEditor) , mpScene(new CScene()) , mpCharNode(new CCharacterNode(mpScene, -1)) + , mPlayAnim(true) + , mLoopAnim(true) + , mPlaybackSpeed(1.f) { ui->setupUi(this); @@ -32,9 +36,15 @@ CCharacterEditor::CCharacterEditor(QWidget *parent) connect(&mRefreshTimer, SIGNAL(timeout()), this, SLOT(RefreshViewport())); mRefreshTimer.start(0); + connect(ui->Viewport, SIGNAL(HoverBoneChanged(u32)), this, SLOT(HoverBoneChanged(u32))); + connect(ui->ActionOpen, SIGNAL(triggered()), this, SLOT(Open())); 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())); + + connect(ui->AnimSlider, SIGNAL(valueChanged(int)), this, SLOT(SetAnimTime(int))); + connect(ui->PlayPauseButton, SIGNAL(pressed()), this, SLOT(PlayPauseButtonPressed())); + connect(ui->LoopButton, SIGNAL(toggled(bool)), this, SLOT(LoopButtonToggled(bool))); + connect(ui->AnimSpeedSpinBox, SIGNAL(valueChanged(double)), this, SLOT(AnimSpeedSpinBoxChanged(double))); } CCharacterEditor::~CCharacterEditor() @@ -42,25 +52,66 @@ CCharacterEditor::~CCharacterEditor() delete ui; } +void CCharacterEditor::UpdateAnimTime() +{ + double Time = CTimer::GlobalTime(); + double DeltaTime = Time - mLastAnimUpdate; + mLastAnimUpdate = Time; + + if (mPlayAnim && !ui->AnimSlider->isSliderDown()) + { + mAnimTime += DeltaTime * mPlaybackSpeed; + + CAnimation *pAnim = CurrentAnimation(); + float AnimLength = (pAnim ? pAnim->Duration() : 0.f); + + if (mAnimTime > AnimLength) + { + if (mLoopAnim) + mAnimTime = fmodf(mAnimTime, AnimLength); + else + mAnimTime = AnimLength; + } + + if (mAnimTime < 0.f) + { + if (mLoopAnim) + mAnimTime = AnimLength + fmodf(mAnimTime, AnimLength); + else + mAnimTime = 0.f; + } + + SetAnimTime(mAnimTime); + } +} + +CAnimation* CCharacterEditor::CurrentAnimation() const +{ + if (mpSet) + return mpSet->Animation(mCurrentAnim); + else + return nullptr; +} + // ************ PUBLIC SLOTS ************ void CCharacterEditor::Open() { QString CharFilename = QFileDialog::getOpenFileName(this, "Open Character", "", "Animation Character Set (*.ANCS)"); if (CharFilename.isEmpty()) return; - TResPtr pSet = gResCache.GetResource(CharFilename.toStdString()); + mpSet = gResCache.GetResource(CharFilename.toStdString()); - if (pSet) + if (mpSet) { - mpCharNode->SetCharacter(pSet); - setWindowTitle("Prime World Editor - Character Editor: " + TO_QSTRING(pSet->Source())); + mpCharNode->SetCharSet(mpSet); + setWindowTitle("Prime World Editor - Character Editor: " + TO_QSTRING(mpSet->Source())); // Set up character combo box mpCharComboBox->blockSignals(true); mpCharComboBox->clear(); - for (u32 iChar = 0; iChar < pSet->NumNodes(); iChar++) - mpCharComboBox->addItem( TO_QSTRING(pSet->NodeName(iChar)) ); + for (u32 iChar = 0; iChar < mpSet->NumNodes(); iChar++) + mpCharComboBox->addItem( TO_QSTRING(mpSet->NodeName(iChar)) ); SetActiveCharacterIndex(0); mpCharComboBox->blockSignals(false); @@ -69,8 +120,8 @@ void CCharacterEditor::Open() mpAnimComboBox->blockSignals(true); mpAnimComboBox->clear(); - for (u32 iAnim = 0; iAnim < pSet->NumAnims(); iAnim++) - mpAnimComboBox->addItem( TO_QSTRING(pSet->AnimName(iAnim)) ); + for (u32 iAnim = 0; iAnim < mpSet->NumAnims(); iAnim++) + mpAnimComboBox->addItem( TO_QSTRING(mpSet->AnimName(iAnim)) ); SetActiveAnimation(0); mpAnimComboBox->blockSignals(false); @@ -81,16 +132,81 @@ void CCharacterEditor::Open() void CCharacterEditor::RefreshViewport() { + UpdateAnimTime(); ui->Viewport->ProcessInput(); ui->Viewport->Render(); } +void CCharacterEditor::HoverBoneChanged(u32 BoneID) +{ + if (BoneID == 0xFFFFFFFF) + ui->StatusBar->clearMessage(); + else + ui->StatusBar->showMessage(QString("Bone %1: %2").arg(BoneID).arg( TO_QSTRING(mpSet->NodeSkeleton(mCurrentChar)->BoneByID(BoneID)->Name()) )); +} + void CCharacterEditor::SetActiveCharacterIndex(int CharIndex) { - mpCharNode->SetActiveCharSet((u32) CharIndex); + mCurrentChar = CharIndex; + mpCharNode->SetActiveChar((u32) CharIndex); } void CCharacterEditor::SetActiveAnimation(int AnimIndex) { + mCurrentAnim = AnimIndex; mpCharNode->SetActiveAnim((u32) AnimIndex); + mLastAnimUpdate = CTimer::GlobalTime(); + + ui->AnimSlider->blockSignals(true); + ui->AnimSlider->setMaximum((int) (mpSet->Animation(AnimIndex)->Duration() * 1000)); + ui->AnimSlider->blockSignals(false); + + SetAnimTime(0.f); +} + +void CCharacterEditor::SetAnimTime(int Time) +{ + float FloatTime = Time / 1000.f; + SetAnimTime(FloatTime); +} + +void CCharacterEditor::SetAnimTime(float Time) +{ + mAnimTime = Time; + + if (ui->AnimSlider != sender()) + { + int IntTime = (int) (Time * 1000); + ui->AnimSlider->setValue(IntTime); + } + + mpCharNode->SetAnimTime(Time); + + CAnimation *pAnim = (mpSet ? mpSet->Animation(mCurrentAnim) : nullptr); + u32 NumKeys = 1, CurKey = 0; + + if (pAnim) + { + NumKeys = pAnim->NumKeys(); + CurKey = Math::Min((u32) (Time / pAnim->TickInterval()) + 1, NumKeys - 1); + } + + ui->FrameLabel->setText(QString("Frame %1 / %2").arg(CurKey).arg(NumKeys - 1)); +} + +void CCharacterEditor::PlayPauseButtonPressed() +{ + mPlayAnim = !mPlayAnim; + QString NewText = (mPlayAnim ? "Pause" : "Play"); + ui->PlayPauseButton->setText(NewText); +} + +void CCharacterEditor::LoopButtonToggled(bool Checked) +{ + mLoopAnim = Checked; +} + +void CCharacterEditor::AnimSpeedSpinBoxChanged(double NewVal) +{ + mPlaybackSpeed = (float) NewVal; } diff --git a/src/Editor/CharacterEditor/CCharacterEditor.h b/src/Editor/CharacterEditor/CCharacterEditor.h index 7a0a0cfa..ce38ddeb 100644 --- a/src/Editor/CharacterEditor/CCharacterEditor.h +++ b/src/Editor/CharacterEditor/CCharacterEditor.h @@ -25,15 +25,35 @@ class CCharacterEditor : public QMainWindow QComboBox *mpAnimComboBox; QTimer mRefreshTimer; + TResPtr mpSet; + u32 mCurrentChar; + u32 mCurrentAnim; + + // Playback Controls + double mLastAnimUpdate; + float mAnimTime; + bool mPlayAnim; + bool mLoopAnim; + float mPlaybackSpeed; + public: explicit CCharacterEditor(QWidget *parent = 0); ~CCharacterEditor(); + void UpdateAnimTime(); + CAnimation* CurrentAnimation() const; public slots: void Open(); void RefreshViewport(); + void HoverBoneChanged(u32 BoneID); void SetActiveCharacterIndex(int CharIndex); void SetActiveAnimation(int AnimIndex); + void SetAnimTime(int Time); + void SetAnimTime(float Time); + + void PlayPauseButtonPressed(); + void LoopButtonToggled(bool Checked); + void AnimSpeedSpinBoxChanged(double NewVal); }; #endif // CCHARACTEREDITORWINDOW_H diff --git a/src/Editor/CharacterEditor/CCharacterEditor.ui b/src/Editor/CharacterEditor/CCharacterEditor.ui index ff403b36..8cc6875f 100644 --- a/src/Editor/CharacterEditor/CCharacterEditor.ui +++ b/src/Editor/CharacterEditor/CCharacterEditor.ui @@ -15,20 +15,113 @@ - - 0 - - - 0 - - - 0 - - - 0 - - + + + + 0 + 1 + + + + + + + + Qt::Horizontal + + + + + + + + + Loop + + + true + + + true + + + + + + + Pause + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Speed: + + + + + + + x + + + 1 + + + -10.000000000000000 + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + + + + 0 + 0 + + + + + 16777215 + 21 + + + + Frame 0 / 0 + + + Qt::PlainText + + + Qt::AlignCenter + + @@ -85,6 +178,11 @@
Editor/CharacterEditor/CCharacterEditorViewport.h
1 + + WDraggableSpinBox + QDoubleSpinBox +
Editor/Widgets/WDraggableSpinBox.h
+
diff --git a/src/Editor/CharacterEditor/CCharacterEditorViewport.cpp b/src/Editor/CharacterEditor/CCharacterEditorViewport.cpp index ac9290d3..16627197 100644 --- a/src/Editor/CharacterEditor/CCharacterEditorViewport.cpp +++ b/src/Editor/CharacterEditor/CCharacterEditorViewport.cpp @@ -24,6 +24,26 @@ void CCharacterEditorViewport::SetNode(CCharacterNode *pNode) mpCharNode = pNode; } +void CCharacterEditorViewport::CheckUserInput() +{ + u32 HoverBoneID = -1; + + if (underMouse() && !IsMouseInputActive()) + { + CRay Ray = CastRay(); + SRayIntersection Intersect = mpCharNode->RayNodeIntersectTest(Ray, 0, mViewInfo); + + if (Intersect.Hit) + HoverBoneID = Intersect.ComponentIndex; + } + + if (HoverBoneID != mHoverBone) + { + mHoverBone = HoverBoneID; + emit HoverBoneChanged(mHoverBone); + } +} + void CCharacterEditorViewport::Paint() { mpRenderer->BeginFrame(); diff --git a/src/Editor/CharacterEditor/CCharacterEditorViewport.h b/src/Editor/CharacterEditor/CCharacterEditorViewport.h index 29a1de4e..18f95bc1 100644 --- a/src/Editor/CharacterEditor/CCharacterEditorViewport.h +++ b/src/Editor/CharacterEditor/CCharacterEditorViewport.h @@ -6,15 +6,22 @@ class CCharacterEditorViewport : public CBasicViewport { + Q_OBJECT + CCharacterNode *mpCharNode; CRenderer *mpRenderer; + u32 mHoverBone; public: CCharacterEditorViewport(QWidget *pParent = 0); ~CCharacterEditorViewport(); void SetNode(CCharacterNode *pNode); + void CheckUserInput(); void Paint(); void OnResize(); + +signals: + void HoverBoneChanged(u32 BoneID); }; #endif // CCHARACTEREDITORVIEWPORT_H diff --git a/src/Math/CTransform4f.cpp b/src/Math/CTransform4f.cpp index 4d7e2b16..269b2ee9 100644 --- a/src/Math/CTransform4f.cpp +++ b/src/Math/CTransform4f.cpp @@ -184,6 +184,13 @@ CTransform4f CTransform4f::RotationOnly() const return Inverse().Transpose(); } +CVector3f CTransform4f::ExtractTranslation() const +{ + CVector3f Test = *this * CVector3f::skZero; + Test = Test; + return CVector3f(m[0][3], m[1][3], m[2][3]); +} + // ************ OPERATORS ************ float* CTransform4f::operator[](long Index) { diff --git a/src/Math/CTransform4f.h b/src/Math/CTransform4f.h index 2f5d3a0a..0487c3c4 100644 --- a/src/Math/CTransform4f.h +++ b/src/Math/CTransform4f.h @@ -39,6 +39,8 @@ public: CTransform4f TranslationOnly() const; CTransform4f RotationOnly() const; + CVector3f ExtractTranslation() const; + // Static static CTransform4f TranslationMatrix(CVector3f Translation); static CTransform4f RotationMatrix(CQuaternion Rotation); diff --git a/src/Math/MathUtil.cpp b/src/Math/MathUtil.cpp index 314d58db..8b3bad32 100644 --- a/src/Math/MathUtil.cpp +++ b/src/Math/MathUtil.cpp @@ -37,7 +37,7 @@ float RadiansToDegrees(float Rad) return Rad * 180.f / skPi; } -std::pair RayPlaneIntersecton(const CRay& rkRay, const CPlane& plane) +std::pair RayPlaneIntersection(const CRay& rkRay, const CPlane& plane) { // Code based on ray/plane intersect code from Ogre // https://bitbucket.org/sinbad/ogre/src/197116fd2ac62c57cdeed1666f9866c3dddd4289/OgreMain/src/OgreMath.cpp?at=default#OgreMath.cpp-350 @@ -246,6 +246,33 @@ std::pair RayLineIntersection(const CRay& rkRay, const CVector3f& rk return std::pair(hit, sc); } +std::pair RaySphereIntersection(const CRay& rkRay, const CVector3f& rkSpherePos, float SphereRadius, bool AllowBackfaces /*= false*/) +{ + std::pair Out(false, 0.f); + float SquaredRadius = (SphereRadius * SphereRadius); + + // Test for ray origin inside sphere + if (!AllowBackfaces && rkRay.Origin().SquaredDistance(rkSpherePos) <= SquaredRadius) + return Out; + + CVector3f RayToSphere = rkSpherePos - rkRay.Origin(); + float CenterDist = RayToSphere.Dot(rkRay.Direction()); + + if (CenterDist >= 0.f) + { + float RayToSphereDistSquared = RayToSphere.SquaredMagnitude(); + float DSquared = RayToSphereDistSquared - (CenterDist * CenterDist); + + if (DSquared >= 0.f && DSquared <= SquaredRadius) + { + Out.first = true; + Out.second = CenterDist - Sqrt(SquaredRadius - DSquared); + } + } + + return Out; +} + std::pair RayTriangleIntersection(const CRay& rkRay, const CVector3f& rkVtxA, const CVector3f& rkVtxB, const CVector3f& rkVtxC, bool AllowBackfaces) diff --git a/src/Math/MathUtil.h b/src/Math/MathUtil.h index 03fc9bd7..30745b90 100644 --- a/src/Math/MathUtil.h +++ b/src/Math/MathUtil.h @@ -22,6 +22,18 @@ float DegreesToRadians(float Deg); float RadiansToDegrees(float Rad); +template +Type Max(const Type& rkA, const Type& rkB) +{ + return (rkA > rkB ? rkA : rkB); +} + +template +Type Min(const Type& rkA, const Type& rkB) +{ + return (rkA < rkB ? rkA : rkB); +} + template Type Lerp(const Type& rkA, const Type& rkB, float t) { @@ -29,13 +41,16 @@ Type Lerp(const Type& rkA, const Type& rkB, float t) return rkA + (Diff * t); } -std::pair RayPlaneIntersecton(const CRay& rkRay, const CPlane& rkPlane); +std::pair RayPlaneIntersection(const CRay& rkRay, const CPlane& rkPlane); std::pair RayBoxIntersection(const CRay& rkRay, const CAABox& rkBox); std::pair RayLineIntersection(const CRay& rkRay, const CVector3f& rkPointA, const CVector3f& rkPointB, float Threshold = 0.02f); +std::pair RaySphereIntersection(const CRay& rkRay, const CVector3f& rkSpherePos, + float SphereRadius, bool AllowBackfaces = false); + std::pair RayTriangleIntersection(const CRay& rkRay, const CVector3f& rkPointA, const CVector3f& rkPointB, const CVector3f& rkPointC, bool AllowBackfaces = false);