qzdoom/src/r_data/models/models.cpp
Christoph Oelckers 79a0f76801 - replaced TexMan.operator() with two functions.
This was done to make reviewing easier, again because it is virtually impossible to search for the operators in the code.

Going through this revealed quite a few places where texture animations were on but shouldn't and even more places that did not check PASLVERS, although they were preparing some paletted rendering.
2018-12-07 02:53:18 +01:00

943 lines
26 KiB
C++

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
/*
** gl_models.cpp
**
** General model handling code
**
**/
#include "w_wad.h"
#include "cmdlib.h"
#include "sc_man.h"
#include "m_crc32.h"
#include "c_console.h"
#include "g_game.h"
#include "doomstat.h"
#include "g_level.h"
#include "r_state.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "r_utility.h"
#include "r_data/models/models.h"
#include "r_data/models/models_ue1.h"
#include "r_data/models/models_obj.h"
#include "i_time.h"
#ifdef _MSC_VER
#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data
#endif
CVAR(Bool, gl_interpolate_model_frames, true, CVAR_ARCHIVE)
EXTERN_CVAR (Bool, r_drawvoxels)
extern TDeletingArray<FVoxel *> Voxels;
extern TDeletingArray<FVoxelDef *> VoxelDefs;
DeletingModelArray Models;
void FModelRenderer::RenderModel(float x, float y, float z, FSpriteModelFrame *smf, AActor *actor, double ticFrac)
{
// Setup transformation.
int translation = 0;
if (!(smf->flags & MDL_IGNORETRANSLATION))
translation = actor->Translation;
// y scale for a sprite means height, i.e. z in the world!
float scaleFactorX = actor->Scale.X * smf->xscale;
float scaleFactorY = actor->Scale.X * smf->yscale;
float scaleFactorZ = actor->Scale.Y * smf->zscale;
float pitch = 0;
float roll = 0;
double rotateOffset = 0;
DRotator angles;
if (actor->renderflags & RF_INTERPOLATEANGLES) // [Nash] use interpolated angles
angles = actor->InterpolatedAngles(ticFrac);
else
angles = actor->Angles;
float angle = angles.Yaw.Degrees;
// [BB] Workaround for the missing pitch information.
if ((smf->flags & MDL_PITCHFROMMOMENTUM))
{
const double x = actor->Vel.X;
const double y = actor->Vel.Y;
const double z = actor->Vel.Z;
if (actor->Vel.LengthSquared() > EQUAL_EPSILON)
{
// [BB] Calculate the pitch using spherical coordinates.
if (z || x || y) pitch = float(atan(z / sqrt(x*x + y*y)) / M_PI * 180);
// Correcting pitch if model is moving backwards
if (fabs(x) > EQUAL_EPSILON || fabs(y) > EQUAL_EPSILON)
{
if ((x * cos(angle * M_PI / 180) + y * sin(angle * M_PI / 180)) / sqrt(x * x + y * y) < 0) pitch *= -1;
}
else pitch = fabs(pitch);
}
}
if (smf->flags & MDL_ROTATING)
{
if (smf->rotationSpeed > 0.0000000001 || smf->rotationSpeed < -0.0000000001)
{
double turns = (I_GetTime() + I_GetTimeFrac()) / (200.0 / smf->rotationSpeed);
turns -= floor(turns);
rotateOffset = turns * 360.0;
}
else
{
rotateOffset = 0.0;
}
}
// Added MDL_USEACTORPITCH and MDL_USEACTORROLL flags processing.
// If both flags MDL_USEACTORPITCH and MDL_PITCHFROMMOMENTUM are set, the pitch sums up the actor pitch and the velocity vector pitch.
if (smf->flags & MDL_USEACTORPITCH)
{
double d = angles.Pitch.Degrees;
if (smf->flags & MDL_BADROTATION) pitch += d;
else pitch -= d;
}
if (smf->flags & MDL_USEACTORROLL) roll += angles.Roll.Degrees;
VSMatrix objectToWorldMatrix;
objectToWorldMatrix.loadIdentity();
// Model space => World space
objectToWorldMatrix.translate(x, z, y);
// [Nash] take SpriteRotation into account
angle += actor->SpriteRotation.Degrees;
// Applying model transformations:
// 1) Applying actor angle, pitch and roll to the model
if (smf->flags & MDL_USEROTATIONCENTER)
{
objectToWorldMatrix.translate(smf->rotationCenterX, smf->rotationCenterZ, smf->rotationCenterY);
}
objectToWorldMatrix.rotate(-angle, 0, 1, 0);
objectToWorldMatrix.rotate(pitch, 0, 0, 1);
objectToWorldMatrix.rotate(-roll, 1, 0, 0);
if (smf->flags & MDL_USEROTATIONCENTER)
{
objectToWorldMatrix.translate(-smf->rotationCenterX, -smf->rotationCenterZ, -smf->rotationCenterY);
}
// 2) Applying Doomsday like rotation of the weapon pickup models
// The rotation angle is based on the elapsed time.
if (smf->flags & MDL_ROTATING)
{
objectToWorldMatrix.translate(smf->rotationCenterX, smf->rotationCenterY, smf->rotationCenterZ);
objectToWorldMatrix.rotate(rotateOffset, smf->xrotate, smf->yrotate, smf->zrotate);
objectToWorldMatrix.translate(-smf->rotationCenterX, -smf->rotationCenterY, -smf->rotationCenterZ);
}
// 3) Scaling model.
objectToWorldMatrix.scale(scaleFactorX, scaleFactorZ, scaleFactorY);
// 4) Aplying model offsets (model offsets do not depend on model scalings).
objectToWorldMatrix.translate(smf->xoffset / smf->xscale, smf->zoffset / smf->zscale, smf->yoffset / smf->yscale);
// 5) Applying model rotations.
objectToWorldMatrix.rotate(-smf->angleoffset, 0, 1, 0);
objectToWorldMatrix.rotate(smf->pitchoffset, 0, 0, 1);
objectToWorldMatrix.rotate(-smf->rolloffset, 1, 0, 0);
// consider the pixel stretching. For non-voxels this must be factored out here
float stretch = (smf->modelIDs[0] != -1 ? Models[smf->modelIDs[0]]->getAspectFactor() : 1.f) / level.info->pixelstretch;
objectToWorldMatrix.scale(1, stretch, 1);
float orientation = scaleFactorX * scaleFactorY * scaleFactorZ;
BeginDrawModel(actor, smf, objectToWorldMatrix, orientation < 0);
RenderFrameModels(smf, actor->state, actor->tics, actor->GetClass(), translation);
EndDrawModel(actor, smf);
}
void FModelRenderer::RenderHUDModel(DPSprite *psp, float ofsX, float ofsY)
{
AActor * playermo = players[consoleplayer].camera;
FSpriteModelFrame *smf = FindModelFrame(playermo->player->ReadyWeapon->GetClass(), psp->GetState()->sprite, psp->GetState()->GetFrame(), false);
// [BB] No model found for this sprite, so we can't render anything.
if (smf == nullptr)
return;
// The model position and orientation has to be drawn independently from the position of the player,
// but we need to position it correctly in the world for light to work properly.
VSMatrix objectToWorldMatrix = GetViewToWorldMatrix();
// Scaling model (y scale for a sprite means height, i.e. z in the world!).
objectToWorldMatrix.scale(smf->xscale, smf->zscale, smf->yscale);
// Aplying model offsets (model offsets do not depend on model scalings).
objectToWorldMatrix.translate(smf->xoffset / smf->xscale, smf->zoffset / smf->zscale, smf->yoffset / smf->yscale);
// [BB] Weapon bob, very similar to the normal Doom weapon bob.
objectToWorldMatrix.rotate(ofsX / 4, 0, 1, 0);
objectToWorldMatrix.rotate((ofsY - WEAPONTOP) / -4., 1, 0, 0);
// [BB] For some reason the jDoom models need to be rotated.
objectToWorldMatrix.rotate(90.f, 0, 1, 0);
// Applying angleoffset, pitchoffset, rolloffset.
objectToWorldMatrix.rotate(-smf->angleoffset, 0, 1, 0);
objectToWorldMatrix.rotate(smf->pitchoffset, 0, 0, 1);
objectToWorldMatrix.rotate(-smf->rolloffset, 1, 0, 0);
float orientation = smf->xscale * smf->yscale * smf->zscale;
BeginDrawHUDModel(playermo, objectToWorldMatrix, orientation < 0);
RenderFrameModels(smf, psp->GetState(), psp->GetTics(), playermo->player->ReadyWeapon->GetClass(), 0);
EndDrawHUDModel(playermo);
}
void FModelRenderer::RenderFrameModels(const FSpriteModelFrame *smf, const FState *curState, const int curTics, const PClass *ti, int translation)
{
// [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;
double inter = 0.;
if (gl_interpolate_model_frames && !(smf->flags & MDL_NOINTERPOLATION))
{
FState *nextState = curState->GetNextState();
if (curState != nextState && nextState)
{
// [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.flags2 & LEVEL2_FROZEN))
{
ticFraction = I_GetTimeFrac();
}
inter = static_cast<double>(curState->Tics - curTics + ticFraction) / static_cast<double>(curState->Tics);
// [BB] For some actors (e.g. ZPoisonShroom) spr->actor->tics can be bigger than curState->Tics.
// In this case inter is negative and we need to set it to zero.
if (curState->Tics < curTics)
inter = 0.;
else
{
// [BB] Workaround for actors that use the same frame twice in a row.
// Most of the standard Doom monsters do this in their see state.
if ((smf->flags & MDL_INTERPOLATEDOUBLEDFRAMES))
{
const FState *prevState = curState - 1;
if ((curState->sprite == prevState->sprite) && (curState->Frame == prevState->Frame))
{
inter /= 2.;
inter += 0.5;
}
if ((curState->sprite == nextState->sprite) && (curState->Frame == nextState->Frame))
{
inter /= 2.;
nextState = nextState->GetNextState();
}
}
if (inter != 0.0)
smfNext = FindModelFrame(ti, nextState->sprite, nextState->Frame, false);
}
}
}
for (int i = 0; i<MAX_MODELS_PER_FRAME; i++)
{
if (smf->modelIDs[i] != -1)
{
FModel * mdl = Models[smf->modelIDs[i]];
FTexture *tex = smf->skinIDs[i].isValid() ? TexMan.GetTexture(smf->skinIDs[i], true) : nullptr;
mdl->BuildVertexBuffer(this);
mdl->PushSpriteMDLFrame(smf, i);
if (smfNext && smf->modelframes[i] != smfNext->modelframes[i])
mdl->RenderFrame(this, tex, smf->modelframes[i], smfNext->modelframes[i], inter, translation);
else
mdl->RenderFrame(this, tex, smf->modelframes[i], smf->modelframes[i], 0.f, translation);
}
}
}
/////////////////////////////////////////////////////////////////////////////
void FlushModels()
{
for (int i = Models.Size() - 1; i >= 0; i--)
{
Models[i]->DestroyVertexBuffer();
}
}
/////////////////////////////////////////////////////////////////////////////
FModel::FModel()
{
for (int i = 0; i < NumModelRendererTypes; i++)
mVBuf[i] = nullptr;
}
FModel::~FModel()
{
DestroyVertexBuffer();
}
void FModel::DestroyVertexBuffer()
{
for (int i = 0; i < NumModelRendererTypes; i++)
{
delete mVBuf[i];
mVBuf[i] = nullptr;
}
}
static TArray<FSpriteModelFrame> SpriteModelFrames;
static int * SpriteModelHash;
//TArray<FStateModelFrame> StateModelFrames;
static void DeleteModelHash()
{
if (SpriteModelHash != nullptr) delete [] SpriteModelHash;
SpriteModelHash = nullptr;
}
//===========================================================================
//
// FindGFXFile
//
//===========================================================================
static int FindGFXFile(FString & fn)
{
int lump = Wads.CheckNumForFullName(fn); // if we find something that matches the name plus the extension, return it and do not enter the substitution logic below.
if (lump != -1) return lump;
int best = -1;
int dot = fn.LastIndexOf('.');
int slash = fn.LastIndexOf('/');
if (dot > slash) fn.Truncate(dot);
static const char * extensions[] = { ".png", ".jpg", ".tga", ".pcx", nullptr };
for (const char ** extp=extensions; *extp; extp++)
{
int lump = Wads.CheckNumForFullName(fn + *extp);
if (lump >= best) best = lump;
}
return best;
}
//===========================================================================
//
// LoadSkin
//
//===========================================================================
FTextureID LoadSkin(const char * path, const char * fn)
{
FString buffer;
buffer.Format("%s%s", path, fn);
int texlump = FindGFXFile(buffer);
const char * const texname = texlump < 0 ? fn : Wads.GetLumpFullName(texlump);
return TexMan.CheckForTexture(texname, ETextureType::Any, FTextureManager::TEXMAN_TryAny);
}
//===========================================================================
//
// ModelFrameHash
//
//===========================================================================
static int ModelFrameHash(FSpriteModelFrame * smf)
{
const uint32_t *table = GetCRCTable ();
uint32_t hash = 0xffffffff;
const char * s = (const char *)(&smf->type); // this uses type, sprite and frame for hashing
const char * se= (const char *)(&smf->hashnext);
for (; s<se; s++)
{
hash = CRC1 (hash, *s, table);
}
return hash ^ 0xffffffff;
}
//===========================================================================
//
// FindModel
//
//===========================================================================
static unsigned FindModel(const char * path, const char * modelfile)
{
FModel * model = nullptr;
FString fullname;
fullname.Format("%s%s", path, modelfile);
int lump = Wads.CheckNumForFullName(fullname);
if (lump<0)
{
Printf("FindModel: '%s' not found\n", fullname.GetChars());
return -1;
}
for(unsigned i = 0; i< Models.Size(); i++)
{
if (!Models[i]->mFileName.CompareNoCase(fullname)) return i;
}
int len = Wads.LumpLength(lump);
FMemLump lumpd = Wads.ReadLump(lump);
char * buffer = (char*)lumpd.GetMem();
if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 )
{
FString anivfile = fullname.GetChars();
anivfile.Substitute("_d.3d","_a.3d");
if ( Wads.CheckNumForFullName(anivfile) > 0 )
{
model = new FUE1Model;
}
}
else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 )
{
FString datafile = fullname.GetChars();
datafile.Substitute("_a.3d","_d.3d");
if ( Wads.CheckNumForFullName(datafile) > 0 )
{
model = new FUE1Model;
}
}
else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 )
{
model = new FOBJModel;
}
else if (!memcmp(buffer, "DMDM", 4))
{
model = new FDMDModel;
}
else if (!memcmp(buffer, "IDP2", 4))
{
model = new FMD2Model;
}
else if (!memcmp(buffer, "IDP3", 4))
{
model = new FMD3Model;
}
if (model != nullptr)
{
if (!model->Load(path, lump, buffer, len))
{
delete model;
return -1;
}
}
else
{
// try loading as a voxel
FVoxel *voxel = R_LoadKVX(lump);
if (voxel != nullptr)
{
model = new FVoxelModel(voxel, true);
}
else
{
Printf("LoadModel: Unknown model format in '%s'\n", fullname.GetChars());
return -1;
}
}
// The vertex buffer cannot be initialized here because this gets called before OpenGL is initialized
model->mFileName = fullname;
return Models.Push(model);
}
//===========================================================================
//
// InitModels
//
//===========================================================================
static void ParseModelDefLump(int Lump);
void InitModels()
{
for (unsigned i = 0; i < Models.Size(); i++)
{
delete Models[i];
}
Models.Clear();
SpriteModelFrames.Clear();
DeleteModelHash();
// First, create models for each voxel
for (unsigned i = 0; i < Voxels.Size(); i++)
{
FVoxelModel *md = new FVoxelModel(Voxels[i], false);
Voxels[i]->VoxelIndex = Models.Push(md);
}
// now create GL model frames for the voxeldefs
for (unsigned i = 0; i < VoxelDefs.Size(); i++)
{
FVoxelModel *md = (FVoxelModel*)Models[VoxelDefs[i]->Voxel->VoxelIndex];
FSpriteModelFrame smf;
memset(&smf, 0, sizeof(smf));
smf.isVoxel = true;
smf.modelIDs[1] = smf.modelIDs[2] = smf.modelIDs[3] = -1;
smf.modelIDs[0] = VoxelDefs[i]->Voxel->VoxelIndex;
smf.skinIDs[0] = md->GetPaletteTexture();
smf.xscale = smf.yscale = smf.zscale = VoxelDefs[i]->Scale;
smf.angleoffset = VoxelDefs[i]->AngleOffset.Degrees;
if (VoxelDefs[i]->PlacedSpin != 0)
{
smf.yrotate = 1.f;
smf.rotationSpeed = VoxelDefs[i]->PlacedSpin / 55.55f;
smf.flags |= MDL_ROTATING;
}
VoxelDefs[i]->VoxeldefIndex = SpriteModelFrames.Push(smf);
if (VoxelDefs[i]->PlacedSpin != VoxelDefs[i]->DroppedSpin)
{
if (VoxelDefs[i]->DroppedSpin != 0)
{
smf.yrotate = 1.f;
smf.rotationSpeed = VoxelDefs[i]->DroppedSpin / 55.55f;
smf.flags |= MDL_ROTATING;
}
else
{
smf.yrotate = 0;
smf.rotationSpeed = 0;
smf.flags &= ~MDL_ROTATING;
}
SpriteModelFrames.Push(smf);
}
}
int Lump;
int lastLump = 0;
while ((Lump = Wads.FindLump("MODELDEF", &lastLump)) != -1)
{
ParseModelDefLump(Lump);
}
// create a hash table for quick access
SpriteModelHash = new int[SpriteModelFrames.Size ()];
atterm(DeleteModelHash);
memset(SpriteModelHash, 0xff, SpriteModelFrames.Size () * sizeof(int));
for (unsigned int i = 0; i < SpriteModelFrames.Size (); i++)
{
int j = ModelFrameHash(&SpriteModelFrames[i]) % SpriteModelFrames.Size ();
SpriteModelFrames[i].hashnext = SpriteModelHash[j];
SpriteModelHash[j]=i;
}
}
static void ParseModelDefLump(int Lump)
{
FScanner sc(Lump);
while (sc.GetString())
{
if (sc.Compare("model"))
{
int index, surface;
FString path = "";
sc.MustGetString();
FSpriteModelFrame smf;
memset(&smf, 0, sizeof(smf));
smf.modelIDs[0] = smf.modelIDs[1] = smf.modelIDs[2] = smf.modelIDs[3] = -1;
smf.xscale=smf.yscale=smf.zscale=1.f;
smf.type = PClass::FindClass(sc.String);
if (!smf.type || smf.type->Defaults == nullptr)
{
sc.ScriptError("MODELDEF: Unknown actor type '%s'\n", sc.String);
}
sc.MustGetStringName("{");
while (!sc.CheckString("}"))
{
sc.MustGetString();
if (sc.Compare("path"))
{
sc.MustGetString();
FixPathSeperator(sc.String);
path = sc.String;
if (path[(int)path.Len()-1]!='/') path+='/';
}
else if (sc.Compare("model"))
{
sc.MustGetNumber();
index = sc.Number;
if (index < 0 || index >= MAX_MODELS_PER_FRAME)
{
sc.ScriptError("Too many models in %s", smf.type->TypeName.GetChars());
}
sc.MustGetString();
FixPathSeperator(sc.String);
smf.modelIDs[index] = FindModel(path.GetChars(), sc.String);
if (smf.modelIDs[index] == -1)
{
Printf("%s: model not found in %s\n", sc.String, path.GetChars());
}
}
else if (sc.Compare("scale"))
{
sc.MustGetFloat();
smf.xscale = sc.Float;
sc.MustGetFloat();
smf.yscale = sc.Float;
sc.MustGetFloat();
smf.zscale = sc.Float;
}
// [BB] Added zoffset reading.
// Now it must be considered deprecated.
else if (sc.Compare("zoffset"))
{
sc.MustGetFloat();
smf.zoffset=sc.Float;
}
// Offset reading.
else if (sc.Compare("offset"))
{
sc.MustGetFloat();
smf.xoffset = sc.Float;
sc.MustGetFloat();
smf.yoffset = sc.Float;
sc.MustGetFloat();
smf.zoffset = sc.Float;
}
// angleoffset, pitchoffset and rolloffset reading.
else if (sc.Compare("angleoffset"))
{
sc.MustGetFloat();
smf.angleoffset = sc.Float;
}
else if (sc.Compare("pitchoffset"))
{
sc.MustGetFloat();
smf.pitchoffset = sc.Float;
}
else if (sc.Compare("rolloffset"))
{
sc.MustGetFloat();
smf.rolloffset = sc.Float;
}
// [BB] Added model flags reading.
else if (sc.Compare("ignoretranslation"))
{
smf.flags |= MDL_IGNORETRANSLATION;
}
else if (sc.Compare("pitchfrommomentum"))
{
smf.flags |= MDL_PITCHFROMMOMENTUM;
}
else if (sc.Compare("inheritactorpitch"))
{
smf.flags |= MDL_USEACTORPITCH | MDL_BADROTATION;
}
else if (sc.Compare("inheritactorroll"))
{
smf.flags |= MDL_USEACTORROLL;
}
else if (sc.Compare("useactorpitch"))
{
smf.flags |= MDL_USEACTORPITCH;
}
else if (sc.Compare("useactorroll"))
{
smf.flags |= MDL_USEACTORROLL;
}
else if (sc.Compare("rotating"))
{
smf.flags |= MDL_ROTATING;
smf.xrotate = 0.;
smf.yrotate = 1.;
smf.zrotate = 0.;
smf.rotationCenterX = 0.;
smf.rotationCenterY = 0.;
smf.rotationCenterZ = 0.;
smf.rotationSpeed = 1.;
}
else if (sc.Compare("rotation-speed"))
{
sc.MustGetFloat();
smf.rotationSpeed = sc.Float;
}
else if (sc.Compare("rotation-vector"))
{
sc.MustGetFloat();
smf.xrotate = sc.Float;
sc.MustGetFloat();
smf.yrotate = sc.Float;
sc.MustGetFloat();
smf.zrotate = sc.Float;
}
else if (sc.Compare("rotation-center"))
{
sc.MustGetFloat();
smf.rotationCenterX = sc.Float;
sc.MustGetFloat();
smf.rotationCenterY = sc.Float;
sc.MustGetFloat();
smf.rotationCenterZ = sc.Float;
}
else if (sc.Compare("interpolatedoubledframes"))
{
smf.flags |= MDL_INTERPOLATEDOUBLEDFRAMES;
}
else if (sc.Compare("nointerpolation"))
{
smf.flags |= MDL_NOINTERPOLATION;
}
else if (sc.Compare("skin"))
{
sc.MustGetNumber();
index=sc.Number;
if (index<0 || index>=MAX_MODELS_PER_FRAME)
{
sc.ScriptError("Too many models in %s", smf.type->TypeName.GetChars());
}
sc.MustGetString();
FixPathSeperator(sc.String);
if (sc.Compare(""))
{
smf.skinIDs[index]=FNullTextureID();
}
else
{
smf.skinIDs[index] = LoadSkin(path.GetChars(), sc.String);
if (!smf.skinIDs[index].isValid())
{
Printf("Skin '%s' not found in '%s'\n",
sc.String, smf.type->TypeName.GetChars());
}
}
}
else if (sc.Compare("surfaceskin"))
{
sc.MustGetNumber();
index = sc.Number;
sc.MustGetNumber();
surface = sc.Number;
if (index<0 || index >= MAX_MODELS_PER_FRAME)
{
sc.ScriptError("Too many models in %s", smf.type->TypeName.GetChars());
}
if (surface<0 || surface >= MD3_MAX_SURFACES)
{
sc.ScriptError("Invalid MD3 Surface %d in %s", MD3_MAX_SURFACES, smf.type->TypeName.GetChars());
}
sc.MustGetString();
FixPathSeperator(sc.String);
if (sc.Compare(""))
{
smf.surfaceskinIDs[index][surface] = FNullTextureID();
}
else
{
smf.surfaceskinIDs[index][surface] = LoadSkin(path.GetChars(), sc.String);
if (!smf.surfaceskinIDs[index][surface].isValid())
{
Printf("Surface Skin '%s' not found in '%s'\n",
sc.String, smf.type->TypeName.GetChars());
}
}
}
else if (sc.Compare("frameindex") || sc.Compare("frame"))
{
bool isframe=!!sc.Compare("frame");
sc.MustGetString();
smf.sprite = -1;
for (int i = 0; i < (int)sprites.Size (); ++i)
{
if (strnicmp (sprites[i].name, sc.String, 4) == 0)
{
if (sprites[i].numframes==0)
{
//sc.ScriptError("Sprite %s has no frames", sc.String);
}
smf.sprite = i;
break;
}
}
if (smf.sprite==-1)
{
sc.ScriptError("Unknown sprite %s in model definition for %s", sc.String, smf.type->TypeName.GetChars());
}
sc.MustGetString();
FString framechars = sc.String;
sc.MustGetNumber();
index=sc.Number;
if (index<0 || index>=MAX_MODELS_PER_FRAME)
{
sc.ScriptError("Too many models in %s", smf.type->TypeName.GetChars());
}
if (isframe)
{
sc.MustGetString();
if (smf.modelIDs[index] != -1)
{
FModel *model = Models[smf.modelIDs[index]];
smf.modelframes[index] = model->FindFrame(sc.String);
if (smf.modelframes[index]==-1) sc.ScriptError("Unknown frame '%s' in %s", sc.String, smf.type->TypeName.GetChars());
}
else smf.modelframes[index] = -1;
}
else
{
sc.MustGetNumber();
smf.modelframes[index] = sc.Number;
}
for(int i=0; framechars[i]>0; i++)
{
char map[29]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int c = toupper(framechars[i])-'A';
if (c<0 || c>=29)
{
sc.ScriptError("Invalid frame character %c found", c+'A');
}
if (map[c]) continue;
smf.frame=c;
SpriteModelFrames.Push(smf);
GetDefaultByType(smf.type)->hasmodel = true;
map[c]=1;
}
}
else if (sc.Compare("dontcullbackfaces"))
{
smf.flags |= MDL_DONTCULLBACKFACES;
}
else if (sc.Compare("userotationcenter"))
{
smf.flags |= MDL_USEROTATIONCENTER;
smf.rotationCenterX = 0.;
smf.rotationCenterY = 0.;
smf.rotationCenterZ = 0.;
}
else
{
sc.ScriptMessage("Unrecognized string \"%s\"", sc.String);
}
}
}
else if (sc.Compare("#include"))
{
sc.MustGetString();
// This is not using sc.Open because it can print a more useful error message when done here
int includelump = Wads.CheckNumForFullName(sc.String, true);
if (includelump == -1)
{
if (strcmp(sc.String, "sentinel.modl") != 0) // Gene Tech mod has a broken #include statement
sc.ScriptError("Lump '%s' not found", sc.String);
}
else
{
ParseModelDefLump(includelump);
}
}
}
}
//===========================================================================
//
// FindModelFrame
//
//===========================================================================
FSpriteModelFrame * FindModelFrame(const PClass * ti, int sprite, int frame, bool dropped)
{
if (GetDefaultByType(ti)->hasmodel)
{
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;
}
}
// Check for voxel replacements
if (r_drawvoxels)
{
spritedef_t *sprdef = &sprites[sprite];
if (frame < sprdef->numframes)
{
spriteframe_t *sprframe = &SpriteFrames[sprdef->spriteframes + frame];
if (sprframe->Voxel != nullptr)
{
int index = sprframe->Voxel->VoxeldefIndex;
if (dropped && sprframe->Voxel->DroppedSpin !=sprframe->Voxel->PlacedSpin) index++;
return &SpriteModelFrames[index];
}
}
}
return nullptr;
}
//===========================================================================
//
// IsHUDModelForPlayerAvailable
//
//===========================================================================
bool IsHUDModelForPlayerAvailable (player_t * player)
{
if (player == nullptr || player->ReadyWeapon == nullptr)
return false;
DPSprite *psp = player->FindPSprite(PSP_WEAPON);
if (psp == nullptr || psp->GetState() == nullptr)
return false;
FState* state = psp->GetState();
FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false);
return ( smf != nullptr );
}