Bone Manip part 1 - bone setters

* split frame info calculation from RenderFrameModels

* clean up fvec4 being used as quat in iqms

* initial work on bone setting

* implement bone setters

* clean up FindModelFrame

* refactor model overrides into its own function

* refactor frame rendering into RenderModelFrame

* split frame processing into ProcessModelFrame

* refactor BoneOverride in preparation for translation/scale overrides

* clean up macros

* SetBoneTranslation/SetBoneScaling

* GetBoneOffset

* fix compilation on linux/mac (fuck you MSVC)

* fix typo 😅

* make sure bone overrides are cleared on model switches, add ClearBoneOffsets to clear them manually

* bone info getters

* fix joint lengths, add joint dirs

* serialize bone overrides

* fix bone dir return type

* GetBoneIndex/GetBoneCount

* helper functions for working with non-quat angles

* add mode enum, add more helper functions

* fix up formatting
This commit is contained in:
Ricardo Luís Vaz Silva 2025-05-01 10:15:46 -03:00 committed by GitHub
commit 3780c5910a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1396 additions and 253 deletions

View file

@ -22,6 +22,7 @@ union FRenderStyle;
class DObject;
class FTextureID;
struct FTranslationID;
struct BoneOverride;
inline bool nullcmp(const void *buffer, size_t length)
{
@ -253,6 +254,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimModelOverri
FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnim &ao, ModelAnim *def);
FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def);
FSerializer &Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
FSerializer &Serialize(FSerializer& arc, const char* key, BoneOverride& value, BoneOverride* defval);
void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);
@ -347,6 +349,16 @@ inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector3 &p, DV
return arc.Array<double>(key, &p[0], def? &(*def)[0] : nullptr, 3, true);
}
inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector4 &p, DVector4 *def)
{
return arc.Array<double>(key, &p[0], def? &(*def)[0] : nullptr, 4, true);
}
inline FSerializer& Serialize(FSerializer& arc, const char* key, DQuaternion& p, DQuaternion* def)
{
return arc.Array<double>(key, &p[0], def ? &(*def)[0] : nullptr, 4, true);
}
inline FSerializer &Serialize(FSerializer &arc, const char *key, DRotator &p, DRotator *def)
{
return arc.Array<DAngle>(key, &p[0], def? &(*def)[0] : nullptr, 3, true);
@ -362,6 +374,11 @@ inline FSerializer& Serialize(FSerializer& arc, const char* key, FVector4& p, FV
return arc.Array<float>(key, &p[0], def ? &(*def)[0] : nullptr, 4, true);
}
inline FSerializer& Serialize(FSerializer& arc, const char* key, FQuaternion& p, FQuaternion* def)
{
return arc.Array<float>(key, &p[0], def ? &(*def)[0] : nullptr, 4, true);
}
inline FSerializer& Serialize(FSerializer& arc, const char* key, FVector3& p, FVector3* def)
{
return arc.Array<float>(key, &p[0], def ? &(*def)[0] : nullptr, 3, true);

View file

@ -24,6 +24,113 @@ enum EModelAnimFlags
MODELANIM_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends
};
FQuaternion InterpolateQuat(const FQuaternion &from, const FQuaternion &to, float t, float invt);
template<typename T>
inline constexpr T DefVal()
{ // [Jay] can't use T DefVal as a template parameter to BoneOverrideComponent without C++20 so we're stuck with this for now, TODO replace with template parameter once gzdoom switches to C++20
if constexpr(std::is_same_v<T, FQuaternion>)
{
return FQuaternion(0,0,0,1);
}
return {};
}
template<typename T, T(*Lerp)(const T &from, const T &to, float t, float invt), T(*Add)(const T &from, const T &to)>
struct BoneOverrideComponent
{
int mode = 0; // 0 = no override, 1 = rotate, 2 = replace
int prev_mode = 0;
double switchtic = 0.0;
double interplen = 0.0;
T prev = DefVal<T>();
T cur = DefVal<T>();
void Set(const T &newValue, double tic, double newInterplen, int newMode)
{
double prev_interp_amt = interplen > 0.0 ? std::clamp(((tic - switchtic) / interplen), 0.0, 1.0) : 1.0;
double prev_interp_amt_inv = 1.0 - prev_interp_amt;
prev = mode > 0 ? Lerp(prev, cur, prev_interp_amt, prev_interp_amt_inv) : DefVal<T>();
// might break slightly if value is switched from absolute to additive before interpolation finishes,
// but shouldn't matter too much, since people will probably mostly stick to one single mode per value
// so not worth the extra complexity (and adding the requirement of needing bone calculation for setters to work) to properly support it
prev_mode = (mode == 0 && prev_mode != 0 && prev_interp_amt_inv > 0.0) ? 1 : mode;
cur = (newMode == 0 ? DefVal<T>() : newValue);
switchtic = tic;
interplen = newInterplen;
mode = newMode;
}
void Modify(T &value, double tic) const
{
double lerp_amt = interplen > 0.0 ? std::clamp(((tic - switchtic) / interplen), 0.0, 1.0) : 1.0;
if(mode > 0 || (prev_mode > 0 && lerp_amt < 1.0))
{
T from = ModifyValue(value, prev, prev_mode);
T to = ModifyValue(value, cur, mode);
value = Lerp(from, to, lerp_amt, 1.0 - lerp_amt);
}
}
T Get(const T &value, double tic) const
{
T newVal = value;
Modify(newVal, tic);
return newVal;
}
private:
inline static T ModifyValue(const T &orig, const T &cur, int mode)
{
if(mode == 0) return orig;
if(mode == 1) return Add(orig, cur);
return cur;
}
};
struct BoneOverride
{
static inline FQuaternion AddQuat(const FQuaternion &from, const FQuaternion &to)
{
return (from * to).Unit();
}
static inline FVector3 LerpVec3(const FVector3 &from, const FVector3 &to, float t, float invt)
{
return from * invt + to * t;
}
static inline FVector3 AddVec3(const FVector3 &from, const FVector3 &to)
{
return from + to;
}
BoneOverrideComponent<FVector3, &LerpVec3, &AddVec3> translation;
BoneOverrideComponent<FQuaternion, &InterpolateQuat, &AddQuat> rotation;
BoneOverrideComponent<FVector3, &LerpVec3, &AddVec3> scaling;
void Modify(TRS &trs, double tic) const
{
translation.Modify(trs.translation, tic);
rotation.Modify(trs.rotation, tic);
scaling.Modify(trs.scaling, tic);
}
};
struct BoneInfo
{
TArray<TRS> bones_anim_only;
TArray<TRS> bones_with_override;
TArray<VSMatrix> positions;
};
struct ModelAnim
{
int firstFrame = 0;

View file

@ -45,7 +45,7 @@
TDeletingArray<FModel*> Models;
TArray<FSpriteModelFrame> SpriteModelFrames;
TMap<void*, FSpriteModelFrame> BaseSpriteModelFrames;
TMap<const PClass*, FSpriteModelFrame> BaseSpriteModelFrames;
/////////////////////////////////////////////////////////////////////////////

View file

@ -16,14 +16,16 @@ class FModelRenderer;
class FGameTexture;
class IModelVertexBuffer;
class FModel;
class PClass;
struct FSpriteModelFrame;
FTextureID LoadSkin(const char* path, const char* fn);
void FlushModels();
extern TDeletingArray<FModel*> Models;
extern TArray<FSpriteModelFrame> SpriteModelFrames;
extern TMap<void*, FSpriteModelFrame> BaseSpriteModelFrames;
extern TMap<const PClass*, FSpriteModelFrame> BaseSpriteModelFrames;
#define MD3_MAX_SURFACES 32
#define MIN_MODELS 4
@ -86,6 +88,18 @@ public:
virtual int FindFrame(const char * name, bool nodefault = false) = 0;
virtual int NumJoints() { return 0; }
virtual int FindJoint(FName name) { return -1; }
virtual int GetJointParent(int joint) { return -1; }
virtual double GetJointLength(int joint) { return 0.0; }
virtual FName GetJointName(int joint) { return NAME_None; }
virtual FVector3 GetJointDir(int joint) { return FVector3(0.0f,0.0f,0.0f); }
virtual void GetJointChildren(int joint, TArray<int> &out) {}
virtual void GetRootJoints(TArray<int> &out) {}
// [RL0] these are used for decoupled iqm animations
virtual int FindFirstFrame(FName name) { return FErr_NotFound; }
virtual int FindLastFrame(FName name) { return FErr_NotFound; }
@ -99,7 +113,7 @@ public:
virtual ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData) { return nullptr; };
virtual const TArray<VSMatrix>* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData) { return nullptr; };
virtual const TArray<VSMatrix>* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, TArray<BoneOverride> *in, BoneInfo *out, double time) { return nullptr; };
void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; }
IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; }

View file

@ -67,10 +67,11 @@ struct IQMAdjacency
struct IQMJoint
{
FString Name;
FName Name;
int32_t Parent; // parent < 0 means this is a root bone
TArray<int> Children; // indices of children
FVector3 Translate;
FVector4 Quaternion;
FQuaternion Quaternion;
FVector3 Scale;
};
@ -122,10 +123,10 @@ public:
const TArray<TRS>* AttachAnimationData() override;
ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData) override;
const TArray<VSMatrix>* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData) override;
const TArray<VSMatrix>* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, TArray<BoneOverride> *in, BoneInfo *out, double time) override;
ModelAnimFramePrecalculatedIQM CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData);
const TArray<VSMatrix>* CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData);
const TArray<VSMatrix>* CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, TArray<BoneOverride> *in, BoneInfo *out, double time);
private:
void LoadGeometry();
@ -140,11 +141,15 @@ private:
int mLumpNum = -1;
TMap<FName, int> NamedAnimations;
TMap<FName, int> NamedJoints;
TArray<IQMMesh> Meshes;
TArray<IQMTriangle> Triangles;
TArray<IQMAdjacency> Adjacency;
TArray<IQMJoint> Joints;
TArray<int> RootJoints;
TArray<IQMPose> Poses;
TArray<IQMAnim> Anims;
TArray<IQMBounds> Bounds;
@ -158,6 +163,47 @@ private:
TArray<VSMatrix> baseframe;
TArray<VSMatrix> inversebaseframe;
TArray<TRS> TRSData;
public:
int NumJoints() override { return Joints.Size(); }
int FindJoint(FName name) override
{
int *j = NamedJoints.CheckKey(name);
return j ? *j : -1;
}
int GetJointParent(int joint) override
{
return (joint >= 0 && joint < Joints.Size()) ? Joints[joint].Parent : -1;
}
FName GetJointName(int joint) override
{
return (joint >= 0 && joint < Joints.Size()) ? Joints[joint].Name : FName(NAME_None);
}
void GetRootJoints(TArray<int> &out) override
{
out = RootJoints;
}
void GetJointChildren(int joint, TArray<int> &out) override
{
if(joint >= 0 && joint < Joints.Size())
{
out = Joints[joint].Children;
}
}
double GetJointLength(int joint) override
{
return (joint >= 0 && joint < Joints.Size()) ? Joints[joint].Translate.Length() : 0.0;
}
FVector3 GetJointDir(int joint) override
{
return (joint >= 0 && joint < Joints.Size()) ? Joints[joint].Translate.Unit() : FVector3(0.0f,0.0f,0.0f);
}
};
struct IQMReadErrorException { };

View file

@ -109,13 +109,26 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
}
reader.SeekTo(ofs_joints);
for (IQMJoint& joint : Joints)
for (int i = 0; i < Joints.Size(); i++)
{
joint.Name = reader.ReadName(text);
IQMJoint& joint = Joints[i];
FString name = reader.ReadName(text);
joint.Name = name;
if(!name.IsEmpty())
{
NamedJoints.Insert(joint.Name, i);
}
joint.Parent = reader.ReadInt32();
joint.Translate.X = reader.ReadFloat();
joint.Translate.Y = reader.ReadFloat();
joint.Translate.Z = reader.ReadFloat();
int len = joint.Translate.Length();
joint.Quaternion.X = reader.ReadFloat();
joint.Quaternion.Y = reader.ReadFloat();
joint.Quaternion.Z = reader.ReadFloat();
@ -124,6 +137,19 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
joint.Scale.X = reader.ReadFloat();
joint.Scale.Y = reader.ReadFloat();
joint.Scale.Z = reader.ReadFloat();
if(joint.Parent < 0)
{
RootJoints.Push(i);
}
else if(joint.Parent >= Joints.Size())
{
I_FatalError("Joint parent index out of bounds in IQM Model");
}
else
{
Joints[joint.Parent].Children.Push(i);
}
}
reader.SeekTo(ofs_poses);
@ -188,7 +214,7 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
translate.Y = p.ChannelOffset[1]; if (p.ChannelMask & 0x02) translate.Y += reader.ReadUInt16() * p.ChannelScale[1];
translate.Z = p.ChannelOffset[2]; if (p.ChannelMask & 0x04) translate.Z += reader.ReadUInt16() * p.ChannelScale[2];
FVector4 quaternion;
FQuaternion quaternion;
quaternion.X = p.ChannelOffset[3]; if (p.ChannelMask & 0x08) quaternion.X += reader.ReadUInt16() * p.ChannelScale[3];
quaternion.Y = p.ChannelOffset[4]; if (p.ChannelMask & 0x10) quaternion.Y += reader.ReadUInt16() * p.ChannelScale[4];
quaternion.Z = p.ChannelOffset[5]; if (p.ChannelMask & 0x20) quaternion.Z += reader.ReadUInt16() * p.ChannelScale[5];
@ -211,30 +237,7 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
{
num_frames = 1;
TRSData.Resize(num_joints);
for (uint32_t j = 0; j < num_joints; j++)
{
FVector3 translate;
translate.X = Joints[j].Translate.X;
translate.Y = Joints[j].Translate.Y;
translate.Z = Joints[j].Translate.Z;
FVector4 quaternion;
quaternion.X = Joints[j].Quaternion.X;
quaternion.Y = Joints[j].Quaternion.Y;
quaternion.Z = Joints[j].Quaternion.Z;
quaternion.W = Joints[j].Quaternion.W;
quaternion.MakeUnit();
FVector3 scale;
scale.X = Joints[j].Scale.X;
scale.Y = Joints[j].Scale.Y;
scale.Z = Joints[j].Scale.Z;
TRSData[j].translation = translate;
TRSData[j].rotation = quaternion;
TRSData[j].scaling = scale;
}
std::copy(Joints.begin(), Joints.end(), TRSData.begin());
}
reader.SeekTo(ofs_bounds);
@ -541,20 +544,29 @@ const TArray<TRS>* IQMModel::AttachAnimationData()
return &TRSData;
}
FQuaternion InterpolateQuat(const FQuaternion &from, const FQuaternion &to, float t, float invt)
{
FQuaternion rot = from * invt;
if ((rot | to * t) < 0)
{
rot.X *= -1;
rot.Y *= -1;
rot.Z *= -1;
rot.W *= -1;
}
rot += to * t;
return rot.Unit();
}
static TRS InterpolateBone(const TRS &from, const TRS &to, float t, float invt)
{
TRS bone;
bone.translation = from.translation * invt + to.translation * t;
bone.rotation = from.rotation * invt;
if ((bone.rotation | to.rotation * t) < 0)
{
bone.rotation.X *= -1; bone.rotation.Y *= -1; bone.rotation.Z *= -1; bone.rotation.W *= -1;
}
bone.rotation += to.rotation * t;
bone.rotation.MakeUnit();
bone.rotation = InterpolateQuat(from.rotation, to.rotation, t, invt);
bone.scaling = from.scaling * invt + to.scaling * t;
return bone;
@ -585,28 +597,34 @@ ModelAnimFrame IQMModel::PrecalculateFrame(const ModelAnimFrame &from, const Mod
}
}
const TArray<VSMatrix>* IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData)
const TArray<VSMatrix>* IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray<TRS>* animationData, TArray<BoneOverride> *in, BoneInfo *out, double time)
{
if(inter <= 0)
{
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData);
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, in, out, time);
}
else if(std::holds_alternative<ModelAnimFrameInterp>(from))
{
auto &from_interp = std::get<ModelAnimFrameInterp>(from);
return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData);
return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData, in, out, time);
}
else if(std::holds_alternative<ModelAnimFramePrecalculatedIQM>(from))
{
return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get<ModelAnimFramePrecalculatedIQM>(from), animationData);
return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get<ModelAnimFramePrecalculatedIQM>(from), animationData, in, out, time);
}
else
{
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData);
return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, in, out, time);
}
}
inline void ModifyBone(const BoneOverride& mod, TRS &bone, double time)
{
mod.Modify(bone, time);
}
// explicitly don't pass modelBoneOverrides when precalculating animation for interpolation, as it's applied _after_ animation
ModelAnimFramePrecalculatedIQM IQMModel::CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData)
{
ModelAnimFramePrecalculatedIQM out;
@ -614,7 +632,7 @@ ModelAnimFramePrecalculatedIQM IQMModel::CalculateFrameIQM(int frame1, int frame
out.precalcBones.Resize(Joints.Size());
if (Joints.Size() > 0)
if (Joints.Size() > 0 && animationFrames.Size() > 0)
{
int numbones = Joints.SSize();
@ -661,12 +679,25 @@ ModelAnimFramePrecalculatedIQM IQMModel::CalculateFrameIQM(int frame1, int frame
return out;
}
const TArray<VSMatrix>* IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData)
const TArray<VSMatrix>* IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray<TRS>* animationData, TArray<BoneOverride> *in, BoneInfo *out, double time)
{
const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData;
if (Joints.Size() > 0)
TArray<VSMatrix>* outMatrix = out ? &out->positions : &boneData;
int numbones = Joints.SSize();
outMatrix->Resize(numbones);
if(out)
{
out->bones_anim_only.Resize(numbones);
out->bones_with_override.Resize(numbones);
}
if(in && in->size() != Joints.Size()) in = nullptr;
if (numbones > 0 && animationFrames.Size() > 0)
{
int numbones = Joints.SSize();
frame1 = clamp(frame1, 0, (animationFrames.SSize() - 1) / numbones);
frame2 = clamp(frame2, 0, (animationFrames.SSize() - 1) / numbones);
@ -689,8 +720,6 @@ const TArray<VSMatrix>* IQMModel::CalculateBonesIQM(int frame1, int frame2, floa
0.0f, 0.0f, 0.0f, 1.0f
};
boneData.Resize(numbones);
for (int i = 0; i < numbones; i++)
{
TRS prev;
@ -721,16 +750,33 @@ const TArray<VSMatrix>* IQMModel::CalculateBonesIQM(int frame1, int frame2, floa
bone = inter < 0 ? animationFrames[offset1 + i] : InterpolateBone(prev, next , inter, invt);
}
if(out)
{
out->bones_anim_only[i] = bone;
if(in)
{
(*in)[i].Modify(bone, time);
}
out->bones_with_override[i] = bone;
}
else if(in)
{
(*in)[i].Modify(bone, time);
}
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 = boneData[i];
VSMatrix& result = (*outMatrix)[i];
if (Joints[i].Parent >= 0)
{
result = boneData[Joints[i].Parent];
assert(Joints[i].Parent < i);
result = (*outMatrix)[Joints[i].Parent];
result.multMatrix(swapYZ);
result.multMatrix(baseframe[Joints[i].Parent]);
result.multMatrix(m);

View file

@ -33,24 +33,27 @@
#pragma once
#include "vectors.h"
#include "quaternion.h"
class TRS
{
public:
FVector3 translation;
FVector4 rotation;
FVector3 scaling;
FVector3 translation = FVector3(0,0,0);
FQuaternion rotation = FQuaternion::Identity();
FVector3 scaling = FVector3(0,0,0);
TRS()
bool operator==(const TRS& other) const
{
translation = FVector3(0,0,0);
rotation = FVector4(0,0,0,1);
scaling = FVector3(0,0,0);
return other.translation == translation && other.rotation == rotation && other.scaling == scaling;
}
bool Equals(TRS& compare)
{
return compare.translation == this->translation && compare.rotation == this->rotation && compare.scaling == this->scaling;
template<typename T>
TRS& operator=(const T& other)
{ // templated because IQMJoint is defined in model_iqm.h
translation = other.Translate;
rotation = other.Quaternion;
scaling = other.Scale;
return *this;
}
};

View file

@ -114,6 +114,22 @@ void VSMatrix::multQuaternion(const TVector4<FLOATTYPE>& q)
multMatrix(m);
}
void VSMatrix::multQuaternion(const TQuaternion<FLOATTYPE>& q)
{
FLOATTYPE m[16] = { FLOATTYPE(0.0) };
m[0 * 4 + 0] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.Y * q.Y - FLOATTYPE(2.0) * q.Z * q.Z;
m[1 * 4 + 0] = FLOATTYPE(2.0) * q.X * q.Y - FLOATTYPE(2.0) * q.W * q.Z;
m[2 * 4 + 0] = FLOATTYPE(2.0) * q.X * q.Z + FLOATTYPE(2.0) * q.W * q.Y;
m[0 * 4 + 1] = FLOATTYPE(2.0) * q.X * q.Y + FLOATTYPE(2.0) * q.W * q.Z;
m[1 * 4 + 1] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.X * q.X - FLOATTYPE(2.0) * q.Z * q.Z;
m[2 * 4 + 1] = FLOATTYPE(2.0) * q.Y * q.Z - FLOATTYPE(2.0) * q.W * q.X;
m[0 * 4 + 2] = FLOATTYPE(2.0) * q.X * q.Z - FLOATTYPE(2.0) * q.W * q.Y;
m[1 * 4 + 2] = FLOATTYPE(2.0) * q.Y * q.Z + FLOATTYPE(2.0) * q.W * q.X;
m[2 * 4 + 2] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.X * q.X - FLOATTYPE(2.0) * q.Y * q.Y;
m[3 * 4 + 3] = FLOATTYPE(1.0);
multMatrix(m);
}
// gl LoadMatrix implementation
void

View file

@ -22,6 +22,7 @@
#include <stdlib.h>
#include "vectors.h"
#include "quaternion.h"
#ifdef USE_DOUBLE
typedef double FLOATTYPE;
@ -54,6 +55,7 @@ class VSMatrix {
multMatrix(aMatrix.mMatrix);
}
void multQuaternion(const TVector4<FLOATTYPE>& q);
void multQuaternion(const TQuaternion<FLOATTYPE>& q);
void loadMatrix(const FLOATTYPE *aMatrix);
#ifdef USE_DOUBLE
void loadMatrix(const float *aMatrix);

View file

@ -13,7 +13,7 @@ public:
TQuaternion() = default;
TQuaternion(vec_t x, vec_t y, vec_t z, vec_t w)
constexpr TQuaternion(vec_t x, vec_t y, vec_t z, vec_t w)
: X(x), Y(y), Z(z), W(w)
{
}
@ -40,6 +40,17 @@ public:
Z = Y = X = W = 0;
}
void MakeIdentity()
{
Z = Y = X = 0;
W = 1;
}
static constexpr TQuaternion Identity()
{
return {0,0,0,1};
}
bool isZero() const
{
return X == 0 && Y == 0 && Z == 0 && W == 0;
@ -346,6 +357,12 @@ public:
return (from * scale0 + to * scale1).Unit();
}
}
template<typename U>
explicit operator TVector4<U>()
{
return TVector4<U>(U(X),U(Y),U(Z),U(W));
}
};
typedef TQuaternion<float> FQuaternion;

View file

@ -733,14 +733,15 @@ class DActorModelData : public DObject
{
DECLARE_CLASS(DActorModelData, DObject);
public:
PClass * modelDef;
TArray<ModelOverride> models;
TArray<FTextureID> skinIDs;
TArray<AnimModelOverride> animationIDs;
TArray<int> modelFrameGenerators;
int flags;
int overrideFlagsSet;
int overrideFlagsClear;
PClass * modelDef;
TArray<ModelOverride> models;
TArray<FTextureID> skinIDs;
TArray<AnimModelOverride> animationIDs;
TArray<int> modelFrameGenerators;
TArray<TArray<BoneOverride>> modelBoneOverrides;
int flags;
int overrideFlagsSet;
int overrideFlagsClear;
ModelAnim curAnim;
ModelAnimFrame prevAnim; // used for interpolation when switching anims

View file

@ -5113,6 +5113,7 @@ static void CleanupModelData(AActor * mobj)
&& mobj->modelData->modelFrameGenerators.Size() == 0
&& mobj->modelData->skinIDs.Size() == 0
&& mobj->modelData->animationIDs.Size() == 0
&& mobj->modelData->modelBoneOverrides.Size() == 0
&& mobj->modelData->modelDef == nullptr
&&(mobj->modelData->flags & ~MODELDATA_HADMODEL) == 0 )
{
@ -5122,6 +5123,626 @@ static void CleanupModelData(AActor * mobj)
}
}
FQuaternion InterpolateQuat(const FQuaternion &from, const FQuaternion &to, float t, float invt);
static void SetModelBoneRotationInternal(AActor * self, FModel * mdl, int model_index, int index, FQuaternion rotation, int mode, double interpolation_duration, double switchTic)
{
if(self->modelData->modelBoneOverrides.Size() <= model_index) self->modelData->modelBoneOverrides.Resize(model_index + 1);
self->modelData->modelBoneOverrides[model_index].Resize(mdl->NumJoints());
self->modelData->modelBoneOverrides[model_index][index].rotation.Set(rotation, switchTic, interpolation_duration, mode);
}
template<bool isSet, bool isOffset>
FModel * SetGetBoneShared(AActor * self, int model_index)
{
if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS))
{
ThrowAbortException(X_OTHER, isSet ? "Cannot set bone offset for non-decoupled actors" : (isOffset ? "Cannot get bone for non-decoupled actors" : "Cannot get bone offset for non-decoupled actors"));
}
if(!BaseSpriteModelFrames.CheckKey(self->GetClass()))
{
ThrowAbortException(X_OTHER, "Actor class is missing a MODELDEF definition or a MODELDEF BaseFrame");
}
EnsureModelData(self);
if(self->modelData->models.Size() > model_index && self->modelData->models[model_index].modelID >= 0 && self->modelData->models[model_index].modelID < Models.Size())
{
return Models[self->modelData->models[model_index].modelID];
}
else if(BaseSpriteModelFrames[self->GetClass()].modelIDs.Size() > model_index)
{
return Models[BaseSpriteModelFrames[self->GetClass()].modelIDs[model_index]];
}
else
{
ThrowAbortException(X_OTHER, "Model Index out of range");
}
}
template<bool isSet, bool isOffset>
FModel * SetGetBoneSharedIndex(AActor * self, int model_index, int &bone_index, FName * bone_name)
{
FModel * mdl = SetGetBoneShared<isSet, isOffset>(self, model_index);
if(bone_name)
{
bone_index = mdl->FindJoint(*bone_name);
if(bone_index < 0 || bone_index >= mdl->NumJoints())
{
Printf(PRINT_NONOTIFY, "Could not find bone '%s'", bone_name->GetChars());
return nullptr;
}
}
else if(bone_index < 0 || bone_index >= mdl->NumJoints())
{
ThrowAbortException(X_OTHER, "bone index out of range");
}
if(self->modelData->modelBoneOverrides.Size() <= model_index) self->modelData->modelBoneOverrides.Resize(model_index + 1);
self->modelData->modelBoneOverrides[model_index].Resize(mdl->NumJoints());
return mdl;
}
FModel * SetBoneOffsetShared(AActor * self, int model_index, int &bone_index, FName * bone_name, int mode, double &interpolation_duration)
{
if(interpolation_duration < 0) interpolation_duration = 0;
if(mode < 0 || mode > 2)
{
ThrowAbortException(X_OTHER, "Invalid mode for setbone");
}
return SetGetBoneSharedIndex<true, true>(self, model_index, bone_index, bone_name);
}
FModel * GetBoneOffsetShared(AActor * self, int model_index, int &bone_index, FName * bone_name)
{
return SetGetBoneSharedIndex<false, true>(self, model_index, bone_index, bone_name);
}
FModel * GetBoneShared(AActor * self, int model_index, int &bone_index, FName * bone_name)
{
return SetGetBoneSharedIndex<false, false>(self, model_index, bone_index, bone_name);
}
//================================================
//
// SetBoneRotation
//
//================================================
static void SetModelBoneRotationNative(AActor * self, int model_index, int bone_index, double rot_x, double rot_y, double rot_z, double rot_w, int mode, double interpolation_duration, double ticFrac)
{
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, nullptr, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].rotation.Set(FQuaternion(rot_x, rot_y, rot_z, rot_w), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetBoneRotationNative(AActor * self, int bone_index, double rot_x, double rot_y, double rot_z, double rot_w, int mode, double interpolation_duration, double ticFrac)
{
SetModelBoneRotationNative(self, 0, bone_index, rot_x, rot_y, rot_z, rot_w, mode, interpolation_duration, ticFrac);
}
static void SetModelNamedBoneRotationNative(AActor * self, int model_index, int boneName_i, double rot_x, double rot_y, double rot_z, double rot_w, int mode, double interpolation_duration, double ticFrac)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, &bone_name, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].rotation.Set(FQuaternion(rot_x, rot_y, rot_z, rot_w), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetNamedBoneRotationNative(AActor * self, int boneName_i, double rot_x, double rot_y, double rot_z, double rot_w, int mode, double interpolation_duration, double ticFrac)
{
SetModelNamedBoneRotationNative(self, 0, boneName_i, rot_x, rot_y, rot_z, rot_w, mode, interpolation_duration, ticFrac);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetBoneRotation, SetBoneRotationNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_FLOAT(rot_w);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetBoneRotationNative(self, boneindex, rot_x, rot_y, rot_z, rot_w, mode, interplen, 1.0);
return 0;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetNamedBoneRotation, SetNamedBoneRotationNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_FLOAT(rot_w);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetNamedBoneRotationNative(self, bonename.GetIndex(), rot_x, rot_y, rot_z, rot_w, mode, interplen, 1.0);
return 0;
}
//================================================
//
// SetBoneTranslation
//
//================================================
static void SetModelBoneTranslationNative(AActor * self, int model_index, int bone_index, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, nullptr, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].translation.Set(FVector3(rot_x, rot_y, rot_z), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetBoneTranslationNative(AActor * self, int bone_index, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
SetModelBoneTranslationNative(self, 0, bone_index, rot_x, rot_y, rot_z, mode, interpolation_duration, ticFrac);
}
static void SetModelNamedBoneTranslationNative(AActor * self, int model_index, int boneName_i, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, &bone_name, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].translation.Set(FVector3(rot_x, rot_y, rot_z), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetNamedBoneTranslationNative(AActor * self, int boneName_i, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
SetModelNamedBoneTranslationNative(self, 0, boneName_i, rot_x, rot_y, rot_z, mode, interpolation_duration, ticFrac);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetBoneTranslation, SetBoneTranslationNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetBoneTranslationNative(self, boneindex, rot_x, rot_y, rot_z, mode, interplen, 1.0);
return 0;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetNamedBoneTranslation, SetNamedBoneTranslationNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetNamedBoneTranslationNative(self, bonename.GetIndex(), rot_x, rot_y, rot_z, mode, interplen, 1.0);
return 0;
}
//================================================
//
// SetBoneScaling
//
//================================================
static void SetModelBoneScalingNative(AActor * self, int model_index, int bone_index, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, nullptr, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].scaling.Set(FVector3(rot_x, rot_y, rot_z), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetBoneScalingNative(AActor * self, int bone_index, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
SetModelBoneScalingNative(self, 0, bone_index, rot_x, rot_y, rot_z, mode, interpolation_duration, ticFrac);
}
static void SetModelNamedBoneScalingNative(AActor * self, int model_index, int boneName_i, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = SetBoneOffsetShared(self, model_index, bone_index, &bone_name, mode, interpolation_duration);
if(!mdl) return;
self->modelData->modelBoneOverrides[model_index][bone_index].scaling.Set(FVector3(rot_x, rot_y, rot_z), self->Level->totaltime + ticFrac, interpolation_duration, mode);
}
static void SetNamedBoneScalingNative(AActor * self, int boneName_i, double rot_x, double rot_y, double rot_z, int mode, double interpolation_duration, double ticFrac)
{
SetModelNamedBoneScalingNative(self, 0, boneName_i, rot_x, rot_y, rot_z, mode, interpolation_duration, ticFrac);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetBoneScaling, SetBoneScalingNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetBoneScalingNative(self, boneindex, rot_x, rot_y, rot_z, mode, interplen, 1.0);
return 0;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetNamedBoneScaling, SetNamedBoneScalingNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
PARAM_FLOAT(rot_x);
PARAM_FLOAT(rot_y);
PARAM_FLOAT(rot_z);
PARAM_INT(mode);
PARAM_FLOAT(interplen);
SetNamedBoneScalingNative(self, bonename.GetIndex(), rot_x, rot_y, rot_z, mode, interplen, 1.0);
return 0;
}
//================================================
//
// ClearBoneOffsets
//
//================================================
static void ClearBoneOffsetsNative(AActor * self)
{
if(self->modelData) self->modelData->modelBoneOverrides[0].Clear();
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, ClearBoneOffsets, ClearBoneOffsetsNative)
{
PARAM_SELF_PROLOGUE(AActor);
ClearBoneOffsetsNative(self);
return 0;
}
//================================================
//
// GetBoneOffset
//
//================================================
DEFINE_ACTION_FUNCTION(AActor, GetBoneOffset)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(bone_index);
FModel * mdl = GetBoneOffsetShared(self, 0, bone_index, nullptr);
DVector3 translation(0,0,0);
DVector4 rotation(0,0,0,1);
DVector3 scaling(0,0,0);
if(mdl)
{
auto &mod = self->modelData->modelBoneOverrides[0][bone_index];
translation = DVector3(mod.translation.Get(FVector3(0,0,0), self->Level->totaltime + 1.0));
rotation = DVector4(mod.rotation.Get(FQuaternion(0,0,0,1), self->Level->totaltime + 1.0));
scaling = DVector3(mod.scaling.Get(FVector3(0,0,0), self->Level->totaltime + 1.0));
}
if(numret > 2)
{
ret[2].SetVector(scaling);
numret = 3;
}
if(numret > 1)
{
ret[1].SetVector(translation);
}
if(numret > 0)
{
ret[0].SetVector4(rotation);
}
return numret;
}
DEFINE_ACTION_FUNCTION(AActor, GetNamedBoneOffset)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bone_name);
int bone_index;
FModel * mdl = GetBoneOffsetShared(self, 0, bone_index, &bone_name);
DVector3 translation(0,0,0);
DVector4 rotation(0,0,0,1);
DVector3 scaling(0,0,0);
if(mdl)
{
auto &mod = self->modelData->modelBoneOverrides[0][bone_index];
translation = DVector3(mod.translation.Get(FVector3(0,0,0), self->Level->totaltime + 1.0));
rotation = DVector4(mod.rotation.Get(FQuaternion(0,0,0,1), self->Level->totaltime + 1.0));
scaling = DVector3(mod.scaling.Get(FVector3(0,0,0), self->Level->totaltime + 1.0));
}
if(numret > 2)
{
ret[2].SetVector(scaling);
numret = 3;
}
if(numret > 1)
{
ret[1].SetVector(translation);
}
if(numret > 0)
{
ret[0].SetVector4(rotation);
}
return numret;
}
//================================================
//
// Bone Info Getters
//
//================================================
static void GetRootBonesNative(AActor * self, TArray<int> *out)
{
if(out)
{
FModel * mdl = SetGetBoneShared<false, false>(self, 0);
mdl->GetRootJoints(*out);
}
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetRootBones, GetRootBonesNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_POINTER(out, TArray<int>);
GetRootBonesNative(self, out);
return 0;
}
static int GetBoneNameNative(AActor * self, int bone_index)
{
FModel * mdl = GetBoneShared(self, 0, bone_index, nullptr);
return mdl->GetJointName(bone_index).GetIndex();
}
static int GetBoneIndexNative(AActor * self, int boneName_i)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = GetBoneShared(self, 0, bone_index, &bone_name);
return bone_index;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneName, GetBoneNameNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
ACTION_RETURN_INT(GetBoneNameNative(self, boneindex));
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneIndex, GetBoneIndexNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
ACTION_RETURN_INT(GetBoneIndexNative(self, bonename.GetIndex()));
}
static int GetBoneParentNative(AActor * self, int bone_index)
{
FModel * mdl = GetBoneShared(self, 0, bone_index, nullptr);
return mdl->GetJointParent(bone_index);
}
static int GetNamedBoneParentNative(AActor * self, int boneName_i)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = GetBoneShared(self, 0, bone_index, &bone_name);
return mdl->GetJointParent(bone_index);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneParent, GetBoneParentNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
ACTION_RETURN_INT(GetBoneParentNative(self, boneindex));
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetNamedBoneParent, GetNamedBoneParentNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
ACTION_RETURN_INT(GetNamedBoneParentNative(self, bonename.GetIndex()));
}
static void GetBoneChildrenNative(AActor * self, int bone_index, TArray<int> *out)
{
if(out)
{
FModel * mdl = GetBoneShared(self, 0, bone_index, nullptr);
mdl->GetJointChildren(bone_index, *out);
}
}
static void GetNamedBoneChildrenNative(AActor * self, int boneName_i, TArray<int> *out)
{
if(out)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = GetBoneShared(self, 0, bone_index, &bone_name);
mdl->GetJointChildren(bone_index, *out);
}
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneChildren, GetBoneChildrenNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
PARAM_POINTER(out, TArray<int>);
GetBoneChildrenNative(self, boneindex, out);
return 0;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetNamedBoneChildren, GetNamedBoneChildrenNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
PARAM_POINTER(out, TArray<int>);
GetNamedBoneChildrenNative(self, bonename.GetIndex(), out);
return 0;
}
static double GetBoneLengthNative(AActor * self, int bone_index)
{
FModel * mdl = GetBoneShared(self, 0, bone_index, nullptr);
return mdl->GetJointLength(bone_index);
}
static double GetNamedBoneLengthNative(AActor * self, int boneName_i)
{
FName bone_name {ENamedName(boneName_i)};
int bone_index;
FModel * mdl = GetBoneShared(self, 0, bone_index, &bone_name);
return mdl->GetJointLength(bone_index);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneLength, GetBoneLengthNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(boneindex);
ACTION_RETURN_FLOAT(GetBoneLengthNative(self, boneindex));
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetNamedBoneLength, GetNamedBoneLengthNative)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bonename);
ACTION_RETURN_FLOAT(GetNamedBoneLengthNative(self, bonename.GetIndex()));
}
DEFINE_ACTION_FUNCTION(AActor, GetBoneDir)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(bone_index);
FModel * mdl = GetBoneShared(self, 0, bone_index, nullptr);
ACTION_RETURN_VEC3(DVector3(mdl->GetJointDir(bone_index)));
}
DEFINE_ACTION_FUNCTION(AActor, GetNamedBoneDir)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_NAME(bone_name);
int bone_index;
FModel * mdl = GetBoneShared(self, 0, bone_index, &bone_name);
ACTION_RETURN_VEC3(DVector3(mdl->GetJointDir(bone_index)));
}
static int GetBoneCountNative(AActor * self)
{
FModel * mdl = SetGetBoneShared<false, false>(self, 0);
return mdl->NumJoints();
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetBoneCount, GetBoneCountNative)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_INT(GetBoneCountNative(self));
}
//================================================
// SetAnimation
//================================================
enum ESetAnimationFlags
{
SAF_INSTANT = 1 << 0,
@ -5449,6 +6070,7 @@ void ChangeModelNative(
}
surfaceSkins.Push(skindata);
mobj->modelData->models.Push({queryModel, std::move(surfaceSkins)});
mobj->modelData->modelFrameGenerators.Push(generatorindex);
}
else
@ -5456,6 +6078,11 @@ void ChangeModelNative(
mobj->modelData->models.Push({queryModel, {}});
mobj->modelData->modelFrameGenerators.Push(generatorindex);
}
if(queryModel != -1 && mobj->modelData->modelBoneOverrides.Size() > modelindex)
{
mobj->modelData->modelBoneOverrides[modelindex].Clear();
}
}
else
{
@ -5475,7 +6102,15 @@ void ChangeModelNative(
mobj->modelData->models[modelindex].surfaceSkinIDs[skinindex] = skindata;
}
}
if(queryModel != -1) mobj->modelData->models[modelindex].modelID = queryModel;
if(queryModel != -1)
{
mobj->modelData->models[modelindex].modelID = queryModel;
if(mobj->modelData->modelBoneOverrides.Size() > modelindex)
{
mobj->modelData->modelBoneOverrides[modelindex].Clear();
}
}
if(generatorindex != -1) mobj->modelData->modelFrameGenerators[modelindex] = generatorindex;
}

View file

@ -1764,6 +1764,30 @@ void SerializeModelID(FSerializer &arc, const char *key, int &id)
}
}
template<typename T>
static FSerializer &SerializeBoneOverrideComponent(FSerializer &arc, const char *key, T &comp)
{
arc.BeginObject(key);
arc("mode", comp.mode);
arc("prev_mode", comp.prev_mode);
arc("switchtic", comp.switchtic);
arc("interplen", comp.interplen);
arc("prev", comp.prev);
arc("cur", comp.cur);
arc.EndObject();
return arc;
}
FSerializer &Serialize(FSerializer &arc, const char *key, BoneOverride &mod, BoneOverride *def)
{
arc.BeginObject(key);
SerializeBoneOverrideComponent(arc, "translation", mod.translation);
SerializeBoneOverrideComponent(arc, "rotation", mod.rotation);
SerializeBoneOverrideComponent(arc, "scaling", mod.scaling);
arc.EndObject();
return arc;
}
FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &mo, ModelOverride *def)
{
arc.BeginObject(key);
@ -1869,6 +1893,7 @@ void DActorModelData::Serialize(FSerializer& arc)
("skinIDs", skinIDs)
("animationIDs", animationIDs)
("modelFrameGenerators", modelFrameGenerators)
("modelBoneOverrides", modelBoneOverrides)
("flags", flags)
("overrideFlagsSet", overrideFlagsSet)
("overrideFlagsClear", overrideFlagsClear)

View file

@ -313,19 +313,18 @@ void calcFrames(const ModelAnim &curAnim, double tic, ModelAnimFrameInterp &to,
}
}
void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor)
CalcModelFrameInfo CalcModelFrame(FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, DActorModelData* data, AActor* actor, bool is_decoupled, double tic)
{
// [BB] Frame interpolation: Find the FSpriteModelFrame smfNext which follows after smf in the animation
// and the scalar value inter ( element of [0,1) ), both necessary to determine the interpolated frame.
int smf_flags = smf->getFlags(actor->modelData);
int smf_flags = smf->getFlags(data);
const FSpriteModelFrame * smfNext = nullptr;
float inter = 0.;
bool is_decoupled = (actor->flags9 & MF9_DECOUPLEDANIMATIONS);
ModelAnimFrameInterp decoupled_frame;
ModelAnimFrame * decoupled_frame_prev = nullptr;
// if prev_frame == -1: interpolate(main_frame, next_frame, inter), else: interpolate(interpolate(main_prev_frame, main_frame, inter_main), interpolate(next_prev_frame, next_frame, inter_next), inter)
// 4-way interpolation is needed to interpolate animation switches between animations that aren't 35hz
@ -333,15 +332,10 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
if(is_decoupled)
{
smfNext = smf = &BaseSpriteModelFrames[actor->GetClass()];
if(actor->modelData && !(actor->modelData->curAnim.flags & MODELANIM_NONE))
if(data && !(data->curAnim.flags & MODELANIM_NONE))
{
double tic = actor->Level->totaltime;
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !actor->isFrozen())
{
tic += I_GetTimeFrac();
}
calcFrames(actor->modelData->curAnim, tic, decoupled_frame, inter);
calcFrames(data->curAnim, tic, decoupled_frame, inter);
decoupled_frame_prev = &data->prevAnim;
}
}
else if (gl_interpolate_model_frames && !(smf_flags & MDL_NOINTERPOLATION))
@ -388,173 +382,236 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
unsigned modelsamount = smf->modelsAmount;
//[SM] - if we added any models for the frame to also render, then we also need to update modelsAmount for this smf
if (actor->modelData != nullptr)
if (data != nullptr)
{
if (actor->modelData->models.Size() > modelsamount)
modelsamount = actor->modelData->models.Size();
if (data->models.Size() > modelsamount)
modelsamount = data->models.Size();
}
TArray<FTextureID> surfaceskinids;
return
{
smf_flags,
smfNext,
inter,
is_decoupled,
decoupled_frame,
decoupled_frame_prev,
modelsamount
};
}
bool CalcModelOverrides(int i, const FSpriteModelFrame *smf, DActorModelData* data, const CalcModelFrameInfo &info, ModelDrawInfo &out, bool is_decoupled)
{
//reset drawinfo
out.modelid = -1;
out.animationid = -1;
out.modelframe = -1;
out.modelframenext = -1;
out.skinid.SetNull();
out.surfaceskinids.Clear();
if (data)
{
//modelID
if (data->models.Size() > i && data->models[i].modelID >= 0)
{
out.modelid = data->models[i].modelID;
}
else if(data->models.Size() > i && data->models[i].modelID == -2)
{
return false;
}
else if(smf->modelsAmount > i)
{
out.modelid = smf->modelIDs[i];
}
//animationID
if (data->animationIDs.Size() > i && data->animationIDs[i] >= 0)
{
out.animationid = data->animationIDs[i];
}
else if(smf->modelsAmount > i)
{
out.animationid = smf->animationIDs[i];
}
if(!is_decoupled)
{
//modelFrame
if (data->modelFrameGenerators.Size() > i
&& (unsigned)data->modelFrameGenerators[i] < info.modelsamount
&& smf->modelframes[data->modelFrameGenerators[i]] >= 0
) {
out.modelframe = smf->modelframes[data->modelFrameGenerators[i]];
if (info.smfNext)
{
if(info.smfNext->modelframes[data->modelFrameGenerators[i]] >= 0)
{
out.modelframenext = info.smfNext->modelframes[data->modelFrameGenerators[i]];
}
else
{
out.modelframenext = info.smfNext->modelframes[i];
}
}
}
else if(smf->modelsAmount > i)
{
out.modelframe = smf->modelframes[i];
if (info.smfNext) out.modelframenext = info.smfNext->modelframes[i];
}
}
//skinID
if (data->skinIDs.Size() > i && data->skinIDs[i].isValid())
{
out.skinid = data->skinIDs[i];
}
else if(smf->modelsAmount > i)
{
out.skinid = smf->skinIDs[i];
}
//surfaceSkinIDs
if(data->models.Size() > i && data->models[i].surfaceSkinIDs.Size() > 0)
{
unsigned sz1 = smf->surfaceskinIDs.Size();
unsigned sz2 = data->models[i].surfaceSkinIDs.Size();
unsigned start = i * MD3_MAX_SURFACES;
out.surfaceskinids = data->models[i].surfaceSkinIDs;
out.surfaceskinids.Resize(MD3_MAX_SURFACES);
for (unsigned surface = 0; surface < MD3_MAX_SURFACES; surface++)
{
if (sz2 > surface && (data->models[i].surfaceSkinIDs[surface].isValid()))
{
continue;
}
if((surface + start) < sz1)
{
out.surfaceskinids[surface] = smf->surfaceskinIDs[surface + start];
}
else
{
out.surfaceskinids[surface].SetNull();
}
}
}
}
else
{
out.modelid = smf->modelIDs[i];
out.animationid = smf->animationIDs[i];
out.modelframe = smf->modelframes[i];
if (info.smfNext) out.modelframenext = info.smfNext->modelframes[i];
out.skinid = smf->skinIDs[i];
}
return (out.modelid >= 0 && out.modelid < Models.size());
}
const TArray<VSMatrix> * ProcessModelFrame(FModel * animation, bool nextFrame, int i, const FSpriteModelFrame *smf, DActorModelData* modelData, const CalcModelFrameInfo &frameinfo, ModelDrawInfo &drawinfo, bool is_decoupled, double tic, BoneInfo *out)
{
const TArray<TRS>* animationData = nullptr;
if (drawinfo.animationid >= 0)
{
animation = Models[drawinfo.animationid];
animationData = animation->AttachAnimationData();
}
const TArray<VSMatrix> *boneData = nullptr;
if(is_decoupled)
{
if(frameinfo.decoupled_frame.frame1 >= 0)
{
boneData = animation->CalculateBones(
frameinfo.decoupled_frame_prev ? *frameinfo.decoupled_frame_prev : nullptr,
frameinfo.decoupled_frame,
frameinfo.inter,
animationData,
modelData->modelBoneOverrides.Size() > i
? &modelData->modelBoneOverrides[i]
: nullptr,
out,
tic);
}
}
else
{
boneData = animation->CalculateBones(
nullptr,
{
nextFrame ? frameinfo.inter : -1.0f,
drawinfo.modelframe,
drawinfo.modelframenext
},
-1.0f,
animationData,
(modelData && modelData->modelBoneOverrides.Size() > i)
? &modelData->modelBoneOverrides[i]
: nullptr,
out,
tic);
}
return boneData;
}
static inline void RenderModelFrame(FModelRenderer *renderer, int i, const FSpriteModelFrame *smf, DActorModelData* modelData, const CalcModelFrameInfo &frameinfo, ModelDrawInfo &drawinfo, bool is_decoupled, double tic, FTranslationID translation, int &boneStartingPosition, bool &evaluatedSingle)
{
FModel * mdl = Models[drawinfo.modelid];
auto tex = drawinfo.skinid.isValid() ? TexMan.GetGameTexture(drawinfo.skinid, true) : nullptr;
mdl->BuildVertexBuffer(renderer);
auto ssidp = drawinfo.surfaceskinids.Size() > 0
? drawinfo.surfaceskinids.Data()
: (((i * MD3_MAX_SURFACES) < smf->surfaceskinIDs.Size()) ? &smf->surfaceskinIDs[i * MD3_MAX_SURFACES] : nullptr);
bool nextFrame = frameinfo.smfNext && drawinfo.modelframe != drawinfo.modelframenext;
// [Jay] while per-model animations aren't done, DECOUPLEDANIMATIONS does the same as MODELSAREATTACHMENTS
if(!evaluatedSingle)
{ // [Jay] TODO per-model decoupled animations
const TArray<VSMatrix> *boneData = ProcessModelFrame(mdl, nextFrame, i, smf, modelData, frameinfo, drawinfo, is_decoupled, tic, nullptr);
if(frameinfo.smf_flags & MDL_MODELSAREATTACHMENTS || is_decoupled)
{
boneStartingPosition = boneData ? screen->mBones->UploadBones(*boneData) : -1;
evaluatedSingle = true;
}
}
mdl->RenderFrame(renderer, tex, drawinfo.modelframe, nextFrame ? drawinfo.modelframenext : drawinfo.modelframe, nextFrame ? frameinfo.inter : -1.f, translation, ssidp, boneStartingPosition);
}
void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor)
{
double tic = actor->Level->totaltime;
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !actor->isFrozen())
{
tic += I_GetTimeFrac();
}
bool is_decoupled = (actor->flags9 & MF9_DECOUPLEDANIMATIONS);
DActorModelData* modelData = actor ? actor->modelData.ForceGet() : nullptr;
CalcModelFrameInfo frameinfo = CalcModelFrame(Level, smf, curState, curTics, modelData, actor, is_decoupled, tic);
ModelDrawInfo drawinfo;
int boneStartingPosition = -1;
bool evaluatedSingle = false;
for (unsigned i = 0; i < modelsamount; i++)
for (unsigned i = 0; i < frameinfo.modelsamount; i++)
{
int modelid = -1;
int animationid = -1;
int modelframe = -1;
int modelframenext = -1;
FTextureID skinid(nullptr);
surfaceskinids.Clear();
if (actor->modelData != nullptr)
if (CalcModelOverrides(i, smf, modelData, frameinfo, drawinfo, is_decoupled))
{
//modelID
if (actor->modelData->models.Size() > i && actor->modelData->models[i].modelID >= 0)
{
modelid = actor->modelData->models[i].modelID;
}
else if(actor->modelData->models.Size() > i && actor->modelData->models[i].modelID == -2)
{
continue;
}
else if(smf->modelsAmount > i)
{
modelid = smf->modelIDs[i];
}
//animationID
if (actor->modelData->animationIDs.Size() > i && actor->modelData->animationIDs[i] >= 0)
{
animationid = actor->modelData->animationIDs[i];
}
else if(smf->modelsAmount > i)
{
animationid = smf->animationIDs[i];
}
if(!is_decoupled)
{
//modelFrame
if (actor->modelData->modelFrameGenerators.Size() > i
&& (unsigned)actor->modelData->modelFrameGenerators[i] < modelsamount
&& smf->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0
) {
modelframe = smf->modelframes[actor->modelData->modelFrameGenerators[i]];
if (smfNext)
{
if(smfNext->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0)
{
modelframenext = smfNext->modelframes[actor->modelData->modelFrameGenerators[i]];
}
else
{
modelframenext = smfNext->modelframes[i];
}
}
}
else if(smf->modelsAmount > i)
{
modelframe = smf->modelframes[i];
if (smfNext) modelframenext = smfNext->modelframes[i];
}
}
//skinID
if (actor->modelData->skinIDs.Size() > i && actor->modelData->skinIDs[i].isValid())
{
skinid = actor->modelData->skinIDs[i];
}
else if(smf->modelsAmount > i)
{
skinid = smf->skinIDs[i];
}
//surfaceSkinIDs
if(actor->modelData->models.Size() > i && actor->modelData->models[i].surfaceSkinIDs.Size() > 0)
{
unsigned sz1 = smf->surfaceskinIDs.Size();
unsigned sz2 = actor->modelData->models[i].surfaceSkinIDs.Size();
unsigned start = i * MD3_MAX_SURFACES;
surfaceskinids = actor->modelData->models[i].surfaceSkinIDs;
surfaceskinids.Resize(MD3_MAX_SURFACES);
for (unsigned surface = 0; surface < MD3_MAX_SURFACES; surface++)
{
if (sz2 > surface && (actor->modelData->models[i].surfaceSkinIDs[surface].isValid()))
{
continue;
}
if((surface + start) < sz1)
{
surfaceskinids[surface] = smf->surfaceskinIDs[surface + start];
}
else
{
surfaceskinids[surface].SetNull();
}
}
}
}
else
{
modelid = smf->modelIDs[i];
animationid = smf->animationIDs[i];
modelframe = smf->modelframes[i];
if (smfNext) modelframenext = smfNext->modelframes[i];
skinid = smf->skinIDs[i];
}
if (modelid >= 0 && modelid < Models.size())
{
FModel * mdl = Models[modelid];
auto tex = skinid.isValid() ? TexMan.GetGameTexture(skinid, true) : nullptr;
mdl->BuildVertexBuffer(renderer);
auto ssidp = surfaceskinids.Size() > 0
? surfaceskinids.Data()
: (((i * MD3_MAX_SURFACES) < smf->surfaceskinIDs.Size()) ? &smf->surfaceskinIDs[i * MD3_MAX_SURFACES] : nullptr);
bool nextFrame = smfNext && modelframe != modelframenext;
// [RL0] while per-model animations aren't done, DECOUPLEDANIMATIONS does the same as MODELSAREATTACHMENTS
if(!evaluatedSingle)
{
const TArray<VSMatrix> *boneData = nullptr;
FModel* animation = mdl;
const TArray<TRS>* animationData = nullptr;
if (animationid >= 0)
{
animation = Models[animationid];
animationData = animation->AttachAnimationData();
}
if(is_decoupled)
{
if(decoupled_frame.frame1 >= 0)
{
boneData = animation->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, animationData);
}
}
else
{
boneData = animation->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, animationData);
}
if(smf_flags & MDL_MODELSAREATTACHMENTS || is_decoupled)
{
boneStartingPosition = boneData ? screen->mBones->UploadBones(*boneData) : -1;
evaluatedSingle = true;
}
}
mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : -1.f, translation, ssidp, boneStartingPosition);
RenderModelFrame(renderer, i, smf, modelData, frameinfo, drawinfo, is_decoupled, tic, translation, boneStartingPosition, evaluatedSingle);
}
}
}
@ -1068,33 +1125,24 @@ void ParseModelDefLump(int Lump)
//
//===========================================================================
FSpriteModelFrame * FindModelFrameRaw(const PClass * ti, int sprite, int frame, bool dropped)
FSpriteModelFrame * FindModelFrameRaw(const AActor * actorDefaults, const PClass * ti, int sprite, int frame, bool dropped)
{
auto def = GetDefaultByType(ti);
if (def->hasmodel)
if(actorDefaults->hasmodel)
{
if(def->flags9 & MF9_DECOUPLEDANIMATIONS)
FSpriteModelFrame smf;
memset(&smf, 0, sizeof(smf));
smf.type = ti;
smf.sprite = sprite;
smf.frame = frame;
int hash = SpriteModelHash[ModelFrameHash(&smf) % SpriteModelFrames.Size()];
while (hash>=0)
{
FSpriteModelFrame * smf = BaseSpriteModelFrames.CheckKey((void*)ti);
if(smf) return smf;
}
else
{
FSpriteModelFrame smf;
memset(&smf, 0, sizeof(smf));
smf.type=ti;
smf.sprite=sprite;
smf.frame=frame;
int hash = SpriteModelHash[ModelFrameHash(&smf) % SpriteModelFrames.Size()];
while (hash>=0)
{
FSpriteModelFrame * smff = &SpriteModelFrames[hash];
if (smff->type==ti && smff->sprite==sprite && smff->frame==frame) return smff;
hash=smff->hashnext;
}
FSpriteModelFrame * smff = &SpriteModelFrames[hash];
if (smff->type == ti && smff->sprite == sprite && smff->frame == frame) return smff;
hash = smff->hashnext;
}
}
@ -1108,28 +1156,52 @@ FSpriteModelFrame * FindModelFrameRaw(const PClass * ti, int sprite, int frame,
if (sprframe->Voxel != nullptr)
{
int index = sprframe->Voxel->VoxeldefIndex;
if (dropped && sprframe->Voxel->DroppedSpin !=sprframe->Voxel->PlacedSpin) index++;
if (dropped && sprframe->Voxel->DroppedSpin != sprframe->Voxel->PlacedSpin) index++;
return &SpriteModelFrames[index];
}
}
}
return nullptr;
}
FSpriteModelFrame * FindModelFrame(const AActor * thing, int sprite, int frame, bool dropped)
FSpriteModelFrame * FindModelFrame(const PClass * ti, int sprite, int frame, bool dropped)
{
if(!thing) return nullptr;
auto def = GetDefaultByType(ti);
if(thing->flags9 & MF9_DECOUPLEDANIMATIONS)
if (def->hasmodel)
{
return BaseSpriteModelFrames.CheckKey((thing->modelData != nullptr && thing->modelData->modelDef != nullptr) ? thing->modelData->modelDef : thing->GetClass());
if(def->flags9 & MF9_DECOUPLEDANIMATIONS)
{
FSpriteModelFrame * smf = BaseSpriteModelFrames.CheckKey(ti);
if(smf) return smf;
}
}
return FindModelFrameRaw(def, ti, sprite, frame, dropped);
}
FSpriteModelFrame * FindModelFrame(const PClass * ti, bool is_decoupled, int sprite, int frame, bool dropped)
{
if(!ti) return nullptr;
if(is_decoupled)
{
return BaseSpriteModelFrames.CheckKey(ti);
}
else
{
return FindModelFrameRaw((thing->modelData != nullptr && thing->modelData->modelDef != nullptr) ? thing->modelData->modelDef : thing->GetClass(), sprite, frame, dropped);
return FindModelFrameRaw(GetDefaultByType(ti), ti, sprite, frame, dropped);
}
}
FSpriteModelFrame * FindModelFrame(AActor * thing, int sprite, int frame, bool dropped)
{
if(!thing) return nullptr;
return FindModelFrame((thing->modelData != nullptr && thing->modelData->modelDef != nullptr) ? thing->modelData->modelDef : thing->GetClass(), (thing->flags9 & MF9_DECOUPLEDANIMATIONS), sprite, frame, dropped);
}
//===========================================================================
//
// IsHUDModelForPlayerAvailable

View file

@ -62,8 +62,11 @@ enum
MDL_FORCECULLBACKFACES = 1<<14,
};
FSpriteModelFrame * FindModelFrame(const AActor * thing, int sprite, int frame, bool dropped);
FSpriteModelFrame * FindModelFrameRaw(const PClass * ti, int sprite, int frame, bool dropped);
FSpriteModelFrame * FindModelFrame(AActor * thing, int sprite, int frame, bool dropped);
FSpriteModelFrame * FindModelFrame(const PClass * ti, bool is_decoupled, int sprite, int frame, bool dropped);
FSpriteModelFrame * FindModelFrame(const PClass * ti, int sprite, int frame, bool dropped);
//FSpriteModelFrame * FindModelFrameRaw(const AActor * actorDefaults, const PClass * ti, int sprite, int frame, bool dropped);
bool IsHUDModelForPlayerAvailable(player_t * player);
// Check if circle potentially intersects with node AABB
@ -114,6 +117,36 @@ void BSPWalkCircle(FLevelLocals *Level, float x, float y, float radiusSquared, c
void RenderModel(FModelRenderer* renderer, float x, float y, float z, FSpriteModelFrame* smf, AActor* actor, double ticFrac);
void RenderHUDModel(FModelRenderer* renderer, DPSprite* psp, FVector3 translation, FVector3 rotation, FVector3 rotation_pivot, FSpriteModelFrame *smf);
struct CalcModelFrameInfo
{
int smf_flags;
const FSpriteModelFrame * smfNext;
float inter;
bool is_decoupled;
ModelAnimFrameInterp decoupled_frame;
ModelAnimFrame * decoupled_frame_prev;
unsigned modelsamount;
};
struct ModelDrawInfo
{
TArray<FTextureID> surfaceskinids;
int modelid;
int animationid;
int modelframe;
int modelframenext;
FTextureID skinid;
};
class DActorModelData;
CalcModelFrameInfo CalcModelFrame(FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, DActorModelData* modelData, AActor* actor, bool is_decoupled, double tic);
// returns true if the model isn't removed
bool CalcModelOverrides(int modelindex, const FSpriteModelFrame *smf, DActorModelData* modelData, const CalcModelFrameInfo &frameinfo, ModelDrawInfo &drawinfo, bool is_decoupled);
const TArray<VSMatrix> * ProcessModelFrame(FModel * animation, bool nextFrame, int i, const FSpriteModelFrame *smf, DActorModelData* modelData, const CalcModelFrameInfo &frameinfo, ModelDrawInfo &drawinfo, bool is_decoupled, double tic, BoneInfo *out);
EXTERN_CVAR(Float, cl_scaleweaponfov)
#endif

View file

@ -159,7 +159,7 @@ void hw_PrecacheTexture(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitl
{
auto &state = cls->GetStates()[i];
spritelist[state.sprite].Insert(gltrans, true);
FSpriteModelFrame * smf = FindModelFrameRaw(cls, state.sprite, state.Frame, false);
FSpriteModelFrame * smf = FindModelFrame(cls, state.sprite, state.Frame, false);
if (smf != NULL)
{
for (int i = 0; i < smf->modelsAmount; i++)

View file

@ -1359,6 +1359,108 @@ class Actor : Thinker native
native bool A_AttachLight(Name lightid, int type, Color lightcolor, int radius1, int radius2, int flags = 0, Vector3 ofs = (0,0,0), double param = 0, double spoti = 10, double spoto = 25, double spotp = 0);
native bool A_RemoveLight(Name lightid);
//================================================
//
// Bone Offset Setters
//
//================================================
native version("4.15.1") void SetBoneRotation(int boneIndex, Quat rotation, int mode = SB_ADD, double interpolation_duration = 1.0);
native version("4.15.1") void SetNamedBoneRotation(Name boneName, Quat rotation, int mode = SB_ADD, double interpolation_duration = 1.0);
version("4.15.1") void SetBoneRotationAngles(int boneIndex, double yaw, double pitch, double roll, int mode = SB_ADD, double interpolation_duration = 1.0)
{
SetBoneRotation(boneIndex, Quat.FromAngles(yaw, pitch, roll), mode, interpolation_duration);
}
version("4.15.1") void SetNamedBoneRotationAngles(Name boneName, double yaw, double pitch, double roll, int mode = SB_ADD, double interpolation_duration = 1.0)
{
SetNamedBoneRotation(boneName, Quat.FromAngles(yaw, pitch, roll), mode, interpolation_duration);
}
version("4.15.1") void ClearBoneRotation(int boneIndex, double interpolation_duration = 1.0)
{
SetBoneRotation(boneIndex, Quat(0, 0, 0, 1), 0, interpolation_duration);
}
version("4.15.1") void ClearNamedBoneRotation(Name boneName, double interpolation_duration = 1.0)
{
SetNamedBoneRotation(boneName, Quat(0, 0, 0, 1), 0, interpolation_duration);
}
native version("4.15.1") void SetBoneTranslation(int boneIndex, Vector3 translation, int mode = SB_ADD, double interpolation_duration = 1.0);
native version("4.15.1") void SetNamedBoneTranslation(Name boneName, Vector3 translation, int mode = SB_ADD, double interpolation_duration = 1.0);
version("4.15.1") void ClearBoneTranslation(int boneIndex, double interpolation_duration = 1.0)
{
SetBoneTranslation(boneIndex, (0, 0, 0), 0, interpolation_duration);
}
version("4.15.1") void ClearNamedBoneTranslation(Name boneName, double interpolation_duration = 1.0)
{
SetNamedBoneTranslation(boneName, (0, 0, 0), 0, interpolation_duration);
}
native version("4.15.1") void SetBoneScaling(int boneIndex, Vector3 scaling, int mode = SB_ADD, double interpolation_duration = 1.0);
native version("4.15.1") void SetNamedBoneScaling(Name boneName, Vector3 scaling, int mode = SB_ADD, double interpolation_duration = 1.0);
version("4.15.1") void ClearBoneScaling(int boneIndex, double interpolation_duration = 1.0)
{
SetBoneScaling(boneIndex, (0, 0, 0), 0, interpolation_duration);
}
version("4.15.1") void ClearNamedBoneScaling(Name boneName, double interpolation_duration = 1.0)
{
SetNamedBoneScaling(boneName, (0, 0, 0), 0, interpolation_duration);
}
native version("4.15.1") void ClearBoneOffsets();
//================================================
//
// Bone Offset Getters
//
//================================================
/* rotation, translation, scaling */
native version("4.15.1") Quat, Vector3, Vector3 GetBoneOffset(int boneIndex);
native version("4.15.1") Quat, Vector3, Vector3 GetNamedBoneOffset(Name boneName);
//================================================
//
// Bone Info Getters
//
//================================================
native version("4.15.1") void GetRootBones(out Array<int> rootBones);
native version("4.15.1") Name GetBoneName(int boneIndex);
native version("4.15.1") int GetBoneIndex(Name boneName);
native version("4.15.1") int GetBoneParent(int boneIndex);
native version("4.15.1") int GetNamedBoneParent(Name boneName); // return value lower than 0 means it's a root bone, and as such has no parent
native version("4.15.1") void GetBoneChildren(int boneIndex, out Array<int> children);
native version("4.15.1") void GetNamedBoneChildren(Name boneName, out Array<int> children);
// this is the length in model units, not in world units, and does not take model scale in MODELDEF, world, etc, or current animation or offset into account at all
native version("4.15.1") double GetBoneLength(int boneIndex);
native version("4.15.1") double GetNamedBoneLength(Name boneName);
// this is the direction of the bone in the armature, does not take the current animation or offset into account at all
native version("4.15.1") Vector3 GetBoneDir(int boneIndex);
native version("4.15.1") Vector3 GetNamedBoneDir(Name boneName);
native version("4.15.1") int GetBoneCount();
//================================================
//
//
//
//================================================
native version("4.12") void SetAnimation(Name animName, double framerate = -1, int startFrame = -1, int loopFrame = -1, int endFrame = -1, int interpolateTics = -1, int flags = 0);
native version("4.12") ui void SetAnimationUI(Name animName, double framerate = -1, int startFrame = -1, int loopFrame = -1, int endFrame = -1, int interpolateTics = -1, int flags = 0);

View file

@ -1547,3 +1547,10 @@ enum EParticleStyle
PT_ROUND = 1,
PT_SMOOTH = 2,
};
enum ESetBoneMode
{
SB_CLEAR = 0,
SB_ADD = 1,
SB_REPLACE = 2,
};