1128 lines
31 KiB
C++
1128 lines
31 KiB
C++
// Filename:- script.cpp
|
|
//
|
|
// file to control model-loading scripts (scripts are purely a viewer convenience for multiple-bolt stuff)
|
|
//
|
|
#include "stdafx.h"
|
|
#include "includes.h"
|
|
#include "files.h"
|
|
#include "r_common.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 "model.h"
|
|
|
|
#include "GenericParser2.h"
|
|
#include "skins.h"
|
|
#include "stl.h"
|
|
SkinSets_t CurrentSkins;
|
|
SkinSetsSurfacePrefs_t CurrentSkinsSurfacePrefs;
|
|
typedef map <string, map<string, int> > G2SkinModelPrefs_t; // <skinfile name, <modelname, junk>> to see if a file contains a model's name
|
|
G2SkinModelPrefs_t G2SkinModelPrefs;
|
|
|
|
|
|
// called from Media_Delete...
|
|
//
|
|
void Skins_KillPreCacheInfo(void)
|
|
{
|
|
G2SkinModelPrefs.clear();
|
|
//
|
|
// these 2 aren't really needed here, but wtf?
|
|
//
|
|
CurrentSkins.clear();
|
|
CurrentSkinsSurfacePrefs.clear();
|
|
}
|
|
|
|
/*
|
|
prefs
|
|
{
|
|
models
|
|
{
|
|
1 "snow"
|
|
}
|
|
|
|
surfaces_off
|
|
{
|
|
name1 "head_side_r_avmed_off"
|
|
name2 "head_side_l_avmed_off"
|
|
name3 "head_side_r_off"
|
|
name4 "head_side_l_off"
|
|
}
|
|
|
|
surfaces_on
|
|
{
|
|
name1 "head_bck_uppr_r_avmed_off"
|
|
name2 "head_bck_uppr_l_avmed_off"
|
|
name3 "head_bck_lwr_r_avmed_off"
|
|
name4 "head_bck_lwr_l_avmed_off"
|
|
name5 "head_bck_lwr_r"
|
|
name6 "head_bck_lwr_l"
|
|
}
|
|
}
|
|
|
|
material
|
|
{
|
|
name "face"
|
|
|
|
group
|
|
{
|
|
name "white"
|
|
|
|
shader1 "models/characters/face_average/face"
|
|
}
|
|
|
|
group
|
|
{
|
|
name "black"
|
|
|
|
texture1 "models/characters/average_face/f_marine_b1"
|
|
}
|
|
}
|
|
|
|
material
|
|
{
|
|
name "face_2sided"
|
|
|
|
group
|
|
{
|
|
name "white"
|
|
|
|
shader1 "models/characters/face_average/face_2sided"
|
|
}
|
|
|
|
group
|
|
{
|
|
name "black"
|
|
|
|
texture1 "models/characters/average_face/f_marine_b1"
|
|
}
|
|
}
|
|
|
|
material
|
|
{
|
|
name "head"
|
|
|
|
group
|
|
{
|
|
name "white"
|
|
|
|
shader1 "models/characters/face_average/head"
|
|
}
|
|
|
|
group
|
|
{
|
|
name "black"
|
|
|
|
texture1 "models/characters/average_face/h_marine_b1"
|
|
}
|
|
}
|
|
|
|
material
|
|
{
|
|
name "arms"
|
|
|
|
group
|
|
{
|
|
name "white"
|
|
|
|
shader1 "models/characters/average_sleeves/arms"
|
|
}
|
|
|
|
group
|
|
{
|
|
name "black"
|
|
|
|
texture1 "models/characters/average_sleeves/a_marine_b1"
|
|
}
|
|
}
|
|
*/
|
|
|
|
#define sSKINKEYWORD_MATERIAL "material"
|
|
#define sSKINKEYWORD_GROUP "group"
|
|
#define sSKINKEYWORD_NAME "name"
|
|
#define sSKINKEYWORD_PREFS "prefs"
|
|
#define sSKINKEYWORD_MODELSUSING "models"
|
|
#define sSKINKEYWORD_SURFACES_ON "surfaces_on"
|
|
#define sSKINKEYWORD_SURFACES_OFF "surfaces_off"
|
|
#define sSKINKEYWORD_SURFACES_OFFNOCHILDREN "surfaces_offnochildren"
|
|
|
|
static LPCSTR Skins_Parse(string strThisSkinFileName, CGPGroup *pFileGroup, CGPGroup *pParseGroup_Prefs)
|
|
{
|
|
LPCSTR psError = NULL;
|
|
|
|
// read any optional surface on/off blocks...
|
|
//
|
|
for (int iSurfaceOnOffType = 0; iSurfaceOnOffType<3; iSurfaceOnOffType++)
|
|
{
|
|
CGPGroup *pSurfaceParseGroup = NULL;
|
|
switch (iSurfaceOnOffType)
|
|
{
|
|
case 0: pSurfaceParseGroup = pParseGroup_Prefs->FindSubGroup(sSKINKEYWORD_SURFACES_ON); break;
|
|
case 1: pSurfaceParseGroup = pParseGroup_Prefs->FindSubGroup(sSKINKEYWORD_SURFACES_OFF); break;
|
|
case 2: pSurfaceParseGroup = pParseGroup_Prefs->FindSubGroup(sSKINKEYWORD_SURFACES_OFFNOCHILDREN); break;
|
|
default: assert(0); break;
|
|
}
|
|
|
|
if (pSurfaceParseGroup)
|
|
{
|
|
CGPValue *pValue = pSurfaceParseGroup->GetPairs();
|
|
while (pValue)
|
|
{
|
|
// string str1 = (*it).first; // junk, eg "name1"
|
|
string str2 = pValue->GetTopValue();
|
|
|
|
switch (iSurfaceOnOffType)
|
|
{
|
|
case 0: CurrentSkinsSurfacePrefs[strThisSkinFileName].vSurfacesOn.push_back(str2); break;
|
|
case 1: CurrentSkinsSurfacePrefs[strThisSkinFileName].vSurfacesOff.push_back(str2); break;
|
|
case 2: CurrentSkinsSurfacePrefs[strThisSkinFileName].vSurfacesOffNoChildren.push_back(str2); break;
|
|
default: assert(0); break;
|
|
}
|
|
|
|
pValue = pValue->GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
// find all the materials and add them to the skin set...
|
|
//
|
|
int iMaterialDeclarationIndex = 0;
|
|
for (CGPGroup *pMaterialGroup = pFileGroup->GetSubGroups(); pMaterialGroup; pMaterialGroup = pMaterialGroup->GetNext(), iMaterialDeclarationIndex++)
|
|
{
|
|
string strKeyWord = pMaterialGroup->GetName();
|
|
|
|
if (strKeyWord == sSKINKEYWORD_MATERIAL)
|
|
{
|
|
string strMaterialName(pMaterialGroup->FindPairValue(sSKINKEYWORD_NAME,""));
|
|
|
|
if (strMaterialName == "")
|
|
{
|
|
psError = va("%s[%d] had no \"%s\" field!\n",sSKINKEYWORD_MATERIAL, iMaterialDeclarationIndex, sSKINKEYWORD_NAME);
|
|
return psError;
|
|
}
|
|
|
|
// now iterate through the ethnic group variants of this material...
|
|
//
|
|
int iEthnicGroupIndex = 0;
|
|
for (CGPGroup *pEthnicGroup = pMaterialGroup->GetSubGroups(); pEthnicGroup; pEthnicGroup = pEthnicGroup->GetNext(), iEthnicGroupIndex++)
|
|
{
|
|
strKeyWord = pEthnicGroup->GetName();
|
|
|
|
if (strKeyWord == sSKINKEYWORD_GROUP)
|
|
{
|
|
string strEthnicGroupName(pEthnicGroup->FindPairValue(sSKINKEYWORD_NAME,""));
|
|
|
|
if (strEthnicGroupName == "")
|
|
{
|
|
psError = va("%s[%d] %s[%d] had no \"%s\" field!\n",sSKINKEYWORD_MATERIAL, iMaterialDeclarationIndex, sSKINKEYWORD_GROUP, iEthnicGroupIndex, sSKINKEYWORD_NAME);
|
|
return psError;
|
|
}
|
|
|
|
// now iterate through the shader variants for this ethnic version of this material... (is anyone reading this...?)
|
|
//
|
|
int iAlternateShaderIndex = 0;
|
|
for (CGPValue *pValue = pEthnicGroup->GetPairs(); pValue; pValue = pValue->GetNext())
|
|
{
|
|
string strField(pValue->GetName());
|
|
|
|
if (strField != sSKINKEYWORD_NAME)
|
|
{
|
|
// ... then it should be a shader...
|
|
//
|
|
string strShader(pValue->GetTopValue());
|
|
|
|
CurrentSkins[strThisSkinFileName][strEthnicGroupName][strMaterialName].push_back(strShader);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return psError;
|
|
}
|
|
|
|
|
|
|
|
// returns NULL if error, else usable (local) path...
|
|
//
|
|
static LPCSTR Skins_ModelNameToSkinPath(LPCSTR psModelFilename)
|
|
{
|
|
static CString str;
|
|
str = psModelFilename;
|
|
str.MakeLower();
|
|
while (str.Replace('\\','/')){}
|
|
|
|
// special case for anything in the "objects" dir...
|
|
//
|
|
int i = str.Find("models/objects/");
|
|
if (i>=0)
|
|
{
|
|
str = str.Left(i+strlen("models/objects/"));
|
|
str+= "skins";
|
|
return (LPCSTR) str;
|
|
}
|
|
|
|
// or anything else...
|
|
//
|
|
i = str.Find("models/");
|
|
if (i>=0)
|
|
{
|
|
str = str.Left(i+strlen("models/"));
|
|
str+= "characters/skins";
|
|
return (LPCSTR) str;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// (params should both be const, but GP1 has crap proto args)
|
|
//
|
|
static bool Skins_ParseThisFile(CGenericParser2 &SkinParser,
|
|
/*const*/char *psSkinFileData, string &strThisModelBaseName,
|
|
CGPGroup *&pFileGroup, CGPGroup *&pParseGroup_Prefs,
|
|
LPCSTR psSkinFileName, G2SkinModelPrefs_t &G2SkinModelPrefs
|
|
)
|
|
{
|
|
bool bParseThisSkinFile = false;
|
|
|
|
char *psDataPtr = psSkinFileData;
|
|
|
|
if (SkinParser.Parse(&psDataPtr, true))
|
|
{
|
|
pFileGroup = SkinParser.GetBaseParseGroup();
|
|
if (pFileGroup)
|
|
{
|
|
pParseGroup_Prefs = pFileGroup->FindSubGroup(sSKINKEYWORD_PREFS);//, true);
|
|
if (pParseGroup_Prefs)
|
|
{
|
|
// see if our current model is amongst those that use this skin...
|
|
//
|
|
// look for a "models" field which says who uses this skin file...
|
|
//
|
|
CGPGroup *pParseGroup_ModelsUsing = pParseGroup_Prefs->FindSubGroup(sSKINKEYWORD_MODELSUSING);
|
|
|
|
if (pParseGroup_ModelsUsing)
|
|
{
|
|
// ok, now search this subgroup for our model name...
|
|
//
|
|
for (CGPValue *pValue = pParseGroup_ModelsUsing->GetPairs(); pValue; pValue = pValue->GetNext())
|
|
{
|
|
string str1 = pValue->GetName();
|
|
string str2 = pValue->GetTopValue();
|
|
|
|
if (str2 == strThisModelBaseName)
|
|
{
|
|
bParseThisSkinFile = true;
|
|
//break; // do NOT stop scanning now
|
|
}
|
|
|
|
// record this model entry...
|
|
//
|
|
G2SkinModelPrefs[ psSkinFileName ][ str2 ] = 1; // any old junk, as long as we make an entry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("{} - Brace mismatch error in file \"%s\"!",psSkinFileName));
|
|
}
|
|
|
|
return bParseThisSkinFile;
|
|
}
|
|
|
|
typedef struct FT_s
|
|
{
|
|
FILETIME ft;
|
|
bool bValid;
|
|
|
|
FT_s()
|
|
{
|
|
bValid = false;
|
|
}
|
|
} FT_t;
|
|
typedef map <string,FT_t> SkinFileTimeDates_t;
|
|
static SkinFileTimeDates_t SkinFileTimeDates;
|
|
|
|
// returns true if at least one set of skin data was read, else false...
|
|
//
|
|
static bool Skins_Read(LPCSTR psModelFilename)
|
|
{
|
|
LPCSTR psError = NULL;
|
|
|
|
CWaitCursor;
|
|
|
|
LPCSTR psSkinsPath = Skins_ModelNameToSkinPath(psModelFilename); // eg "models/characters/skins"
|
|
|
|
if (psSkinsPath)
|
|
{
|
|
string strThisModelBaseName(String_ToLower(Filename_WithoutExt(Filename_WithoutPath(psModelFilename))));
|
|
|
|
char **ppsSkinFiles;
|
|
int iSkinFiles;
|
|
|
|
// scan for skin files...
|
|
//
|
|
ppsSkinFiles = //ri.FS_ListFiles( "shaders", ".shader", &iSkinFiles );
|
|
Sys_ListFiles( va("%s%s",gamedir,psSkinsPath),// const char *directory,
|
|
".g2skin", // const char *extension,
|
|
NULL, // char *filter,
|
|
&iSkinFiles,// int *numfiles,
|
|
qfalse // qboolean wantsubs
|
|
);
|
|
|
|
if ( !ppsSkinFiles || !iSkinFiles )
|
|
{
|
|
CurrentSkins.clear();
|
|
CurrentSkinsSurfacePrefs.clear();
|
|
return false;
|
|
}
|
|
|
|
if ( iSkinFiles > MAX_SKIN_FILES )
|
|
{
|
|
WarningBox(va("%d skin files found, capping to %d\n\n(tell me if this ever happens -Ste)", iSkinFiles, MAX_SKIN_FILES ));
|
|
|
|
iSkinFiles = MAX_SKIN_FILES;
|
|
}
|
|
|
|
// load and parse skin files...
|
|
//
|
|
// for now, I just scan each file and if it's out of date then I invalidate it's model-prefs info...
|
|
//
|
|
extern bool GetFileTime(LPCSTR psFileName, FILETIME &ft);
|
|
for (int i=0; i<iSkinFiles; i++)
|
|
{
|
|
bool bReParseThisFile = false;
|
|
|
|
char sFileName[MAX_QPATH];
|
|
LPCSTR psFileName = ppsSkinFiles[i];
|
|
Com_sprintf( sFileName, sizeof( sFileName ), "%s/%s", psSkinsPath, psFileName );
|
|
psFileName = &sFileName[0];
|
|
|
|
// have a go at getting this time/date stamp if not already present...
|
|
//
|
|
if (!SkinFileTimeDates[psFileName].bValid)
|
|
{
|
|
FILETIME ft;
|
|
if (GetFileTime(psFileName, ft))
|
|
{
|
|
SkinFileTimeDates[psFileName].ft = ft;
|
|
SkinFileTimeDates[psFileName].bValid = true;
|
|
}
|
|
}
|
|
|
|
// now see if there's a valid time-stamp, and use it if so, else give up and re-scan all files...
|
|
//
|
|
if (SkinFileTimeDates[psFileName].bValid)
|
|
{
|
|
FILETIME ft;
|
|
if (GetFileTime(psFileName, ft))
|
|
{
|
|
LONG l = CompareFileTime( &SkinFileTimeDates[psFileName].ft, &ft);
|
|
|
|
bReParseThisFile = (l<0);
|
|
}
|
|
else
|
|
{
|
|
bReParseThisFile = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bReParseThisFile = true;
|
|
}
|
|
|
|
if (bReParseThisFile)
|
|
{
|
|
G2SkinModelPrefs[sFileName].clear();
|
|
}
|
|
}
|
|
|
|
if (1)//bReParseSkinFiles || !CurrentSkins.size())
|
|
{
|
|
CurrentSkins.clear();
|
|
CurrentSkinsSurfacePrefs.clear();
|
|
|
|
char *buffers[MAX_SKIN_FILES]={0};
|
|
// long iTotalBytesLoaded = 0;
|
|
for ( int i=0; i<iSkinFiles && !psError; i++ )
|
|
{
|
|
char sFileName[MAX_QPATH];
|
|
|
|
string strThisSkinFile(ppsSkinFiles[i]);
|
|
|
|
Com_sprintf( sFileName, sizeof( sFileName ), "%s/%s", psSkinsPath, strThisSkinFile.c_str() );
|
|
StatusMessage( va("Scanning skin %d/%d: \"%s\"...",i+1,iSkinFiles,sFileName));
|
|
|
|
//ri.Printf( PRINT_ALL, "...loading '%s'\n", sFileName );
|
|
|
|
bool _bDiskLoadOccured = false; // debug use only, but wtf?
|
|
|
|
#define LOAD_SKIN_FILE \
|
|
/*iTotalBytesLoaded += */ ri.FS_ReadFile( sFileName, (void **)&buffers[i] ); \
|
|
if ( !buffers[i] ) \
|
|
{ \
|
|
CurrentSkins.clear(); \
|
|
CurrentSkinsSurfacePrefs.clear(); \
|
|
\
|
|
ri.Error( ERR_DROP, "Couldn't load %s", sFileName );\
|
|
} \
|
|
_bDiskLoadOccured = true;
|
|
|
|
|
|
// see if we should pay attention to the contents of this file...
|
|
//
|
|
CGPGroup *pFileGroup = NULL;
|
|
CGPGroup *pParseGroup_Prefs = NULL;
|
|
CGenericParser2 SkinParser;
|
|
//
|
|
bool bParseThisFile = false;
|
|
//
|
|
// if we have any information about this skin file as regards what models it refers to, use the info...
|
|
//
|
|
if (G2SkinModelPrefs[sFileName].size())
|
|
{
|
|
map<string, int>::iterator it = G2SkinModelPrefs[sFileName].find( strThisModelBaseName );
|
|
if (it != G2SkinModelPrefs[sFileName].end())
|
|
{
|
|
// this skin file contains this entry, so just check that we can setup the parse groups ok...
|
|
//
|
|
LOAD_SKIN_FILE;
|
|
|
|
char *psDataPtr = buffers[i];
|
|
if (SkinParser.Parse(&psDataPtr, true))
|
|
{
|
|
pFileGroup = SkinParser.GetBaseParseGroup();
|
|
if (pFileGroup)
|
|
{
|
|
pParseGroup_Prefs = pFileGroup->FindSubGroup(sSKINKEYWORD_PREFS);//, true);
|
|
if (pParseGroup_Prefs)
|
|
{
|
|
bParseThisFile = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("{} - Brace mismatch error in file \"%s\"!",sFileName));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no size info for this file, so check it manually...
|
|
//
|
|
LOAD_SKIN_FILE;
|
|
|
|
if (Skins_ParseThisFile(SkinParser, buffers[i], strThisModelBaseName, pFileGroup, pParseGroup_Prefs,
|
|
sFileName, G2SkinModelPrefs)
|
|
)
|
|
{
|
|
bParseThisFile = true;
|
|
}
|
|
}
|
|
|
|
if (bParseThisFile)
|
|
{
|
|
psError = Skins_Parse( strThisSkinFile, pFileGroup, pParseGroup_Prefs);
|
|
if (psError)
|
|
{
|
|
ErrorBox(va("Skins_Read(): Error reading file \"%s\"!\n\n( Skins will be ignored for this model )\n\nError was:\n\n",sFileName,psError));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//OutputDebugString(va("Skipping parse of file \"%s\" %s\n",sFileName, _bDiskLoadOccured?"":"( and no load! )"));
|
|
}
|
|
}
|
|
//
|
|
// free loaded skin files...
|
|
//
|
|
for ( i=0; i<iSkinFiles; i++ )
|
|
{
|
|
if (buffers[i])
|
|
{
|
|
ri.FS_FreeFile( buffers[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
StatusMessage(NULL);
|
|
Sys_FreeFileList( ppsSkinFiles );
|
|
}
|
|
else
|
|
{
|
|
CurrentSkins.clear();
|
|
CurrentSkinsSurfacePrefs.clear();
|
|
}
|
|
|
|
if (psError)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return !!(CurrentSkins.size());
|
|
}
|
|
|
|
|
|
|
|
// ask if there are any skins available for the supplied GLM model path...
|
|
//
|
|
bool Skins_FilesExist(LPCSTR psModelFilename)
|
|
{
|
|
return Skins_Read(psModelFilename);
|
|
}
|
|
|
|
|
|
|
|
static Skins_ApplyVariant(ModelContainer_t *pContainer, SkinSet_t::iterator itEthnic, ShadersForMaterial_t::iterator itMaterialShaders, string strMaterialName, int iVariant)
|
|
{
|
|
if (itMaterialShaders != (*itEthnic).second.end())
|
|
{
|
|
LPCSTR psShaderName = (*itMaterialShaders).second[iVariant].c_str(); // shader name to load from skinset
|
|
|
|
pContainer->MaterialShaders[strMaterialName] = psShaderName;
|
|
|
|
LPCSTR psLocalTexturePath = R_FindShader( psShaderName ); // shader->texture name
|
|
|
|
if (psLocalTexturePath && strlen(psLocalTexturePath))
|
|
{
|
|
TextureHandle_t hTexture = TextureHandle_ForName( psLocalTexturePath );
|
|
|
|
if (hTexture == -1)
|
|
{
|
|
hTexture = Texture_Load(psLocalTexturePath);
|
|
}
|
|
|
|
GLuint uiBind = Texture_GetGLBind( hTexture );
|
|
|
|
pContainer->MaterialBinds[strMaterialName] = uiBind;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Skins_SetSurfacePrefs(ModelContainer_t *pContainer, StringVector_t &SurfaceNames, SurfaceOnOff_t eStatus)
|
|
{
|
|
bool bFailureOccured = false;
|
|
|
|
for (int i=0; i<SurfaceNames.size() && !bFailureOccured; i++)
|
|
{
|
|
string strSurfaceName = SurfaceNames[i];
|
|
|
|
if (!Model_GLMSurface_SetStatus( pContainer->hModel, strSurfaceName.c_str(), eStatus))
|
|
bFailureOccured = true;
|
|
}
|
|
}
|
|
|
|
// this fills in a modelcontainer's "MaterialBinds" and "MaterialShaders" fields (registering textures etc)
|
|
// based on the skinset pointed at by pContainer->SkinSets and strSkinFile
|
|
//
|
|
bool Skins_ApplySkinFile(ModelContainer_t *pContainer, string strSkinFile, string strEthnic,
|
|
bool bApplySurfacePrefs, bool bDefaultSurfaces,
|
|
string strMaterial, int iVariant) // optional params, else "" and -1
|
|
{
|
|
CWaitCursor wait;
|
|
|
|
bool bReturn = true;
|
|
|
|
|
|
// new bit, if surface prefs active, then enable/disable model surfaces...
|
|
//
|
|
if (bApplySurfacePrefs || bDefaultSurfaces)
|
|
{
|
|
Model_GLMSurfaces_DefaultAll(pContainer->hModel);
|
|
}
|
|
|
|
if (bApplySurfacePrefs)
|
|
{
|
|
// now go through surface prefs list and turn them on/off accordingly...
|
|
//
|
|
SkinSetsSurfacePrefs_t::iterator itSurfPrefs = pContainer->SkinSetsSurfacePrefs.find(strSkinFile.c_str());
|
|
if (itSurfPrefs != pContainer->SkinSetsSurfacePrefs.end())
|
|
{
|
|
SurfaceOnOffPrefs_t &prefs = (*itSurfPrefs).second;
|
|
|
|
Skins_SetSurfacePrefs(pContainer, prefs.vSurfacesOn, SURF_ON);
|
|
Skins_SetSurfacePrefs(pContainer, prefs.vSurfacesOff, SURF_OFF);
|
|
Skins_SetSurfacePrefs(pContainer, prefs.vSurfacesOffNoChildren, SURF_NO_DESCENDANTS);
|
|
}
|
|
}
|
|
|
|
// setup a map of material names first, which we can then read from and lookup shaders for...
|
|
//
|
|
if (iVariant != -1)
|
|
{
|
|
// just clear this one...
|
|
//
|
|
assert(!strMaterial.empty());
|
|
pContainer->MaterialShaders [strMaterial] = "";
|
|
pContainer->MaterialBinds [strMaterial] = (GLuint) 0;
|
|
}
|
|
else
|
|
{
|
|
pContainer->strCurrentSkinFile = strSkinFile;
|
|
pContainer->strCurrentSkinEthnic= strEthnic;
|
|
|
|
pContainer->MaterialBinds.clear();
|
|
pContainer->MaterialShaders.clear();
|
|
|
|
for (int iSurface = 0; iSurface<pContainer->iNumSurfaces; iSurface++)
|
|
{
|
|
// when we're at this point we know it's GLM model, and that the shader name is in fact a material name...
|
|
//
|
|
LPCSTR psMaterialName = GLMModel_GetSurfaceShaderName( pContainer->hModel, iSurface );
|
|
|
|
pContainer->MaterialShaders [psMaterialName] = ""; // just insert the key for now, so the map<> is legit.
|
|
pContainer->MaterialBinds [psMaterialName] = (GLuint) 0; // default to gl-white-notfound texture
|
|
}
|
|
}
|
|
|
|
SkinSets_t::iterator itSkins = pContainer->SkinSets.find(strSkinFile);
|
|
if (itSkins != pContainer->SkinSets.end())
|
|
{
|
|
SkinSet_t::iterator itEthnic = (*itSkins).second.find(strEthnic);
|
|
|
|
// new bit, auto-hack the ethnic-variant if we don't find it to use the first one (so lazy NPC->MVS code works)...
|
|
//
|
|
if (itEthnic == (*itSkins).second.end())
|
|
{
|
|
itEthnic = (*itSkins).second.begin();
|
|
string strNewEthnic = (*itEthnic).first;
|
|
pContainer->strCurrentSkinEthnic= strNewEthnic;
|
|
}
|
|
|
|
if (itEthnic != (*itSkins).second.end())
|
|
{
|
|
// ok, we've found the ethnic skin set specified, now use it to look up the shaders for the materials...
|
|
//
|
|
// query these materials, and find the appropriate skinfile/ethnic shader (variant 0 for now)...
|
|
//
|
|
if (iVariant != -1)
|
|
{
|
|
ShadersForMaterial_t::iterator itMaterialShaders = (*itEthnic).second.find(strMaterial);
|
|
|
|
Skins_ApplyVariant(pContainer, itEthnic, itMaterialShaders, strMaterial, iVariant);
|
|
}
|
|
else
|
|
{
|
|
for (MappedString_t::iterator itMaterial = pContainer->MaterialShaders.begin(); itMaterial != pContainer->MaterialShaders.end(); ++itMaterial)
|
|
{
|
|
LPCSTR psMaterialName = (*itMaterial).first.c_str();
|
|
|
|
// find this material in the skinset...
|
|
//
|
|
ShadersForMaterial_t::iterator itMaterialShaders = (*itEthnic).second.find((*itMaterial).first);
|
|
|
|
Skins_ApplyVariant(pContainer, itEthnic, itMaterialShaders, psMaterialName, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
// sets up valid skin tables based on first entries loaded, also registers/binds appropriate textures...
|
|
//
|
|
void Skins_ApplyDefault(ModelContainer_t *pContainer)
|
|
{
|
|
for (SkinSets_t::iterator itSkins = pContainer->SkinSets.begin(); itSkins != pContainer->SkinSets.end(); ++itSkins)
|
|
{
|
|
string strCurrentSkinFile = (*itSkins).first;
|
|
|
|
for (SkinSet_t::iterator itEthnic = (*itSkins).second.begin(); itEthnic != (*itSkins).second.end(); ++itEthnic)
|
|
{
|
|
string strCurrentEthnic = (*itEthnic).first; // eg "white"
|
|
|
|
Skins_ApplySkinFile(pContainer, strCurrentSkinFile, strCurrentEthnic, true, true);
|
|
return; // :-)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// returns true if at least one set of skin data was read, else false...
|
|
//
|
|
bool Skins_Read(LPCSTR psModelFilename, ModelContainer_t *pContainer)
|
|
{
|
|
if (Skins_Read(psModelFilename))
|
|
{
|
|
pContainer->SkinSets = CurrentSkins; // huge stl-nested-map copy
|
|
pContainer->SkinSetsSurfacePrefs = CurrentSkinsSurfacePrefs;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
typedef map <string, StringVector_t> EthnicMaterials_t;
|
|
typedef map < string, EthnicMaterials_t > SkinFileMaterialsMissing_t; // [skinfile][ethnic][missing materials]
|
|
static void SkinSet_Validate_BuildList(StringSet_t &strUniqueSkinShaders, SkinSets_t::iterator itSkins, StringSet_t &MaterialsPresentInModel, SkinFileMaterialsMissing_t &SkinFileMaterialsMissing)
|
|
{
|
|
string strSkinName((*itSkins).first); // eg "thug1"
|
|
|
|
// race variants...
|
|
//
|
|
for (SkinSet_t::iterator itEthnic = (*itSkins).second.begin(); itEthnic != (*itSkins).second.end(); ++itEthnic)
|
|
{
|
|
string strEthnicName((*itEthnic).first); // eg "white"
|
|
|
|
map <string, int> MaterialsDefinedInThisEthnicVariant;
|
|
|
|
// materials...
|
|
//
|
|
for (ShadersForMaterial_t::iterator itMaterial = (*itEthnic).second.begin(); itMaterial != (*itEthnic).second.end(); ++itMaterial)
|
|
{
|
|
string strMaterialName((*itMaterial).first); // eg "face"
|
|
|
|
// keep a note of all materials defined in this file...
|
|
//
|
|
MaterialsDefinedInThisEthnicVariant[strMaterialName]=1; // 1 = whatever
|
|
|
|
// available shader variants for this material...
|
|
//
|
|
for (int iShaderVariantIndex=0; iShaderVariantIndex<(*itMaterial).second.size(); iShaderVariantIndex++)
|
|
{
|
|
string strShaderVariantName((*itMaterial).second[iShaderVariantIndex]); // eg "models/characters/average_sleeves/face"
|
|
|
|
strUniqueSkinShaders.insert(strUniqueSkinShaders.end(),strShaderVariantName);
|
|
}
|
|
}
|
|
|
|
// now scan through the materials defined in this ethnic variant, and see if there were any materials
|
|
// mentioned/referenced by the model which weren't defined here...
|
|
//
|
|
for (StringSet_t::iterator itMaterialsReferenced = MaterialsPresentInModel.begin(); itMaterialsReferenced != MaterialsPresentInModel.end(); ++itMaterialsReferenced)
|
|
{
|
|
string strModelMaterial = (*itMaterialsReferenced);
|
|
|
|
if (!strModelMaterial.empty() && stricmp(strModelMaterial.c_str(),"[NoMaterial]"))
|
|
{
|
|
if (MaterialsDefinedInThisEthnicVariant.find(strModelMaterial) == MaterialsDefinedInThisEthnicVariant.end())
|
|
{
|
|
SkinFileMaterialsMissing[strSkinName][strEthnicName].push_back(strModelMaterial);
|
|
//
|
|
// OutputDebugString(va("strModelMaterial = %s\n",strModelMaterial.c_str()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// UI-code...
|
|
//
|
|
bool Skins_FileHasSurfacePrefs(ModelContainer_t *pContainer, LPCSTR psSkin)
|
|
{
|
|
SkinSetsSurfacePrefs_t::iterator itSurfPrefs = pContainer->SkinSetsSurfacePrefs.find(psSkin);
|
|
if (itSurfPrefs != pContainer->SkinSetsSurfacePrefs.end())
|
|
{
|
|
SurfaceOnOffPrefs_t &prefs = (*itSurfPrefs).second;
|
|
|
|
return prefs.vSurfacesOn.size() ||
|
|
prefs.vSurfacesOff.size() ||
|
|
prefs.vSurfacesOffNoChildren.size()
|
|
;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Skins_ApplyEthnic( ModelContainer_t *pContainer, LPCSTR psSkin, LPCSTR psEthnic, bool bApplySurfacePrefs, bool bDefaultSurfaces)
|
|
{
|
|
return Skins_ApplySkinFile(pContainer, psSkin, psEthnic, bApplySurfacePrefs, bDefaultSurfaces);
|
|
}
|
|
|
|
bool Skins_ApplySkinShaderVariant(ModelContainer_t *pContainer, LPCSTR psSkin, LPCSTR psEthnic, LPCSTR psMaterial, int iVariant )
|
|
{
|
|
return Skins_ApplySkinFile(pContainer, psSkin, psEthnic, false, false, psMaterial, iVariant );
|
|
}
|
|
|
|
|
|
// returns false for "at least one shader or material missing", else true for all ok...
|
|
//
|
|
extern bool g_bReportImageLoadErrors;
|
|
bool Skins_Validate( ModelContainer_t *pContainer, int iSkinNumber )
|
|
{
|
|
bool bReturn = true;
|
|
bool bPREV_bReportImageLoadErrors = g_bReportImageLoadErrors;
|
|
g_bReportImageLoadErrors = false;
|
|
|
|
bool bCheckMissingMaterials = true;//GetYesNo("Check for materials referenced by model but missing in skinfile(s)?\n\n( Note: This can give false alarms for skins which don't use (eg) \"scarf\" )");
|
|
|
|
// first, build up a list of all model materials...
|
|
//
|
|
StringSet_t MaterialsPresentInModel;
|
|
for (int iSurface = 0; iSurface < pContainer->iNumSurfaces; iSurface++)
|
|
{
|
|
bool bOnOff = GLMModel_SurfaceIsON(pContainer->hModel, iSurface);
|
|
|
|
if (bOnOff)
|
|
{
|
|
LPCSTR psMaterial = GLMModel_GetSurfaceShaderName( pContainer->hModel, iSurface);
|
|
|
|
MaterialsPresentInModel.insert(MaterialsPresentInModel.end(),psMaterial);
|
|
}
|
|
}
|
|
|
|
// build up a list of shaders used...
|
|
//
|
|
StringSet_t UniqueSkinShaders;
|
|
SkinFileMaterialsMissing_t SkinFileMaterialsMissing;
|
|
int iThisSkinIndex = 0;
|
|
for (SkinSets_t::iterator itSkins = pContainer->SkinSets.begin(); itSkins != pContainer->SkinSets.end(); ++itSkins, iThisSkinIndex++)
|
|
{
|
|
if (iSkinNumber == iThisSkinIndex || iSkinNumber == -1)
|
|
{
|
|
SkinSet_Validate_BuildList(UniqueSkinShaders, itSkins, MaterialsPresentInModel, SkinFileMaterialsMissing);
|
|
}
|
|
}
|
|
|
|
// now process the unique list we've just built...
|
|
//
|
|
CWaitCursor wait;
|
|
string strFoundList;
|
|
string strNotFoundList;
|
|
int iUniqueIndex = 0;
|
|
for (StringSet_t::iterator it = UniqueSkinShaders.begin(); it != UniqueSkinShaders.end(); ++it, iUniqueIndex++)
|
|
{
|
|
string strShader(*it);
|
|
|
|
StatusMessage(va("Processing shader %d/%d: \"%s\"\n",iUniqueIndex,UniqueSkinShaders.size(),strShader.c_str()));
|
|
|
|
OutputDebugString(va("Unique: \"%s\"... ",strShader.c_str()));
|
|
|
|
int iTextureHandle = Texture_Load(strShader.c_str(), true); // bInhibitStatus
|
|
|
|
GLuint uiGLBind = Texture_GetGLBind( iTextureHandle );
|
|
|
|
if (uiGLBind == 0)
|
|
{
|
|
OutputDebugString("NOT FOUND\n");
|
|
|
|
strNotFoundList += strShader;
|
|
strNotFoundList += "\n";
|
|
}
|
|
else
|
|
{
|
|
OutputDebugString("found\n");
|
|
|
|
strFoundList += strShader;
|
|
strFoundList += "\n";
|
|
}
|
|
}
|
|
|
|
StatusMessage(NULL);
|
|
|
|
|
|
// see if we were missing any model materials in these skins...
|
|
//
|
|
CString strModelMaterialsMissing;
|
|
if (SkinFileMaterialsMissing.size())
|
|
{
|
|
for (SkinFileMaterialsMissing_t::iterator itSkinFileMaterialsMissing = SkinFileMaterialsMissing.begin(); itSkinFileMaterialsMissing != SkinFileMaterialsMissing.end(); ++itSkinFileMaterialsMissing)
|
|
{
|
|
string strSkinFileName((*itSkinFileMaterialsMissing).first);
|
|
|
|
if (iSkinNumber == -1)
|
|
{
|
|
strModelMaterialsMissing += va("\nSkin \"%s\":\n",strSkinFileName.c_str());
|
|
}
|
|
|
|
for (EthnicMaterials_t::iterator itSkinFile = (*itSkinFileMaterialsMissing).second.begin(); itSkinFile != (*itSkinFileMaterialsMissing).second.end(); ++itSkinFile)
|
|
{
|
|
string strEthnicFileName((*itSkinFile).first);
|
|
|
|
strModelMaterialsMissing += va("Ethnic \"%s\": ",strEthnicFileName.c_str());
|
|
|
|
StringVector_t& MaterialStrings = (*itSkinFile).second;
|
|
|
|
for (int iMaterial = 0; iMaterial != MaterialStrings.size(); ++iMaterial)
|
|
{
|
|
string strMaterial(MaterialStrings[iMaterial]);
|
|
|
|
strModelMaterialsMissing += va("%s\"%s\"",(iMaterial==0)?"":", ",strMaterial.c_str());
|
|
}
|
|
strModelMaterialsMissing += "\n";
|
|
}
|
|
}
|
|
}
|
|
if (!strModelMaterialsMissing.IsEmpty())
|
|
{
|
|
if (iSkinNumber == -1)
|
|
{
|
|
strModelMaterialsMissing.Insert(0, "One or more skin files are missing some material definitions referenced by this model's currently-active surfaces.\nList follows...\n\n");
|
|
}
|
|
else
|
|
{
|
|
strModelMaterialsMissing.Insert(0, "This skin file is missing one or more material definitions referenced by this model's currently-active surfaces.\nList follows...\n\n");
|
|
}
|
|
}
|
|
|
|
|
|
if (!strModelMaterialsMissing.IsEmpty())
|
|
{
|
|
if (bCheckMissingMaterials)
|
|
{
|
|
WarningBox(va("Summary Part 1: Missing materials\n\n%s",(LPCSTR)strModelMaterialsMissing));
|
|
}
|
|
}
|
|
|
|
|
|
// Now output results...
|
|
|
|
// If too many lines to fit on screen (which is now happening), send 'em to notepad instead...
|
|
//
|
|
// ( tacky way of counting lines...)
|
|
CString strTackyCount(strNotFoundList.c_str());
|
|
strTackyCount += strFoundList.c_str();
|
|
|
|
int iLines = strTackyCount.Replace('\n','?'); // :-)
|
|
|
|
#define MAX_BOX_LINES_HERE 50
|
|
|
|
if (strNotFoundList.empty())
|
|
{
|
|
if (iLines > MAX_BOX_LINES_HERE)
|
|
{
|
|
if (GetYesNo(va("All shaders found... :-)\n\nList has > %d entries, send to Notepad?",MAX_BOX_LINES_HERE)))
|
|
{
|
|
SendStringToNotepad(va("All shaders found... :-)\n\nList follows:\n\n%s",strFoundList.c_str()),"found_shaders.txt");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InfoBox(va("All shaders found... :-)\n\nList follows:\n\n%s",strFoundList.c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iLines > MAX_BOX_LINES_HERE)
|
|
{
|
|
if (GetYesNo(va("Some missing shader, some found, but list is > %d entries, send to Notepad?",MAX_BOX_LINES_HERE)))
|
|
{
|
|
SendStringToNotepad(va("Missing shaders:\n\n%s\n\nFound shaders:\n\n%s",strNotFoundList.c_str(),strFoundList.c_str()),"found_shaders.txt");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WarningBox(va("Missing shaders:\n\n%s\n\nFound shaders:\n\n%s",strNotFoundList.c_str(),strFoundList.c_str()));
|
|
}
|
|
bReturn = false;
|
|
}
|
|
|
|
|
|
g_bReportImageLoadErrors = bPREV_bReportImageLoadErrors;
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
|
|
bool Skins_ApplyToTree (HTREEITEM hTreeItem_Parent, ModelContainer_t *pContainer)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
if (pContainer->SkinSets.size())
|
|
{
|
|
bReturn = true;
|
|
|
|
TreeItemData_t TreeItemData={0};
|
|
TreeItemData.iItemType = TREEITEMTYPE_SKINSHEADER;
|
|
TreeItemData.iModelHandle = pContainer->hModel;
|
|
|
|
HTREEITEM hTreeItem_SkinsHeader = ModelTree_InsertItem("G2Skins available", hTreeItem_Parent, TreeItemData.uiData);
|
|
|
|
// skins...
|
|
//
|
|
int iSkinNumber = 0;
|
|
for (SkinSets_t::iterator itSkins = pContainer->SkinSets.begin(); itSkins != pContainer->SkinSets.end(); ++itSkins, iSkinNumber++)
|
|
{
|
|
string strSkinName((*itSkins).first); // eg "thug1"
|
|
|
|
TreeItemData.iItemNumber = iSkinNumber;
|
|
TreeItemData.iItemType = TREEITEMTYPE_SKIN;
|
|
|
|
HTREEITEM hTreeItem_ThisSkin = ModelTree_InsertItem(strSkinName.c_str(), hTreeItem_SkinsHeader, TreeItemData.uiData);
|
|
|
|
// race variants...
|
|
//
|
|
int iEthnicNumber = 0;
|
|
for (SkinSet_t::iterator itEthnic = (*itSkins).second.begin(); itEthnic != (*itSkins).second.end(); ++itEthnic, iEthnicNumber++)
|
|
{
|
|
string strEthnicName((*itEthnic).first); // eg "white"
|
|
|
|
TreeItemData.iItemNumber = iEthnicNumber;
|
|
TreeItemData.iItemType = TREEITEMTYPE_SKINETHNIC;
|
|
|
|
HTREEITEM hTreeItem_ThisEthnic = ModelTree_InsertItem(strEthnicName.c_str(), hTreeItem_ThisSkin, TreeItemData.uiData);
|
|
|
|
// materials...
|
|
//
|
|
int iMaterialNumber = 0;
|
|
for (ShadersForMaterial_t::iterator itMaterial = (*itEthnic).second.begin(); itMaterial != (*itEthnic).second.end(); ++itMaterial, iMaterialNumber++)
|
|
{
|
|
string strMaterialName((*itMaterial).first); // eg "face"
|
|
|
|
TreeItemData.iItemNumber = iMaterialNumber;
|
|
TreeItemData.iItemType = TREEITEMTYPE_SKINMATERIAL;
|
|
|
|
HTREEITEM hTreeItem_ThisMaterial = ModelTree_InsertItem(strMaterialName.c_str(), hTreeItem_ThisEthnic, TreeItemData.uiData);
|
|
|
|
// available shader variants for this material...
|
|
//
|
|
for (int iShaderVariantIndex=0; iShaderVariantIndex<(*itMaterial).second.size(); iShaderVariantIndex++)
|
|
{
|
|
string strShaderVariantName((*itMaterial).second[iShaderVariantIndex]); // eg "models/characters/average_sleeves/face"
|
|
|
|
TreeItemData.iItemNumber = iShaderVariantIndex;
|
|
TreeItemData.iItemType = TREEITEMTYPE_SKINMATERIALSHADER;
|
|
|
|
HTREEITEM hTreeItem_ThisShaderVariant = ModelTree_InsertItem(strShaderVariantName.c_str(), hTreeItem_ThisMaterial, TreeItemData.uiData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
|
|
|
|
// called directly from renderer...
|
|
//
|
|
extern ModelContainer_t* gpContainerBeingRendered;
|
|
GLuint AnySkin_GetGLBind( ModelHandle_t hModel, LPCSTR psMaterialName, LPCSTR psSurfaceName )
|
|
{
|
|
ModelContainer_t *pContainer = gpContainerBeingRendered; // considerably faster during rendering process :-)
|
|
|
|
if (pContainer == NULL)
|
|
{
|
|
pContainer = ModelContainer_FindFromModelHandle(hModel);
|
|
}
|
|
|
|
if (pContainer)
|
|
{
|
|
if (pContainer->SkinSets.size())
|
|
{
|
|
return pContainer->MaterialBinds[psMaterialName];
|
|
}
|
|
return OldSkins_GetGLBind(pContainer,psSurfaceName);
|
|
}
|
|
|
|
return (GLuint) 0;
|
|
}
|
|
|
|
/////////////////// eof /////////////////
|
|
|
|
|