Decoupled IQM Model Animations

This commit is contained in:
Ricardo Luís Vaz Silva 2023-11-20 18:54:49 -03:00 committed by Rachael Alexanderson
parent 1a8dfd2dfa
commit 5d5e7ca042
14 changed files with 515 additions and 75 deletions

View file

@ -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);

View file

@ -47,6 +47,7 @@
TArray<FString> savedModelFiles;
TDeletingArray<FModel*> Models;
TArray<FSpriteModelFrame> SpriteModelFrames;
TMap<void*, FSpriteModelFrame> BaseSpriteModelFrames;
/////////////////////////////////////////////////////////////////////////////

View file

@ -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<FString> savedModelFiles;
extern TDeletingArray<FModel*> Models;
extern TArray<FSpriteModelFrame> SpriteModelFrames;
extern TMap<void*, FSpriteModelFrame> 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<VSMatrix>& 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<TRS>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) { return {}; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* 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];
};

View file

@ -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<VSMatrix>& boneData, int boneStartPosition) override;
void BuildVertexBuffer(FModelRenderer* renderer) override;
void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override;
const TArray<TRS>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* 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<FName, int> NamedAnimations;
TArray<IQMMesh> Meshes;
TArray<IQMTriangle> Triangles;
TArray<IQMAdjacency> Adjacency;

View file

@ -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<VSMatrix>& boneData, int boneStartPosition)
{
renderer->SetupFrame(this, 0, 0, NumVertices, boneData, boneStartPosition);
@ -517,7 +541,26 @@ const TArray<TRS>* IQMModel::AttachAnimationData()
return &TRSData;
}
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* 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<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index)
{
const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData;
if (Joints.Size() > 0)
@ -534,8 +577,13 @@ const TArray<VSMatrix> 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<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double i
TArray<bool> 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])
{

View file

@ -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; }

View file

@ -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<FTextureID> 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<ModelOverride> models;
TArray<FTextureID> skinIDs;
TArray<int> animationIDs;
TArray<int> modelFrameGenerators;
int flags;
AnimOverride curAnim;
AnimOverride prevAnim; // used for interpolation when switching anims
DActorModelData() = default;
virtual void Serialize(FSerializer& arc) override;

View file

@ -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)

View file

@ -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()

View file

@ -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<FTextureID> surfaceskinids;
TArray<VSMatrix> boneData = TArray<VSMatrix>();
TArray<VSMatrix> 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<TRS>* 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<TRS>* 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;
}

View file

@ -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;

View file

@ -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),

View file

@ -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);

View file

@ -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
{