IQM Refactor start

- Refactored IQM and calculateBones to process TRS at runtime which resolves some of the faulty animations with large rotations. Will also make bone manipulations much easier to do
This commit is contained in:
Shiny Metagross 2022-10-26 11:30:59 -07:00 committed by Christoph Oelckers
parent c36da35e37
commit 3f3cc5bbc3
7 changed files with 142 additions and 98 deletions

View file

@ -4,6 +4,8 @@
#include "textureid.h"
#include "i_modelvertexbuffer.h"
#include "matrix.h"
#include "TRS.h"
#include "d_player.h"
class FModelRenderer;
class FGameTexture;
@ -74,8 +76,8 @@ public:
virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0;
virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0;
virtual float getAspectFactor(float vscale) { return 1.f; }
virtual const TArray<VSMatrix>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData) { return {}; };
virtual const TArray<TRS>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, AActor* actor) { return {}; };
void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; }
IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; }

View file

@ -112,8 +112,8 @@ public:
void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray<VSMatrix>& boneData, int boneStartPosition) override;
void BuildVertexBuffer(FModelRenderer* renderer) override;
void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override;
const TArray<VSMatrix>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData) override;
const TArray<TRS>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, AActor* actor) override;
private:
void LoadGeometry();
@ -132,7 +132,6 @@ private:
TArray<IQMJoint> Joints;
TArray<IQMPose> Poses;
TArray<IQMAnim> Anims;
TArray<VSMatrix> FrameTransforms;
TArray<IQMBounds> Bounds;
TArray<IQMVertexArray> VertexArrays;
uint32_t NumVertices = 0;
@ -141,6 +140,7 @@ private:
TArray<VSMatrix> baseframe;
TArray<VSMatrix> inversebaseframe;
TArray<TRS> TRSData;
};
struct IQMReadErrorException { };

View file

@ -5,6 +5,7 @@
#include "texturemanager.h"
#include "modelrenderer.h"
#include "engineerrors.h"
#include "r_utility.h"
IQMModel::IQMModel()
{
@ -168,18 +169,10 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
{
baseframe[i] = m;
inversebaseframe[i] = invm;
}
}
}
// Swap YZ axis as we did that with the vertices down in LoadGeometry.
// This is an unfortunate side effect of the coordinate system in the gzdoom model rendering system
float swapYZ[16] = { 0.0f };
swapYZ[0 + 0 * 4] = 1.0f;
swapYZ[1 + 2 * 4] = 1.0f;
swapYZ[2 + 1 * 4] = 1.0f;
swapYZ[3 + 3 * 4] = 1.0f;
FrameTransforms.Resize(num_frames * num_poses);
TRSData.Resize(num_frames * num_poses);
reader.SeekTo(ofs_frames);
for (uint32_t i = 0; i < num_frames; i++)
{
@ -204,39 +197,15 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
scale.Y = p.ChannelOffset[8]; if (p.ChannelMask & 0x100) scale.Y += reader.ReadUInt16() * p.ChannelScale[8];
scale.Z = p.ChannelOffset[9]; if (p.ChannelMask & 0x200) scale.Z += reader.ReadUInt16() * p.ChannelScale[9];
TRSData[i * num_poses + j].translation = translate;
TRSData[i * num_poses + j].rotation = quaternion;
TRSData[i * num_poses + j].scaling = scale;
VSMatrix m;
m.loadIdentity();
m.translate(translate.X, translate.Y, translate.Z);
m.multQuaternion(quaternion);
m.scale(scale.X, scale.Y, scale.Z);
// Concatenate each pose with the inverse base pose to avoid doing this at animation time.
// If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose.
// Thus it all negates at animation time like so:
// (parentPose * parentInverseBasePose) * (parentBasePose * childPose * childInverseBasePose) =>
// parentPose * (parentInverseBasePose * parentBasePose) * childPose * childInverseBasePose =>
// parentPose * childPose * childInverseBasePose
VSMatrix& result = FrameTransforms[i * num_poses + j];
if (p.Parent >= 0)
{
result = baseframe[p.Parent];
result.multMatrix(m);
result.multMatrix(inversebaseframe[j]);
}
else
{
result = m;
result.multMatrix(inversebaseframe[j]);
}
}
for (uint32_t j = 0; j < num_poses; j++)
{
VSMatrix m;
m.loadMatrix(swapYZ);
m.multMatrix(FrameTransforms[i * num_poses + j]);
m.multMatrix(swapYZ);
FrameTransforms[i * num_poses + j] = m;
}
}
@ -244,7 +213,6 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
if (num_frames <= 0)
{
num_frames = 1;
FrameTransforms.Resize(num_joints);
for (uint32_t j = 0; j < num_joints; j++)
{
@ -270,28 +238,6 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
m.translate(translate.X, translate.Y, translate.Z);
m.multQuaternion(quaternion);
m.scale(scale.X, scale.Y, scale.Z);
VSMatrix& result = FrameTransforms[j];
if (Joints[j].Parent >= 0)
{
result = baseframe[Joints[j].Parent];
result.multMatrix(m);
result.multMatrix(inversebaseframe[j]);
}
else
{
result = m;
result.multMatrix(inversebaseframe[j]);
}
}
for (uint32_t j = 0; j < num_joints; j++)
{
VSMatrix m;
m.loadMatrix(swapYZ);
m.multMatrix(FrameTransforms[j]);
m.multMatrix(swapYZ);
FrameTransforms[j] = m;
}
}
@ -372,8 +318,8 @@ void IQMModel::LoadPosition(IQMFileReader& reader, const IQMVertexArray& vertexA
for (FModelVertex& v : Vertices)
{
v.x = reader.ReadFloat();
v.z = reader.ReadFloat();
v.y = reader.ReadFloat();
v.z = reader.ReadFloat();
v.lu = lu;
v.lv = lv;
@ -412,7 +358,7 @@ void IQMModel::LoadNormal(IQMFileReader& reader, const IQMVertexArray& vertexArr
float y = reader.ReadFloat();
float z = reader.ReadFloat();
v.SetNormal(x, z, y);
v.SetNormal(x, y, z);
}
}
else
@ -511,24 +457,25 @@ void IQMModel::RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int fra
{
meshSkin = TexMan.GetGameTexture(surfaceskinids[i], true);
}
else if (!Meshes[i].Skin.isValid())
else if (Meshes[i].Skin.isValid())
{
meshSkin = TexMan.GetGameTexture(Meshes[i].Skin, true);
}
else
{
continue;
}
else
{
meshSkin = TexMan.GetGameTexture(Meshes[i].Skin, true);
}
if (!meshSkin) continue;
}
if (meshSkin != lastSkin)
if (meshSkin->isValid())
{
renderer->SetMaterial(meshSkin, false, translation);
lastSkin = meshSkin;
if (meshSkin != lastSkin)
{
renderer->SetMaterial(meshSkin, false, translation);
lastSkin = meshSkin;
}
renderer->DrawElements(Meshes[i].NumTriangles * 3, Meshes[i].FirstTriangle * 3 * sizeof(unsigned int));
}
renderer->DrawElements(Meshes[i].NumTriangles * 3, Meshes[i].FirstTriangle * 3 * sizeof(unsigned int));
}
}
@ -562,17 +509,26 @@ void IQMModel::AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids)
}
}
const TArray<VSMatrix>* IQMModel::AttachAnimationData()
const TArray<TRS>* IQMModel::AttachAnimationData()
{
return &FrameTransforms;
return &TRSData;
}
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData)
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, AActor* actor)
{
const TArray<VSMatrix>& animationFrames = &animationData ? animationData : FrameTransforms;
const TArray<TRS>& animationFrames = &animationData ? animationData : TRSData;
int numbones = Joints.Size();
if (actor->boneComponentData == nullptr)
{
auto ptr = Create<DBoneComponents>();
ptr->trscomponents.Resize(numbones);
ptr->trsmatrix.Resize(numbones);
actor->boneComponentData = ptr;
GC::WriteBarrier(actor, ptr);
}
frame1 = clamp(frame1, 0, ((int)animationFrames.Size() - 1) / numbones);
frame2 = clamp(frame2, 0, ((int)animationFrames.Size() - 1) / numbones);
@ -584,27 +540,49 @@ const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double i
TArray<VSMatrix> bones(numbones, true);
for (int i = 0; i < numbones; i++)
{
const float* from = animationFrames[offset1 + i].get();
const float* to = animationFrames[offset2 + i].get();
// Interpolate bone between the two frames
float bone[16];
for (int j = 0; j < 16; j++)
TRS bone;
bone.translation = animationFrames[offset1 + i].translation * invt + animationFrames[offset2 + i].translation * t;
bone.rotation = animationFrames[offset1 + i].rotation * invt;
if ((bone.rotation | animationFrames[offset2 + i].rotation * t) < 0)
{
bone[j] = from[j] * invt + to[j] * t;
bone.rotation.X *= -1; bone.rotation.Y *= -1; bone.rotation.Z *= -1; bone.rotation.W *= -1;
}
bone.rotation += animationFrames[offset2 + i].rotation * t;
bone.rotation.MakeUnit();
bone.scaling = animationFrames[offset1 + i].scaling * invt + animationFrames[offset2 + i].scaling * t;
// Apply parent bone
if (Joints[i].Parent >= 0)
if (actor->boneComponentData->trscomponents[i].Equals(bone))
{
bones[i] = bones[Joints[i].Parent];
bones[i].multMatrix(bone);
bones[i] = actor->boneComponentData->trsmatrix[i];
continue;
}
else
{
bones[i].loadMatrix(bone);
actor->boneComponentData->trscomponents[i] = bone;
}
VSMatrix m;
m.loadIdentity();
m.translate(bone.translation.X, bone.translation.Y, bone.translation.Z);
m.multQuaternion(bone.rotation);
m.scale(bone.scaling.X, bone.scaling.Y, bone.scaling.Z);
VSMatrix& result = bones[i];
if (Joints[i].Parent >= 0)
{
result = bones[Joints[i].Parent];
result.multMatrix(baseframe[Joints[i].Parent]);
result.multMatrix(m);
result.multMatrix(inversebaseframe[i]);
}
else
{
result = m;
result.multMatrix(inversebaseframe[i]);
}
}
actor->boneComponentData->trsmatrix = bones;
return bones;
}

49
src/common/utility/TRS.h Normal file
View file

@ -0,0 +1,49 @@
/*
** TRS
**
**---------------------------------------------------------------------------
** Copyright 2022 Andrew Clarke
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#pragma once
#include "vectors.h"
class TRS
{
public:
FVector3 translation;
FVector4 rotation;
FVector3 scaling;
bool Equals(TRS& compare)
{
return compare.translation == this->translation && compare.rotation == this->rotation && compare.scaling == this->scaling;
}
};

View file

@ -47,6 +47,8 @@
#include "g_level.h"
#include "tflags.h"
#include "portal.h"
#include "matrix.h"
#include "TRS.h"
struct subsector_t;
struct FBlockNode;
@ -689,6 +691,16 @@ public:
virtual void Serialize(FSerializer& arc) override;
};
class DBoneComponents : public DObject
{
DECLARE_CLASS(DBoneComponents, DObject);
public:
TArray<TRS> trscomponents;
TArray<VSMatrix> trsmatrix;
DBoneComponents() = default;
};
class DViewPosition : public DObject
{
DECLARE_CLASS(DViewPosition, DObject);
@ -1085,6 +1097,7 @@ public:
double Speed;
double FloatSpeed;
TObjPtr<DActorModelData*> modelData;
TObjPtr<DBoneComponents*> boneComponentData;
// interaction info
FBlockNode *BlockNode; // links in blocks (if needed)

View file

@ -159,6 +159,7 @@ CVAR (Int, cl_bloodtype, 0, CVAR_ARCHIVE);
// CODE --------------------------------------------------------------------
IMPLEMENT_CLASS(DActorModelData, false, false);
IMPLEMENT_CLASS(DBoneComponents, false, false);
IMPLEMENT_CLASS(AActor, false, true)
IMPLEMENT_POINTERS_START(AActor)
@ -174,6 +175,7 @@ IMPLEMENT_POINTERS_START(AActor)
IMPLEMENT_POINTER(alternative)
IMPLEMENT_POINTER(ViewPos)
IMPLEMENT_POINTER(modelData)
IMPLEMENT_POINTER(boneComponentData)
IMPLEMENT_POINTERS_END
AActor::~AActor ()

View file

@ -352,7 +352,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
auto& ssids = surfaceskinids.Size() > 0 ? surfaceskinids : smf->surfaceskinIDs;
auto ssidp = (unsigned)(i * MD3_MAX_SURFACES) < ssids.Size() ? &ssids[i * MD3_MAX_SURFACES] : nullptr;
const TArray<VSMatrix>* animationData = nullptr;
const TArray<TRS>* animationData = nullptr;
bool nextFrame = smfNext && modelframe != modelframenext;
@ -363,7 +363,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false)
{
boneData = animation->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, *animationData);
boneData = animation->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, *animationData, actor);
boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1);
evaluatedSingle = true;
}
@ -372,7 +372,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
{
if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false)
{
boneData = mdl->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, *animationData);
boneData = mdl->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, *animationData, actor);
boneStartingPosition = renderer->SetupFrame(mdl, 0, 0, 0, boneData, -1);
evaluatedSingle = true;
}