Files
Jedi-Academy/tools/ModView/model.cpp
2013-04-04 14:32:05 -07:00

5589 lines
153 KiB
C++

// Filename:- model.cpp
//
// non-format specific model routines entry point, calls format-specific code from within here
//
// ( This is the nice clean gateway module into all the evil crap I have to call from other codebases )
//
#include "stdafx.h"
#include "includes.h"
#include "ModViewTreeView.h"
#include "glm_code.h"
#include "R_Model.h"
#include "R_Surface.h"
#include "textures.h"
#include "TEXT.H"
#include "sequence.h"
#include "script.h"
#include "shader.h"
#include "skins.h"
//
#include "model.h"
static int Model_MultiSeq_GetSeqHint(ModelContainer_t *pContainer, bool bPrimary);
static void Model_MultiSeq_SetSeqHint(ModelContainer_t *pContainer, bool bPrimary, int iHint);
static bool Model_MultiSeq_EnsureSeqHintLegal(ModelContainer_t *pContainer, int iFrame, bool bPrimary);
#define sERROR_MODEL_NOT_LOADED "Error: Model not loaded, you shouldn't get here! -Ste"
#define sERROR_CONTAINER_NOT_FOUND "Error: Could not resolve model handle to container ptr, you shouldn't get here! -Ste"
#define sSECONDARY_ANIM_STATS_STRING "(Secondary anim)" // try and keep this fairly short, since it occupies roughly the same space as "bolt: <boltname>"
#define POINT_SCALE 64.0f
#define POINT_ST_SCALE 16384.0f
ModViewAppVars_t AppVars;
bool gbRenderInhibit = false; // MUST stay in this state except when loading a model
// some protos...
//
static void ModelDraw_InfoText_Totals(void);
static void ModelDraw_InfoText_Header(void);
static void R_ModelContainer_CallBack_InfoText(ModelContainer_t *pContainer, void *pvData);
typedef struct // simple struct for passing text data to callback functions during info printing (ZEROMEM'd)
{
int iTextY;
int iTextX;
int iPrevX;
int iTextXForVertStats;
char sString[1024];
int iTot_RenderedVerts;
int iTot_RenderedTris;
int iTot_RenderedSurfs;
int iTot_XformedG2Bones;
int iTot_RenderedBoneWeights;
int iTot_OmittedBoneWeights;
// auto-measure stuff, finding longest string for neater padding...
//
int iFrameDigitsNeeded;
int iAttachedViaCharsNeeded;
int iSequenceNameCharsNeeded;
int iModelNameCharsNeeded;
int iModelVertInfoCharsNeeded;
//
int iMostMultiLockedSequences;
bool bAnyMultiLockedSecondarySequences;
int iMultiLockedTextX;
} TextData_t;
TextData_t TextData;
double getDoubleTime (void)
{
return (double)clock() / (double)CLOCKS_PER_SEC;
}
// returns NULL if not attached to anything, else name of tag-surface or boltpoint
//
static LPCSTR Stats_GetParentAttachmentPointString(ModelContainer_t *pContainer)
{
LPCSTR psAttachedVia = (!pContainer->pBoneBolt_ParentContainer)?
((!pContainer->pSurfaceBolt_ParentContainer)?
NULL:
pContainer->pSurfaceBolt_ParentContainer->tSurfaceBolt_BoltPoints[pContainer->iSurfaceBolt_ParentBoltIndex].sAttachName.c_str())
:
pContainer->pBoneBolt_ParentContainer->tBoneBolt_BoltPoints[pContainer->iBoneBolt_ParentBoltIndex].sAttachName.c_str();
return psAttachedVia;
}
// returned string will be valid for printing, even if only blank...
//
static LPCSTR Stats_GetAttachmentString(ModelContainer_t *pContainer)
{
LPCSTR psAttachedVia = Stats_GetParentAttachmentPointString(pContainer);
LPCSTR psAttachmentString = va("%s", psAttachedVia?va("(bolt: \"%s\")",psAttachedVia):"");
return psAttachmentString;
}
static void R_ModelContainer_Apply_Actual(ModelContainer_t* pContainer, void (*pFunction) ( ModelContainer_t* pContainer, void *pvData), void *pvData, bool bFromBottomUp )
{
if (!bFromBottomUp )
{
// process this...
//
pFunction(pContainer, pvData);
}
// process this container's bone bolts...
//
for (int iBoltPoint=0; iBoltPoint < pContainer->iBoneBolt_MaxBoltPoints; iBoltPoint++)
{
BoltPoint_t *pBoltPoint = &pContainer->tBoneBolt_BoltPoints[ iBoltPoint ];
for (int iBoltOn = 0; iBoltOn < pBoltPoint->vBoltedContainers.size(); iBoltOn++)
{
R_ModelContainer_Apply(&pBoltPoint->vBoltedContainers[ iBoltOn ], pFunction, pvData);
}
}
// process this container's surface bolts...
//
for (iBoltPoint=0; iBoltPoint<pContainer->iSurfaceBolt_MaxBoltPoints; iBoltPoint++)
{
BoltPoint_t *pBoltPoint = &pContainer->tSurfaceBolt_BoltPoints[ iBoltPoint ];
for (int iBoltOn = 0; iBoltOn < pBoltPoint->vBoltedContainers.size(); iBoltOn++)
{
R_ModelContainer_Apply(&pBoltPoint->vBoltedContainers[ iBoltOn ], pFunction, pvData);
}
}
if (bFromBottomUp )
{
// process this... (which has no children by now)
//
pFunction(pContainer, pvData);
}
}
void R_ModelContainer_Apply(ModelContainer_t* pContainer, void (*pFunction) ( ModelContainer_t* pContainer, void *pvData), void *pvData)
{
R_ModelContainer_Apply_Actual(pContainer, pFunction, pvData, false);
}
// same as above, but calls from bottom of recursion tree to top, so can destroy ptrs to lower elements during freeup...
//
static void R_ModelContainer_ApplyFromBottomUp(ModelContainer_t* pContainer, void (*pFunction) ( ModelContainer_t* pContainer, void *pvData), void *pvData = NULL);
static void R_ModelContainer_ApplyFromBottomUp(ModelContainer_t* pContainer, void (*pFunction) ( ModelContainer_t* pContainer, void *pvData), void *pvData )
{
R_ModelContainer_Apply_Actual(pContainer, pFunction, pvData, true);
}
// set the supplied container to be empty... (note that because of stl, 99% of this works for either init or dealloc)
//
static void ModelContainer_Clear(ModelContainer_t* pContainer, void *pvData = NULL);// last field provided for R_ModelContainer_Apply() only
static void ModelContainer_Clear(ModelContainer_t* pContainer, void *pvData)
{
pContainer->hModel = 0;
pContainer->eModType = MOD_BAD;
ZEROMEM(pContainer->sLocalPathName);
ZEROMEM(pContainer->slist);
ZEROMEM(pContainer->blist);
pContainer->iBoneNum_SecondaryStart = -1; // default, meaning "ignore", else bone num to stop primary animation on, and begin secondary
pContainer->iSurfaceNum_RootOverride = -1;
pContainer->iCurrentFrame_Primary = 0;
pContainer->iOldFrame_Primary = 0;
pContainer->iCurrentFrame_Secondary = 0;
pContainer->iOldFrame_Secondary = 0;
pContainer->iSequenceLockNumber_Primary = -1;
pContainer->iSequenceLockNumber_Secondary = -1;
pContainer->iNumFrames = 0;
pContainer->iNumLODs = 0;
pContainer->iNumBones = 0;
pContainer->iNumSurfaces = 0;
// stats only...
pContainer->iRenderedTris = 0;
pContainer->iRenderedVerts = 0;
pContainer->iRenderedSurfs = 0;
pContainer->iXformedG2Bones = 0;
pContainer->iRenderedBoneWeights = 0;
pContainer->iOmittedBoneWeights = 0;
pContainer->SequenceList.clear();
pContainer->bSeqMultiLock_Primary_Active = false;
pContainer->SeqMultiLock_Primary.clear();
pContainer->bSeqMultiLock_Secondary_Active = false;
pContainer->SeqMultiLock_Secondary.clear();
pContainer->iSeqMultiLock_Primary_SeqHint =0; // not really important what number is picked
pContainer->iSeqMultiLock_Secondary_SeqHint =0; // ""
pContainer->SkinSets.clear();
pContainer->SkinSetsSurfacePrefs.clear();
pContainer->OldSkinSets.clear();
pContainer->strCurrentSkinFile = "";
pContainer->strCurrentSkinEthnic= "";
pContainer->MaterialBinds.clear();
pContainer->MaterialShaders.clear();
pContainer->SurfaceEdgeInfoPerLOD.clear();
pContainer->iBoneHighlightNumber = iITEMHIGHLIGHT_NONE;
pContainer->iSurfaceHighlightNumber = iITEMHIGHLIGHT_NONE;
// pContainer->iRenderedBoneWeightsThisSurface = 0;
pContainer->Aliases.clear();
pContainer->pModelInfoFunction = NULL;
pContainer->pModelGetBoneNameFunction = NULL;
pContainer->pModelGetBoneBoltNameFunction = NULL;
pContainer->pModelGetSurfaceNameFunction = NULL;
pContainer->pModelGetSurfaceBoltNameFunction= NULL;
// some freaky stuff...
//
ZEROMEM(pContainer->XFormedG2Bones);
ZEROMEM(pContainer->XFormedG2BonesValid);
ZEROMEM(pContainer->XFormedG2TagSurfs);
ZEROMEM(pContainer->XFormedG2TagSurfsValid);
// special linkage stuff (this can all safely be cleared since during freeup we're called recursively backwards)...
//
if (pContainer->tBoneBolt_BoltPoints.size() || pContainer->tSurfaceBolt_BoltPoints.size())
{
AppVars.iTotalContainers--; // we're calling this to free stuff, not to init it
}
//
// now do specific bolt-free code...
//
{// bone bolts...
pContainer->pBoneBolt_ParentContainer = NULL;
pContainer->iBoneBolt_ParentBoltIndex = -1; // .. if we're the root model, seems a reasonable default (as is NULL on line above)
pContainer->iBoneBolt_MaxBoltPoints = -1; // similar to modtype_bad, should never exist when init code called after this
pContainer->tBoneBolt_BoltPoints.clear();
}
{// surface bolts...
pContainer->pSurfaceBolt_ParentContainer = NULL;
pContainer->iSurfaceBolt_ParentBoltIndex = -1; // .. if we're the root model, seems a reasonable default (as is NULL on line above)
pContainer->iSurfaceBolt_MaxBoltPoints = -1; // similar to modtype_bad, should never exist when init code called after this
pContainer->tSurfaceBolt_BoltPoints.clear();
}
pContainer->hTreeItem_ModelName = NULL;
pContainer->hTreeItem_BoltOns = NULL;
}
// this is stuff that gets cleared during both AppVars_OnceOnlyInit() and Model_Delete()...
//
static void AppVars_Delete(void)
{
// int iNumBolts = 0;
R_ModelContainer_ApplyFromBottomUp(&AppVars.Container, ModelContainer_Clear);//, &iNumBolts);
AppVars.iSurfaceNumToHighlight = iITEMHIGHLIGHT_NONE;
AppVars.hModelToHighLight = 0;
// do this stuff so new models don't load and immediately animate...
//
AppVars.bAnimate = false;
AppVars.bForceWrapWhenAnimating = false;
AppVars.iLOD = 0;
AppVars.iTotalContainers = 0;
AppVars.strLoadedModelPath = "";
AppVars.hModelLastLoaded = NULL;
}
void AppVars_ResetViewParams(void)
{
#if 0
// FOV 90 params...
//
AppVars.xPos = 0.0f;
AppVars.yPos = 0.0f;
AppVars.zPos = -2.0f;
AppVars.rotAngleX = 0.0f;
AppVars.rotAngleY = 0.0f;
AppVars.rotAngleZ = -90.0f;
AppVars.dFOV = 90.0f;
#else
// FOV 10 params... (and slightly rotated to pleasing angle)
//
AppVars.xPos = 0.0f;
AppVars.yPos = 0.0f;
AppVars.zPos = -30.0f;
AppVars.rotAngleX = 15.5f;
AppVars.rotAngleY = 44.0f;
AppVars.rotAngleZ = -90.0f;
AppVars.dFOV = 10.0f;
#endif
AppVars.xPos_SCROLL = 0.0f;
AppVars.yPos_SCROLL = 0.0f;
AppVars.zPos_SCROLL = 0.0f;
AppVars.rotAngleX_SCROLL = 0.0f;
AppVars.rotAngleY_SCROLL = 0.0f;
AppVars.rotAngleZ_SCROLL = 0.0f;
}
void AppVars_OnceOnlyInit(void)
{
AppVars.bFinished = false;
AppVars.bBilinear = true;
AppVars.bInterpolate = true;
AppVars.bUseAlpha = false;
AppVars.bWireFrame = false;
AppVars.bOriginLines = false;
AppVars.bBBox = false;
AppVars.bFloor = false;
AppVars.fFloorZ = -50;
AppVars.bRuler = false;
AppVars.bBoneHighlight = true;
AppVars.bBoneWeightThreshholdingActive = false;
AppVars.fBoneWeightThreshholdPercent = 5.0f; //
AppVars.bSurfaceHighlight = true;
AppVars.bSurfaceHighlightShowsBoneWeighting = false;
AppVars.bTriIndexes = false;
AppVars.bVertIndexes = false;
AppVars.bVertWeighting = false;
AppVars.bAtleast1VertWeightDisplayed = false;
AppVars.bVertexNormals = false;
AppVars.bShowOriginsAsRGB = true;
AppVars.bForceWhite = false;
AppVars.bCleanScreenShots = true;
AppVars.bFullPathsInSequenceTreeitems = false;
AppVars.bCrackHighlight = false;
AppVars.bShowUnshadowableSurfaces = false;
AppVars.bAllowGLAOverrides = false;
AppVars.bShowPolysAsDoubleSided = true;
// crap...
//
AppVars.iSurfaceNumToHighlight = iITEMHIGHLIGHT_NONE;
AppVars.hModelToHighLight = NULL;
AppVars.hModelLastLoaded = NULL;
AppVars.bAlwaysOnTop = false;
AppVars.bSortSequencesByAlpha = false;
AppVars.iLOD = 0;
AppVars_ResetViewParams();
AppVars._R = AppVars._G = AppVars._B = 256/5; // dark grey
AppVars.dAnimSpeed = 0.05; // so 1/this = 20 = 20FPS
AppVars.dTimeStamp1 = getDoubleTime();
AppVars.fFramefrac = 0.0f;
AppVars.bAnimate = false;
AppVars.bForceWrapWhenAnimating = false;
AppVars_Delete();
}
void AppVars_WriteIdeal(void)
{
if (!AppVars.strLoadedModelPath.IsEmpty())
{
CString strOut;
#define OUTBYTE(blah) strOut += va("%s:%d\n",#blah,AppVars.blah);
#define OUTDOUBLE(blah) strOut += va("%s:%f\n",#blah,AppVars.blah);
OUTBYTE(bBilinear);
OUTBYTE(bOriginLines);
OUTBYTE(bBBox);
OUTBYTE(bUseAlpha);
OUTBYTE(bWireFrame);
OUTBYTE(_R);
OUTBYTE(_G);
OUTBYTE(_B);
OUTDOUBLE(dFOV);
OUTDOUBLE(dAnimSpeed);
OUTDOUBLE(xPos);
OUTDOUBLE(yPos);
OUTDOUBLE(zPos);
OUTDOUBLE(rotAngleX);
OUTDOUBLE(rotAngleY);
OUTDOUBLE(rotAngleZ);
LPCSTR psIdealName = va("%s.ideal",Filename_WithoutExt(AppVars.strLoadedModelPath));
FILE *fHandle = fopen(psIdealName,"wt");
if (fHandle)
{
fprintf(fHandle,(LPCSTR)strOut);
fclose(fHandle);
}
else
{
ErrorBox(va("Unable to write \"%s\"!, write protected?",psIdealName));
}
}
else
{
ErrorBox("Cannot write out a .ideal file if no model loaded");
}
}
void AppVars_ReadIdeal(void)
{
if (!AppVars.strLoadedModelPath.IsEmpty())
{
LPCSTR psIdealName = va("%s.ideal",Filename_WithoutExt(AppVars.strLoadedModelPath));
FILE *fHandle = fopen(psIdealName,"rt");
if (fHandle)
{
CString str;
char sLine[1024];
while (fgets(sLine,sizeof(sLine),fHandle)!=NULL)
{
// deal with CR stuff manually, in case file was edited by hand and last line doesn't have one...
//
if (strchr(sLine,'\n'))
*strchr(sLine,'\n') = '\0';
str += sLine;
str += "\n";
}
fclose(fHandle);
extern bool Gallery_Active(void);
if (Gallery_Active())
{
// this won't actually put up a box if the gallery is active, but it will add it to the
// overall report file so they'll know about it...
//
// (I may offer the ability to use these, but can't be bothered hassling them with extra
// Yes/No queries at the moment)
//
WarningBox(va("Ignoring settings file \"%s\" during gallery snapshots",psIdealName));
return;
}
// now check for certain values...
//
while (1)
{
int iLoc = str.Find('\n');
if (iLoc == -1)
break;
CString strThis = str.Left(iLoc);
if (strThis.IsEmpty())
break;
str = str.Mid(iLoc+1);
iLoc = strThis.Find(':');
if (iLoc == -1)
break;
CString strValue = strThis.Mid(iLoc+1);
strThis = strThis.Left(iLoc);
// now look for one of the named/saved fields...
//
#define CHECKBOOL(blah) \
if (strThis.CompareNoCase(#blah) == 0) \
{ \
AppVars.blah = !!atoi(strValue); \
continue; \
}
#define CHECKBYTE(blah) \
if (strThis.CompareNoCase(#blah) == 0) \
{ \
AppVars.blah = atoi(strValue); \
continue; \
}
#define CHECKDOUBLE(blah) \
if (strThis.CompareNoCase(#blah) == 0) \
{ \
AppVars.blah = atof(strValue); \
continue; \
}
CHECKBOOL(bBilinear);
CHECKBOOL(bOriginLines);
CHECKBOOL(bBBox);
CHECKBOOL(bUseAlpha);
CHECKBOOL(bWireFrame);
CHECKBYTE(_R);
CHECKBYTE(_G);
CHECKBYTE(_B);
CHECKDOUBLE(dFOV);
CHECKDOUBLE(dAnimSpeed);
CHECKDOUBLE(xPos);
CHECKDOUBLE(yPos);
CHECKDOUBLE(zPos);
CHECKDOUBLE(rotAngleX);
CHECKDOUBLE(rotAngleY);
CHECKDOUBLE(rotAngleZ);
}
TextureList_SetFilter(); // in case filtering was changed
ModelList_ForceRedraw();
}
}
}
// the global stuff for any loaded model, regardless of format...
//
// this deletes the primary model, all bolted models, and the low-level model cache
//
void Model_Delete(void)
{
// delete common stuff...
//
// SAFEFREE(pvLoadedModel);
ModelTree_DeleteAllItems();
// delete any format-specific stuff that this code doesn't know about...
//
GLMModel_DeleteExtra(); // delete anything specific to this format
RE_DeleteModels();
// delete other app vars...
//
AppVars_Delete();
extern bool g_bReportImageLoadErrors;
g_bReportImageLoadErrors = false; // uninhibit any inhibited errors
}
LPCSTR Model_GetSupportedTypesFilter(bool bScriptsEtcAlsoAllowed /* = false */)
{
static char sFilterString[1024];
strcpy(sFilterString,"Model files (*.glm)|*.glm|");
if (bScriptsEtcAlsoAllowed)
{
strcat(sFilterString, Script_GetFilter(false));
}
strcat(sFilterString,"All Files(*.*)|*.*||");
return sFilterString;
}
// findme: All code that uses cut/paste from other projects should go through here, since it allows the whole
// code-exit mechanism that they use to be trapped properly...
//
// call this before calling any cut/paste other-format model code
ModelHandle_t Model_Register( CString strLocalFilename )
{
ModelHandle_t hModel = NULL;
try
{
StatusMessage(va("Registering model: \"%s\"\n",(LPCSTR)strLocalFilename));
hModel = RE_RegisterModel( strLocalFilename );
}
catch(LPCSTR psMessage)
{
Model_Delete();
ErrorBox(psMessage);
hModel = NULL;
}
StatusMessage(NULL);
return hModel;
}
ModelContainer_t* pMatchingContainer;
static void ModelContainer_CallBack_HandleCheck(ModelContainer_t* pContainer, void *pvData )
{
if (pContainer->hModel == *((ModelHandle_t*) pvData))
{
pMatchingContainer = pContainer;
}
}
ModelContainer_t* ModelContainer_FindFromModelHandle(ModelHandle_t hModel)
{
pMatchingContainer = NULL;
R_ModelContainer_Apply(&AppVars.Container, ModelContainer_CallBack_HandleCheck, &hModel);
if (pMatchingContainer)
return pMatchingContainer;
return NULL;
}
// read in a model using main engine code, then parse any extra stuff that this modview apps wants to know about...
//
// note that this doesn't know or care whether it's the parent container or a bolt on, and neither should it.
// Any error will delete all loaded models (as per usual)...
//
static ModelHandle_t ModelContainer_RegisterModel(LPCSTR psLocalFilename, ModelContainer_t *pContainer, HTREEITEM hTreeItem_Parent = NULL);
static ModelHandle_t ModelContainer_RegisterModel(LPCSTR psLocalFilename, ModelContainer_t *pContainer, HTREEITEM hTreeItem_Parent)
{
CWaitCursor wait;
ModelContainer_Clear(pContainer); // ZEROMEM(*pContainer);
ModelHandle_t hModel = Model_Register( psLocalFilename );
strncpy(pContainer->sLocalPathName,psLocalFilename,sizeof(pContainer->sLocalPathName));
pContainer->sLocalPathName[sizeof(pContainer->sLocalPathName)-1] = '\0';
if (hModel)
{
pContainer->hModel = hModel;
modtype_t modtype = MOD_BAD; // reasonable default
// do any game-type post-process code... (hence the try-catch block)
//
try
{
if ( (modtype = RE_GetModelType( hModel )) == MOD_MDXM)
{
trap_G2_SurfaceOffList(hModel, &pContainer->slist);
trap_G2_Init_Bone_List(&pContainer->blist);
//trap_G2_Set_Bone_Anim(ent->ghoulmodel, ent->s.blist, "model_root", 0, 9, BONE_ANIM_OVERRIDE_LOOP, 0.1f);
}
}
catch(LPCSTR psMessage)
{
Model_Delete();
ErrorBox(psMessage);
hModel = NULL;
}
// now do any of my post-process stuff... (which doesn't need try-catch because it's well written :-)
//
if (hModel)
{
pContainer->eModType = modtype;
bool bModelOk = true;
switch (modtype)
{
case MOD_MDXM:
bModelOk = GLMModel_Parse( pContainer, psLocalFilename, hTreeItem_Parent);
if (bModelOk)
{
// specific to this format...
//
assert(pContainer->pModelGetBoneNameFunction);
assert(pContainer->pModelGetBoneBoltNameFunction);
assert(pContainer->pModelGetSurfaceBoltNameFunction);
}
break;
default:
//assert(0);
bModelOk = false;
ErrorBox(va("The model \"%s\" is valid, but ModView doesn't fully support this type at present",psLocalFilename));
break;
}
if (bModelOk)
{
// the above switch-case should have filled in these per-format...
//
assert(pContainer->iBoneBolt_MaxBoltPoints != -1); // check that deliberate illegal default is overwritten
assert(pContainer->iSurfaceBolt_MaxBoltPoints != -1); // check that deliberate illegal default is overwritten
assert(pContainer->iNumLODs);
assert(pContainer->iNumFrames);
assert(pContainer->pModelInfoFunction);
assert(pContainer->pModelGetSurfaceNameFunction);
// if failed to read any sequence files then make a default one...
//
if (!pContainer->SequenceList.size())
{
pContainer->SequenceList.push_back( *Sequence_CreateDefault(pContainer->iNumFrames) );
}
// default bolton stuff (ensure that matrix mem initialised, and bolton array resized correctly...)
//
// bone bolts...
//
pContainer->tBoneBolt_BoltPoints.resize(pContainer->iBoneBolt_MaxBoltPoints);
for (int iBoltPoint = 0; iBoltPoint < pContainer->iBoneBolt_MaxBoltPoints; iBoltPoint++)
{
BoltPoint_t *pBoltPoint = &pContainer->tBoneBolt_BoltPoints[ iBoltPoint ];
pBoltPoint->vMatricesPerFrame.resize( pContainer->iNumFrames );
pBoltPoint->sAttachName = pContainer->pModelGetBoneBoltNameFunction(pContainer->hModel, iBoltPoint);
pBoltPoint->vBoltedContainers.clear(); // probably not nec., but wtf?
}
//
// surface bolts...
//
pContainer->tSurfaceBolt_BoltPoints.resize(pContainer->iSurfaceBolt_MaxBoltPoints);
for (iBoltPoint=0; iBoltPoint<pContainer->iSurfaceBolt_MaxBoltPoints; iBoltPoint++)
{
BoltPoint_t *pBoltPoint = &pContainer->tSurfaceBolt_BoltPoints[iBoltPoint];
pBoltPoint->vMatricesPerFrame.resize( pContainer->iNumFrames );
pBoltPoint->sAttachName = pContainer->pModelGetSurfaceBoltNameFunction(pContainer->hModel, iBoltPoint);
pBoltPoint->vBoltedContainers.clear();
}
// finally, we can do stuff like skin file code that can popup GetYesNo boxes, which cause grief
// if they happen before the bolt stuff above has occured...
//
switch (modtype)
{
case MOD_MDXM:
// only one of these will be valid at once, so no need to check...
//
Skins_ApplyDefault(pContainer);
OldSkins_ApplyDefault(pContainer);
break;
default:
assert(0);
break;
}
}
if (!bModelOk)
{
Model_Delete();
hModel = NULL;
}
}
}
return hModel;
}
void ModelTree_DeleteAllItems(void)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
gModViewTreeViewhandle->DeleteAllItems();
}
}
DWORD ModelTree_GetItemData(HTREEITEM hTreeItem)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return gModViewTreeViewhandle->GetTreeCtrl().GetItemData(hTreeItem);
}
assert(0);
return NULL;
}
bool ModelTree_SetItemText(HTREEITEM hTreeItem, LPCSTR psText)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return !!gModViewTreeViewhandle->GetTreeCtrl().SetItemText(hTreeItem, psText);
}
assert(0);
return NULL;
}
// param 'bPure' should be TRUE if you want to strip stuff like "////" from "///////// surfacename",
// and return the original un-decorated text by querying the model directly if possible...
//
// this function was written so Keith could remote-query from ConfuseEd...
//
LPCSTR ModelTree_GetItemText(HTREEITEM hTreeItem, bool bPure /* = false */)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
if (bPure)
{
// let's see if this is a treeitem type that can return pure text...
//
TreeItemData_t TreeItemData;
TreeItemData.uiData = ModelTree_GetItemData(hTreeItem);
if (TreeItemData.iItemType == TREEITEMTYPE_GLM_SURFACE
||
TreeItemData.iItemType == TREEITEMTYPE_GLM_TAGSURFACE
)
{
return GLMModel_GetSurfaceName( TreeItemData.iModelHandle, TreeItemData.iItemNumber );
}
}
// whatever it is, just return its itemtext in full...
//
// do NOT use the CString(input) constructor here!!
//
static CString string;
string = gModViewTreeViewhandle->GetTreeCtrl().GetItemText(hTreeItem);
return (LPCSTR) string;
}
assert(0);
return NULL;
}
// search tree for an item whose userdata matches what's been passed...
//
// hTreeItem = tree item to start from, pass NULL to start from root
//
static HTREEITEM R_ModelTree_FindItemWithThisData(HTREEITEM hTreeItem, UINT32 uiData2Match, int *piItemsScanned = NULL);
static HTREEITEM R_ModelTree_FindItemWithThisData(HTREEITEM hTreeItem, UINT32 uiData2Match, int *piItemsScanned/*=NULL*/)
{
if (!hTreeItem)
hTreeItem = ModelTree_GetRootItem();
if (hTreeItem)
{
if (piItemsScanned)
{
*piItemsScanned +=1;
}
// LPCSTR psText = ModelTree_GetItemText(hTreeItem);
// OutputDebugString(va("Scanning item %X (%s)\n",hTreeItem,psText));
// check this tree item...
//
TreeItemData_t TreeItemData;
TreeItemData.uiData = ModelTree_GetItemData(hTreeItem);
// match?...
//
if (TreeItemData.uiData == uiData2Match)
return hTreeItem;
// check child...
//
HTREEITEM hTreeItem_Child = ModelTree_GetChildItem(hTreeItem);
if (hTreeItem_Child)
{
HTREEITEM hTreeItemFound = R_ModelTree_FindItemWithThisData(hTreeItem_Child, uiData2Match, piItemsScanned);
if (hTreeItemFound)
return hTreeItemFound;
}
// process siblings...
//
HTREEITEM hTreeItem_Sibling = ModelTree_GetNextSiblingItem(hTreeItem);
if (hTreeItem_Sibling)
{
HTREEITEM hTreeItemFound = R_ModelTree_FindItemWithThisData(hTreeItem_Sibling, uiData2Match, piItemsScanned);
if (hTreeItemFound)
return hTreeItemFound;
}
// this treeitem isnt a match, and neither are its siblings or children, so...
//
return NULL;
}
// we must have called this when the treeview was uninitialised... (duh!)
//
ASSERT(0);
return NULL;
}
int ModelTree_GetChildCount(HTREEITEM hTreeItem)
{
int iChildCount = 0;
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
if (gModViewTreeViewhandle->GetTreeCtrl().ItemHasChildren(hTreeItem))
{
hTreeItem = gModViewTreeViewhandle->GetTreeCtrl().GetChildItem(hTreeItem);
R_ModelTree_FindItemWithThisData(hTreeItem, 0xDEADDEAD, &iChildCount); // massive-function abuse here! :-)
}
}
return iChildCount;
}
bool ModelTree_ItemHasChildren(HTREEITEM hTreeItem)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return !!gModViewTreeViewhandle->GetTreeCtrl().ItemHasChildren(hTreeItem);
}
assert(0);
return NULL;
}
HTREEITEM ModelTree_GetChildItem(HTREEITEM hTreeItem)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return gModViewTreeViewhandle->GetTreeCtrl().GetChildItem(hTreeItem);
}
assert(0);
return NULL;
}
HTREEITEM ModelTree_GetNextSiblingItem(HTREEITEM hTreeItem)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return gModViewTreeViewhandle->GetTreeCtrl().GetNextSiblingItem(hTreeItem);
}
assert(0);
return NULL;
}
HTREEITEM ModelTree_GetRootItem(void)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return gModViewTreeViewhandle->GetRootItem();
}
ASSERT(0);
return NULL;
}
bool ModelTree_DeleteItem(HTREEITEM hTreeItem)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return !!gModViewTreeViewhandle->GetTreeCtrl().DeleteItem(hTreeItem);
}
ASSERT(0);
return NULL;
}
// this should only be called when you know it's a GLM model for the moment... (put in for Keith's remote access)
//
HTREEITEM ModelTree_GetRootSurface(ModelHandle_t hModel)
{
if (gModViewTreeViewhandle && Model_Loaded(hModel)) // will be valid unless this is called from app exit
{
TreeItemData_t TreeItemData = {0};
TreeItemData.iItemType = TREEITEMTYPE_SURFACEHEADER;
TreeItemData.iModelHandle = hModel;
HTREEITEM hTreeItem_SurfaceHeader = R_ModelTree_FindItemWithThisData(NULL, TreeItemData.uiData);
if (hTreeItem_SurfaceHeader)
{
return ModelTree_GetChildItem(hTreeItem_SurfaceHeader);
}
}
ASSERT(0);
return NULL;
}
// this should only be called when you know it's a GLM model for the moment... (put in for Keith's remote access)
//
HTREEITEM ModelTree_GetRootBone(ModelHandle_t hModel)
{
if (gModViewTreeViewhandle && Model_Loaded(hModel)) // will be valid unless this is called from app exit
{
TreeItemData_t TreeItemData = {0};
TreeItemData.iItemType = TREEITEMTYPE_BONEHEADER;
TreeItemData.iModelHandle = hModel;
HTREEITEM hTreeItem_BoneHeader = R_ModelTree_FindItemWithThisData(NULL, TreeItemData.uiData);
if (hTreeItem_BoneHeader)
{
return ModelTree_GetChildItem(hTreeItem_BoneHeader);
}
}
ASSERT(0);
return NULL;
}
static int SortSequenceBy_Alpha(const void *elem1, const void *elem2)
{
Sequence_t *pSeq1 = *(Sequence_t**)elem1;
Sequence_t *pSeq2 = *(Sequence_t**)elem2;
return stricmp(pSeq1->sName, pSeq2->sName);
}
static int SortSequenceBy_FrameNum(const void *elem1, const void *elem2)
{
Sequence_t *pSeq1 = *(Sequence_t**)elem1;
Sequence_t *pSeq2 = *(Sequence_t**)elem2;
return pSeq1->iStartFrame - pSeq2->iStartFrame;
}
void ModelTree_InsertSequences(ModelHandle_t hModel, HTREEITEM hTreeItem_Sequences)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
ModelTree_InsertSequences(pContainer, hTreeItem_Sequences);
return;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
void ModelTree_InsertSequences(ModelContainer_t *pContainer, HTREEITEM hTreeItem_Sequences)
{
CWaitCursor wait;
// delete any child items already present...
//
HTREEITEM hTreeItemChild = gModViewTreeViewhandle->GetTreeCtrl().GetChildItem(hTreeItem_Sequences);
if (hTreeItemChild)
{
while (hTreeItemChild)
{
HTREEITEM hNext = gModViewTreeViewhandle->GetTreeCtrl().GetNextSiblingItem(hTreeItemChild);
gModViewTreeViewhandle->GetTreeCtrl().DeleteItem(hTreeItemChild);
hTreeItemChild = hNext;
}
}
// make new list...
//
vector <Sequence_t*> Sequences;
// add sequence ptrs to list...
//
for (int iSequenceIndex = 0; iSequenceIndex<pContainer->SequenceList.size(); iSequenceIndex++)
{
Sequence_t *pSequence = &pContainer->SequenceList[iSequenceIndex];
Sequences.push_back(pSequence);
}
// sort 'em...
//
qsort(&Sequences[0], Sequences.size(), sizeof(Sequence_t*), AppVars.bSortSequencesByAlpha ? SortSequenceBy_Alpha : SortSequenceBy_FrameNum);
// add to tree...
//
for (int i=0; i<Sequences.size(); i++)
{
Sequence_t *pSequence = Sequences[i];
iSequenceIndex = Sequence_GetIndex( pSequence, pContainer );
if (iSequenceIndex != -1)
{
TreeItemData_t TreeItemData = {0};
TreeItemData.iItemType = TREEITEMTYPE_SEQUENCE;
TreeItemData.iItemNumber = iSequenceIndex;
TreeItemData.iModelHandle = pContainer->hModel;
HTREEITEM htiThis = ModelTree_InsertItem( Sequence_CreateTreeName(pSequence), // LPCTSTR psName,
hTreeItem_Sequences, // HTREEITEM hParent
TreeItemData.uiData // TREEITEMTYPE_GLM_SURFACE | iThisSurfaceIndex // UINT32 uiUserData
);
}
else
{
assert(0 && "ModelTree_InsertSequences(): Unknown sequence ptr!");
}
}
}
// hParent can be NULL...
//
HTREEITEM ModelTree_InsertItem(LPCTSTR psName, HTREEITEM hParent, UINT32 uiUserData /* = NULL */ , HTREEITEM hInsertAfter /* = TVI_LAST */)
{
if (gModViewTreeViewhandle) // will be valid unless this is called from app exit
{
return gModViewTreeViewhandle->InsertItem(psName, hParent, uiUserData, hInsertAfter);
}
ASSERT(0);
return NULL;
}
bool Model_Loaded(ModelHandle_t hModel /* = NULL */)
{
if (hModel)
{
return !!ModelContainer_FindFromModelHandle(hModel);
// return !!(AppVars.mcPrimary.hModel == hModel);
}
return !!AppVars.Container.hModel; // query primary container
}
static ModelContainer_t *ModelContainer_AllocNew(void)
{
ModelContainer_t *pNewContainer = new ModelContainer_t;
ModelContainer_Clear(pNewContainer);
return pNewContainer;
}
// ensure that there's a "BoltOns" header in a container, and if not, add one and add it to the tree also.
//
static bool ModelContainer_EnsureBoltOnHeader(ModelContainer_t *pContainer)
{
bool bReturn = false;
if (pContainer->hTreeItem_BoltOns)
return true; // already got one
TreeItemData_t TreeItemData={0};
TreeItemData.iItemType = TREEITEMTYPE_BOLTONSHEADER;
TreeItemData.iModelHandle = pContainer->hModel;
pContainer->hTreeItem_BoltOns = ModelTree_InsertItem("BoltOns", pContainer->hTreeItem_ModelName, TreeItemData.uiData);
return !!pContainer->hTreeItem_BoltOns;
}
static bool _Actual_Model_LoadPrimary(LPCSTR psFullPathedFilename)
{
bool bReturn = false;
SetQdirFromPath( psFullPathedFilename );
// I'll check for file existance first, to avoid deleting current model until a valid replacement is ready...
//
if (FileExists( psFullPathedFilename ))
{
Model_Delete();
CString strLocalFilename( psFullPathedFilename );
Filename_RemoveQUAKEBASE(strLocalFilename);
// AppVars.Container = *ModelContainer_AllocNew();
ModelContainer_Clear(&AppVars.Container);
ModelHandle_t hModel = ModelContainer_RegisterModel(strLocalFilename, &AppVars.Container);//ModelContainer_AllocNew());
if (hModel)
{
AppVars.strLoadedModelPath = psFullPathedFilename;
AppVars.iTotalContainers = 1;
AppVars.hModelLastLoaded = hModel;
bReturn = true;
AppVars_ReadIdeal(); // check for optional "modelname.ideal"...
}
}
else
{
ErrorBox(va("File not found: \"%s\"!",psFullPathedFilename));
}
if (!bReturn)
{
Model_Delete();
}
ModelList_ForceRedraw();
return bReturn;
}
bool Model_LoadPrimary(LPCSTR psFullPathedFilename)
{
gbRenderInhibit = true;
bool b = _Actual_Model_LoadPrimary(psFullPathedFilename);
gbRenderInhibit = false;
ModelList_ForceRedraw();
return b;
}
void Model_ApplyOldSkin( ModelHandle_t hModel, LPCSTR psSkin )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
OldSkins_Apply(pContainer, psSkin);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
}
bool Model_SkinHasSurfacePrefs( ModelHandle_t hModel, LPCSTR psSkin )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Skins_FileHasSurfacePrefs(pContainer, psSkin);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return false;
}
void Model_ApplyEthnicSkin(ModelHandle_t hModel, LPCSTR psSkin, LPCSTR psEthnic, bool bApplySurfacePrefs, bool bDefaultSurfaces )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
Skins_ApplyEthnic(pContainer, psSkin, psEthnic, bApplySurfacePrefs, bDefaultSurfaces );
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
}
void Model_ApplySkinShaderVariant( ModelHandle_t hModel, LPCSTR psSkin, LPCSTR psEthnic, LPCSTR psMaterial, int iVariant)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
Skins_ApplySkinShaderVariant(pContainer, psSkin, psEthnic, psMaterial, iVariant );
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
}
void Model_ValidateSkin(ModelHandle_t hModel, int iSkinNumber)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (pContainer->SkinSets.size())
{
Skins_Validate( pContainer, iSkinNumber );
}
else
if (pContainer->OldSkinSets.size())
{
OldSkins_Validate(pContainer, iSkinNumber);
}
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
}
// deletes whatever's bolted to the supplied bolt number of the suppplied model...
//
bool Model_DeleteBoltOn(ModelHandle_t hModel, int iBoltPointToDelete, bool bBoltIsBone, int iBoltOnAtBoltPoint)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_DeleteBoltOn(pContainer, iBoltPointToDelete, bBoltIsBone, iBoltOnAtBoltPoint);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return bReturn;
}
// deletes <iBoltOnAtBoltPoint / all(-1)> from <iBoltPointToDelete> on <pContainer>
//
bool Model_DeleteBoltOn(ModelContainer_t *pContainer, int iBoltPointToDelete, bool bBoltIsBone, int iBoltOnAtBoltPoint)
{
bool bReturn = false;
vector <BoltPoint_t> &vBoltPoints = bBoltIsBone ? pContainer->tBoneBolt_BoltPoints : pContainer->tSurfaceBolt_BoltPoints;
int &iMaxBoltPoints= bBoltIsBone ? pContainer->iBoneBolt_MaxBoltPoints : pContainer->iSurfaceBolt_MaxBoltPoints;
if (iBoltPointToDelete < (bBoltIsBone ? pContainer->iBoneBolt_MaxBoltPoints : pContainer->iSurfaceBolt_MaxBoltPoints))
{
if (iBoltPointToDelete < (bBoltIsBone ? pContainer->tBoneBolt_BoltPoints.size() : pContainer->tSurfaceBolt_BoltPoints.size()))
{
BoltPoint_t *pBoltOn = bBoltIsBone ? &pContainer->tBoneBolt_BoltPoints [iBoltPointToDelete] :
&pContainer->tSurfaceBolt_BoltPoints[iBoltPointToDelete];
// stl-type limits, ie [Begin, End)...
//
int iBoltOnBegin = (iBoltOnAtBoltPoint == -1) ? 0 : iBoltOnAtBoltPoint;
int iBoltOnEnd = (iBoltOnAtBoltPoint == -1) ? pBoltOn->vBoltedContainers.size() : iBoltOnAtBoltPoint+1;
for (int iBoltOn = iBoltOnBegin; iBoltOn != iBoltOnEnd; iBoltOn++)
{
ModelContainer_t *pBoltedContainer = &pBoltOn->vBoltedContainers[ iBoltOn ];
// delete all tree items from the bolt downwards... (including models bolted to the bolt)
//
ModelTree_DeleteItem(pBoltedContainer->hTreeItem_ModelName);
// delete underlying containers... (including models bolted to the bolt)
//
R_ModelContainer_ApplyFromBottomUp(pBoltedContainer, ModelContainer_Clear);
/* // delete the bolt itself and mark it as empty...
//
if (bBoltIsBone)
{
delete (pContainer->tBoneBolt_BoltPoints[iBoltPointToDelete].pBoltedContainer);
pContainer->tBoneBolt_BoltPoints[iBoltPointToDelete].pBoltedContainer = NULL;
}
else
{
delete (pContainer->tSurfaceBolt_BoltPoints[iBoltPointToDelete].pBoltedContainer);
pContainer->tSurfaceBolt_BoltPoints[iBoltPointToDelete].pBoltedContainer = NULL;
}
*/
}
int iSize = pBoltOn->vBoltedContainers.size();
pBoltOn->vBoltedContainers.erase(pBoltOn->vBoltedContainers.begin() + iBoltOnBegin, pBoltOn->vBoltedContainers.begin() + iBoltOnEnd);
int iSize2 = pBoltOn->vBoltedContainers.size();
// finally, for neatness, check if our BoltOns treeitem is now empty (no children), and delete it if so.
// (a new one will be created if a bolt is added again later)
//
if (!ModelTree_ItemHasChildren(pContainer->hTreeItem_BoltOns))
{
ModelTree_DeleteItem(pContainer->hTreeItem_BoltOns);
pContainer->hTreeItem_BoltOns = NULL;
}
bReturn = true;
}
else
{
ErrorBox(va("Model_DeleteBoltOn(): %s bolt index %d is legal (max %d), but container only has %d entries!!! (Tell me! -Ste)",
bBoltIsBone?"bone":"surface",
iBoltPointToDelete,
iMaxBoltPoints,
vBoltPoints.size()
)
);
}
}
else
{
ErrorBox(va("Model_DeleteBoltOn(): Illegal %s bolt index %d (max = %d)!",bBoltIsBone?"bone":"surface",iBoltPointToDelete, bBoltIsBone?pContainer->iBoneBolt_MaxBoltPoints:pContainer->iSurfaceBolt_MaxBoltPoints));
}
if (bReturn)
{
ModelList_ForceRedraw();
}
return bReturn;
}
// returns -1 for unknown or error...
//
static int Model_GetBoltOnNumber(ModelContainer_t *pContainer, ModelContainer_t *pParentContainer, int iParentBoltIndex, bool bBoltIsBone)
{
vector <BoltPoint_t> &vParentBoltPoints = bBoltIsBone ? pParentContainer->tBoneBolt_BoltPoints : pParentContainer->tSurfaceBolt_BoltPoints;
if (iParentBoltIndex < vParentBoltPoints.size())
{
BoltPoint_t *pBoltOn = &vParentBoltPoints[ iParentBoltIndex ];
for (int iBoltOn = 0; iBoltOn < pBoltOn->vBoltedContainers.size(); iBoltOn++)
{
if ( pContainer == &pBoltOn->vBoltedContainers[ iBoltOn ] )
return iBoltOn;
}
}
else
{
ErrorBox(va("Model_GetBoltOnNumber(): Error, illegal %s bolt index %d (max = %d)! (Tell me! - Ste)\n",(bBoltIsBone?"bone":"surface"), iParentBoltIndex, vParentBoltPoints.size()));
}
return -1;
}
// deletes the supplied container of a bolted model from whatever its bolted to...
//
// (this works for either bone or surface boltons)
//
bool Model_DeleteBoltOn(ModelContainer_t *pContainer)
{
bool bReturn = false;
if (pContainer->pBoneBolt_ParentContainer)
{
int iBoltOnNumber = Model_GetBoltOnNumber(pContainer, pContainer->pBoneBolt_ParentContainer, pContainer->iBoneBolt_ParentBoltIndex, true);
return Model_DeleteBoltOn(pContainer->pBoneBolt_ParentContainer, pContainer->iBoneBolt_ParentBoltIndex, true, iBoltOnNumber);
}
if (pContainer->pSurfaceBolt_ParentContainer)
{
int iBoltOnNumber = Model_GetBoltOnNumber(pContainer, pContainer->pSurfaceBolt_ParentContainer, pContainer->iSurfaceBolt_ParentBoltIndex, false);
return Model_DeleteBoltOn(pContainer->pSurfaceBolt_ParentContainer, pContainer->iSurfaceBolt_ParentBoltIndex, false, iBoltOnNumber);
}
return bReturn;
}
// deletes the supplied boltOn from whatever it's bolted to...
//
// (this works for either bone or surface boltons)
//
bool Model_DeleteBoltOn(ModelHandle_t hModelBoltOn)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModelBoltOn);
if (pContainer)
{
return Model_DeleteBoltOn(pContainer);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return bReturn;
}
// used for UI update check...
//
bool Model_HasParent(ModelHandle_t hModel)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return (pContainer->pBoneBolt_ParentContainer || pContainer->pSurfaceBolt_ParentContainer);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return bReturn;
}
// used for UI update check... (also used in renderer now, to work out whether or not to xform a g2 tag surface
//
int Model_CountItemsBoltedHere(ModelHandle_t hModel, int iBoltIndex, bool bBoltIsBone)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (bBoltIsBone)
{
if (iBoltIndex < pContainer->iBoneBolt_MaxBoltPoints)
{
return pContainer->tBoneBolt_BoltPoints[ iBoltIndex ].vBoltedContainers.size();
}
}
else
{
if (iBoltIndex < pContainer->iSurfaceBolt_MaxBoltPoints)
{
return pContainer->tSurfaceBolt_BoltPoints[ iBoltIndex ].vBoltedContainers.size();
}
}
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return 0;
}
bool Model_LoadBoltOn(LPCSTR psFullPathedFilename, ModelHandle_t hModel, LPCSTR psBoltName, bool bBoltIsBone, bool bBoltReplacesAllExisting)
{
int iBoltIndex = Model_GetBoltIndex(hModel, psBoltName, bBoltIsBone);
if (iBoltIndex == -1)
return false;
return Model_LoadBoltOn(psFullPathedFilename, hModel, iBoltIndex, bBoltIsBone, bBoltReplacesAllExisting);
}
static bool _Actual_Model_LoadBoltOn(LPCSTR psFullPathedFilename, ModelHandle_t hModel, int iBoltIndex, bool bBoltIsBone, bool bBoltReplacesAllExisting)
{
if (!hModel)
{
ErrorBox("Attempting to bolt to NULL model!");
return false;
}
// soft-error here for this, that doesn't zap anything important...
//
if (!FileExists( psFullPathedFilename ))
{
ErrorBox(va("File not found: \"%s\"!",psFullPathedFilename));
return false;
}
// update: zap anything already there!...
//
if (bBoltReplacesAllExisting)
{
Model_DeleteBoltOn(hModel, iBoltIndex, bBoltIsBone, -1);
}
// ok, any errors from here on in will delete the primary model if they occur...
//
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
ModelContainer_EnsureBoltOnHeader(pContainer);
// some rather unfortunate return-logic here for soft-errors, but...
//
if (iBoltIndex < (bBoltIsBone?pContainer->iBoneBolt_MaxBoltPoints:pContainer->iSurfaceBolt_MaxBoltPoints) && iBoltIndex >= 0)
{
CString strLocalFilename( psFullPathedFilename );
Filename_RemoveQUAKEBASE(strLocalFilename);
ModelContainer_t *pContainer_BoltOn = ModelContainer_AllocNew();
// do this further down now...
// ModelHandle_t hModel = ModelContainer_RegisterModel(strLocalFilename, pContainer_BoltOn, pContainer->hTreeItem_BoltOns);
// note that we do this code to link everything in whether or not we succeeded in registering the model
// so that the Model_Delete code catches everything, ie we don't have any orphaned pointers
if (bBoltIsBone)
{
pContainer->tBoneBolt_BoltPoints[iBoltIndex].vBoltedContainers.push_back(*pContainer_BoltOn);
}
else
{
pContainer->tSurfaceBolt_BoltPoints[iBoltIndex].vBoltedContainers.push_back(*pContainer_BoltOn);
}
vector <ModelContainer_t> &vBoltedContainers = bBoltIsBone ?
pContainer->tBoneBolt_BoltPoints [ iBoltIndex ].vBoltedContainers
:
pContainer->tSurfaceBolt_BoltPoints [ iBoltIndex ].vBoltedContainers;
ModelContainer_t *pNewContainerLocation = &vBoltedContainers[ vBoltedContainers.size() -1 ];
ModelHandle_t hModel = ModelContainer_RegisterModel(strLocalFilename, pNewContainerLocation, pContainer->hTreeItem_BoltOns);
if (bBoltIsBone)
{
pNewContainerLocation->pBoneBolt_ParentContainer = pContainer;
pNewContainerLocation->iBoneBolt_ParentBoltIndex = iBoltIndex;
}
else
{
pNewContainerLocation->pSurfaceBolt_ParentContainer = pContainer;
pNewContainerLocation->iSurfaceBolt_ParentBoltIndex = iBoltIndex;
}
delete (pContainer_BoltOn);
if (hModel)
{
AppVars.iTotalContainers++;
AppVars.hModelLastLoaded = hModel;
bReturn = true;
}
}
else
{
ErrorBox(va("Illegal %s bolt index %d specified, max = %d",(bBoltIsBone?"bone":"surface"),iBoltIndex, bBoltIsBone?pContainer->iBoneBolt_MaxBoltPoints:pContainer->iSurfaceBolt_MaxBoltPoints));
}
}
else
{
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
// problems?...
//
if (!bReturn)
{
Model_Delete();
}
ModelList_ForceRedraw(); // update window, needed for when called by external client progs
return bReturn;
}
bool Model_LoadBoltOn(LPCSTR psFullPathedFilename, ModelHandle_t hModel, int iBoltIndex, bool bBoltIsBone, bool bBoltReplacesAllExisting)
{
gbRenderInhibit = true;
bool b = _Actual_Model_LoadBoltOn(psFullPathedFilename, hModel, iBoltIndex, bBoltIsBone, bBoltReplacesAllExisting);
gbRenderInhibit = false;
ModelList_ForceRedraw();
return b;
}
bool Model_SurfaceIsTag( ModelContainer_t *pContainer, int iSurfaceIndex)
{
if (Model_Loaded(pContainer->hModel))
{
return GLMModel_SurfaceIsTag( pContainer->hModel, iSurfaceIndex );
}
ErrorBox( "Model_SurfaceIsTag(): No model loaded" );
return false;
}
bool Model_SurfaceIsTag( ModelHandle_t hModel, int iSurfaceIndex)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_SurfaceIsTag( pContainer, iSurfaceIndex );
}
return false;
}
LPCSTR Model_GLMSurfaceInfo( ModelHandle_t hModel, int iSurfaceIndex, bool bShortVersionForTag )
{
if (Model_Loaded(hModel))
{
return GLMModel_SurfaceInfo( hModel, iSurfaceIndex, bShortVersionForTag );
}
return sERROR_MODEL_NOT_LOADED;
}
// generate info suitable for sending to Notepad (can be a BIG string)...
//
LPCSTR Model_GLMSurfaceVertInfo( ModelHandle_t hModel, int iSurfaceIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_SurfaceVertInfo( hModel, iSurfaceIndex );
}
return sERROR_MODEL_NOT_LOADED;
}
bool Model_SurfaceContainsBoneReference(ModelHandle_t hModel, int iLODNumber, int iSurfaceNumber, int iBoneNumber)
{
// fixme: at some point this'll need to be non-GLM specific, but for now...
//
if (Model_Loaded(hModel))
{
return GLMModel_SurfaceContainsBoneReference(hModel, iLODNumber, iSurfaceNumber, iBoneNumber);
}
ErrorBox( "Model_SurfaceContainsBoneReference(): No model loaded" );
return false;
}
LPCSTR Model_GLMBoneInfo( ModelHandle_t hModel, int iBoneIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_BoneInfo( hModel, iBoneIndex );
}
return sERROR_MODEL_NOT_LOADED;
}
int Model_GetNumFrames( ModelHandle_t hModel )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
return pContainer->iNumFrames;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return 0;
}
LPCSTR Model_Info( ModelHandle_t hModel )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
if (pContainer->pModelInfoFunction)
{
return pContainer->pModelInfoFunction(hModel);
}
assert(0);
return "Model_Info(): No function defined!";
}
return sERROR_CONTAINER_NOT_FOUND;
}
LPCSTR Model_GetBoneName( ModelHandle_t hModel, int iBoneIndex )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
if (pContainer->pModelGetBoneNameFunction)
{
return pContainer->pModelGetBoneNameFunction(hModel, iBoneIndex);
}
assert(0);
return "Model_GetBoneName(): No function defined!";
}
return sERROR_CONTAINER_NOT_FOUND;
}
LPCSTR Model_GetSurfaceName( ModelContainer_t *pContainer, int iSurfaceIndex )
{
if (pContainer->pModelGetSurfaceNameFunction)
{
return pContainer->pModelGetSurfaceNameFunction(pContainer->hModel, iSurfaceIndex);
}
ErrorBox("Model_GetSurfaceName(): No function defined!");
exit(1);
return NULL;
}
LPCSTR Model_GetSurfaceName( ModelHandle_t hModel, int iSurfaceIndex )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
return Model_GetSurfaceName( pContainer, iSurfaceIndex);
}
return sERROR_CONTAINER_NOT_FOUND;
}
// this function isn't terribly fast, so try not to call it somewhere speed dependant...
//
// so far only used for test code, but may be useful to keep around...
//
// returns -1 for error, so check it!!
//
int Model_GetBoltIndex( ModelHandle_t hModel, LPCSTR psBoltName, bool bBoltIsBone)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
return Model_GetBoltIndex( pContainer, psBoltName, bBoltIsBone );
}
ErrorBox( "Model_GetBoltIndex(): " sERROR_CONTAINER_NOT_FOUND );
return -1;
}
// this function isn't terribly fast, so try not to call it somewhere speed dependant...
//
// so far only used for test code, but may be useful to keep around...
//
// returns -1 for error, so check it!!
//
int Model_GetBoltIndex( ModelContainer_t *pContainer, LPCSTR psBoltName, bool bBoltIsBone ) // semi-recursive now
{
if (pContainer->pModelGetBoneBoltNameFunction && pContainer->pModelGetSurfaceBoltNameFunction)
{
// check against bolt names...
//
for (int i=0; i<(bBoltIsBone?pContainer->iBoneBolt_MaxBoltPoints:pContainer->iSurfaceBolt_MaxBoltPoints); i++)
{
if (!stricmp(Model_GetBoltName( pContainer, i, bBoltIsBone), psBoltName ))
return i;
}
if (bBoltIsBone)
{
// check aliases (uses recursion)
//
bool bAlreadyHere = false;
if (!bAlreadyHere)
{
for (MappedString_t::iterator it = pContainer->Aliases.begin(); it != pContainer->Aliases.end(); ++it)
{
string strAliasName = (*it).second;
if (!stricmp(strAliasName.c_str(), psBoltName))
{
string strRealName = (*it).first;
bAlreadyHere = true;
int iAnswer = Model_GetBoltIndex( pContainer, strRealName.c_str(), bBoltIsBone );
bAlreadyHere = false;
return iAnswer;
}
}
}
}
ErrorBox(va("Model_GetBoltIndex(): Unable to find bolt called \"%s\"!",psBoltName));
return -1;
}
ErrorBox( "Model_GetBoltIndex(): need either a ->pModelGetBoneBoltNameFunction or ->pModelGetSurfaceBoltNameFunction!");
return -1;
}
LPCSTR Model_GetBoltName( ModelHandle_t hModel, int iBoltIndex, bool bBoltIsBone )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
return Model_GetBoltName( pContainer, iBoltIndex, bBoltIsBone );
}
return sERROR_CONTAINER_NOT_FOUND;
}
LPCSTR Model_GetBoltName( ModelContainer_t *pContainer, int iBoltIndex, bool bBoltIsBone )
{
if (bBoltIsBone)
{
if (pContainer->pModelGetBoneBoltNameFunction)
{
return pContainer->pModelGetBoneBoltNameFunction(pContainer->hModel, iBoltIndex);
}
}
else
{
if (pContainer->pModelGetSurfaceBoltNameFunction)
{
return pContainer->pModelGetSurfaceBoltNameFunction(pContainer->hModel, iBoltIndex);
}
}
assert(0);
return "Model_GetBoltName(): No <bolttype>GetName() function defined!";
}
LPCSTR Model_GetFilename( ModelHandle_t hModel )
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
return pContainer->sLocalPathName;
}
return sERROR_CONTAINER_NOT_FOUND;
}
// this returns the full disk path of the primary model... (mainly only used for new-file browsing)
//
LPCSTR Model_GetFullPrimaryFilename( void )
{
if (Model_Loaded())
{
return AppVars.strLoadedModelPath;
}
// only usually gets here on first File-Open if not launched via file-association...
//
return "";
}
bool Model_GLMSurface_Off( ModelHandle_t hModel, int iSurfaceIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_Surface_Off(hModel, iSurfaceIndex );
}
assert(0);
return false;
}
bool Model_GLMSurface_SetStatus( ModelHandle_t hModel, int iSurfaceIndex, SurfaceOnOff_t eStatus )
{
if (Model_Loaded(hModel))
{
return GLMModel_Surface_SetStatus(hModel, iSurfaceIndex, eStatus );
}
assert(0);
return false;
}
bool Model_GLMSurface_SetStatus( ModelHandle_t hModel, LPCSTR psSurfaceName, SurfaceOnOff_t eStatus )
{
if (Model_Loaded(hModel))
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if (pContainer)
{
// convert surface name to index...
//
for (int iSurface=0; iSurface<pContainer->iNumSurfaces; iSurface++)
{
if (!stricmp(psSurfaceName, pContainer->pModelGetSurfaceNameFunction( pContainer->hModel, iSurface )))
{
return Model_GLMSurface_SetStatus( pContainer->hModel, iSurface, eStatus);
}
}
}
WarningBox(va("Model_GLMSurface_SetStatus():\nSurface \"%s\" not found!\n\n ( Model: \"%s\" )",psSurfaceName,pContainer->sLocalPathName));
return false;
}
assert(0);
return false;
}
void Model_GLMSurfaces_DefaultAll(ModelHandle_t hModel)
{
if (Model_Loaded(hModel))
{
GLMModel_Surfaces_DefaultAll(hModel);
}
}
bool Model_GLMSurface_On( ModelHandle_t hModel, int iSurfaceIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_Surface_On(hModel, iSurfaceIndex );
}
assert(0);
return false;
}
bool Model_GLMSurface_NoDescendants( ModelHandle_t hModel, int iSurfaceIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_Surface_NoDescendants(hModel, iSurfaceIndex );
}
assert(0);
return false;
}
SurfaceOnOff_t Model_GLMSurface_GetStatus( ModelHandle_t hModel, int iSurfaceIndex )
{
if (Model_Loaded(hModel))
{
return GLMModel_Surface_GetStatus( hModel, iSurfaceIndex );
}
assert(0);
return SURF_ERROR;
}
// subroutinised so there's common logic for the 2 functions below this (important!)...
//
static bool Model_SurfaceIsDifferentFromDefault(ModelContainer_t *pContainer, SurfaceOnOff_t eStatusToCheck, int iSurface)
{
SurfaceOnOff_t eThisStatus = Model_GLMSurface_GetStatus( pContainer->hModel, iSurface );
if (eThisStatus == SURF_INHERENTLYOFF)
{
extern SurfaceOnOff_t MyFlags;
eThisStatus = MyFlags; // get the real state, not the inherent one
}
if (eThisStatus == eStatusToCheck)
{
// this surface's status matches the query, now is it naturally this way?
//
LPCSTR psSurfaceName = Model_GetSurfaceName( pContainer, iSurface );
if (psSurfaceName) // problems if we don't have this of course, but errormessaged already
{
bool bSurfaceNameIncludesOFF = !stricmp("_off", &psSurfaceName[strlen(psSurfaceName)-4]);
switch (eThisStatus)
{
case SURF_ON:
if (bSurfaceNameIncludesOFF)
return true;
break;
case SURF_OFF:
if (!bSurfaceNameIncludesOFF)
return true;
break;
case SURF_NO_DESCENDANTS:
// no surface will ever have this set initially, the loader can only set ON or OFF...
//
return true;
}
}
}
return false;
}
// script-query function, asks how many surfaces are set to 'eStatus' by the user, in other words how many
// are different from their default state...
//
int Model_GetNumSurfacesDifferentFromDefault(ModelContainer_t *pContainer, SurfaceOnOff_t eStatus)
{
int iTotalMatchingSurfaces = 0;
for (int iSurface=0; iSurface<pContainer->iNumSurfaces; iSurface++)
{
if (Model_SurfaceIsDifferentFromDefault(pContainer, eStatus, iSurface))
iTotalMatchingSurfaces++;
}
return iTotalMatchingSurfaces;
}
LPCSTR Model_GetSurfaceDifferentFromDefault(ModelContainer_t *pContainer, SurfaceOnOff_t eStatus, int iSurfaceIndex)
{
for (int iSurface=0; iSurface<pContainer->iNumSurfaces; iSurface++)
{
if (Model_SurfaceIsDifferentFromDefault(pContainer, eStatus, iSurface))
{
if (!iSurfaceIndex--)
return Model_GetSurfaceName( pContainer, iSurface );
}
}
assert(0);
return NULL;
}
void App_OnceOnly(void)
{
AppVars_OnceOnlyInit();
OnceOnlyCrap(); // init some rubbish that cut/paste code wants for other formats
TextureList_OnceOnlyInit();
}
ModelHandle_t Model_GetPrimaryHandle(void)
{
return AppVars.Container.hModel;
}
static void ModelContainer_GenerateBBox(ModelContainer_t *pContainer, int iFrame, vec3_t &v3Mins, vec3_t &v3Maxs)
{
v3Mins[0] = -50;
v3Mins[1] = -50;
v3Mins[2] = -50;
v3Maxs[0] = 50;
v3Maxs[1] = 50;
v3Maxs[2] = 50;
GLMModel_GetBounds(pContainer->hModel, 0, iFrame, v3Mins, v3Maxs);
}
// called from menu for a set-floor-height function...
//
float Model_GetLowestPointOnPrimaryModel(void)
{
ModelContainer_t *pContainer = &AppVars.Container; // primary container
if (pContainer->hModel) // model loaded?
{
vec3_t v3Mins, v3Maxs;
ModelContainer_GenerateBBox(pContainer, pContainer->iCurrentFrame_Primary, v3Mins, v3Maxs);
return v3Mins[2];
}
assert(0);
return 0.0f;
}
static void ModelDraw_Floor( ModelContainer_t *pContainer, bool bDrawingForOriginalContainer )
{
if (bDrawingForOriginalContainer && !gbTextInhibit)
{
if (AppVars.bFloor)
{
#define FLOOR_TILE_DIM 15.0f // arb
#define FLOOR_TILES_ACROSS 10 // ""
glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_POLYGON_BIT);
{
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
for (int y=0; y<FLOOR_TILES_ACROSS; y++)
{
for (int x=0; x<FLOOR_TILES_ACROSS; x++)
{
float fX = (FLOOR_TILE_DIM*(float)x) - (((float)FLOOR_TILES_ACROSS/2.0f)*FLOOR_TILE_DIM);
float fY = (FLOOR_TILE_DIM*(float)y) - (((float)FLOOR_TILES_ACROSS/2.0f)*FLOOR_TILE_DIM);
float fZ = AppVars.fFloorZ;
if ((y&1)^(x&1))
{
glColor3ub(21,81,32); // a sort of dirty yellow/green check
}
else
{
glColor3ub(125,131,18); // ""
}
glBegin(GL_QUADS);
{
// frontface...
//
glVertex3f(fX, fY, fZ);
glVertex3f(fX , fY+FLOOR_TILE_DIM, fZ);
glVertex3f(fX+FLOOR_TILE_DIM, fY+FLOOR_TILE_DIM, fZ);
glVertex3f(fX+FLOOR_TILE_DIM, fY, fZ);
//
// backface...
//
glVertex3f(fX, fY, fZ);
glVertex3f(fX+FLOOR_TILE_DIM, fY, fZ);
glVertex3f(fX+FLOOR_TILE_DIM, fY+FLOOR_TILE_DIM, fZ);
glVertex3f(fX , fY+FLOOR_TILE_DIM, fZ);
}
glEnd();
}
}
}
glPopAttrib();
}
}
}
// this is kinda slow, but you only use it when you need to...
//
static void ModelDraw_BoundingBox( ModelContainer_t *pContainer, bool bDrawingForOriginalContainer )
{
if (AppVars.bBBox && !gbTextInhibit)
{
glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
{
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
vec3_t v3Mins,v3Maxs;
ModelContainer_GenerateBBox(pContainer,pContainer->iCurrentFrame_Primary,v3Mins,v3Maxs);
vec3_t v3Corners[8];
VectorSet(v3Corners[0],v3Mins[0],v3Mins[1],v3Mins[2]);
VectorSet(v3Corners[1],v3Maxs[0],v3Mins[1],v3Mins[2]);
VectorSet(v3Corners[2],v3Maxs[0],v3Mins[1],v3Maxs[2]);
VectorSet(v3Corners[3],v3Mins[0],v3Mins[1],v3Maxs[2]);
VectorSet(v3Corners[4],v3Mins[0],v3Maxs[1],v3Maxs[2]);
VectorSet(v3Corners[5],v3Maxs[0],v3Maxs[1],v3Maxs[2]);
VectorSet(v3Corners[6],v3Maxs[0],v3Maxs[1],v3Mins[2]);
VectorSet(v3Corners[7],v3Mins[0],v3Maxs[1],v3Mins[2]);
glColor3f(0.8,0.8,0.8);//1,1,1);
/* glBegin(GL_LINE_LOOP);
{
glVertex3fv(v3Corners[0]);
glVertex3fv(v3Corners[1]);
glVertex3fv(v3Corners[2]);
glVertex3fv(v3Corners[3]);
glVertex3fv(v3Corners[4]);
glVertex3fv(v3Corners[5]);
glVertex3fv(v3Corners[6]);
glVertex3fv(v3Corners[7]);
}
glEnd();
*/
// new version, draw the above shape, but without 3 of the lines (and therefore without GL_LINE_LOOP)
// because otherwise the white lines fight with the coloured ones below... (aesthetics)
//
glBegin(GL_LINES);
{
glVertex3fv(v3Corners[2]);
glVertex3fv(v3Corners[3]);
glVertex3fv(v3Corners[3]);
glVertex3fv(v3Corners[4]);
glVertex3fv(v3Corners[4]);
glVertex3fv(v3Corners[5]);
glVertex3fv(v3Corners[5]);
glVertex3fv(v3Corners[6]);
glVertex3fv(v3Corners[6]);
glVertex3fv(v3Corners[7]);
}
glEnd();
// draw the thicker lines for the 3 dimension axis...
//
glLineWidth(3);
{
// X...
//
glColor3f(1,0,0);
glBegin(GL_LINES);
{
glVertex3fv(v3Corners[0]);
glVertex3fv(v3Corners[1]);
}
glEnd();
// Y...
//
glColor3f(0,1,0);
glBegin(GL_LINES);
{
glVertex3fv(v3Corners[1]);
glVertex3fv(v3Corners[2]);
}
glEnd();
// Z...
//
glColor3f(0,0,1);
glBegin(GL_LINES);
{
glVertex3fv(v3Corners[7]);
glVertex3fv(v3Corners[0]);
}
glEnd();
}
glLineWidth(1);
// now draw the coords...
//
for (int i=0; i<sizeof(v3Corners)/sizeof(v3Corners[0]); i++)
{
extern LPCSTR vtos(vec3_t v3);
LPCSTR psCoordString = vtos(v3Corners[i]);
Text_Display(psCoordString,v3Corners[i],200,70,0);//228/2,107/2,35/2);
}
// now draw the 3 dimensions (sizes)...
//
float fXDim = v3Maxs[0] - v3Mins[0];
float fYDim = v3Maxs[1] - v3Mins[1];
float fZDim = v3Maxs[2] - v3Mins[2];
vec3_t v3Pos;
// X...
//
VectorAdd (v3Corners[0],v3Corners[1],v3Pos);
VectorScale (v3Pos,0.5,v3Pos);
Text_Display(va(" X = %.3f",fXDim),v3Pos,255,128,128); // ruler-pink
//
// Y...
//
VectorAdd (v3Corners[7],v3Corners[0],v3Pos);
VectorScale (v3Pos,0.5,v3Pos);
Text_Display(va(" Y = %.3f",fYDim),v3Pos,255,128,128);
//
// Z...
//
VectorAdd (v3Corners[1],v3Corners[2],v3Pos);
VectorScale (v3Pos,0.5,v3Pos);
Text_Display(va(" Z = %.3f",fZDim),v3Pos,255,128,128);
}
glPopAttrib();
}
}
static void ModelDraw_OriginLines(bool bDrawingForOriginalContainer)
{
if (!gbTextInhibit)
{
if (AppVars.bOriginLines)
{
// draw origin lines...
//
#define ORIGIN_LINE_RADIUS 20
static vec3_t v3X = { ORIGIN_LINE_RADIUS,0,0};
static vec3_t v3XNeg = {-ORIGIN_LINE_RADIUS,0,0};
static vec3_t v3Y = {0, ORIGIN_LINE_RADIUS,0};
static vec3_t v3YNeg = {0,-ORIGIN_LINE_RADIUS,0};
static vec3_t v3Z = {0,0, ORIGIN_LINE_RADIUS};
static vec3_t v3ZNeg = {0,0,-ORIGIN_LINE_RADIUS};
glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
{
glColor3f(0,0,1);
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
glBegin(GL_LINES);
{
glVertex3fv(v3X);
glVertex3fv(v3XNeg);
glVertex3fv(v3Y);
glVertex3fv(v3YNeg);
glVertex3fv(v3Z);
glVertex3fv(v3ZNeg);
}
glEnd();
#define ORIGIN_TEXT_COLOUR 255,0,255
Text_Display( "X", v3X, ORIGIN_TEXT_COLOUR);
Text_Display("-X", v3XNeg, ORIGIN_TEXT_COLOUR);
Text_Display( "Y", v3Y, ORIGIN_TEXT_COLOUR);
Text_Display("-Y", v3YNeg, ORIGIN_TEXT_COLOUR);
Text_Display( "Z", v3Z, ORIGIN_TEXT_COLOUR);
Text_Display("-Z", v3ZNeg, ORIGIN_TEXT_COLOUR);
}
glPopAttrib();
}
if (AppVars.bRuler && bDrawingForOriginalContainer)
{
// draw a height graph thing...
//
glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
{
glColor3f(1,0.5,0.5);
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glDisable(GL_BLEND);
glBegin(GL_LINES);
{
#define iRULER_HEIGHT (ORIGIN_LINE_RADIUS*8)
glVertex3f(-ORIGIN_LINE_RADIUS, 0, iRULER_HEIGHT/2);
glVertex3f(-ORIGIN_LINE_RADIUS, 0, -iRULER_HEIGHT/2);
for (int i=0; i<iRULER_HEIGHT; i++)
{
glVertex3f(-ORIGIN_LINE_RADIUS, 0, i-(iRULER_HEIGHT/2));
glVertex3f( 0, 0, i-(iRULER_HEIGHT/2));
}
}
glEnd();
for (int i=0; i<iRULER_HEIGHT; i++)
{
vec3_t v3 = {-(ORIGIN_LINE_RADIUS+(ORIGIN_LINE_RADIUS/6)), 0, i-(iRULER_HEIGHT/2)};
Text_Display( va("%d __",i-(iRULER_HEIGHT/2)), v3, 255,128,128);//ORIGIN_TEXT_COLOUR);
}
}
glPopAttrib();
}
}
}
static void BoneMatrix2GLMatrix(const mdxaBone_t *pBone, float *pGLMatrix)
{
pGLMatrix[0] = pBone->matrix[0][0]; pGLMatrix[4] = pBone->matrix[0][1]; pGLMatrix[8] = pBone->matrix[0][2]; pGLMatrix[12] = pBone->matrix[0][3];
pGLMatrix[1] = pBone->matrix[1][0]; pGLMatrix[5] = pBone->matrix[1][1]; pGLMatrix[9] = pBone->matrix[1][2]; pGLMatrix[13] = pBone->matrix[1][3];
pGLMatrix[2] = pBone->matrix[2][0]; pGLMatrix[6] = pBone->matrix[2][1]; pGLMatrix[10]= pBone->matrix[2][2]; pGLMatrix[14] = pBone->matrix[2][3];
pGLMatrix[3] = 0; pGLMatrix[7] = 0; pGLMatrix[11]= 0; pGLMatrix[15] = 1;
}
typedef struct
{
float matrix[16];
} GLMatrix_t;
typedef vector <GLMatrix_t> PreRenderedMatrixPtrs_t;
PreRenderedMatrixPtrs_t PreRenderedMatrixPtrs;
void PreRenderedMatrixPtrs_glMultiply(void)
{
// reverse-iterate matrix chain to do all transforms...
//
for (int iMatrix = PreRenderedMatrixPtrs.size()-1; iMatrix>=0; iMatrix--)
{
GLMatrix_t *pMatrix = &PreRenderedMatrixPtrs[iMatrix];
glMultMatrixf(pMatrix->matrix);
}
}
// specialised use during render process only (assumes current valid GL modelview matrix)
//
static bool ModelContainer_ApplyRenderedMatrixToGL(ModelContainer_t *pContainer, int iBoltIndex, bool bBoltIsBone)
{
bool bProceed = false;
switch (pContainer->eModType)
{
case MOD_MDXM:
if (bBoltIsBone)
{
bProceed = (pContainer->XFormedG2BonesValid[iBoltIndex] == true);
if (bProceed)
{
// get lerped and xformed matrix from last render...
//
GLMatrix_t m;
BoneMatrix2GLMatrix(&pContainer->XFormedG2Bones[iBoltIndex], &m.matrix[0]);
// multiply by base pose matrix... (G2 specific)
//
GLMatrix_t m2;
BoneMatrix2GLMatrix(GLMModel_GetBasePoseMatrix(pContainer->hModel, iBoltIndex), &m2.matrix[0]);
//glMultMatrixf(m);
//glMultMatrixf(m2);
PreRenderedMatrixPtrs.push_back(m2); // note reverse order because of push_back (reverse iteration later)
PreRenderedMatrixPtrs.push_back(m);
}
}
else
{
bProceed = (pContainer->XFormedG2TagSurfsValid[iBoltIndex] == true);
//OutputDebugString(va("Checking tag surf %d: %s\n",iBoltIndex, bProceed?"VALID":"BAD"));
if (bProceed)
{
// get lerped and xformed matrix from last render...
//
GLMatrix_t m;
BoneMatrix2GLMatrix(&pContainer->XFormedG2TagSurfs[iBoltIndex], &m.matrix[0]);
PreRenderedMatrixPtrs.push_back(m);
}
}
break;
default:
assert(0);
break;
}
return bProceed;
}
// this goes recursively up the bolting hierarchy and makes a list of matrices to multiply against,
// which we then iterate forwards through to do all the translations... (simpler code, but more cpu work)
//
static bool R_ModelContainer_ApplyGLParentMatrices(ModelContainer_t *pContainer)
{
bool bProceed = true;
if (pContainer->pBoneBolt_ParentContainer)
{
bProceed = ModelContainer_ApplyRenderedMatrixToGL(pContainer->pBoneBolt_ParentContainer, pContainer->iBoneBolt_ParentBoltIndex, true); // bBoltIsBone
if (bProceed)
{
bProceed = R_ModelContainer_ApplyGLParentMatrices(pContainer->pBoneBolt_ParentContainer);
}
}
else
if (pContainer->pSurfaceBolt_ParentContainer)
{
bProceed = ModelContainer_ApplyRenderedMatrixToGL(pContainer->pSurfaceBolt_ParentContainer, pContainer->iSurfaceBolt_ParentBoltIndex, false); // bBoltIsBone
if (bProceed)
{
bProceed = R_ModelContainer_ApplyGLParentMatrices(pContainer->pSurfaceBolt_ParentContainer);
}
}
return bProceed;
}
// sometimes I really hate this stuff, so I bury it away quietly...
//
static bool ModelContainer_HandleAllEvilMatrixCode(ModelContainer_t *pContainer)
{
PreRenderedMatrixPtrs.clear();
bool bProceed = R_ModelContainer_ApplyGLParentMatrices(pContainer);
if (bProceed)
{
PreRenderedMatrixPtrs_glMultiply();
}
return bProceed;
}
ModelContainer_t *gpContainerBeingRendered = NULL;
static void ModelContainer_CallBack_AddToDrawList(ModelContainer_t* pContainer, void *pvData)
{
glPushMatrix();
{
bool bProceed = ModelContainer_HandleAllEvilMatrixCode(pContainer);
if (bProceed)
{
R_ModView_BeginEntityAdd(); // ##################
// updateme?
R_ModView_AddEntity(pContainer->hModel, // ModelHandle_t hModel,
pContainer->iCurrentFrame_Primary,// int iFrame,
AppVars.bInterpolate?pContainer->iOldFrame_Primary:pContainer->iCurrentFrame_Primary, // int iOldFrame,
pContainer->iBoneNum_SecondaryStart,
pContainer->iCurrentFrame_Secondary,// int iFrame,
AppVars.bInterpolate?pContainer->iOldFrame_Secondary:pContainer->iCurrentFrame_Secondary, // int iOldFrame,
pContainer->iSurfaceNum_RootOverride,
AppVars.fFramefrac, // float fLerp
pContainer->slist,
pContainer->blist,
pContainer->XFormedG2Bones,
pContainer->XFormedG2BonesValid,
pContainer->XFormedG2TagSurfs,
pContainer->XFormedG2TagSurfsValid,
//
&pContainer->iRenderedTris,
&pContainer->iRenderedVerts,
&pContainer->iRenderedSurfs,
&pContainer->iXformedG2Bones,
// &pContainer->iRenderedBoneWeightsThisSurface,
&pContainer->iRenderedBoneWeights,
&pContainer->iOmittedBoneWeights
);
// render process will fill these in...
//
pContainer->iRenderedTris = 0;
pContainer->iRenderedVerts = 0;
pContainer->iRenderedSurfs = 0;
pContainer->iXformedG2Bones = 0;
// pContainer->iRenderedBoneWeightsThisSurface = 0;
pContainer->iRenderedBoneWeights = 0;
pContainer->iOmittedBoneWeights = 0;
//
// and will overwrite some/most of these, but to default them...
//
ZEROMEM(pContainer->XFormedG2Bones);
ZEROMEM(pContainer->XFormedG2BonesValid);
ZEROMEM(pContainer->XFormedG2TagSurfs);
ZEROMEM(pContainer->XFormedG2TagSurfsValid);
//############
glColor3f(1,1,1);
gpContainerBeingRendered = pContainer; // aaaaarrrgghhh!!! hack city!!!!
{
RE_GenerateDrawSurfs();
RE_RenderDrawSurfs();
}
gpContainerBeingRendered = NULL;
ModelDraw_BoundingBox( pContainer, !!(pContainer == &AppVars.Container) );
ModelDraw_Floor ( pContainer, !!(pContainer == &AppVars.Container) );
ModelDraw_OriginLines( !!(pContainer == &AppVars.Container) );
//#####R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_InfoText, &TextData);
R_ModelContainer_CallBack_InfoText(pContainer, &TextData);
}
}
glPopMatrix();
}
static void ModelList_AddModelsToDrawList(void)
{
ModelDraw_InfoText_Header();
//######### R_ModView_BeginEntityAdd();
// add all models to draw list... (now draws them as well, and prints stats)
//
R_ModelContainer_Apply(&AppVars.Container, ModelContainer_CallBack_AddToDrawList);
ModelDraw_InfoText_Totals();
}
// used by both bones and tag surfaces.
//
// bHilitAsPure is true if this is a direct highlight, else false when called as a referenced bone during surface
// highlighting. This just affects outputs size and brightness, to make things easier to see.
//
static void DrawTagOrigin(bool bHilitAsPure, LPCSTR psTagText /* can be NULL */)
{
// draw origin lines...
//
#define TAGHIGHLIGHT_LINE_LEN (bHilitAsPure ? 20 : 10) // spot the retro fit... ;-)
#define TAGHIGHLIGHT_TEXT_COLOUR bHilitAsPure?255:150, 0,bHilitAsPure?255:150 // ""
#define TAGHIGHLIGHT_TEXT_DISTANCE (bHilitAsPure ? 20 : 10) // spot the retro fit... ;-)
/*static */vec3_t v3X = { TAGHIGHLIGHT_LINE_LEN,0,0};
/*static */vec3_t v3XNeg = {-TAGHIGHLIGHT_LINE_LEN,0,0};
/*static */vec3_t v3Y = {0, TAGHIGHLIGHT_LINE_LEN,0};
/*static */vec3_t v3YNeg = {0,-TAGHIGHLIGHT_LINE_LEN,0};
/*static */vec3_t v3Z = {0,0, TAGHIGHLIGHT_LINE_LEN};
/*static */vec3_t v3ZNeg = {0,0,-TAGHIGHLIGHT_LINE_LEN};
glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT);
{
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
if (AppVars.bShowOriginsAsRGB)
{
glLineWidth(bHilitAsPure?4:2);
{
// X (red)...
//
glColor3f( (bHilitAsPure?1.0f:0.8f), 0, 0);
glBegin(GL_LINES);
{
glVertex3fv(v3X);
glVertex3f (0,0,0);
}
glEnd();
// Y (green)...
//
glColor3f( 0, (bHilitAsPure?1.0f:0.8f), 0);
glBegin(GL_LINES);
{
glVertex3fv(v3Y);
glVertex3f (0,0,0);
}
glEnd();
// Z (blue)...
//
glColor3f( 0, 0, (bHilitAsPure?1.0f:0.8f));
glBegin(GL_LINES);
{
glVertex3fv(v3Z);
glVertex3f (0,0,0);
}
glEnd();
}
glLineWidth(1);
}
else
{
glColor3f(0,bHilitAsPure?1:0.8,0);
glBegin(GL_LINES);
{
glVertex3fv(v3X);
glVertex3fv(v3XNeg);
glVertex3fv(v3Y);
glVertex3fv(v3YNeg);
glVertex3fv(v3Z);
glVertex3fv(v3ZNeg);
}
glEnd();
Text_Display( "X", v3X, TAGHIGHLIGHT_TEXT_COLOUR);
Text_Display("-X", v3XNeg, TAGHIGHLIGHT_TEXT_COLOUR);
Text_Display( "Y", v3Y, TAGHIGHLIGHT_TEXT_COLOUR);
Text_Display("-Y", v3YNeg, TAGHIGHLIGHT_TEXT_COLOUR);
Text_Display( "Z", v3Z, TAGHIGHLIGHT_TEXT_COLOUR);
Text_Display("-Z", v3ZNeg, TAGHIGHLIGHT_TEXT_COLOUR);
}
if (psTagText)
{
glColor3f(0.5,0.5,0.5);
/*static */vec3_t v3NamePos = {TAGHIGHLIGHT_TEXT_DISTANCE,TAGHIGHLIGHT_TEXT_DISTANCE,TAGHIGHLIGHT_TEXT_DISTANCE};
glLineStipple( 8, 0xAAAA);
glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINES);
{
glVertex3f(0,0,0);
glVertex3fv(v3NamePos);
}
glEnd();
Text_Display( psTagText, v3NamePos, TAGHIGHLIGHT_TEXT_COLOUR );
}
}
glPopAttrib();
}
static void ModelContainer_DrawTagSurfaceHighlights(ModelContainer_t *pContainer)
{
if ( !gbTextInhibit &&
(AppVars.bSurfaceHighlight && pContainer->iSurfaceHighlightNumber != iITEMHIGHLIGHT_NONE)
)
{
for (int iSurfaceIndex=0; iSurfaceIndex < pContainer->iNumSurfaces; iSurfaceIndex++)
{
bool bHighLit = ( pContainer->iSurfaceHighlightNumber == iSurfaceIndex ||
pContainer->iSurfaceHighlightNumber == iITEMHIGHLIGHT_ALL_TAGSURFACES
);
if (bHighLit && Model_SurfaceIsTag( pContainer, iSurfaceIndex))
{
// this may get called twice, so pre-eval it here for speed...
//
LPCSTR psSurfaceName = Model_GetSurfaceName( pContainer->hModel, iSurfaceIndex );
glPushMatrix();
{
PreRenderedMatrixPtrs.clear();
bool bProceed = ModelContainer_ApplyRenderedMatrixToGL(pContainer, iSurfaceIndex, false); // bBoneIsBolt
if (bProceed)
{
// bone wants to be highlighted, and isn't disabled by virtue of disabled parent surface...
//
PreRenderedMatrixPtrs_glMultiply();
// note special logic for first bool, in other words, if explicitly highlighting this surface, then draw
// as normal, else if highlighting all tags, then just do as smaller/dimmer...
//
DrawTagOrigin( !(pContainer->iSurfaceHighlightNumber == iITEMHIGHLIGHT_ALL_TAGSURFACES), psSurfaceName);
}
}
glPopMatrix();
}
}
}
}
// draw origin lines and name by any bone that's highlighted...
//
static void ModelContainer_DrawBoneHighlights(ModelContainer_t *pContainer)
{
bool bBoneAliasHighlightingActive = (AppVars.bSurfaceHighlight && AppVars.bSurfaceHighlightShowsBoneWeighting && pContainer->iSurfaceHighlightNumber >=0 ); // -ve numbers are NONE, ALL or ALIASED, none of which we want
if ( !gbTextInhibit &&
(
(AppVars.bBoneHighlight && pContainer->iBoneHighlightNumber != iITEMHIGHLIGHT_NONE)
||
bBoneAliasHighlightingActive
)
)
{
for (int iBoneIndex=0; iBoneIndex < pContainer->iNumBones; iBoneIndex++)
{
// this may get called twice, so pre-eval it here for speed...
//
LPCSTR psBoneName = Model_GetBoneName( pContainer->hModel, iBoneIndex );
bool bHilitAsPureBone = ( pContainer->iBoneHighlightNumber == iITEMHIGHLIGHT_ALL
||
pContainer->iBoneHighlightNumber == iBoneIndex
||
(pContainer->iBoneHighlightNumber == iITEMHIGHLIGHT_ALIASED && pContainer->Aliases.find(psBoneName)!=pContainer->Aliases.end())
);
bool bHilitAsBoneWeight = (bBoneAliasHighlightingActive &&
Model_SurfaceContainsBoneReference(pContainer->hModel, AppVars.iLOD, pContainer->iSurfaceHighlightNumber, iBoneIndex)
);
if ( bHilitAsPureBone || bHilitAsBoneWeight )
{
glPushMatrix();
{
PreRenderedMatrixPtrs.clear();
bool bProceed = ModelContainer_ApplyRenderedMatrixToGL(pContainer, iBoneIndex, true); // bBoneIsBolt
if (bProceed)
{
// bone wants to be highlighted, and isn't disabled by virtue of disabled parent surface...
//
PreRenderedMatrixPtrs_glMultiply();
// note special logic for first bool, in other words, if explicitly highlighting this bone, then draw as normal,
// else if highlighting as either part of a boneref, or one of ALL then draw smaller/dimmer...
//
DrawTagOrigin( (pContainer->iBoneHighlightNumber < 0 && pContainer->iBoneHighlightNumber != iITEMHIGHLIGHT_NONE)?false:bHilitAsPureBone,
va("%s%s%s",
psBoneName,
(!bHilitAsPureBone || pContainer->iBoneHighlightNumber != iITEMHIGHLIGHT_ALIASED)?"":va(" (Alias: %s)",pContainer->Aliases[psBoneName].c_str()),
bHilitAsBoneWeight?va(" ( %d )",iBoneIndex):""
)
);
}
}
glPopMatrix();
}
}
}
}
static Sequence_t *Stats_GetSequenceDisplayInfo(ModelContainer_t *pContainer, bool bPrimary, byte *pR = NULL, byte *pG = NULL, byte *pB = NULL, bool *pLocked = NULL);
static Sequence_t *Stats_GetSequenceDisplayInfo(ModelContainer_t *pContainer, bool bPrimary, byte *pR, byte *pG, byte *pB, bool *pLocked)
{
// return either the locked local frame number in red (if anim locking is on), or just the anim sequence name
// if there's one corresponding to this...
//
Sequence_t* pLockedSequence = NULL;
if (Model_MultiSeq_IsActive(pContainer, bPrimary))
{
int iSequenceNumber = Model_MultiSeq_SeqIndexFromFrame(pContainer, bPrimary ? pContainer->iCurrentFrame_Primary : pContainer->iCurrentFrame_Secondary, bPrimary, false );
pLockedSequence = (iSequenceNumber == -1) ? NULL : &pContainer->SequenceList[iSequenceNumber];
}
else
{
int iSequenceNumber = bPrimary ? pContainer->iSequenceLockNumber_Primary : pContainer->iSequenceLockNumber_Secondary;
pLockedSequence = (iSequenceNumber == -1) ? NULL : &pContainer->SequenceList[iSequenceNumber];
}
if (pLockedSequence)
{
if ( pLocked)
*pLocked = true;
if ( pR)
*pR = 255;
if ( pG)
*pG = 0;
if ( pB)
*pB = 0;
}
else
{
// no locked sequence, so print as normal and try and derive the one we just happen to be within...
//
if ( pLocked)
*pLocked = false;
if ( pR)
*pR = 0; // dim(ish) cyan
if ( pG)
*pG = 200; // ...
if ( pB)
*pB = 200; // ...
pLockedSequence = Sequence_DeriveFromFrame( bPrimary ? pContainer->iCurrentFrame_Primary : pContainer->iCurrentFrame_Secondary, pContainer );
}
return pLockedSequence;
}
//#define ARB_VERTINFO_PADDING 30 //15 // 20
#define ARB_LOD_PADDING 9
static LPCSTR Stats_GetVertInfo(int iVerts, int iTris, int iSurfs, int iXFormedG2Bones, int iRenderedBoneWeights, int iOmittedBoneWeights)
{
return va("(V:%4d T:%4d S:%2d%s)",
iVerts,
iTris,
iSurfs,
(iXFormedG2Bones!=0)
?
va(" XF:%3d WT:%4d%s",iXFormedG2Bones,iRenderedBoneWeights,AppVars.bBoneWeightThreshholdingActive?va("+%d",iOmittedBoneWeights):"")
:
""
);
}
static LPCSTR Stats_GetVertInfo(ModelContainer_t *pContainer)
{
return Stats_GetVertInfo( pContainer->iRenderedVerts, // int iVerts,
pContainer->iRenderedTris, // int iTris,
pContainer->iRenderedSurfs, // int iSurfs,
(pContainer->eModType == MOD_MDXM)?pContainer->iXformedG2Bones:0, // int iXFormedG2Bones
(pContainer->eModType == MOD_MDXM)?pContainer->iRenderedBoneWeights:0, //int iRenderedBoneWeights,
(pContainer->eModType == MOD_MDXM)?pContainer->iOmittedBoneWeights:0 //int iOmittedBoneWeights,
);
}
static LPCSTR Stats_GetName(ModelContainer_t *pContainer)
{
return va("%s",Filename_WithoutExt(Filename_WithoutPath(pContainer->sLocalPathName)));
}
static void R_ModelContainer_CallBack_MeasureDigits(ModelContainer_t *pContainer, void *pvData)
{
TextData_t *pTextData = (TextData_t *) pvData;
// heh... can you believe this?...
//
char sTest[1024];
// frames readout...
//
sprintf(sTest,"%d",pContainer->iNumFrames);
if (pTextData->iFrameDigitsNeeded < strlen(sTest))
pTextData->iFrameDigitsNeeded = strlen(sTest);
// attachment string...
//
sprintf(sTest,Stats_GetAttachmentString(pContainer));
if (pTextData->iAttachedViaCharsNeeded < strlen(sTest))
pTextData->iAttachedViaCharsNeeded = strlen(sTest);
// "(Secondary Anim)" goes in same place as attachment string, but on line below (if present),
// so measure and store against same field...
//
if (AppVars.iTotalContainers>1) // because I don't bother printing the header if there's only one model
{
sprintf(sTest,sSECONDARY_ANIM_STATS_STRING);
if (pTextData->iAttachedViaCharsNeeded < strlen(sTest))
pTextData->iAttachedViaCharsNeeded = strlen(sTest);
}
// sequence name string...
//
if (AppVars.iTotalContainers>1 || Model_SecondaryAnimLockingActive(pContainer))
{
// measure primary...
//
Sequence_t *pSequence = Stats_GetSequenceDisplayInfo( pContainer, true );
int iLongestSeqString = pSequence?strlen(Sequence_GetName(pSequence,true)):0;
if (pTextData->iSequenceNameCharsNeeded < iLongestSeqString)
pTextData->iSequenceNameCharsNeeded = iLongestSeqString;
// measure secondary...
//
if (Model_SecondaryAnimLockingActive(pContainer))
{
pSequence = Stats_GetSequenceDisplayInfo( pContainer, false );
iLongestSeqString = pSequence?strlen(Sequence_GetName(pSequence,true)):0;
if (pTextData->iSequenceNameCharsNeeded < iLongestSeqString)
pTextData->iSequenceNameCharsNeeded = iLongestSeqString;
}
}
int iLongestModelNameString = strlen(Stats_GetName(pContainer));
if (pTextData->iModelNameCharsNeeded < iLongestModelNameString)
pTextData->iModelNameCharsNeeded = iLongestModelNameString;
int iLongestVertInfoString = strlen(Stats_GetVertInfo(pContainer));
if (pTextData->iModelVertInfoCharsNeeded < iLongestVertInfoString)
pTextData->iModelVertInfoCharsNeeded = iLongestVertInfoString;
// measure number of lines needed for multi-lock sequences...
//
int iMultiLockSeqs_Primary = //Model_MultiSeq_IsActive(pContainer,true )?
Model_MultiSeq_GetNumEntries(pContainer, true)
//:0
;
int iMultiLockSeqs_Secondary= //Model_MultiSeq_IsActive(pContainer,false)?
Model_MultiSeq_GetNumEntries(pContainer, false)
//:0
;
if (Model_SecondaryAnimLockingActive(pContainer))//iMultiLockSeqs_Secondary)
pTextData->bAnyMultiLockedSecondarySequences = true;
pTextData->iMostMultiLockedSequences = max(pTextData->iMostMultiLockedSequences,iMultiLockSeqs_Primary);
pTextData->iMostMultiLockedSequences = max(pTextData->iMostMultiLockedSequences,iMultiLockSeqs_Secondary);
}
static void R_ModelContainer_CallBack_InfoText(ModelContainer_t *pContainer, void *pvData)
{
TextData_t *pTextData = (TextData_t *) pvData;
pTextData->iTextX = pTextData->iPrevX;
// model name and basic frame info...
//
{
// name...
//
pTextData->iTextX = Text_DisplayFlat( String_EnsureMinLength( Stats_GetName(pContainer), pTextData->iModelNameCharsNeeded),
pTextData->iTextX, pTextData->iTextY,
255,255,0 // RGB (yellow)
);
// frame info...
//
pTextData->iTextX = Text_DisplayFlat( va("Frame: %*d/%*d",
pTextData->iFrameDigitsNeeded, pContainer->iCurrentFrame_Primary,
pTextData->iFrameDigitsNeeded, pContainer->iNumFrames
),
pTextData->iTextX + (2*TEXT_WIDTH), pTextData->iTextY,
0, 255,0 // RGB (green)
);
}
// LOD info...
//
{
int iWhichLOD = (AppVars.iLOD < pContainer->iNumLODs) ? AppVars.iLOD : pContainer->iNumLODs-1;
pTextData->iTextX = Text_DisplayFlat( String_EnsureMinLength(va("(LOD %d/%d)",iWhichLOD,pContainer->iNumLODs),ARB_LOD_PADDING),
pTextData->iTextX+(2*TEXT_WIDTH), pTextData->iTextY,
255/2,255,255/2, // RGB
false
);
}
// vert / tri / surfaces info...
//
{
pTextData->iTextXForVertStats = pTextData->iTextX+(2*TEXT_WIDTH); // record for "totals" later
pTextData->iTextX =
Text_DisplayFlat( String_EnsureMinLength( Stats_GetVertInfo(pContainer), pTextData->iModelVertInfoCharsNeeded ),
pTextData->iTextXForVertStats, pTextData->iTextY,
255, 255/2, 255/2, // RGB (pink)
false
);
pTextData->iTot_RenderedVerts += pContainer->iRenderedVerts;
pTextData->iTot_RenderedTris += pContainer->iRenderedTris;
pTextData->iTot_RenderedSurfs += pContainer->iRenderedSurfs;
if (pContainer->eModType == MOD_MDXM)
{
pTextData->iTot_XformedG2Bones += pContainer->iXformedG2Bones;
pTextData->iTot_RenderedBoneWeights += pContainer->iRenderedBoneWeights;
pTextData->iTot_OmittedBoneWeights += pContainer->iOmittedBoneWeights;
}
}
// attached-via info... (bone bolt or surface bolt)
//
int iAttachedVia_X = pTextData->iTextX+(2*TEXT_WIDTH); // also used for "secondary anim" printf later
{
pTextData->iTextX =
Text_DisplayFlat( String_EnsureMinLength( Stats_GetAttachmentString(pContainer), pTextData->iAttachedViaCharsNeeded ),
iAttachedVia_X, pTextData->iTextY,
0, 255/2,0, // RGB
false
);
}
// sequence / anim info...
//
{
bool bLocked = true;
byte _R = 255;
byte _G = 0;
byte _B = 0;
// primary...
//
Sequence_t *pSequence = Stats_GetSequenceDisplayInfo(pContainer, true, &_R, &_G, &_B, &bLocked);
int iPrimarySeqTextX = pTextData->iTextX + (2*TEXT_WIDTH);
if (pSequence && !pSequence->bIsDefault) // no point printing default sequence info
{
// no space after "Seq: now because Sequence_GetName() prepends with either ' ' or '-'...
//
pTextData->iTextX = Text_DisplayFlat( va("Seq:%s",String_EnsureMinLength(Sequence_GetName(pSequence,true),pTextData->iSequenceNameCharsNeeded)), iPrimarySeqTextX, pTextData->iTextY, _R,_G,_B);
pTextData->iTextX = Text_DisplayFlat( va("(%d/%d)",pContainer->iCurrentFrame_Primary - pSequence->iStartFrame, pSequence->iFrameCount),
pTextData->iTextX+(1*TEXT_WIDTH), pTextData->iTextY,
_R,
_G,
_B
);
}
// secondary...
//
if (Model_SecondaryAnimLockingActive(pContainer))
{
pTextData->iTextY += TEXT_DEPTH;
if (AppVars.iTotalContainers>1) // looks neater if i don't bother printing this when only one model
{
// "(Secondary anim)"...
//
Text_DisplayFlat( String_EnsureMinLength( sSECONDARY_ANIM_STATS_STRING, pTextData->iAttachedViaCharsNeeded ),
iAttachedVia_X, pTextData->iTextY,
128,128,128, // RGB
false
);
}
// sequence name...
//
pSequence = Stats_GetSequenceDisplayInfo(pContainer, false, &_R, &_G, &_B, &bLocked);
if (pSequence && !pSequence->bIsDefault) // no point printing default sequence info
{
// no space after "Seq: now because Sequence_GetName() prepends with either ' ' or '-'...
//
pTextData->iTextX = Text_DisplayFlat( va("Seq:%s",String_EnsureMinLength(Sequence_GetName(pSequence,true),pTextData->iSequenceNameCharsNeeded)), iPrimarySeqTextX, pTextData->iTextY, _R,_G,_B);
pTextData->iTextX = Text_DisplayFlat( va("(%d/%d)",pContainer->iCurrentFrame_Secondary - pSequence->iStartFrame, pSequence->iFrameCount),
pTextData->iTextX+(1*TEXT_WIDTH), pTextData->iTextY,
_R,
_G,
_B
);
}
}
}
// multi-lock sequence info...
//
{
// (AppVars.iTotalContainers>1) // looks neater if i don't bother printing this when only one model
for (int iLockPass = 0; iLockPass<2; iLockPass++) // 2 passes, primary and secondary lock
{
if (//Model_MultiSeq_IsActive ( pContainer, !iLockPass )
//&&
Model_MultiSeq_GetNumEntries( pContainer, !iLockPass )
)
{
// note leading spaces in all text items to match leading-spaces on sequence names (to column-match
// for some that have leading '-' on them...
//
byte r,g,b;
// OutputDebugString(va("most locked = %d\n",pTextData->iMostMultiLockedSequences));
int iMultiLockedTextY = g_iScreenHeight - ((pTextData->iMostMultiLockedSequences
+1 // <name>
+(pTextData->bAnyMultiLockedSecondarySequences?1:0) // <(primary)>
+1 // (spacing between header and sequences)
+3 // to move it up above other text already onscreen
)*TEXT_DEPTH);
// OutputDebugString(va("iMultiLockedTextY = %d\n",iMultiLockedTextY));
int iFurthestX = 0;
// <name>..
r=255,g=255,b=0; // RGB (yellow)
if (!Model_MultiSeq_IsActive( pContainer, !iLockPass ))
{
r=128,g=128,b=0; // dim yellow if inactive
}
int
iTempX = Text_DisplayFlat( va(" %s",Filename_WithoutExt(Filename_WithoutPath(pContainer->sLocalPathName))),
pTextData->iMultiLockedTextX, iMultiLockedTextY,
r,g,b
);
iFurthestX = max(iFurthestX,iTempX);
iMultiLockedTextY += TEXT_DEPTH;
// (primary) or (secondary)...
//
if (pTextData->bAnyMultiLockedSecondarySequences
&&
Model_SecondaryAnimLockingActive(pContainer) // this probably makes the above check spurious, but wtf?
)
{
iTempX = Text_DisplayFlat( !iLockPass?" ( Primary )":" ( Secondary )",
pTextData->iMultiLockedTextX, iMultiLockedTextY,
128,128,128 // grey
);
iFurthestX = max(iFurthestX,iTempX);
iMultiLockedTextY += TEXT_DEPTH;
}
// (space)...
//
iMultiLockedTextY += TEXT_DEPTH;
// now list the actual multilock sequences...
//
int iMultiLockedSequenceWereWithin = Model_MultiSeq_SeqIndexFromFrame(pContainer, !iLockPass?pContainer->iCurrentFrame_Primary:pContainer->iCurrentFrame_Secondary, !iLockPass, false );
for (int iSeqIndex=0; iSeqIndex<Model_MultiSeq_GetNumEntries(pContainer, !iLockPass); iSeqIndex++)
{
int iSeqEntry = Model_MultiSeq_GetEntry(pContainer, iSeqIndex, !iLockPass);
LPCSTR psSeqName = Model_Sequence_GetName (pContainer, iSeqEntry, true );
r=0,g=200,b=200; // default cyan
if (!Model_MultiSeq_IsActive( pContainer, !iLockPass ))
{
r=100,g=100,b=100; // greyed out
}
else
{
// locked?
if (iSeqEntry == iMultiLockedSequenceWereWithin)
r=255,g=0,b=0; // red
}
iTempX = Text_DisplayFlat( psSeqName,
pTextData->iMultiLockedTextX, iMultiLockedTextY,
r,g,b
);
iFurthestX = max(iFurthestX,iTempX);
iMultiLockedTextY += TEXT_DEPTH;
}
// move to next column for next model with multilocks...
//
pTextData->iMultiLockedTextX = iFurthestX + (2*TEXT_WIDTH);
}
}
}
pTextData->iTextY += TEXT_DEPTH;
ModelContainer_DrawBoneHighlights(pContainer);
ModelContainer_DrawTagSurfaceHighlights(pContainer);
}
static void ModelDraw_InfoText_Header(void)
{
ZEROMEM(TextData);
// display current picmip state...
//
sprintf(TextData.sString,"( PICMIP: %d )",TextureList_GetMip());
Text_DisplayFlat(TextData.sString,
1*TEXT_WIDTH,
1*TEXT_DEPTH,
100,100,100,
false
);
// display main model's skin file data if applicable (SOF2 models only, currently)...
//
ModelContainer_t *pContainer = &AppVars.Container; // primary model only
strcpy(TextData.sString,"");
if (pContainer->SkinSets.size())
{
sprintf(TextData.sString,"Skin File: '%s' Ethnic version: '%s'",
pContainer->strCurrentSkinFile.c_str(),
pContainer->strCurrentSkinEthnic.c_str()
);
}
if (pContainer->OldSkinSets.size())
{
sprintf(TextData.sString,"Skin File: '%s'",pContainer->strCurrentSkinFile.c_str());
}
if (strlen(TextData.sString))
{
Text_DisplayFlat(TextData.sString,
1*TEXT_WIDTH,
g_iScreenHeight - (2*TEXT_DEPTH),
100,100,100,
false
);
}
// display current FPS and interp state...
//
sprintf(TextData.sString,"FPS: %2.2f %s",1/(AppVars.dAnimSpeed),AppVars.bAnimate?"(Playing)":"(Stopped)");
int iFPS_Xpos = (g_iScreenWidth/2)-( (strlen(TextData.sString)/2)*TEXT_WIDTH);
if (AppVars.bBoneWeightThreshholdingActive)
{
LPCSTR psThreshholdString = va("BoneWeightThresh: %g%%",AppVars.fBoneWeightThreshholdPercent);
Text_DisplayFlat(psThreshholdString, iFPS_Xpos - ((strlen(psThreshholdString)+2)*TEXT_WIDTH),1*TEXT_DEPTH,
180,180,180,false);
}
TextData.iTextX =
Text_DisplayFlat(TextData.sString, iFPS_Xpos, 1*TEXT_DEPTH,
255,255,255,
false
);
if (AppVars.bInterpolate)
{
TextData.iTextX = Text_DisplayFlat("(Interpolated)", TextData.iTextX+(2*TEXT_WIDTH),1*TEXT_DEPTH, 255/2,255/2,255/2,false);
}
TextData.iTextX = Text_DisplayFlat(va("(LOD: %d)",AppVars.iLOD+1), TextData.iTextX+(2*TEXT_WIDTH), 1*TEXT_DEPTH, 255/2,255,255/2,false);
/* Text_DisplayFlat(sString, g_iScreenWidth-((strlen(sString)+2)*TEXT_WIDTH),
2 *TEXT_DEPTH,
255,255,255,
false
);*/
TextData.iTextX = Text_DisplayFlat(va("( FOV: %g )",AppVars.dFOV), TextData.iTextX+(2*TEXT_WIDTH),1*TEXT_DEPTH, 255, 255, 255, false);
if (AppVars.bUseAlpha)
{
Text_DisplayFlat("( Alpha )", TextData.iTextX+(2*TEXT_WIDTH),1*TEXT_DEPTH, 128, 128, 128, false);
}
// display verts/tris/surfaces info for all rendered models...
//
TextData.iTextY = 3*TEXT_DEPTH;
TextData.iPrevX = 2*TEXT_WIDTH;
TextData.iTot_RenderedVerts = 0;
TextData.iTot_RenderedTris = 0;
TextData.iTot_RenderedSurfs = 0;
TextData.iTot_XformedG2Bones= 0;
TextData.iTot_RenderedBoneWeights = 0;
TextData.iTot_OmittedBoneWeights = 0;
// a tacky bit of code that looks nicer on output, basically I want to know how many digits I need for
// the frame printing to make all the loaded models line up nicely... :-)
//
TextData.iFrameDigitsNeeded = 0;
TextData.iAttachedViaCharsNeeded = 0;
TextData.iSequenceNameCharsNeeded = 0;
TextData.iModelNameCharsNeeded = 0;
TextData.iModelVertInfoCharsNeeded = 0;
TextData.iMostMultiLockedSequences = 0;
TextData.iMultiLockedTextX = 1*TEXT_WIDTH; // was 2 until I added leading spaces to seq-name for +/- distinction
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_MeasureDigits, &TextData);
}
static void ModelDraw_InfoText_Totals(void)
{
// print totals... (but only if > 1 model loaded, else no point)
//
if (AppVars.iTotalContainers>1)
{
TextData.iTextX = TextData.iPrevX;
TextData.iTextY += TEXT_DEPTH;
strcpy(TextData.sString,String_EnsureMinLength("( totals )",TextData.iModelNameCharsNeeded));
TextData.iTextX = Text_DisplayFlat(TextData.sString, TextData.iTextX, TextData.iTextY, 255,255,0); // yellow
TextData.iTextX =
Text_DisplayFlat( String_EnsureMinLength(
Stats_GetVertInfo( TextData.iTot_RenderedVerts, // int iVerts
TextData.iTot_RenderedTris, // int iTris
TextData.iTot_RenderedSurfs, // int iSurfs
TextData.iTot_XformedG2Bones, // int iXFormedG2Bones
TextData.iTot_RenderedBoneWeights,// int iRenderedBoneWeights
TextData.iTot_OmittedBoneWeights // int iOmittedBoneWeights
),
TextData.iModelVertInfoCharsNeeded
),
TextData.iTextXForVertStats/*TextData.iTextX+(2*TEXT_WIDTH)*/, TextData.iTextY,
255, 255/2, 255/2, // RGB (pink)
false
);
TextData.iTextY += TEXT_DEPTH;
}
// kinda nice to know about this...
//
if (AppVars.bForceWhite)
{
TextData.iTextY += TEXT_DEPTH*2;
TextData.iTextX = TEXT_WIDTH*1;
Text_DisplayFlat( "( FORCEWHITE )",
TextData.iTextX, TextData.iTextY,
255, 255, 255, // RGB white
false
);
}
if (AppVars.bVertWeighting && AppVars.bAtleast1VertWeightDisplayed)
{
// TextData.iTextY += TEXT_DEPTH;
// TextData.iTextY += TEXT_DEPTH;
TextData.iTextY = g_iScreenHeight/2;
TextData.iTextX = TextData.iPrevX;
Text_DisplayFlat( "# BoneWeights per vert:",
TextData.iTextX, TextData.iTextY,
255, 255/2, 255/2, // RGB (pink)
false
);
TextData.iTextY += TEXT_DEPTH;
byte r,g,b;
for (int i= (AppVars.iSurfaceNumToHighlight == iITEMHIGHLIGHT_ALL)?3:1; i<6; i++)
{
GetWeightColour(i,r,g,b);
Text_DisplayFlat( va("%d%s",i,i>=5?"+":""),
TextData.iTextX, TextData.iTextY,
r,g,b,
false
);
TextData.iTextY += TEXT_DEPTH;
}
AppVars.bAtleast1VertWeightDisplayed = false;
}
/*
// temp general test...
//
Text_DisplayFlat("top left",0,0,255,255,255);
Text_DisplayFlat("top right",g_iScreenWidth - (TEXT_WIDTH*strlen("top right")) ,0,255,255,255);
Text_DisplayFlat("bottom left",0,g_iScreenHeight - TEXT_DEPTH,255,255,255);
Text_DisplayFlat("bottom right",g_iScreenWidth - (TEXT_WIDTH*strlen("bottom right")),g_iScreenHeight - TEXT_DEPTH,255,255,255);
*/
}
int giRenderCount=0; // reset/checked during gallery loop
static void ModelList_Render_Actual(int iWindowWidth, int iWindowHeight)
{
if (!gbRenderInhibit)
{
giRenderCount++;
bool bCatchError = false;
// giTotVertsDrawn = 0; // stats
// giTotTrisDrawn = 0; //
// giTotSurfsDrawn = 0;
GL_Enter3D( AppVars.dFOV, iWindowWidth, iWindowHeight, AppVars.bWireFrame, false);
{// CLS, & setup GL stuff...
glClearColor((float)1/((float)256/(float)AppVars._R), (float)1/((float)256/(float)AppVars._G), (float)1/((float)256/(float)AppVars._B), 0.0f);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity ();
glTranslatef ( AppVars.xPos, AppVars.yPos , AppVars.zPos );
glScalef ( (float)0.05 , (float)0.05, (float)0.05 );
glRotatef ( AppVars.rotAngleX, 1.0 , 0.0 , 0.0 );
glRotatef ( AppVars.rotAngleY, 0.0 , 1.0 , 0.0 );
glRotatef ( AppVars.rotAngleZ, 1.0 , 0.0 , 0.0 );
extern bool gbScrollLockActive;
if (gbScrollLockActive)
{
glTranslatef ( AppVars.xPos_SCROLL, AppVars.yPos_SCROLL , AppVars.zPos_SCROLL );
// glRotatef ( AppVars.rotAngleX_SCROLL, 1.0 , 0.0 , 0.0 );
// glRotatef ( AppVars.rotAngleY_SCROLL, 0.0 , 1.0 , 0.0 );
// glRotatef ( AppVars.rotAngleZ_SCROLL, 1.0 , 0.0 , 0.0 );
}
if (AppVars.bUseAlpha && !AppVars.bWireFrame)
{
glEnable (GL_BLEND);
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
glDisable(GL_BLEND);
}
}
if (!Model_Loaded())
return;
try
{
ModelList_AddModelsToDrawList();
// call the huge pile of cut-paste renderer code from elsewhere... (fingers crossed)
//
////// ####RE_GenerateDrawSurfs();
////// ####RE_RenderDrawSurfs();
// RB_GetDrawStats(&giTotVertsDrawn, &giTotTrisDrawn, &giTotSurfsDrawn);
}
catch(LPCSTR psMessage)
{
Model_Delete();
ErrorBox(psMessage);
bCatchError = true;
}
// if all went well, draw view axis etc...
//
if (!bCatchError)
{
glDisable(GL_BLEND);
// ( functions inhibit-checked internally )
//
//##### ModelDraw_OriginLines();
//##### ModelDraw_InfoText();
}
//if (bFinalReturn)
{
// static int z=0;
// z++;
// OutputDebugString(va("ModelList_Render: %d\n",z));
}
}
}
bool gbInRenderer = false;
void ModelList_Render(int iWindowWidth, int iWindowHeight)
{
gbInRenderer = true;
ModelList_Render_Actual(iWindowWidth, iWindowHeight);
gbInRenderer = false;
}
/*
int Sys_Milliseconds (void)
{
static bool bInitialised = false;
static int sys_timeBase;
int sys_curtime;
if (!bInitialised)
{
sys_timeBase = timeGetTime();
bInitialised = true;
}
sys_curtime = timeGetTime() - sys_timeBase;
return sys_curtime;
}
float GetFloatTime(void)
{
float fTime = (float)Sys_Milliseconds() / 1000.0f; // reduce to game-type seconds
return fTime;
}
*/
// returns appropriate sequence index else -1 for not frame-not-found-in-sequences...
//
int Model_MultiSeq_SeqIndexFromFrame(ModelContainer_t *pContainer, int iFrame, bool bPrimary, bool bIsOldFrame )
{
if (bIsOldFrame)
{
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer, bPrimary); i++)
{
int iSeqIndex = Model_MultiSeq_GetEntry(pContainer, i, bPrimary);
Sequence_t *pSequence = &pContainer->SequenceList[iSeqIndex];
if (Sequence_FrameIsWithin(pSequence, iFrame))
{
return iSeqIndex; // this sequence is within our multiseq list, so ok to return it
}
}
}
else
{
if (Model_MultiSeq_EnsureSeqHintLegal(pContainer, iFrame, bPrimary))
{
return Model_MultiSeq_GetSeqHint(pContainer, bPrimary);
}
}
// seq index isn't within our multiseq array, so...
//
return -1;
}
// similar to above, but returns the entrynum [ 0..<num entries-1> ] that container the seq index
int Model_MultiSeq_EntryIndexFromFrame(ModelContainer_t *pContainer, int iFrame, bool bPrimary )
{
// Can't use this method, because sequences can have overlapping ranges unfortunately,
// so Sequence_DeriveFromFrame() can return wrong indexes if there's more than one covering the same range.
/*
// find the real sequence index, then verify that it's within our multiseq array...
//
int iSeqIndex = Sequence_DeriveFromFrame( pContainer, iFrame );
if (iSeqIndex != -1)
{
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer, bPrimary); i++)
{
if ( iSeqIndex == Model_MultiSeq_GetEntry(pContainer, i, bPrimary))
{
return i; // this sequence is within our multiseq list, so return list index
}
}
}
*/
int iSeqIndex = Model_MultiSeq_GetSeqHint(pContainer, bPrimary);
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer, bPrimary); i++)
{
int iThisSeqIndex = Model_MultiSeq_GetEntry(pContainer, i, bPrimary);
Sequence_t *pSequence = &pContainer->SequenceList[iThisSeqIndex];
if (iSeqIndex == iThisSeqIndex && Sequence_FrameIsWithin(pSequence, iFrame))
{
return i; // this sequence is within our multiseq list, so ok to return its index within this list
}
}
// seq index isn't within our multiseq array, so...
//
return -1;
}
static int Model_MultiSeq_GetSeqHint(ModelContainer_t *pContainer, bool bPrimary)
{
if (bPrimary)
{
return pContainer->iSeqMultiLock_Primary_SeqHint;
}
else
{
return pContainer->iSeqMultiLock_Secondary_SeqHint;
}
}
static void Model_MultiSeq_SetSeqHint(ModelContainer_t *pContainer, bool bPrimary, int iHint)
{
int iPrev = Model_MultiSeq_GetSeqHint(pContainer, bPrimary);
if (iPrev != iHint)
{
int z=1;
}
if (bPrimary)
{
pContainer->iSeqMultiLock_Primary_SeqHint = iHint;
}
else
{
pContainer->iSeqMultiLock_Secondary_SeqHint = iHint;
}
}
// this is really naff, but i needed to add it once I found that workout out the sequence number from a frame
// wasn't working properly because of frames appearing in multiple sequences...
//
// Note that this should only be called when Model_MultiSeq_IsActive(), and probably also when
// Model_MultiSeq_GetNumEntries() != 0
//
// returns false if did not fall within legal range...
//
static bool Model_MultiSeq_EnsureSeqHintLegal(ModelContainer_t *pContainer, int iFrame, bool bPrimary)
{
bool bReturn = true;
int iLegalSeqForThisFrame = -1;
int iCurrentHint = Model_MultiSeq_GetSeqHint(pContainer, bPrimary);
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer, bPrimary); i++)
{
int iSeqIndex = Model_MultiSeq_GetEntry(pContainer, i, bPrimary);
Sequence_t *pSequence = &pContainer->SequenceList[iSeqIndex];
if (Sequence_FrameIsWithin(pSequence, iFrame))
{
if (iSeqIndex == iCurrentHint)
{
return true; // all is well, our hint is pointing at a sequence that contains our current frame
}
// this isn't our hint, but it's legal for this frame, so preserve in case we need any old legal one later
//
iLegalSeqForThisFrame = iSeqIndex;
}
}
// apparently our seq hint wasn't legal, so make it so...
//
if (iLegalSeqForThisFrame == -1)
{
// whoooaaaaa! Ok, this isn't good, our current frame isn't even within any of the multi-lock sequences,
// so presumably the frame legaliser hasn't kicked in yet, so for now, I guess we should just grab the
// first multi-locked sequence?...
//
if (Model_MultiSeq_GetNumEntries(pContainer, bPrimary))
{
iLegalSeqForThisFrame = Model_MultiSeq_GetEntry(pContainer, 0, bPrimary);
}
else
{
// should never get here if this is called properly, but jic...
//
iLegalSeqForThisFrame = 0; // should be at least globally legal
}
bReturn = false;
}
Model_MultiSeq_SetSeqHint(pContainer, bPrimary, iLegalSeqForThisFrame);
return bReturn;
}
// gets the next proposed frame in a multilocked sequence, where iStepVal can be +ve or -ve, (though usually just -1/0/1),
// note that this will return a good nextframe number if everything's already legal, else it'll just return a non-locked
// number and let the frame legaliser sort things out, but be right from then on. Basically think of this as a nextframe-num hinter
//
// (this isn't staggeringly fast at certain points, but 99% of the time it executes quickly)
//
static int Model_MultiSeq_GetNextFrame(ModelContainer_t *pContainer, int iFrame, int iStepVal, bool bPrimary)
{
if (Model_MultiSeq_IsActive( pContainer, bPrimary))
{
if (Model_MultiSeq_GetNumEntries(pContainer, bPrimary))
{
if (Model_MultiSeq_EnsureSeqHintLegal(pContainer, iFrame, bPrimary))
{
// process the 'StepVal' param in a loop, 1 iteration per loop...
//
int iNewFrame = iFrame;
int iStepDir = (iStepVal<0)?-1:1;
for (int iStepValRemaining = iStepVal; iStepValRemaining!=0; iStepValRemaining += -iStepDir)
{
int iThisFrame = iNewFrame;
int iMultiLockEntryNum = Model_MultiSeq_EntryIndexFromFrame(pContainer, iThisFrame, bPrimary );
if (iMultiLockEntryNum != -1)
{
Sequence_t *pSeq = &pContainer->SequenceList[Model_MultiSeq_GetEntry(pContainer,iMultiLockEntryNum,bPrimary)];
assert(pSeq); //
if (pSeq) // itu?
{
int iNextFrame = iNewFrame + ((pSeq->iFPS<0)?-iStepDir:iStepDir);
// this is the sequence we're currently in, does the proposed next frame also sit within this one?...
//
int iSeqFrameFirst = pSeq->iStartFrame;
int iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1;
if (iNextFrame >= iSeqFrameFirst && iNextFrame <= iSeqFrameLast)
{
// yes, so adopt it...
//
iNewFrame = iNextFrame;
}
else
{
// new frame is outside of current group, so get the index of the sequence after (or before) this one,
// and adopt the first or last frame of it...
//
iMultiLockEntryNum += iStepDir;
if (iMultiLockEntryNum >= Model_MultiSeq_GetNumEntries(pContainer, bPrimary))
iMultiLockEntryNum = 0;
if (iMultiLockEntryNum < 0)
iMultiLockEntryNum = Model_MultiSeq_GetNumEntries(pContainer, bPrimary)-1;
int iNextSeqIndex = Model_MultiSeq_GetEntry(pContainer, iMultiLockEntryNum, bPrimary);
pSeq = &pContainer->SequenceList[iNextSeqIndex];
assert(pSeq); //
if (pSeq) // itu?
{
Model_MultiSeq_SetSeqHint(pContainer, bPrimary, iNextSeqIndex);
iSeqFrameFirst = pSeq->iStartFrame;
iSeqFrameLast = (iSeqFrameFirst + pSeq->iFrameCount)-1;
if (pSeq->iFPS < 0) // stepping into a backwards sequence?
{
iNewFrame = (iStepDir>0)?iSeqFrameLast:iSeqFrameFirst;
}
else
{
iNewFrame = (iStepDir>0)?iSeqFrameFirst:iSeqFrameLast;
}
}
else
{
return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning
}
}
}
else
{
// I don't think we'll ever get here, but wtf...
//
return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning
}
}
else
{
// err...
return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning
}
}
return iNewFrame;
}
else
{
return iFrame+iStepVal; // ... let the overall legaliser adjust this frame sometime after returning
}
}
else
{
// multi seq locking on, but no entries...
//
return iFrame+iStepVal;
}
}
else
{
// single-seq locking on? if so, check for -ve dir sequences...
//
const int iSequenceLockNumber = bPrimary ? pContainer->iSequenceLockNumber_Primary : pContainer->iSequenceLockNumber_Secondary;
const Sequence_t *pSequence = (iSequenceLockNumber == -1) ? NULL : &pContainer->SequenceList[iSequenceLockNumber];
if (pSequence)
{
if (pSequence->iFPS < 0)
{
return iFrame + -iStepVal;
}
}
}
return iFrame+iStepVal;
}
// does NOT affect original container frame number, just legalises and returns it...
//
// new bit, passing in -1 as frame number is used during GoToEndFrame logic for legalising to end-of-range, instead of start-of-range
//
int ModelContainer_EnsureFrameLegal(ModelContainer_t *pContainer, int iFrame, bool bPrimary, bool bIsOldFrame)
{
bool bLegaliseToStart = true;
if (iFrame == -1)
{
bLegaliseToStart = false;
}
// multi-locking active? (highest priority)...
//
if (Model_MultiSeq_IsActive(pContainer, bPrimary))
{
if (Model_MultiSeq_GetNumEntries(pContainer, bPrimary))
{
// is the current frame within one of the multi-lock seqs?...
//
if (Model_MultiSeq_SeqIndexFromFrame(pContainer, iFrame, bPrimary, bIsOldFrame ) == -1)
{
// no, so try and fix it so it is...
//
// if we got here then we're outside all current multi-seqs, unfortunately because the specified sequences
// can be at any position within the master list order I can't just work out which 2 sequences I'm between
// and jump to the start of the higher one, so working on the principle that 99% of the time this code is
// used when lerping between frame & frame+1 I'll see if I can find iFrame-1 anywhere...
//
iFrame--;
// try and find the seq we may have just come from...
//
// find the real sequence index, then verify that it's within our multiseq array...
//
int iSeqIndex = Sequence_DeriveFromFrame( pContainer, iFrame );
if (iSeqIndex != -1)
{
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer, bPrimary); i++)
{
if ( iSeqIndex == Model_MultiSeq_GetEntry(pContainer, i, bPrimary))
{
// got it, so is there another sequence after this?
//
if (++i <Model_MultiSeq_GetNumEntries(pContainer, bPrimary))
{
// yes, so adopt first frame of this this seq...
//
iSeqIndex = Model_MultiSeq_GetEntry(pContainer,i,bPrimary);
if (iSeqIndex < pContainer->SequenceList.size())
{
if (bLegaliseToStart)
return pContainer->SequenceList[iSeqIndex].iStartFrame;
return (pContainer->SequenceList[iSeqIndex].iStartFrame +
pContainer->SequenceList[iSeqIndex].iFrameCount)-1;
}
else
{
ErrorBox(va("ModelContainer_EnsureFrameLegal(): Logic error trying to legalise frame %d",++iFrame)); // ++ to counter -- aboveand therefore get original param
}
}
else
{
// reached end of multi-lock seq list...
//
break;
}
}
}
}
// sod it, whatever happened, let's start at the beginning of the list again...
//
iSeqIndex = Model_MultiSeq_GetEntry(pContainer, 0, bPrimary);
Sequence_t *pSeq = &pContainer->SequenceList[iSeqIndex];
iFrame = bLegaliseToStart ? pSeq->iStartFrame : (pSeq->iStartFrame + pSeq->iFrameCount)-1;
}
else
{
// iFrame is within the legal range of one of our multilocked sequences, so don't worry about it...
//
}
}
else
{
// no entries in table, so whatever frame we're on is legal...
//
}
}
else
{
// normal frame locking?...
//
const int iSequenceLockNumber = bPrimary ? pContainer->iSequenceLockNumber_Primary : pContainer->iSequenceLockNumber_Secondary;
const Sequence_t *pSequence = (iSequenceLockNumber == -1) ? NULL : &pContainer->SequenceList[iSequenceLockNumber];
if (pSequence)
{
// anim-locked to this sequence...
//
if (iFrame > (pSequence->iStartFrame + pSequence->iFrameCount)-1)
{
// OOR above, loop or stop?...
//
if (pSequence->iLoopFrame != -1)
{
// account for loop frame...
//
iFrame = pSequence->iStartFrame + pSequence->iLoopFrame;
}
else
{
// stop...
//
if (AppVars.bForceWrapWhenAnimating)
{
iFrame = pSequence->iStartFrame; // wrap
}
else
{
iFrame =(pSequence->iStartFrame + pSequence->iFrameCount)-1; // stop at end
}
}
}
else
if (iFrame < pSequence->iStartFrame)
{
// OOR below, legalise only...
//
iFrame = (pSequence->iFPS >= 0 && bLegaliseToStart)?pSequence->iStartFrame:(pSequence->iStartFrame + pSequence->iFrameCount)-1;
//iFrame = pSequence->iStartFrame;
}
}
}
// sanity...
//
if (iFrame >= pContainer->iNumFrames)
{
iFrame = 0;
}
if (iFrame < 0)
iFrame = pContainer->iNumFrames-1;
return iFrame;
}
static void R_ModelContainer_CallBack_AffectedByLerping(ModelContainer_t* pContainer, void *pvData )
{
bool *pbFeedbackIfTrue = (bool*) pvData;
if (pContainer->iOldFrame_Primary != pContainer->iCurrentFrame_Primary) // is this model lerping between 2 frames?
{
*pbFeedbackIfTrue = true;
}
else // else = optional really
if (Model_SecondaryAnimLockingActive(pContainer) &&
pContainer->iOldFrame_Secondary != pContainer->iCurrentFrame_Secondary
)
{
*pbFeedbackIfTrue = true;
}
}
// called only for speed-opt check for rendering. This just says whether all models are unaffected by lerping,
// eg if every loaded model has the currentframe and oldframe set to the same thing...
//
static bool ModelList_AffectedByLerping(void)
{
bool bReturn = false;
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_AffectedByLerping, &bReturn);
return bReturn;
}
static void R_ModelContainer_CallBack_LegaliseFrame(ModelContainer_t* pContainer, void *pvData )
{
bool *pbModelUpdated = (bool *) pvData;
// primary...
//
int iFrame = ModelContainer_EnsureFrameLegal(pContainer,pContainer->iCurrentFrame_Primary,true, false);
if (pContainer->iCurrentFrame_Primary != iFrame)
{
pContainer->iCurrentFrame_Primary = iFrame;
*pbModelUpdated = true;
}
// primary old... (added to catch backlerping errors)
iFrame = ModelContainer_EnsureFrameLegal(pContainer,pContainer->iOldFrame_Primary,true, true);
if (pContainer->iOldFrame_Primary != iFrame)
{
pContainer->iOldFrame_Primary = iFrame;
*pbModelUpdated = true;
}
// secondary...
//
iFrame = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iCurrentFrame_Secondary,false, false);
if (pContainer->iCurrentFrame_Secondary != iFrame)
{
pContainer->iCurrentFrame_Secondary = iFrame;
*pbModelUpdated = true;
}
// secondary old... (added to catch backlerping errors)
iFrame = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iOldFrame_Secondary,false, true);
if (pContainer->iOldFrame_Secondary != iFrame)
{
pContainer->iOldFrame_Secondary = iFrame;
*pbModelUpdated = true;
}
}
// called by timer @ 100fps
//
// return true == screen update wanted because of anim change
//
bool gbRedrawNeeded = false;
bool ModelList_Animation(void)
{
if (!gbRenderInhibit)
{
bool bModelUpdated = false;
bool bFinalReturn = false;
bool bNothingUpdated= false;
if (Model_Loaded())
{
//
// sanity check, legalise any locked frames in case they were missed elsewhere (dunno why/how that would occur, but...)
//
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_LegaliseFrame, &bModelUpdated);
// ok, now do animation...
//
if (!AppVars.bAnimate)
{
float fPrevFrac = AppVars.fFramefrac;
if (AppVars.bInterpolate)
AppVars.fFramefrac = 0.5f;
else
AppVars.fFramefrac = 0.0f;
if (fPrevFrac != AppVars.fFramefrac)
bModelUpdated = true;
}
else
{
double dTimeStamp2 = getDoubleTime();
AppVars.fFramefrac = (float)((dTimeStamp2 - AppVars.dTimeStamp1) / AppVars.dAnimSpeed);
if (AppVars.fFramefrac > 1.0f)
{
bModelUpdated = true;
AppVars.fFramefrac = 0;
AppVars.dTimeStamp1 = dTimeStamp2;
bNothingUpdated = !ModelList_StepFrame(1, false);
}
else
{
bNothingUpdated = !ModelList_AffectedByLerping();
}
}
bFinalReturn = (AppVars.bAnimate && AppVars.bInterpolate);
}
// bleurgh...
//
bFinalReturn = bNothingUpdated ? gbRedrawNeeded/*false*/ : (bFinalReturn || gbRedrawNeeded || bModelUpdated);
gbRedrawNeeded = false;
return bFinalReturn;
}
return false;
}
// sometimes MFC & C++ are a real fucking pain as regards what can talk to what, so...
void ModelList_ForceRedraw(void)
{
gbRedrawNeeded = true; // this was put in so model updated ok when not animating and treeview toggled a surface
}
// called whenever qdir changes (to avoid using cached textures from other dirs), or on program shutdown
void Media_Delete(void)
{
Model_Delete();
RE_ModelBinCache_DeleteAll();
TextureList_DeleteAll();
KillAllShaderFiles();
Skins_KillPreCacheInfo();
}
// UI-Query function...
//
bool Model_SecondaryAnimLockingActive(ModelHandle_t hModel)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
return Model_SecondaryAnimLockingActive(pContainer);
}
return false;
}
bool Model_SecondaryAnimLockingActive(const ModelContainer_t *pContainer)
{
return (pContainer->iBoneNum_SecondaryStart != -1);
}
// gallery query function...
//
// returns NULL if no lock present...
//
LPCSTR Model_Sequence_GetLockedName( ModelHandle_t hModel, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
int iSeqLockNumber = bPrimary ? pContainer->iSequenceLockNumber_Primary : pContainer->iSequenceLockNumber_Secondary;
if (iSeqLockNumber != -1)
{
return Model_Sequence_GetName( pContainer, iSeqLockNumber );
}
}
return NULL;
}
// UI query function.
//
// Note, since -1 = no locked sequence, you can pass this in to see if NO sequences are locked...
//
bool Model_Sequence_IsLocked( ModelHandle_t hModel, int iSequenceNumber, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
if (bPrimary)
{
return (pContainer->iSequenceLockNumber_Primary == iSequenceNumber);
}
return (pContainer->iSequenceLockNumber_Secondary == iSequenceNumber);
}
return false;
}
// UI query function. ('bUsedForDisplay' signifies that return can be prepended with '-' if a reverse sequence)
//
LPCSTR Model_Sequence_GetName(ModelHandle_t hModel, int iSequenceNumber, bool bUsedForDisplay /* = false */)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
return Model_Sequence_GetName(pContainer, iSequenceNumber, bUsedForDisplay);
}
return sERROR_CONTAINER_NOT_FOUND;
}
// ('bUsedForDisplay' signifies that return can be prepended with '-' if a reverse sequence)
//
LPCSTR Model_Sequence_GetName(ModelContainer_t *pContainer, int iSequenceNumber, bool bUsedForDisplay /* = false */)
{
if (iSequenceNumber < pContainer->SequenceList.size())
{
return Sequence_GetName(&pContainer->SequenceList[iSequenceNumber], bUsedForDisplay);
}
else
{
// could be fun if this appears in a dialogue... :-)
//
return va("Model_Sequence_GetName(): Illegal index %d, max = %d!",iSequenceNumber, pContainer->SequenceList.size()-1);
}
}
// returns seq index for supplied name, else -1 for error...
//
int Model_Sequence_IndexForName(ModelContainer_t *pContainer, LPCSTR psSeqName)
{
for (int iSeq=0; iSeq<pContainer->SequenceList.size(); iSeq++)
{
if (!stricmp(pContainer->SequenceList[iSeq].sName, psSeqName))
return iSeq;
}
ErrorBox(va("Model_Sequence_IndexForName(): Unable to resolve index for \"%s\"",psSeqName));
return -1;
}
// re-eval function for when toggling full path names on/off (Jarrod request)
//
LPCSTR Model_Sequence_GetTreeName(ModelHandle_t hModel, int iSequenceNumber)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
if (iSequenceNumber < pContainer->SequenceList.size())
{
return Sequence_CreateTreeName(&pContainer->SequenceList[iSequenceNumber]);
}
else
{
// could be fun if this appears in a treeitem... :-)
//
return va("Model_Sequence_GetTreeName(): Illegal index %d, max = %d!",iSequenceNumber, pContainer->SequenceList.size()-1);
}
}
return sERROR_CONTAINER_NOT_FOUND;
}
// a fairly inefficient function because of what it calls, but it's only used in "bursts" rather than every loop,
// so who cares?...
//
bool Model_Sequence_Lock( ModelHandle_t hModel, LPCSTR psSequenceName, bool bPrimary, bool bOktoShowErrorBox /* = true */)
{
int iTotSequences = Model_GetNumSequences(hModel);
for (int i=0; i<iTotSequences; i++)
{
if (!stricmp(Model_Sequence_GetName ( hModel, i), psSequenceName))
return Model_Sequence_Lock( hModel, i, bPrimary);
}
if (bOktoShowErrorBox)
{
ErrorBox(va("Model_Sequence_Lock(): Unable to find index for sequence \"%s\"!",psSequenceName));
}
return false;
}
bool Model_Sequence_Lock( ModelHandle_t hModel, int iSequenceNumber, bool bPrimary)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
if (iSequenceNumber < pContainer->SequenceList.size())
{
if (bPrimary)
pContainer->iSequenceLockNumber_Primary = iSequenceNumber;
else
{
if (!Model_SecondaryAnimLockingActive(pContainer))
iSequenceNumber = -1; // don't allow secondary locking if no scondary bone anim start is set
pContainer->iSequenceLockNumber_Secondary = iSequenceNumber;
}
ModelList_StepFrame(0, false); // to legalise old frame so if no anim and you lock to another sequence then you don't try and lerp to an old one outside that sequence
bReturn = true;
}
else
{
ErrorBox(va("Attempting to lock sequence # %d when max is %d!",iSequenceNumber, pContainer->SequenceList.size()-1));
}
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
return bReturn;
}
bool Model_Sequence_UnLock( ModelHandle_t hModel, bool bPrimary)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
assert(pContainer);
if (pContainer)
{
if (bPrimary)
pContainer->iSequenceLockNumber_Primary = -1;
else
pContainer->iSequenceLockNumber_Secondary = -1;
bReturn = true;
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw(); // only needed to update lock stats onscr
return bReturn;
}
static void R_ModelContainer_CallBack_Rewind(ModelContainer_t* pContainer, void *pvData )
{
pContainer->iCurrentFrame_Primary = ModelContainer_EnsureFrameLegal(pContainer, 0, true, false);
pContainer->iCurrentFrame_Secondary = ModelContainer_EnsureFrameLegal(pContainer, 0, false, false);
}
// rewind all loaded models...
//
void ModelList_Rewind()
{
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_Rewind);
ModelList_ForceRedraw();
}
static void R_ModelContainer_CallBack_GoToEndFrame(ModelContainer_t* pContainer, void *pvData )
{
pContainer->iCurrentFrame_Primary = ModelContainer_EnsureFrameLegal(pContainer, -1, true, false);
pContainer->iCurrentFrame_Secondary = ModelContainer_EnsureFrameLegal(pContainer, -1, false, false);
}
void ModelList_GoToEndFrame()
{
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_GoToEndFrame);
ModelList_ForceRedraw();
}
typedef struct // one-off struct for data passing during StepFrame recursion
{
int iStepVal;
bool bSomethingHasUpdated;
} StepFrameData_t;
static void R_ModelContainer_CallBack_UpdateFrame(ModelContainer_t* pContainer, void *pvData )
{
StepFrameData_t *pStepFrameData = (StepFrameData_t *) pvData;
// primary...
//
{
int iPrevOldFrame = pContainer->iOldFrame_Primary;
int iPrevFrame = pContainer->iCurrentFrame_Primary;
// this is the only place in the code where this gets updated...
//
pContainer->iOldFrame_Primary = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iCurrentFrame_Primary, true, true); // for locking
pContainer->iCurrentFrame_Primary = Model_MultiSeq_GetNextFrame (pContainer, pContainer->iCurrentFrame_Primary, pStepFrameData->iStepVal, true);
pContainer->iCurrentFrame_Primary = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iCurrentFrame_Primary, true, false);
if (iPrevOldFrame != pContainer->iOldFrame_Primary ||
iPrevFrame != pContainer->iCurrentFrame_Primary
)
{
pStepFrameData->bSomethingHasUpdated = true;
}
}
// secondary...
//
if (Model_SecondaryAnimLockingActive(pContainer))
{
int iPrevOldFrame = pContainer->iOldFrame_Secondary;
int iPrevFrame = pContainer->iCurrentFrame_Secondary;
// this is the only place in the code where this gets updated...
//
pContainer->iOldFrame_Secondary = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iCurrentFrame_Secondary, false, true); // for locking
pContainer->iCurrentFrame_Secondary = Model_MultiSeq_GetNextFrame (pContainer, pContainer->iCurrentFrame_Secondary, pStepFrameData->iStepVal, false);
pContainer->iCurrentFrame_Secondary = ModelContainer_EnsureFrameLegal(pContainer, pContainer->iCurrentFrame_Secondary, false, false);
if (iPrevOldFrame != pContainer->iOldFrame_Secondary ||
iPrevFrame != pContainer->iCurrentFrame_Secondary
)
{
pStepFrameData->bSomethingHasUpdated = true;
}
}
}
// advance/decrease all loaded model frames...
//
// return = true if at least one model has changed frames...
//
bool ModelList_StepFrame(int iStepVal, bool bAutoAnimOff /* = true */)
{
StepFrameData_t StepFrameData;
StepFrameData.iStepVal = iStepVal;
StepFrameData.bSomethingHasUpdated = false; // used for draw-optimising so when anim is on but all models are at non-wrap endframe, then no screen update
R_ModelContainer_Apply(&AppVars.Container, R_ModelContainer_CallBack_UpdateFrame, &StepFrameData);
if (bAutoAnimOff)
{
AppVars.bAnimate = false;
AppVars.bInterpolate= false;
}
if (bAutoAnimOff || (!bAutoAnimOff && StepFrameData.bSomethingHasUpdated))
{
ModelList_ForceRedraw();
}
return StepFrameData.bSomethingHasUpdated;
}
// remote helper func...
//
int Model_GetNumBoneAliases(ModelHandle_t hModel)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return pContainer->Aliases.size();
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return false;
}
// note that it's only valid to call this straight after you've called Model_GetNumAliases(), if you add
// any more to this then the indexes are invalidated. ie, only call during an interrogate loop, and don't
// store the indexes anywhere!
//
bool Model_GetBoneAliasPair(ModelHandle_t hModel, int iAliasIndex, string &strRealName,string &strAliasName)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return ModelContainer_GetBoneAliasPair(pContainer, iAliasIndex, strRealName, strAliasName);
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return false;
}
// note that it's only valid to call this straight after you've called Model_GetNumAliases(), if you add
// any more to this then the indexes are invalidated. ie, only call during an interrogate loop, and don't
// store the indexes anywhere!
//
bool ModelContainer_GetBoneAliasPair(ModelContainer_t *pContainer, int iAliasIndex, string &strRealName, string &strAliasName)
{
int iLocalAliasIndex = iAliasIndex;
for (MappedString_t::iterator it = pContainer->Aliases.begin(); it != pContainer->Aliases.end(); ++it)
{
if (!iLocalAliasIndex--)
{
strRealName = ((*it).first);
strAliasName= ((*it).second);
return true;
}
}
assert(0);
ErrorBox(va("Illegal alias index %d, max = %d",iAliasIndex,pContainer->Aliases.size()));
return false;
}
// remote helper func only...
//
int Model_GetNumSequences(ModelHandle_t hModel)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return pContainer->SequenceList.size();
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return 0;
}
// remote helper func only... returns a string with sequence name, startframe, duration etc
//
// NULL return = error
//
LPCSTR Model_GetSequenceString(ModelHandle_t hModel, int iSequenceNum)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer && iSequenceNum < pContainer->SequenceList.size())
{
return Sequence_ReturnRemoteQueryString(&pContainer->SequenceList[iSequenceNum]);
}
assert(0);
return NULL;
}
// I don't bother with validating the index because if it's wrong then it just doesn't highlight anything...
//
bool Model_SetSurfaceHighlight(ModelHandle_t hModel, int iSurfaceIndex)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
pContainer->iSurfaceHighlightNumber = iSurfaceIndex;
AppVars.iSurfaceNumToHighlight = iSurfaceIndex; // a bit tacky, but saves passing container info down the callstack
AppVars.hModelToHighLight = hModel;
ModelList_ForceRedraw();
return true;
}
assert(0);
return false;
}
// this version uses bone names instead, used for easier remote calling...
//
bool Model_SetSurfaceHighlight(ModelHandle_t hModel, LPCSTR psSurfaceName)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
// find this surface (fortunately, all names are unique within a model)...
//
for (int iSurface=0; iSurface<pContainer->iNumSurfaces; iSurface++)
{
if (!stricmp(psSurfaceName, pContainer->pModelGetSurfaceNameFunction( hModel, iSurface )))
{
Model_SetSurfaceHighlight(hModel, iSurface);
return true;
}
}
}
return false;
}
// I don't bother with validating the index because if it's wrong then it just doesn't highlight anything...
//
bool Model_SetBoneHighlight(ModelHandle_t hModel, int iBoneIndex)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
pContainer->iBoneHighlightNumber = iBoneIndex;
ModelList_ForceRedraw();
return true;
}
assert(0);
return false;
}
// returns -1 for error, else bone index
//
// this is now slightly recursive, at least if you supply an aliased bone name...
//
int ModelContainer_BoneIndexFromName(ModelContainer_t *pContainer, LPCSTR psBoneName)
{
// find this bone (fortunately, all names are unique within a model)...
//
for (int iBone=0; iBone<pContainer->iNumBones; iBone++)
{
if (!stricmp(psBoneName, pContainer->pModelGetBoneNameFunction( pContainer->hModel, iBone )))
{
return iBone;
}
}
static bool bAlreadyHere = false;
if (!bAlreadyHere)
{
// new bit, if we didn't find the bone name, check the aliases (I could interrogate the tree, but this
// seems nicer...
//
// (Note that the alias <map> is opposite to what you'd think, ie [realname] = aliasname.
//
// This makes for slower name lookup on these occasional calls, but faster is-alias-present-for-this-bone
// checks during rendering.
//
/*
for (MappedString_t::iterator it = pContainer->Aliases.begin(); it != pContainer->Aliases.end(); ++it)
{
CString strBoneName_Real (((*it).first).c_str());
CString strBoneName_Alias(((*it).second).c_str());
if (!stricmp(((*it).second).c_str(), psBoneName)) // bone name arg matches this alias?
{
bAlreadyHere = true;
iBone = ModelContainer_BoneIndexFromName(pContainer, ((*it).first).c_str()); // pass real bone name
bAlreadyHere = false;
if (iBone != -1)
return iBone;
break;
}
}
*/
int iAliases = Model_GetNumBoneAliases(pContainer->hModel);
for (int i=0; i<iAliases; i++)
{
string strBoneNameReal;
string strBoneNameAlias;
if (ModelContainer_GetBoneAliasPair(pContainer, i, strBoneNameReal, strBoneNameAlias))
{
if (!stricmp(strBoneNameAlias.c_str(), psBoneName))
{
bAlreadyHere = true;
iBone = ModelContainer_BoneIndexFromName(pContainer, strBoneNameReal.c_str()); // pass real bone name
bAlreadyHere = false;
if (iBone != -1)
return iBone;
break;
}
}
else
{
break;
}
}
}
assert(0);
return -1;
}
// this version uses bone names instead, used for easier remote calling...
//
bool Model_SetBoneHighlight(ModelHandle_t hModel, LPCSTR psBoneName)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
int iBoneIndex = ModelContainer_BoneIndexFromName(pContainer, psBoneName);
if (iBoneIndex != -1)
{
Model_SetBoneHighlight(hModel, iBoneIndex);
return true;
}
}
return false;
}
void Model_StartAnim(bool bForceWrap /* = false */)
{
AppVars.bAnimate = true;
AppVars.bForceWrapWhenAnimating = bForceWrap;
}
void Model_StopAnim()
{
AppVars.bAnimate = false;
}
// for use with script files and remote control, since it's safer to use bone names rather than indexes, in case they change
// after the script was saved out or something
//
bool Model_SetSecondaryAnimStart(ModelHandle_t hModel, LPCSTR psBoneName)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
int iBoneNum = Model_GetBoltIndex( hModel, psBoneName, true);
return Model_SetSecondaryAnimStart(hModel, iBoneNum );
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
return bReturn;
}
// iBoneNum of -1 = clear secondary anim start
//
bool Model_SetSecondaryAnimStart(ModelHandle_t hModel, int iBoneNum)
{
bool bReturn = false;
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (iBoneNum == 0) // UI-update check on popup menu will not allow 0 to be sent, but remote-commands etc can do it.
{
WarningBox("Model_SetSecondaryAnimStart(): Attempting to start seconday anim from bone 0\n\nThis is probably bad, and is almost certainly pointless anyway");
iBoneNum = -1; // clear it
}
else
if (iBoneNum >= pContainer->iNumBones)
{
ErrorBox(va("Model_SetSecondaryAnimStart(): attempting to set start on bone %d, but max = %d!",iBoneNum,pContainer->iNumBones-1));
iBoneNum = -1; // clear it
}
else
{
bReturn = true;
}
pContainer->iBoneNum_SecondaryStart = iBoneNum;
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
return bReturn;
}
int Model_GetSecondaryAnimStart(ModelHandle_t hModel)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return pContainer->iBoneNum_SecondaryStart;
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return -1; // return "no secondary anim start"
}
bool Model_MultiSeq_AlreadyContains(ModelContainer_t *pContainer, int iSequenceNumber, bool bPrimary)
{
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer,bPrimary); i++)
{
if (iSequenceNumber == Model_MultiSeq_GetEntry(pContainer,i,bPrimary))
{
return true;
}
}
return false;
}
bool Model_MultiSeq_AlreadyContains(ModelHandle_t hModel, int iSequenceNumber, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_MultiSeq_AlreadyContains(pContainer, iSequenceNumber, bPrimary);
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return false;
}
bool Model_MultiSeq_Add(ModelHandle_t hModel, int iSequenceNumber, bool bPrimary, bool bActivate /* = true */)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (iSequenceNumber<pContainer->SequenceList.size())
{
if (!Model_MultiSeq_AlreadyContains(pContainer, iSequenceNumber, bPrimary))
{
if (bPrimary)
{
pContainer->SeqMultiLock_Primary.push_back(iSequenceNumber);
if (bActivate)
{
pContainer->bSeqMultiLock_Primary_Active = true;
}
}
else
{
pContainer->SeqMultiLock_Secondary.push_back(iSequenceNumber);
if (bActivate)
{
pContainer->bSeqMultiLock_Secondary_Active = true;
}
}
ModelList_ForceRedraw(); // because of lerping
}
return true;
}
else
{
ErrorBox(va("Model_MultiSeqLock_Add(): Illegal sequence number %d",iSequenceNumber));
}
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
return false;
}
// returns -1 else surface number to use instead of root...
//
int Model_GetG2SurfaceRootOverride(ModelContainer_t *pContainer)
{
return pContainer->iSurfaceNum_RootOverride;
}
// returns -1 else surface number to use instead of root...
//
int Model_GetG2SurfaceRootOverride(ModelHandle_t hModel)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if ( pContainer )
{
return Model_GetG2SurfaceRootOverride( pContainer );
}
assert(0);
return -1;
}
bool Model_SetG2SurfaceRootOverride (ModelContainer_t *pContainer, LPCSTR psSurfaceName)
{
for (int iSurface = 0; iSurface<pContainer->iNumSurfaces; iSurface++)
{
LPCSTR psScannedSurfaceName = Model_GetSurfaceName( pContainer, iSurface );
if (!stricmp(psScannedSurfaceName,psSurfaceName))
{
Model_SetG2SurfaceRootOverride(pContainer, iSurface);
return true;
}
}
return false;
}
// iSurfaceNum of -1 = clear surface root override
//
bool Model_SetG2SurfaceRootOverride(ModelContainer_t *pContainer, int iSurfaceNum)
{
bool bReturn = false;
if (iSurfaceNum >= pContainer->iNumSurfaces)
{
ErrorBox(va("Model_SetG2SurfaceRootOverride(): attempting to set surface %d as root, but max = %d!",iSurfaceNum, pContainer->iNumSurfaces-1));
}
else
{
pContainer->iSurfaceNum_RootOverride = iSurfaceNum;
bReturn = true;
}
ModelList_ForceRedraw();
return bReturn;
}
bool Model_SetG2SurfaceRootOverride(ModelHandle_t hModel, int iSurfaceNum)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
if ( pContainer )
{
return Model_SetG2SurfaceRootOverride( pContainer, iSurfaceNum );
}
else
{
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
ModelList_ForceRedraw();
return false;
}
// ui-query...
//
bool Model_MultiSeq_IsActive(ModelHandle_t hModel, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_MultiSeq_IsActive(pContainer, bPrimary);
}
assert(0);
return false;
}
bool Model_MultiSeq_IsActive(ModelContainer_t *pContainer, bool bPrimary)
{
if (bPrimary)
{
return pContainer->bSeqMultiLock_Primary_Active;
}
else
{
// secondary has special rule...
//
if (Model_SecondaryAnimLockingActive(pContainer))
return pContainer->bSeqMultiLock_Secondary_Active;
return false;
}
}
int Model_MultiSeq_GetEntry(ModelContainer_t *pContainer, int iEntry, bool bPrimary)
{
if (bPrimary)
{
if (iEntry < pContainer->SeqMultiLock_Primary.size())
return pContainer->SeqMultiLock_Primary[iEntry];
}
else
{
if (iEntry < pContainer->SeqMultiLock_Secondary.size())
return pContainer->SeqMultiLock_Secondary[iEntry];
}
ErrorBox(va("Model_MultiSeq_GetEntry() illegal %s index %d ( max is %d )",bPrimary?"primary":"secondary",iEntry,bPrimary?pContainer->SeqMultiLock_Primary.size()-1:pContainer->SeqMultiLock_Secondary.size()-1));
return NULL;
}
int Model_MultiSeq_GetNumEntries(ModelContainer_t *pContainer, bool bPrimary)
{
if (bPrimary)
{
return pContainer->SeqMultiLock_Primary.size();
}
else
{
return pContainer->SeqMultiLock_Secondary.size();
}
}
// note that this can have entries but still be inactive
//
int Model_MultiSeq_GetNumEntries(ModelHandle_t hModel, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_MultiSeq_GetNumEntries(pContainer, bPrimary);
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return 0;
}
void Model_MultiSeq_Clear(ModelHandle_t hModel, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (bPrimary)
{
pContainer->SeqMultiLock_Primary.clear();
}
else
{
pContainer->SeqMultiLock_Secondary.clear();
}
ModelList_ForceRedraw(); // needed because of lerping
return;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
// this is a tacky way of getting round the fact of not having a proper GUI for editing these lists,
// basically you just delete the last thing entered if you make a mistake...
//
void Model_MultiSeq_DeleteLast(ModelHandle_t hModel, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (bPrimary)
{
if (pContainer->SeqMultiLock_Primary.size() >= 1)
pContainer->SeqMultiLock_Primary.pop_back();
}
else
{
if (pContainer->SeqMultiLock_Secondary.size() >= 1)
pContainer->SeqMultiLock_Secondary.pop_back();
}
ModelList_ForceRedraw(); // important, in case current frame was within locked-seq we just deleted from set
return;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
void Model_MultiSeq_Delete( ModelHandle_t hModel, int iSeqIndex, bool bPrimary)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
// now we have to turn this sequence index into a multilock list index...
//
for (int i=0; i<Model_MultiSeq_GetNumEntries(pContainer,bPrimary); i++)
{
int iThisSeqIndex = Model_MultiSeq_GetEntry ( pContainer, i, bPrimary );
if (iThisSeqIndex == iSeqIndex)
{
if (bPrimary)
{
if (pContainer->SeqMultiLock_Primary.size() > i)
{
pContainer->SeqMultiLock_Primary.erase(pContainer->SeqMultiLock_Primary.begin() + i);
}
}
else
{
if (pContainer->SeqMultiLock_Secondary.size() > i)
{
pContainer->SeqMultiLock_Secondary.erase(pContainer->SeqMultiLock_Secondary.begin() + i);
}
}
}
}
ModelList_ForceRedraw(); // important, in case current frame was within locked-seq we just deleted from set
return;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
void Model_MultiSeq_SetActive(ModelHandle_t hModel, bool bPrimary, bool bActive)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
if (bPrimary)
{
pContainer->bSeqMultiLock_Primary_Active = bActive;
}
else
{
pContainer->bSeqMultiLock_Secondary_Active = bActive;
}
ModelList_ForceRedraw(); // even just unlocking can affect lerping, so redraw needed
return;
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
}
// return value is the corrected (if over the model limit) LOD level...
//
int Model_EnsureGenerated_VertEdgeInfo(ModelContainer_t *pContainer, int iLOD)
{
return GLMModel_EnsureGenerated_VertEdgeInfo(pContainer->hModel, iLOD, pContainer->SurfaceEdgeInfoPerLOD);
}
// return value is the corrected (if over the model limit) LOD level, -1 = failure !
//
int Model_EnsureGenerated_VertEdgeInfo(ModelHandle_t hModel, int iLOD)
{
ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
if (pContainer)
{
return Model_EnsureGenerated_VertEdgeInfo(pContainer, iLOD);
}
assert(0);
ErrorBox(sERROR_CONTAINER_NOT_FOUND);
return -1;
}
static inline void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross )
{
cross[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
cross[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
cross[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
}
static inline vec_t VectorNormalize( vec3_t v ) {
float length, ilength;
length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
length = sqrt (length);
if ( length > 0.0001f ) {
ilength = 1/length;
v[0] *= ilength;
v[1] *= ilength;
v[2] *= ilength;
}
return length;
}
typedef struct {
unsigned int normal;
short vertCoords[3];
unsigned int tangent;
unsigned int uiNmWeightsAndBoneIndexes;
byte BoneWeightings[iMAX_G2_BONEWEIGHTS_PER_VERT];
} mdxmVertexComp_t;
typedef struct {
short texCoords[2];
} mdxmVertexTexCoordComp_t;
static vec3_t _tangents[1000];
static void BuildTangentVectors(mdxmVertex_t *v, int numVerts, mdxmTriangle_t *tri, int numTriangles)
{
memset(_tangents, 0, sizeof(vec3_t) * 1000);
mdxmVertexTexCoord_t *pTex = (mdxmVertexTexCoord_t *) &v[numVerts];
for(int i = 0; i < numTriangles; i ++)
{
vec3_t vec1, vec2, du, dv, cp;
vec1[0] = v[tri[i].indexes[1]].vertCoords[0] - v[tri[i].indexes[0]].vertCoords[0];
vec1[1] = pTex[tri[i].indexes[1]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec1[2] = pTex[tri[i].indexes[1]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
vec2[0] = v[tri[i].indexes[2]].vertCoords[0] - v[tri[i].indexes[0]].vertCoords[0];
vec2[1] = pTex[tri[i].indexes[2]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec2[2] = pTex[tri[i].indexes[2]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
CrossProduct(vec1, vec2, cp);
if(cp[0] == 0.0f)
cp[0] = 0.001f;
du[0] = -cp[1] / cp[0];
dv[0] = -cp[2] / cp[0];
vec1[0] = v[tri[i].indexes[1]].vertCoords[1] - v[tri[i].indexes[0]].vertCoords[1];
vec1[1] = pTex[tri[i].indexes[1]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec1[2] = pTex[tri[i].indexes[1]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
vec2[0] = v[tri[i].indexes[2]].vertCoords[1] - v[tri[i].indexes[0]].vertCoords[1];
vec2[1] = pTex[tri[i].indexes[2]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec2[2] = pTex[tri[i].indexes[2]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
CrossProduct(vec1, vec2, cp);
if(cp[0] == 0.0f)
cp[0] = 0.001f;
du[1] = -cp[1] / cp[0];
dv[1] = -cp[2] / cp[0];
vec1[0] = v[tri[i].indexes[1]].vertCoords[2] - v[tri[i].indexes[0]].vertCoords[2];
vec1[1] = pTex[tri[i].indexes[1]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec1[2] = pTex[tri[i].indexes[1]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
vec2[0] = v[tri[i].indexes[2]].vertCoords[2] - v[tri[i].indexes[0]].vertCoords[2];
vec2[1] = pTex[tri[i].indexes[2]].texCoords[0] - pTex[tri[i].indexes[0]].texCoords[0];
vec2[2] = pTex[tri[i].indexes[2]].texCoords[1] - pTex[tri[i].indexes[0]].texCoords[1];
CrossProduct(vec1, vec2, cp);
if(cp[0] == 0.0f)
cp[0] = 0.001f;
du[2] = -cp[1] / cp[0];
dv[2] = -cp[2] / cp[0];
_tangents[tri[i].indexes[0]][0] += du[0];
_tangents[tri[i].indexes[0]][1] += du[0];
_tangents[tri[i].indexes[0]][2] += du[0];
_tangents[tri[i].indexes[1]][0] += du[0];
_tangents[tri[i].indexes[1]][1] += du[0];
_tangents[tri[i].indexes[1]][2] += du[0];
_tangents[tri[i].indexes[1]][0] += du[0];
_tangents[tri[i].indexes[1]][1] += du[0];
_tangents[tri[i].indexes[1]][2] += du[0];
}
for(i = 0; i < numVerts; i++)
{
VectorNormalize(_tangents[i]);
}
}
bool Model_Save(LPCSTR psFullPathedFilename)
{
long pos;
mdxmLOD_t* lod;
int l, i;
mdxmTriangle_t* tri;
mdxmSurface_t* surf;
mdxmVertex_t* v;
int lodOfsEnd, headOfsEnd;
mdxmLODSurfOffset_t* newindexes;
mdxmHeader_t *pHeader = (mdxmHeader_t *) RE_GetModelData(AppVars.hModelLastLoaded);
if(!pHeader)
return false;
headOfsEnd = pHeader->ofsEnd;
FILE *pFile = fopen(psFullPathedFilename, "wb");
if(!pFile)
return false;
FILE *flog = fopen( "C:\\modlog.txt", "a" );
// Skipping thru the header at this point, because we have to change
// header values as we go thru the model
fseek(pFile, sizeof(*pHeader), SEEK_SET);
// Write the surface heirarchy stuff
// We dont change anything here, so its just a raw write
byte *ptr = ((byte *) pHeader + sizeof(*pHeader));
fwrite(ptr, 1, pHeader->ofsLODs - sizeof(*pHeader), pFile);
// Offset to the first LOD
lod = (mdxmLOD_t *) ( (byte *)pHeader + pHeader->ofsLODs );
for ( l = 0 ; l < pHeader->numLODs ; l++)
{
int triCount = 0;
lodOfsEnd = lod->ofsEnd;
mdxmLODSurfOffset_t *indexes = (mdxmLODSurfOffset_t *)((char*)lod + sizeof(mdxmLOD_t));
newindexes = new mdxmLODSurfOffset_t[pHeader->numSurfaces];
memcpy(newindexes, indexes, sizeof(mdxmLODSurfOffset_t) * pHeader->numSurfaces);
// iterate thru the surfaces
surf = (mdxmSurface_t *) ( (byte *)lod + sizeof (mdxmLOD_t) + (pHeader->numSurfaces * sizeof(mdxmLODSurfOffset_t)) );
for ( i = 0 ; i < pHeader->numSurfaces ; i++)
{
// Calculate the memory savings for this surface
int save = (surf->numVerts * (sizeof(mdxmVertex_t) + sizeof(mdxmVertexTexCoord_t))) -
(surf->numVerts * (sizeof(mdxmVertexComp_t) + sizeof(mdxmVertexTexCoordComp_t)));
lodOfsEnd -= save;
headOfsEnd -= save;
for ( int s = i + 1; s < pHeader->numSurfaces; s++ )
{
newindexes[s].offsets[0] -= save;
}
surf = (mdxmSurface_t *)( (byte *)surf + surf->ofsEnd );
}
// We've calculate our new offsets, so write out this LOD
// Write the new LOD offset
mdxmLOD_t newlod;
newlod.ofsEnd = lodOfsEnd;
fwrite(&newlod, 1, sizeof(newlod), pFile);
pos = ftell(pFile);
// Write the surface offset array
fwrite(newindexes, 1, sizeof(mdxmLODSurfOffset_t) * pHeader->numSurfaces, pFile);
delete [] newindexes;
pos = ftell(pFile);
// Write each surface
surf = (mdxmSurface_t *) ( (byte *)lod + sizeof (mdxmLOD_t) + (pHeader->numSurfaces * sizeof(mdxmLODSurfOffset_t)) );
for ( i = 0; i < pHeader->numSurfaces; i++)
{
// Calculate the memory savings for this surface
int save = (surf->numVerts * (sizeof(mdxmVertex_t) + sizeof(mdxmVertexTexCoord_t))) -
(surf->numVerts * (sizeof(mdxmVertexComp_t) + sizeof(mdxmVertexTexCoordComp_t)));
mdxmSurface_t newsurf;
memcpy(&newsurf, surf, sizeof(mdxmSurface_t));
newsurf.ofsEnd -= save;
newsurf.ofsBoneReferences -= save;
// Write this surface struct
fwrite(&newsurf, 1, sizeof(mdxmSurface_t), pFile);
pos = ftell(pFile);
// Write the triangles
tri = (mdxmTriangle_t *) ( (byte *)surf + surf->ofsTriangles );
fwrite(tri, 1, sizeof(mdxmTriangle_t) * surf->numTriangles, pFile);
pos = ftell(pFile);
// Write the vertices
v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts );
// Compute tangent vectors
BuildTangentVectors(v, surf->numVerts, tri, surf->numTriangles);
v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts );
for(int ver = 0; ver < surf->numVerts; ver++)
{
mdxmVertexComp_t compv;
compv.uiNmWeightsAndBoneIndexes = v->uiNmWeightsAndBoneIndexes;
for(int m = 0; m < iMAX_G2_BONEWEIGHTS_PER_VERT; m++)
{
compv.BoneWeightings[m] = v->BoneWeightings[m];
}
compv.vertCoords[0] = (short)(LittleFloat( v->vertCoords[0] ) * POINT_SCALE );
compv.vertCoords[1] = (short)(LittleFloat( v->vertCoords[1] ) * POINT_SCALE );
compv.vertCoords[2] = (short)(LittleFloat( v->vertCoords[2] ) * POINT_SCALE );
/*compv.normal[0] = (short)(LittleFloat( v->normal[0] ) * POINT_SCALE );
compv.normal[1] = (short)(LittleFloat( v->normal[1] ) * POINT_SCALE );
compv.normal[2] = (short)(LittleFloat( v->normal[2] ) * POINT_SCALE );*/
/*compv.normal = ( ( ((DWORD)(v->normal[2] * 511.0f)) & 0x3ff ) << 22L ) |
( ( ((DWORD)(v->normal[1] * 1023.0f)) & 0x7ff ) << 11L ) |
( ( ((DWORD)(v->normal[0] * 1023.0f)) & 0x7ff ) << 0L );
compv.tangent = ( ( ((DWORD)(_tangents[ver][2] * 511.0f)) & 0x3ff ) << 22L ) |
( ( ((DWORD)(_tangents[ver][1] * 1023.0f)) & 0x7ff ) << 11L ) |
( ( ((DWORD)(_tangents[ver][0] * 1023.0f)) & 0x7ff ) << 0L );*/
unsigned int ix = unsigned int( (v->normal[0] * 127.f) + 128.f );
unsigned int iy = unsigned int( (v->normal[1] * 127.f) + 128.f );
unsigned int iz = unsigned int( (v->normal[2] * 127.f) + 128.f );
unsigned int iw = 0; // we don't use the w component (just padding)
compv.normal = (iw << 24) | (ix << 16) | (iy << 8) | (iz << 0);
ix = unsigned int( (_tangents[ver][0] * 127.f) + 128.f );
iy = unsigned int( (_tangents[ver][1] * 127.f) + 128.f );
iz = unsigned int( (_tangents[ver][2] * 127.f) + 128.f );
compv.tangent = (iw << 24) | (ix << 16) | (iy << 8) | (iz << 0);
fwrite(&compv, 1, sizeof(mdxmVertexComp_t), pFile);
v++;
}
pos = ftell(pFile);
// Write the texture coords
v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts );
mdxmVertexTexCoord_t *pTexCoords = (mdxmVertexTexCoord_t *) &v[surf->numVerts];
// fwrite(pTexCoords, 1, sizeof(mdxmVertexTexCoord_t) * surf->numVerts, pFile);
for(int m = 0; m < surf->numVerts; m++)
{
float tempTex[2];
tempTex[0] = pTexCoords->texCoords[0] * POINT_ST_SCALE;
tempTex[1] = pTexCoords->texCoords[1] * POINT_ST_SCALE;
assert( tempTex[0] > -32768.0f && tempTex[0] < 32767.0f );
assert( tempTex[1] > -32768.0f && tempTex[1] < 32767.0f );
if (tempTex[0] < -32768.0f || tempTex[0] > 32767.0f)
fprintf( flog, "ERROR: UV overflow in %s [%f]\n", psFullPathedFilename, tempTex[0] );
if (tempTex[1] < -32768.0f || tempTex[1] > 32767.0f)
fprintf( flog, "ERROR: UV overflow in %s [%f]\n", psFullPathedFilename, tempTex[1] );
mdxmVertexTexCoordComp_t tex;
tex.texCoords[0] = (short)( tempTex[0] );
tex.texCoords[1] = (short)( tempTex[1] );
fwrite(&tex, 1, sizeof(mdxmVertexTexCoordComp_t), pFile);
pTexCoords++;
}
// Write the bone references
int *boneref = (int *)((byte*)surf + surf->ofsBoneReferences);
fwrite(boneref, 1, sizeof(int) * surf->numBoneReferences, pFile);
pos = ftell(pFile);
surf = (mdxmSurface_t *)( (byte *)surf + surf->ofsEnd );
}
lod = (mdxmLOD_t *)((byte *)lod + lod->ofsEnd);
}
pos = ftell(pFile);
// Weve written all the data, so skip back up to the top
// and write the model header
fseek(pFile, 0, SEEK_SET);
mdxmHeader_t head;
memcpy(&head, pHeader, sizeof(mdxmHeader_t));
head.ofsEnd = headOfsEnd;
fwrite(&head, 1, sizeof(mdxmHeader_t), pFile);
fclose(pFile);
fclose( flog );
return true;
}
///////////////// eof //////////////////