From 5d5e7ca04274acdc6f20c35c8a642e6562c3e5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Mon, 20 Nov 2023 18:54:49 -0300 Subject: [PATCH] Decoupled IQM Model Animations --- src/common/engine/serializer.h | 3 +- src/common/models/model.cpp | 1 + src/common/models/model.h | 15 +- src/common/models/model_iqm.h | 9 +- src/common/models/models_iqm.cpp | 83 ++++++-- src/common/textures/textureid.h | 1 + src/playsim/actor.h | 34 ++- src/playsim/p_actionfunctions.cpp | 206 ++++++++++++++++++- src/playsim/p_mobj.cpp | 27 ++- src/r_data/models.cpp | 193 +++++++++++++---- src/rendering/hwrenderer/scene/hw_weapon.cpp | 4 +- src/scripting/thingdef_data.cpp | 1 + wadsrc/static/zscript/actors/actor.zs | 6 + wadsrc/static/zscript/constants.zs | 7 + 14 files changed, 515 insertions(+), 75 deletions(-) diff --git a/src/common/engine/serializer.h b/src/common/engine/serializer.h index ec1d109490..f3afd3724f 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -240,7 +240,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *d FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def); FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def); FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); -FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &sid, struct ModelOverride *def); +FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &mo, struct ModelOverride *def); +FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def); FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval); void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p); diff --git a/src/common/models/model.cpp b/src/common/models/model.cpp index 2df92a3700..4b9ab0b00d 100644 --- a/src/common/models/model.cpp +++ b/src/common/models/model.cpp @@ -47,6 +47,7 @@ TArray savedModelFiles; TDeletingArray Models; TArray SpriteModelFrames; +TMap BaseSpriteModelFrames; ///////////////////////////////////////////////////////////////////////////// diff --git a/src/common/models/model.h b/src/common/models/model.h index 69b947ae18..84adab5565 100644 --- a/src/common/models/model.h +++ b/src/common/models/model.h @@ -6,6 +6,8 @@ #include "matrix.h" #include "palettecontainer.h" #include "TRS.h" +#include "tarray.h" +#include "name.h" class DBoneComponents; class FModelRenderer; @@ -19,6 +21,7 @@ void FlushModels(); extern TArray savedModelFiles; extern TDeletingArray Models; extern TArray SpriteModelFrames; +extern TMap BaseSpriteModelFrames; #define MD3_MAX_SURFACES 32 #define MIN_MODELS 4 @@ -72,13 +75,20 @@ public: virtual ~FModel(); virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) = 0; + virtual int FindFrame(const char * name, bool nodefault = false) = 0; + + // [RL0] these are used for decoupled iqm animations + virtual int FindFirstFrame(FName name) { return FErr_NotFound; } + virtual int FindLastFrame(FName name) { return FErr_NotFound; } + virtual double FindFramerate(FName name) { return FErr_NotFound; } + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, FTranslationID translation, const FTextureID* surfaceskinids, const TArray& boneData, int boneStartPosition) = 0; virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0; virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0; virtual float getAspectFactor(float vscale) { return 1.f; } virtual const TArray* AttachAnimationData() { return nullptr; }; - virtual const TArray CalculateBones(int frame1, int frame2, double inter, const TArray* animationData, DBoneComponents* bones, int index) { return {}; }; + virtual const TArray CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index) { return {}; }; void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } @@ -86,7 +96,8 @@ public: bool hasSurfaces = false; FString mFileName; - + + FSpriteModelFrame *baseFrame; private: IModelVertexBuffer *mVBuf[NumModelRendererTypes]; }; diff --git a/src/common/models/model_iqm.h b/src/common/models/model_iqm.h index 701c1e7937..464f5c13ad 100644 --- a/src/common/models/model_iqm.h +++ b/src/common/models/model_iqm.h @@ -6,6 +6,7 @@ #include "matrix.h" #include "common/rendering/i_modelvertexbuffer.h" #include "m_swap.h" +#include "name.h" class DBoneComponents; @@ -112,11 +113,14 @@ public: bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; int FindFrame(const char* name, bool nodefault) override; + int FindFirstFrame(FName name) override; + int FindLastFrame(FName name) override; + double FindFramerate(FName name) override; void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, FTranslationID translation, const FTextureID* surfaceskinids, const TArray& boneData, int boneStartPosition) override; void BuildVertexBuffer(FModelRenderer* renderer) override; void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; const TArray* AttachAnimationData() override; - const TArray CalculateBones(int frame1, int frame2, double inter, const TArray* animationData, DBoneComponents* bones, int index) override; + const TArray CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index) override; private: void LoadGeometry(); @@ -129,6 +133,9 @@ private: void LoadBlendWeights(IQMFileReader& reader, const IQMVertexArray& vertexArray); int mLumpNum = -1; + + TMap NamedAnimations; + TArray Meshes; TArray Triangles; TArray Adjacency; diff --git a/src/common/models/models_iqm.cpp b/src/common/models/models_iqm.cpp index df65a61d6a..429da9b14c 100644 --- a/src/common/models/models_iqm.cpp +++ b/src/common/models/models_iqm.cpp @@ -137,9 +137,12 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt } reader.SeekTo(ofs_anims); - for (IQMAnim& anim : Anims) + for(int i = 0; i < Anims.Size(); i++) { + IQMAnim& anim = Anims[i]; anim.Name = reader.ReadName(text); + NamedAnimations.Insert(anim.Name, i); + anim.FirstFrame = reader.ReadUInt32(); anim.NumFrames = reader.ReadUInt32(); anim.Framerate = reader.ReadFloat(); @@ -445,6 +448,27 @@ int IQMModel::FindFrame(const char* name, bool nodefault) return FErr_NotFound; } +int IQMModel::FindFirstFrame(FName name) +{ + int * nam = NamedAnimations.CheckKey(name); + if(nam) return Anims[*nam].FirstFrame; + return FErr_NotFound; +} + +int IQMModel::FindLastFrame(FName name) +{ + int * nam = NamedAnimations.CheckKey(name); + if(nam) return Anims[*nam].FirstFrame + Anims[*nam].NumFrames; + return FErr_NotFound; +} + +double IQMModel::FindFramerate(FName name) +{ + int * nam = NamedAnimations.CheckKey(name); + if(nam) return Anims[*nam].Framerate; + return FErr_NotFound; +} + void IQMModel::RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame1, int frame2, double inter, FTranslationID translation, const FTextureID* surfaceskinids, const TArray& boneData, int boneStartPosition) { renderer->SetupFrame(this, 0, 0, NumVertices, boneData, boneStartPosition); @@ -517,7 +541,26 @@ const TArray* IQMModel::AttachAnimationData() return &TRSData; } -const TArray IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray* animationData, DBoneComponents* boneComponentData, int index) +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.scaling = from.scaling * invt + to.scaling * t; + + return bone; +} + +const TArray IQMModel::CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* boneComponentData, int index) { const TArray& animationFrames = animationData ? *animationData : TRSData; if (Joints.Size() > 0) @@ -534,8 +577,13 @@ const TArray IQMModel::CalculateBones(int frame1, int frame2, double i int offset1 = frame1 * numbones; int offset2 = frame2 * numbones; - float t = (float)inter; - float invt = 1.0f - t; + + int offset1_1 = frame1_prev * numbones; + int offset2_1 = frame2_prev * numbones; + + float invt = 1.0f - inter; + float invt1 = 1.0f - inter1_prev; + float invt2 = 1.0f - inter2_prev; float swapYZ[16] = { 0.0f }; swapYZ[0 + 0 * 4] = 1.0f; @@ -547,19 +595,26 @@ const TArray IQMModel::CalculateBones(int frame1, int frame2, double i TArray modifiedBone(numbones, true); for (int i = 0; i < numbones; i++) { - TRS bone; - TRS from = animationFrames[offset1 + i]; - TRS to = animationFrames[offset2 + i]; + TRS prev; - bone.translation = from.translation * invt + to.translation * t; - bone.rotation = from.rotation * invt; - if ((bone.rotation | to.rotation * t) < 0) + if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0)) { - bone.rotation.X *= -1; bone.rotation.Y *= -1; bone.rotation.Z *= -1; bone.rotation.W *= -1; + prev = inter1_prev < 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1); + } + + TRS next; + + if(frame2 >= 0 && (frame2_prev >= 0 || inter2_prev < 0)) + { + next = inter2_prev < 0 ? animationFrames[offset2 + i] : InterpolateBone(animationFrames[offset2_1 + i], animationFrames[offset2 + i], inter2_prev, invt2); + } + + TRS bone; + + if(frame1 >= 0 || inter < 0) + { + bone = inter < 0 ? animationFrames[offset1 + i] : InterpolateBone(prev, next , inter, invt); } - bone.rotation += to.rotation * t; - bone.rotation.MakeUnit(); - bone.scaling = from.scaling * invt + to.scaling * t; if (Joints[i].Parent >= 0 && modifiedBone[Joints[i].Parent]) { diff --git a/src/common/textures/textureid.h b/src/common/textures/textureid.h index c441cb9d5e..ae025c76cb 100644 --- a/src/common/textures/textureid.h +++ b/src/common/textures/textureid.h @@ -28,6 +28,7 @@ class FTextureID public: FTextureID() = default; + FTextureID(nullptr_t) : texnum(0) {} bool isNull() const { return texnum == 0; } bool isValid() const { return texnum > 0; } bool Exists() const { return texnum >= 0; } diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 84c199eb6c..0892baeb17 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -438,10 +438,11 @@ enum ActorFlag8 // --- mobj.flags9 --- enum ActorFlag9 { - MF9_SHADOWAIM = 0x00000001, // [inkoalawetrust] Monster still gets aim penalty from aiming at shadow actors even with MF6_SEEINVISIBLE on. - MF9_DOSHADOWBLOCK = 0x00000002, // [inkoalawetrust] Should the monster look for SHADOWBLOCK actors ? - MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty. - MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors. + MF9_SHADOWAIM = 0x00000001, // [inkoalawetrust] Monster still gets aim penalty from aiming at shadow actors even with MF6_SEEINVISIBLE on. + MF9_DOSHADOWBLOCK = 0x00000002, // [inkoalawetrust] Should the monster look for SHADOWBLOCK actors ? + MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty. + MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors. + MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states }; // --- mobj.renderflags --- @@ -689,13 +690,31 @@ enum EViewPosFlags // [MC] Flags for SetViewPos. VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. }; +enum EAnimOverrideFlags +{ + ANIMOVERRIDE_NONE = 1 << 0, // no animation + ANIMOVERRIDE_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends +}; + +struct AnimOverride +{ + int firstFrame; + int lastFrame; + int loopFrame; + double startFrame; + int flags = ANIMOVERRIDE_NONE; + float framerate; + double startTic; // when the animation starts if interpolating from previous animation + double switchTic; // when the animation was changed -- where to interpolate the switch from +}; + struct ModelOverride { int modelID; TArray surfaceSkinIDs; }; -enum ModelDataFlags +enum EModelDataFlags { MODELDATA_HADMODEL = 1 << 0, }; @@ -705,11 +724,14 @@ class DActorModelData : public DObject DECLARE_CLASS(DActorModelData, DObject); public: FName modelDef; - int flags; TArray models; TArray skinIDs; TArray animationIDs; TArray modelFrameGenerators; + int flags; + + AnimOverride curAnim; + AnimOverride prevAnim; // used for interpolation when switching anims DActorModelData() = default; virtual void Serialize(FSerializer& arc) override; diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 3da0e5aab4..3f19303ac7 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -71,6 +71,7 @@ #include "types.h" #include "model.h" #include "shadowinlines.h" +#include "i_time.h" static FRandom pr_camissile ("CustomActorfire"); static FRandom pr_cabullet ("CustomBullet"); @@ -5105,7 +5106,8 @@ static void EnsureModelData(AActor * mobj) static void CleanupModelData(AActor * mobj) { - if ( mobj->modelData->models.Size() == 0 + if ( !(mobj->flags9 & MF9_DECOUPLEDANIMATIONS) + && mobj->modelData->models.Size() == 0 && mobj->modelData->modelFrameGenerators.Size() == 0 && mobj->modelData->skinIDs.Size() == 0 && mobj->modelData->animationIDs.Size() == 0 @@ -5117,11 +5119,157 @@ static void CleanupModelData(AActor * mobj) } } +enum ESetAnimationFlags +{ + SAF_INSTANT = 1 << 0, + SAF_LOOP = 1 << 1, +}; + +void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags, double ticFrac) +{ + if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self"); + + if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS)) + { + ThrowAbortException(X_OTHER, "Cannot set animation for non-decoupled actors"); + } + + if(interpolateTics <= 0) interpolateTics = 1; + + EnsureModelData(self); + + if(animName == NAME_None) + { + self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + return; + } + + if(!(flags & SAF_INSTANT)) + { + self->modelData->prevAnim = self->modelData->curAnim; + } + + double tic = self->Level->totaltime; + if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !self->Level->isFrozen()) + { + tic += ticFrac; + } + + FModel * mdl = Models[(self->modelData->models.Size() > 0 && self->modelData->models[0].modelID >= 0) ? self->modelData->models[0].modelID : BaseSpriteModelFrames[self->GetClass()].modelIDs[0]]; + + int animStart = mdl->FindFirstFrame(animName); + if(animStart == FErr_NotFound) + { + self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + Printf("Could not find animation %s", animName.GetChars()); + return; + } + int animEnd = mdl->FindLastFrame(animName); + + if(framerate < 0) + { + framerate = mdl->FindFramerate(animName); + } + + int len = animEnd - animStart; + + if(startFrame >= len) + { + self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + Printf("frame %d is past the end of animation %s", startFrame, animName.GetChars()); + return; + } + else if(loopFrame >= len) + { + self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + Printf("frame %d is past the end of animation %s", startFrame, animName.GetChars()); + return; + } + + self->modelData->curAnim.firstFrame = animStart; + self->modelData->curAnim.lastFrame = animEnd - 1; + self->modelData->curAnim.startFrame = startFrame < 0 ? animStart : animStart + startFrame; + self->modelData->curAnim.loopFrame = loopFrame < 0 ? animStart : animStart + loopFrame; + self->modelData->curAnim.flags = (flags&SAF_LOOP) ? ANIMOVERRIDE_LOOP : 0; + self->modelData->curAnim.switchTic = tic; + self->modelData->curAnim.framerate = (float)framerate; + + if(!(flags & SAF_INSTANT)) + { + self->modelData->curAnim.startTic = floor(tic) + interpolateTics; + } + else + { + self->modelData->curAnim.startTic = tic; + } +} + +void SetAnimationNative(AActor * self, int i_animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags) +{ + SetAnimationInternal(self, FName(ENamedName(i_animName)), framerate, startFrame, loopFrame, interpolateTics, flags, 1); +} + +void SetAnimationUINative(AActor * self, int i_animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags) +{ + SetAnimationInternal(self, FName(ENamedName(i_animName)), framerate, startFrame, loopFrame, interpolateTics, flags, I_GetTimeFrac()); +} + +extern double getCurrentFrame(const AnimOverride &anim, double tic); + +void SetAnimationFrameRateInternal(AActor * self, double framerate, double ticFrac) +{ + if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self"); + + if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS)) + { + ThrowAbortException(X_OTHER, "Cannot set animation for non-decoupled actors"); + } + + EnsureModelData(self); + + if(self->modelData->curAnim.flags & ANIMOVERRIDE_NONE) return; + + if(framerate < 0) + { + ThrowAbortException(X_OTHER, "Cannot set negative framerate"); + } + + + if(self->modelData->curAnim.startTic < ticFrac) + { + self->modelData->curAnim.framerate = (float)framerate; + return; + } + + double tic = self->Level->totaltime; + if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !self->Level->isFrozen()) + { + tic += ticFrac; + } + + double frame = getCurrentFrame(self->modelData->curAnim, tic); + + self->modelData->curAnim.startFrame = frame; + self->modelData->curAnim.startTic = tic; + self->modelData->curAnim.switchTic = tic; + self->modelData->curAnim.framerate = (float)framerate; +} + +void SetAnimationFrameRateNative(AActor * self, double framerate) +{ + SetAnimationFrameRateInternal(self, framerate, 1); +} + +void SetAnimationFrameRateUINative(AActor * self, double framerate) +{ + SetAnimationFrameRateInternal(self, framerate, I_GetTimeFrac()); +} + enum ChangeModelFlags { - CMDL_WEAPONTOPLAYER = 1, - CMDL_HIDEMODEL = 1 << 1, - CMDL_USESURFACESKIN = 1 << 2, + CMDL_WEAPONTOPLAYER = 1 << 0, + CMDL_HIDEMODEL = 1 << 1, + CMDL_USESURFACESKIN = 1 << 2, }; void ChangeModelNative( @@ -5335,6 +5483,56 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_ChangeModel, ChangeModelNative) return 0; } +DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimation, SetAnimationNative) +{ + PARAM_ACTION_PROLOGUE(AActor); + PARAM_NAME(animName); + PARAM_FLOAT(framerate); + PARAM_INT(startFrame); + PARAM_INT(loopFrame); + PARAM_INT(interpolateTics); + PARAM_INT(flags); + + SetAnimationInternal(self, animName, framerate, startFrame, loopFrame, interpolateTics, flags, 1); + + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationUI, SetAnimationUINative) +{ + PARAM_ACTION_PROLOGUE(AActor); + PARAM_NAME(animName); + PARAM_FLOAT(framerate); + PARAM_INT(startFrame); + PARAM_INT(loopFrame); + PARAM_INT(interpolateTics); + PARAM_INT(flags); + + SetAnimationInternal(self, animName, framerate, startFrame, loopFrame, interpolateTics, flags, I_GetTimeFrac()); + + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRate, SetAnimationFrameRateNative) +{ + PARAM_ACTION_PROLOGUE(AActor); + PARAM_FLOAT(framerate); + + SetAnimationFrameRateInternal(self, framerate, 1); + + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRateUI, SetAnimationFrameRateUINative) +{ + PARAM_ACTION_PROLOGUE(AActor); + PARAM_FLOAT(framerate); + + SetAnimationFrameRateInternal(self, framerate, I_GetTimeFrac()); + + return 0; +} + // This needs to account for the fact that internally renderstyles are stored as a series of operations, // but the script side only cares about symbolic constants. DEFINE_ACTION_FUNCTION(AActor, GetRenderStyle) diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 986afc44bb..ddb5f047a1 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -1327,11 +1327,27 @@ bool AActor::Massacre () // //---------------------------------------------------------------------------- -FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &sid, ModelOverride *def) +FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &mo, ModelOverride *def) { arc.BeginObject(key); - arc("modelID", sid.modelID); - arc("surfaceSkinIDs", sid.surfaceSkinIDs); + arc("modelID", mo.modelID); + arc("surfaceSkinIDs", mo.surfaceSkinIDs); + arc.EndObject(); + return arc; +} + +FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def) +{ + //TODO + arc.BeginObject(key); + arc("firstFrame", ao.firstFrame); + arc("lastFrame", ao.lastFrame); + arc("loopFrame", ao.loopFrame); + arc("startFrame", ao.startFrame); + arc("flags", ao.flags); + arc("framerate", ao.framerate); + arc("startTic", ao.startTic); + arc("switchTic", ao.switchTic); arc.EndObject(); return arc; } @@ -1342,10 +1358,11 @@ void DActorModelData::Serialize(FSerializer& arc) arc("modelDef", modelDef) ("models", models) ("skinIDs", skinIDs) - //("surfaceSkinIDs", surfaceSkinIDs) ("animationIDs", animationIDs) ("modelFrameGenerators", modelFrameGenerators) - ("flags", flags); + ("flags", flags) + ("curAnim", curAnim) + ("prevAnim", prevAnim); } void DActorModelData::OnDestroy() diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 89fa8893f2..0cfb14b0ae 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -253,13 +253,88 @@ void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, FVector3 translatio renderer->EndDrawHUDModel(playermo->RenderStyle, smf); } +double getCurrentFrame(const AnimOverride &anim, double tic) +{ + if(anim.framerate <= 0) return anim.startFrame; + + double duration = double(anim.lastFrame - anim.firstFrame) / double(anim.framerate); // duration in seconds + double startPos = double(anim.startFrame - anim.firstFrame) / double(anim.framerate); + + double pos = startPos + ((tic - anim.startTic) / GameTicRate); // position in seconds + + return (((anim.flags & ANIMOVERRIDE_LOOP) ? fmod(pos, duration) : min(pos, duration)) * anim.framerate) + anim.firstFrame; +} + +static void calcFrame(const AnimOverride &anim, double tic, double &inter, int &prev, int &next) +{ + double frame = getCurrentFrame(anim, tic); + + prev = int(floor(frame)); + + inter = frame - prev; + + if(frame > anim.lastFrame) + { + if(anim.flags & ANIMOVERRIDE_LOOP) + { + next = anim.loopFrame + (prev - anim.lastFrame); + } + else + { + inter = 0; + prev = next = anim.lastFrame; + } + } + else + { + next = int(ceil(frame)); + } +} + void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor) { // [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. - FSpriteModelFrame * smfNext = nullptr; + const FSpriteModelFrame * smfNext = nullptr; double inter = 0.; - if (gl_interpolate_model_frames && !(smf->flags & MDL_NOINTERPOLATION)) + double inter_main = -1.f; + double inter_next = -1.f; + + bool is_decoupled = (actor->flags9 & MF9_DECOUPLEDANIMATIONS); + + int decoupled_main_prev_frame = -1; + int decoupled_next_prev_frame = -1; + + int decoupled_main_frame = -1; + int decoupled_next_frame = -1; + + // 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 + + if(is_decoupled) + { + smfNext = smf = &BaseSpriteModelFrames[actor->GetClass()]; + if(actor->modelData && !(actor->modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + { + double tic = actor->Level->totaltime; + if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !Level->isFrozen()) + { + tic += I_GetTimeFrac(); + } + if(actor->modelData->curAnim.startTic > tic) + { + inter = (tic - actor->modelData->curAnim.switchTic) / (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchTic); + + calcFrame(actor->modelData->curAnim, actor->modelData->curAnim.startTic, inter_next, decoupled_next_prev_frame, decoupled_next_frame); + calcFrame(actor->modelData->prevAnim, actor->modelData->curAnim.switchTic, inter_main, decoupled_main_prev_frame, decoupled_main_frame); + } + else + { + calcFrame(actor->modelData->curAnim, tic, inter, decoupled_main_frame, decoupled_next_frame); + } + } + } + else if (gl_interpolate_model_frames && !(smf->flags & MDL_NOINTERPOLATION)) { FState *nextState = curState->GetNextState(); if (curState != nextState && nextState) @@ -267,7 +342,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr // [BB] To interpolate at more than 35 fps we take tic fractions into account. float ticFraction = 0.; // [BB] In case the tic counter is frozen we have to leave ticFraction at zero. - if (ConsoleState == c_up && menuactive != MENU_On && !Level->isFrozen()) + if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !Level->isFrozen()) { ticFraction = I_GetTimeFrac(); } @@ -311,7 +386,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr TArray surfaceskinids; - TArray boneData = TArray(); + TArray boneData; int boneStartingPosition = 0; bool evaluatedSingle = false; @@ -321,7 +396,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr int animationid = -1; int modelframe = -1; int modelframenext = -1; - FTextureID skinid; skinid.SetNull(); + FTextureID skinid(nullptr); surfaceskinids.Clear(); @@ -350,30 +425,32 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr { 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]]; - //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) + if (smfNext) { - modelframenext = smfNext->modelframes[actor->modelData->modelFrameGenerators[i]]; - } - else - { - modelframenext = smfNext->modelframes[i]; + 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]; + else if(smf->modelsAmount > i) + { + modelframe = smf->modelframes[i]; + if (smfNext) modelframenext = smfNext->modelframes[i]; + } } //skinID @@ -444,29 +521,47 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr GC::WriteBarrier(actor, ptr); } - if (animationid >= 0) + // [RL0] while per-model animations aren't done, DECOUPLEDANIMATIONS does the same as MODELSAREATTACHMENTS + if ((!(smf->flags & MDL_MODELSAREATTACHMENTS) && !is_decoupled) || !evaluatedSingle) { - FModel* animation = Models[animationid]; - const TArray* animationData = animation->AttachAnimationData(); - - if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false) + if (animationid >= 0) { - boneData = animation->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, animationData, actor->boneComponentData, i); + FModel* animation = Models[animationid]; + const TArray* animationData = animation->AttachAnimationData(); + + if(is_decoupled) + { + if(decoupled_main_frame != -1) + { + boneData = animation->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, animationData, actor->boneComponentData, i); + } + } + else + { + boneData = animation->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, animationData, actor->boneComponentData, i); + } boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1); evaluatedSingle = true; } - } - else - { - if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false) + else { - boneData = mdl->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, nullptr, actor->boneComponentData, i); + if(is_decoupled) + { + if(decoupled_main_frame != -1) + { + boneData = mdl->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, nullptr, actor->boneComponentData, i); + } + } + else + { + boneData = mdl->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, nullptr, actor->boneComponentData, i); + } boneStartingPosition = renderer->SetupFrame(mdl, 0, 0, 0, boneData, -1); evaluatedSingle = true; } } - mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, translation, ssidp, boneData, boneStartingPosition); + mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : -1.f, translation, ssidp, boneData, boneStartingPosition); } } } @@ -849,6 +944,16 @@ static void ParseModelDefLump(int Lump) } } } + else if (sc.Compare("baseframe")) + { + FSpriteModelFrame *smfp = &BaseSpriteModelFrames.Insert(type, smf); + for(int modelID : smf.modelIDs) + { + if(modelID >= 0) + Models[modelID]->baseFrame = smfp; + } + GetDefaultByType(type)->hasmodel = true; + } else if (sc.Compare("frameindex") || sc.Compare("frame")) { bool isframe=!!sc.Compare("frame"); @@ -1008,7 +1113,16 @@ FSpriteModelFrame * FindModelFrameRaw(const PClass * ti, int sprite, int frame, FSpriteModelFrame * FindModelFrame(const AActor * thing, int sprite, int frame, bool dropped) { - return FindModelFrameRaw((thing->modelData != nullptr && thing->modelData->modelDef != NAME_None) ? PClass::FindActor(thing->modelData->modelDef) : thing->GetClass(), sprite, frame, dropped); + if(!thing) return nullptr; + + if(thing->flags9 & MF9_DECOUPLEDANIMATIONS) + { + return &BaseSpriteModelFrames[thing->GetClass()]; + } + else + { + return FindModelFrameRaw((thing->modelData != nullptr && thing->modelData->modelDef != NAME_None) ? PClass::FindActor(thing->modelData->modelDef) : thing->GetClass(), sprite, frame, dropped); + } } //=========================================================================== @@ -1025,8 +1139,7 @@ bool IsHUDModelForPlayerAvailable (player_t * player) // [MK] check that at least one psprite uses models for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext()) { - FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr; - if ( smf != nullptr ) return true; + if ( FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) != nullptr ) return true; } return false; } diff --git a/src/rendering/hwrenderer/scene/hw_weapon.cpp b/src/rendering/hwrenderer/scene/hw_weapon.cpp index 7c16b3729f..207f9fb696 100644 --- a/src/rendering/hwrenderer/scene/hw_weapon.cpp +++ b/src/rendering/hwrenderer/scene/hw_weapon.cpp @@ -707,7 +707,7 @@ void HWDrawInfo::PreparePlayerSprites2D(sector_t * viewsector, area_t in_area) { if (!psp->GetState()) continue; - FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr; + FSpriteModelFrame *smf = FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false); // This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work. if (smf) continue; @@ -792,7 +792,7 @@ void HWDrawInfo::PreparePlayerSprites3D(sector_t * viewsector, area_t in_area) for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext()) { if (!psp->GetState()) continue; - FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr; + FSpriteModelFrame *smf = FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false); // This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work. if (!smf) continue; diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index cedf305b0b..52121569dc 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -351,6 +351,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF9, DOSHADOWBLOCK, AActor, flags9), DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9), DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9), + DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 04a5ecdd47..65cd2a3c59 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -1278,6 +1278,12 @@ 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); + native version("4.12") void SetAnimation(Name animName, double framerate = -1, int startFrame = -1, int loopFrame= -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 interpolateTics = -1, int flags = 0); + + native version("4.12") void SetAnimationFrameRate(double framerate); + native version("4.12") ui void SetAnimationFrameRateUI(double framerate); + int ACS_NamedExecute(name script, int mapnum=0, int arg1=0, int arg2=0, int arg3=0) { return ACS_Execute(-int(script), mapnum, arg1, arg2, arg3); diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index 4488b223f5..e9708d9771 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -368,6 +368,13 @@ enum ERadiusGiveFlags RGF_EITHER = 1 << 17, }; +// SetAnimation flags +enum ESetAnimationFlags +{ + SAF_INSTANT = 1 << 0, + SAF_LOOP = 1 << 1, +}; + // Change model flags enum ChangeModelFlags {