From 3780c5910ae378fb2759e60554943cdcc024ce37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Thu, 1 May 2025 10:15:46 -0300 Subject: [PATCH] Bone Manip part 1 - bone setters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- src/common/engine/serializer.h | 17 + src/common/models/bonecomponents.h | 107 ++++ src/common/models/model.cpp | 2 +- src/common/models/model.h | 18 +- src/common/models/model_iqm.h | 54 +- src/common/models/models_iqm.cpp | 144 +++-- src/common/utility/TRS.h | 23 +- src/common/utility/matrix.cpp | 16 + src/common/utility/matrix.h | 2 + src/common/utility/quaternion.h | 19 +- src/playsim/actor.h | 17 +- src/playsim/p_actionfunctions.cpp | 637 ++++++++++++++++++++++- src/playsim/p_mobj.cpp | 25 + src/r_data/models.cpp | 472 ++++++++++------- src/r_data/models.h | 37 +- src/rendering/hwrenderer/hw_precache.cpp | 2 +- wadsrc/static/zscript/actors/actor.zs | 102 ++++ wadsrc/static/zscript/constants.zs | 7 + 18 files changed, 1422 insertions(+), 279 deletions(-) diff --git a/src/common/engine/serializer.h b/src/common/engine/serializer.h index fb906aee8..d72e44440 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -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(key, &p[0], def? &(*def)[0] : nullptr, 3, true); } +inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector4 &p, DVector4 *def) +{ + return arc.Array(key, &p[0], def? &(*def)[0] : nullptr, 4, true); +} + +inline FSerializer& Serialize(FSerializer& arc, const char* key, DQuaternion& p, DQuaternion* def) +{ + return arc.Array(key, &p[0], def ? &(*def)[0] : nullptr, 4, true); +} + inline FSerializer &Serialize(FSerializer &arc, const char *key, DRotator &p, DRotator *def) { return arc.Array(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(key, &p[0], def ? &(*def)[0] : nullptr, 4, true); } +inline FSerializer& Serialize(FSerializer& arc, const char* key, FQuaternion& p, FQuaternion* def) +{ + return arc.Array(key, &p[0], def ? &(*def)[0] : nullptr, 4, true); +} + inline FSerializer& Serialize(FSerializer& arc, const char* key, FVector3& p, FVector3* def) { return arc.Array(key, &p[0], def ? &(*def)[0] : nullptr, 3, true); diff --git a/src/common/models/bonecomponents.h b/src/common/models/bonecomponents.h index f4f221f3b..db8957f8a 100644 --- a/src/common/models/bonecomponents.h +++ b/src/common/models/bonecomponents.h @@ -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 +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) + { + return FQuaternion(0,0,0,1); + } + return {}; +} + +template +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 cur = DefVal(); + + 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(); + + // 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() : 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 translation; + BoneOverrideComponent rotation; + BoneOverrideComponent 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 bones_anim_only; + TArray bones_with_override; + TArray positions; +}; + struct ModelAnim { int firstFrame = 0; diff --git a/src/common/models/model.cpp b/src/common/models/model.cpp index 0f1603415..bc1a02efd 100644 --- a/src/common/models/model.cpp +++ b/src/common/models/model.cpp @@ -45,7 +45,7 @@ TDeletingArray Models; TArray SpriteModelFrames; -TMap BaseSpriteModelFrames; +TMap BaseSpriteModelFrames; ///////////////////////////////////////////////////////////////////////////// diff --git a/src/common/models/model.h b/src/common/models/model.h index b6d3f4803..23381bed1 100644 --- a/src/common/models/model.h +++ b/src/common/models/model.h @@ -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 Models; extern TArray SpriteModelFrames; -extern TMap BaseSpriteModelFrames; +extern TMap 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 &out) {} + + virtual void GetRootJoints(TArray &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* animationData) { return nullptr; }; - virtual const TArray* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData) { return nullptr; }; + virtual const TArray* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, TArray *in, BoneInfo *out, double time) { return nullptr; }; void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } diff --git a/src/common/models/model_iqm.h b/src/common/models/model_iqm.h index cf221595f..9374e5f85 100644 --- a/src/common/models/model_iqm.h +++ b/src/common/models/model_iqm.h @@ -67,10 +67,11 @@ struct IQMAdjacency struct IQMJoint { - FString Name; + FName Name; int32_t Parent; // parent < 0 means this is a root bone + TArray Children; // indices of children FVector3 Translate; - FVector4 Quaternion; + FQuaternion Quaternion; FVector3 Scale; }; @@ -122,10 +123,10 @@ public: const TArray* AttachAnimationData() override; ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData) override; - const TArray* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData) override; + const TArray* CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, TArray *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* animationData); - const TArray* CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData); + const TArray* CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData, TArray *in, BoneInfo *out, double time); private: void LoadGeometry(); @@ -140,11 +141,15 @@ private: int mLumpNum = -1; TMap NamedAnimations; + TMap NamedJoints; TArray Meshes; TArray Triangles; TArray Adjacency; TArray Joints; + + TArray RootJoints; + TArray Poses; TArray Anims; TArray Bounds; @@ -158,6 +163,47 @@ private: TArray baseframe; TArray inversebaseframe; TArray 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 &out) override + { + out = RootJoints; + } + + void GetJointChildren(int joint, TArray &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 { }; diff --git a/src/common/models/models_iqm.cpp b/src/common/models/models_iqm.cpp index d5fb6a152..719f15062 100644 --- a/src/common/models/models_iqm.cpp +++ b/src/common/models/models_iqm.cpp @@ -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* 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* IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData) +const TArray* IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, TArray *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(from)) { auto &from_interp = std::get(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(from)) { - return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get(from), animationData); + return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get(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* 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* 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* animationData) +const TArray* 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* animationData, TArray *in, BoneInfo *out, double time) { const TArray& animationFrames = animationData ? *animationData : TRSData; - if (Joints.Size() > 0) + + TArray* 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* 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* 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); diff --git a/src/common/utility/TRS.h b/src/common/utility/TRS.h index c39a870ad..dd72ab5c5 100644 --- a/src/common/utility/TRS.h +++ b/src/common/utility/TRS.h @@ -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 + 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; } }; diff --git a/src/common/utility/matrix.cpp b/src/common/utility/matrix.cpp index 3d84d48e7..eb6abdb1f 100644 --- a/src/common/utility/matrix.cpp +++ b/src/common/utility/matrix.cpp @@ -114,6 +114,22 @@ void VSMatrix::multQuaternion(const TVector4& q) multMatrix(m); } +void VSMatrix::multQuaternion(const TQuaternion& 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 diff --git a/src/common/utility/matrix.h b/src/common/utility/matrix.h index 265981e1b..74d764bb2 100644 --- a/src/common/utility/matrix.h +++ b/src/common/utility/matrix.h @@ -22,6 +22,7 @@ #include #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& q); + void multQuaternion(const TQuaternion& q); void loadMatrix(const FLOATTYPE *aMatrix); #ifdef USE_DOUBLE void loadMatrix(const float *aMatrix); diff --git a/src/common/utility/quaternion.h b/src/common/utility/quaternion.h index 1437b56be..a1c506e6e 100644 --- a/src/common/utility/quaternion.h +++ b/src/common/utility/quaternion.h @@ -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 + explicit operator TVector4() + { + return TVector4(U(X),U(Y),U(Z),U(W)); + } }; typedef TQuaternion FQuaternion; diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 00993106d..66b85ea79 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -733,14 +733,15 @@ class DActorModelData : public DObject { DECLARE_CLASS(DActorModelData, DObject); public: - PClass * modelDef; - TArray models; - TArray skinIDs; - TArray animationIDs; - TArray modelFrameGenerators; - int flags; - int overrideFlagsSet; - int overrideFlagsClear; + PClass * modelDef; + TArray models; + TArray skinIDs; + TArray animationIDs; + TArray modelFrameGenerators; + TArray> modelBoneOverrides; + int flags; + int overrideFlagsSet; + int overrideFlagsClear; ModelAnim curAnim; ModelAnimFrame prevAnim; // used for interpolation when switching anims diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 1c5a91d6d..98897d873 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -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 +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 +FModel * SetGetBoneSharedIndex(AActor * self, int model_index, int &bone_index, FName * bone_name) +{ + FModel * mdl = SetGetBoneShared(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(self, model_index, bone_index, bone_name); +} + +FModel * GetBoneOffsetShared(AActor * self, int model_index, int &bone_index, FName * bone_name) +{ + return SetGetBoneSharedIndex(self, model_index, bone_index, bone_name); +} + +FModel * GetBoneShared(AActor * self, int model_index, int &bone_index, FName * bone_name) +{ + return SetGetBoneSharedIndex(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 *out) +{ + if(out) + { + FModel * mdl = SetGetBoneShared(self, 0); + + mdl->GetRootJoints(*out); + } +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetRootBones, GetRootBonesNative) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_POINTER(out, TArray); + + 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 *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 *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); + + GetBoneChildrenNative(self, boneindex, out); + + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetNamedBoneChildren, GetNamedBoneChildrenNative) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_NAME(bonename); + PARAM_POINTER(out, TArray); + + 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(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; } diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 8c804d55b..00d20722b 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -1764,6 +1764,30 @@ void SerializeModelID(FSerializer &arc, const char *key, int &id) } } +template +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) diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index bc9473b07..8226cf411 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -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 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 * 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* animationData = nullptr; + + if (drawinfo.animationid >= 0) + { + animation = Models[drawinfo.animationid]; + animationData = animation->AttachAnimationData(); + } + + const TArray *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 *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 *boneData = nullptr; - FModel* animation = mdl; - const TArray* 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 diff --git a/src/r_data/models.h b/src/r_data/models.h index 4a7154446..a62d59333 100644 --- a/src/r_data/models.h +++ b/src/r_data/models.h @@ -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 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 * 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 diff --git a/src/rendering/hwrenderer/hw_precache.cpp b/src/rendering/hwrenderer/hw_precache.cpp index d6837252d..699faf706 100644 --- a/src/rendering/hwrenderer/hw_precache.cpp +++ b/src/rendering/hwrenderer/hw_precache.cpp @@ -159,7 +159,7 @@ void hw_PrecacheTexture(uint8_t *texhitlist, TMap &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++) diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 1668b3a8c..1598ee697 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -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 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 children); + native version("4.15.1") void GetNamedBoneChildren(Name boneName, out Array 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); diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index 249d031bb..5c1480e62 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -1547,3 +1547,10 @@ enum EParticleStyle PT_ROUND = 1, PT_SMOOTH = 2, }; + +enum ESetBoneMode +{ + SB_CLEAR = 0, + SB_ADD = 1, + SB_REPLACE = 2, +};