Files
FC1/CryAnimation/CryModelLoader.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

651 lines
19 KiB
C++

#include "stdafx.h"
#include <StlUtils.h>
#include <CryCompiledFile.h>
#include "ChunkFileReader.h"
#include "CryModel.h"
#include "CryModelState.h"
#include "CryModelLoader.h"
#include "CryModelGeometryLoader.h"
#include "StringUtils.h"
#include "CrySkinMorph.h"
#include "CrySkinMorphBuilder.h"
#include "ControllerManager.h"
#include "CgfUtils.h"
#include "CVars.h"
#include "CryModelSubmesh.h"
using namespace CryStringUtils;
class CAutoFile
{
FILE* m_f;
public:
CAutoFile (const char* szName, const char* szMode)
{
m_f = g_GetPak()->FOpen (szName, szMode);
}
~CAutoFile ()
{
if (m_f)
g_GetPak()->FClose (m_f);
}
long GetSize()
{
if (g_GetPak()->FSeek (m_f, 0, SEEK_END))
return -1;
long nSize = g_GetPak()->FTell (m_f);
if (g_GetPak()->FSeek(m_f, 0, SEEK_SET))
return -1;
return nSize;
}
bool Read (void* pData, unsigned nSize)
{
return (1 == g_GetPak()->FRead (pData, nSize, 1, m_f));
}
operator FILE*() {return m_f;}
};
CryModelLoader::CryModelLoader (CControllerManager* pControllerManager):
m_pControllerManager (pControllerManager),
m_fCalFile (NULL),
m_nCafFindFileHandle (-1),
m_pModel (NULL),
m_bLoadFromCCG (false),
m_bExtCCG(false)
{
}
CryModelLoader::~CryModelLoader()
{
// clean up the resources
clear();
}
// cleans up the resources allocated during load
void CryModelLoader::clear()
{
m_strGeomFileNameNoExt = "";
m_strCalFileName = "";
if (m_fCalFile)
{
g_GetPak()->FClose (m_fCalFile);
m_fCalFile = NULL;
}
if (m_nCafFindFileHandle != -1)
{
g_GetPak()->FindClose(m_nCafFindFileHandle);
m_nCafFindFileHandle = -1;
}
if (m_pModel)
{
delete m_pModel;
m_pModel = NULL;
}
m_arrLodFiles.clear();
m_arrBufferCCG.clear();
}
bool IsValidFile (const char* szFilePath)
{
FILE* f = g_GetPak()->FOpen(szFilePath, "rb");
if (f)
{
g_GetPak()->FClose (f);
return true;
}
return false;
}
CryModel* CryModelLoader::loadNew (CryCharBody* pBody, const string& strGeomFileName, float fScale)
{
AUTO_PROFILE_SECTION(g_dTimeGeomLoad);
// make sure we call done() whenever we return from this function
CAutoClearLoader _autoClear (this);
const char* szExt = FindExtension(strGeomFileName.c_str());
m_strGeomFileNameNoExt.assign (strGeomFileName.c_str(), *szExt?szExt-1:szExt);
m_bExtCCG = !stricmp(szExt, "ccg");
m_fScale = fScale;
m_bLoadFromCCG = g_GetCVars()->ca_EnableCCG() && IsValidFile (getCCGFilePath().c_str());
// first, initialize the search of animations. If the file has no animations at all (no cal file, no _..caf files)
// then this is not an animated file and must be loaded somewhere else (namely in the 3D Engine)
if (!m_bLoadFromCCG)
searchAnimations (); // no need to exit if no animations: maybe it's a body part
g_GetLog()->UpdateLoadingScreen ("\003Loading %s", cutString(m_bLoadFromCCG?getCCGFilePath():strGeomFileName, 40).c_str());
// find how many LODs we have
if (!(m_bLoadFromCCG?preloadCCG():preloadCGFs()))
return NULL;
m_pModel = new CryModel (pBody, m_pControllerManager);
if (m_bLoadFromCCG)
{
if (!loadCCG())
return NULL;
}
else
{
if (!loadCGFs())
return NULL;
//m_pModel->buildMorphSkins();
if (!loadTextures())
return NULL;
m_pModel->m_pDefaultModelState->GetCryModelSubmesh(0)->GenerateRenderArrays (strGeomFileName.c_str());
if (!loadAnimations())
return NULL;
}
#if 0
// needed for XBox development
extern void exportTestModel(CryGeometryInfo* pGeometry, CLeafBuffer* pLeafBuffer);
// on this stage, if m_pCryModel == NULL, it means the loader couldn't load the model
if (m_pModel)
exportTestModel( m_pModel->getGeometryInfo(), m_pModel->m_pDefaultModelState->m_pLeafBuffers[0]);
#endif
if (g_GetCVars()->ca_ZDeleteConstructionData())
{
m_pModel->clearConstructionData();
}
else
g_GetLog()->LogWarning ("\005The construction data wasn't deleted");
// return the initialized(loaded) model, forgetting about it so that it doesn't get destructed by the following call to done()
return detachModel();
}
// tries to find out if there are any animations for this file; if there are some,
// prepares to load them and returns true; otherwise returns false
bool CryModelLoader::searchAnimations ()
{
AUTO_PROFILE_SECTION(g_dTimeGeomChunkLoad);
// make up the cal file name
m_strCalFileName = m_strGeomFileNameNoExt + ".cal";
// try to find out - if there are any animations for this file. if there are none, then return an error
m_fCalFile = g_GetPak()->FOpen(m_strCalFileName.c_str(), "r");
m_nCafFindFileHandle = -1;
if (!m_fCalFile)
{
// finish making search path
string strSeachFilter = m_strGeomFileNameNoExt + "_*.caf";
// search
m_nCafFindFileHandle = g_GetPak()->FindFirst (strSeachFilter.c_str(), &m_fileinfo);
}
return m_fCalFile || m_nCafFindFileHandle != -1;
}
// searches for lod models for the given model; returns false in case of some error
bool CryModelLoader::preloadCGFs()
{
AUTO_PROFILE_SECTION (g_dTimeGeomChunkLoadFileIO);
CChunkFileReader_AutoPtr pReader = new CChunkFileReader ();
// first try to open LOD 0
if (!pReader->open (m_strGeomFileNameNoExt + ".cgf"))
{
g_GetLog()->LogError ("\003CryModelLoader::preloadCGFs(%s): main file not found", m_strGeomFileNameNoExt.c_str());
return false;
}
if (0 == pReader->numChunksOfType(ChunkType_BoneAnim))
return false; // the cgf doesn't contain bone info
m_arrLodFiles.reinit (1);
m_arrLodFiles[0] = pReader;
for (unsigned nLOD = 1; nLOD < g_nMaxGeomLodLevels; ++nLOD)
{
pReader = new CChunkFileReader();
if (pReader->open(m_strGeomFileNameNoExt + "_lod" + toString(nLOD) + ".cgf"))
m_arrLodFiles.push_back(pReader);
else
break; // we have opened all the LOD files so far
indicateProgress();
}
// we have LOD 0, so it's optional to have any other and we return true anyway
return true;
}
// loads the CCG in memory buffer m_arrBufferCCG
bool CryModelLoader::preloadCCG()
{
AUTO_PROFILE_SECTION(g_dTimeGeomChunkLoadFileIO);
CAutoFile fIn (getCCGFilePath().c_str(), "rb");
if (!fIn)
return false;
long nSize = fIn.GetSize();
if (nSize <= 0)
return false;
m_arrBufferCCG.resize(nSize);
if (!fIn.Read (&m_arrBufferCCG[0], nSize))
return false;
// data has been read - automatically close the file and return success
return true;
}
// loads animations for already loaded model
bool CryModelLoader::loadAnimations()
{
AUTO_PROFILE_SECTION(g_dTimeAnimLoadBind);
// the number of animations loaded
unsigned numAnimations = 0;
{
AUTO_PROFILE_SECTION (g_dTimeTest1);
if(m_fCalFile)
numAnimations = loadAnimationsWithCAL ();
else
if (m_nCafFindFileHandle != -1)
numAnimations = loadAnimationsNoCAL ();
}
{
AUTO_PROFILE_SECTION(g_dTimeTest2);
if (!numAnimations && !m_pModel->numMorphTargets())
{
g_GetLog()->LogWarning ("\004CryModelLoader::loadAnimations(%s): couldn't find any animations for the model. Standalone character will be useless.", m_strGeomFileNameNoExt.c_str());
//return false;
}
if (!m_bLoadFromCCG)
{
g_GetLog()->UpdateLoadingScreenPlus ("\003 precomputing");
m_pModel->LoadPostInitialize (!m_bBoneInitPosInitialized);
g_GetLog()->UpdateLoadingScreenPlus ("\003done.");
}
}
if (numAnimations)
g_GetLog()->UpdateLoadingScreen(" %d animations loaded (total animations: %d)", numAnimations, m_pControllerManager->NumAnimations());
// m_pControllerManager->LogUsageStats();
//m_pModel->shrinkControllerArrays();
return m_pModel->m_pDefaultModelState
&& ((m_pModel->numBoneInfos()
&& m_pModel->m_pDefaultModelState->getRootBone())
|| m_pModel->numMorphTargets());
}
// loads the animations from the array: pre-allocates the necessary controller arrays
// the 0th animation is the default animation
unsigned CryModelLoader::loadAnimationArray (const AnimFileArray& arrAnimFiles)
{
unsigned nAnimID = 0;
if (arrAnimFiles.empty())
return 0;
indicateProgress("\003 Anims");
{
AUTO_PROFILE_SECTION(g_dTimeAnimLoadBindPreallocate);
m_pModel->prepareLoadCAFs ((unsigned)arrAnimFiles.size());
}
indicateProgress("\003:");
// load the default animation - it must be always loaded synchronously
unsigned nDefAnimFlags = arrAnimFiles[0].nAnimFlags;
if (!stricmp(arrAnimFiles[0].strAnimName.c_str(), "default"))
nDefAnimFlags |= GlobalAnimation::FLAGS_DEFAULT_ANIMATION;
if(m_pModel->LoadCAF(arrAnimFiles[0].strFileName.c_str(), m_fScale, nAnimID, arrAnimFiles[0].strAnimName.c_str(), nDefAnimFlags) >= 0)
nAnimID++;
else
if (g_GetCVars()->ca_Debug())
g_GetLog()->LogWarning ("\005Default pose for %s was not found, object may not skin as expected", m_strGeomFileNameNoExt.c_str());
for (unsigned i = 1; i < arrAnimFiles.size(); ++i)
{
if (m_pModel->LoadCAF(arrAnimFiles[i].strFileName.c_str(), m_fScale, nAnimID, arrAnimFiles[i].strAnimName.c_str(),arrAnimFiles[i].nAnimFlags) >= 0)
{
nAnimID++;
//if((i%10)==0)
// indicateProgress();
}
else
g_GetLog()->LogWarning ("\002Animation (Caf) file \"%s\" could not be read (it's an animation of \"%s.cgf\")", arrAnimFiles[i].strFileName.c_str(), m_strGeomFileNameNoExt.c_str());
}
indicateProgress("\003done;");
return nAnimID;
}
//////////////////////////////////////////////////////////////////////////
// loads animations for the given file from the given directory, loading
// all cal files which begin with the cgf file name and underscope (this
// is the convention for the cgf's that don't have cal file associated)
// PARAMETERS:
// m_strGeomFileNameNoExt - the name of the cgf/cid file without extension
// m_nCafFindFileHandle - the search handle opened by _findfirst() for all cal files belonging to this cgf
// m_fScale - the scale factor to be applied to all controllers
unsigned CryModelLoader::loadAnimationsNoCAL ()
{
// animation files to load, excluding the default animation
AnimFileArray arrAnimFiles;
{
AUTO_PROFILE_SECTION(g_dTimeAnimLoadBindNoCal);
// make search path
string strDirName = GetParentDirectory (m_strGeomFileNameNoExt).c_str();
// load the default pose first
string strDefaultPose = (m_strGeomFileNameNoExt + "_default.caf").c_str();
arrAnimFiles.reserve (64);
// we need default animation immediately, but unlikely we'll need it in the future (so we can unload it)
arrAnimFiles.push_back (SAnimFile(strDefaultPose, "default", GlobalAnimation::FLAGS_DEFAULT_ANIMATION));
// the name of the base cgf (before the understrike) + 1
unsigned nBaseNameLength = unsigned(m_strGeomFileNameNoExt.length() - strDirName.length());
indicateProgress();
do
{
SAnimFile AnimFile;
AnimFile.strFileName = strDirName + "\\" + m_fileinfo.name;
if(!stricmp(AnimFile.strFileName.c_str(), strDefaultPose.c_str()))
// skip the default pose as it has already been loaded
continue;
//if (!stricmp(FindExtension(fileinfo.name), "caf")) // actually ,according to the search mask, this should be met automatically
char* szExtension = StripFileExtension(m_fileinfo.name);
assert (!stricmp(szExtension, "caf"));
assert (strlen(m_fileinfo.name) > nBaseNameLength);
AnimFile.strAnimName = m_fileinfo.name + nBaseNameLength;
arrAnimFiles.push_back (AnimFile);
}
while (g_GetPak()->FindNext( m_nCafFindFileHandle, &m_fileinfo ) != -1);
}
return loadAnimationArray(arrAnimFiles);
}
//////////////////////////////////////////////////////////////////////////
// loads animations for this cgf from the given cal file
// does NOT close the file (the file belongs to the calling party)
// PARAMETERS
// m_strGeomFileNameNoExt - the name of the cgf/cid file without extension
// m_fCalFile - the file opened by fopen() for the cal associated with this cgf
// m_fScale - the scale factor to be applied to all controllers
unsigned CryModelLoader::loadAnimationsWithCAL ()
{
AnimFileArray arrAnimFiles;
{
AUTO_PROFILE_SECTION(g_dTimeAnimLoadBindWithCal);
// Load cal file and load animations from animations folder
// make anim folder name
// make search path
string strDirName = GetParentDirectory(m_strGeomFileNameNoExt).c_str();
string strAnimDirName = GetParentDirectory(strDirName, 2) + "\\animations";
// the flags applicable to the currently being loaded animation
unsigned nAnimFlags = 0;
arrAnimFiles.reserve (256);
indicateProgress();
for (int i = 0; m_fCalFile && !g_GetPak()->FEof(m_fCalFile); ++i)
{
char sBuffer[512]="";
g_GetPak()->FGets(sBuffer,512,m_fCalFile);
char*szAnimName;
char*szFileName;
if(sBuffer[0] == '/' || sBuffer[0]=='\r' || sBuffer[0]=='\n' || sBuffer[0]==0)
continue;
//if(sscanf(sBuffer, "%s=%s", szAnimName, szFileName) != 2)
// continue;
szAnimName = strtok (sBuffer, " \t\n\r=");
if (!szAnimName)
continue;
szFileName = strtok(NULL, " \t\n\r=");
if (!szFileName || szFileName[0] == '?')
{
m_pModel->RegisterDummyAnimation(szAnimName);
continue;
}
if (szAnimName[0] == '/' && szAnimName[1] == '/')
continue; // comment
{ // remove firsrt '\' and replace '/' with '\'
while(szFileName[0]=='/' || szFileName[0]=='\\')
memmove(szFileName,szFileName+1,sizeof(szFileName)-1);
for(char * p = szFileName+strlen(szFileName); p>=szFileName; p--)
if(*p == '/')
*p = '\\';
}
// process the possible directives
if (szAnimName[0] == '$')
{
const char* szDirective = szAnimName + 1;
if (!stricmp(szDirective, "AnimationDir")
||!stricmp(szDirective, "AnimDir")
||!stricmp(szDirective, "AnimationDirectory")
||!stricmp(szDirective, "AnimDirectory"))
{
strAnimDirName = strDirName + "\\" + szFileName;
// delete the trailing slashes
while (
!strAnimDirName.empty()
&& strAnimDirName [strAnimDirName.length()-1] == '\\'
)
strAnimDirName[strAnimDirName.length()-1] = '\0';
}
else
if (!stricmp (szDirective, "ModelOffsetX"))
{
float fValue;
if (sscanf (szFileName, "%f", &fValue) != 1)
g_GetLog ()->LogToFile("\003Warning:directive ModelOffsetX %s couldn't be read in file %s.cal", szFileName, m_strGeomFileNameNoExt.c_str());
else
m_pModel->m_vModelOffset.x = fValue;
}
else
if (!stricmp (szDirective, "ModelOffsetY"))
{
float fValue;
if (sscanf (szFileName, "%f", &fValue) != 1)
g_GetLog ()->LogToFile("\003Warning:directive ModelOffsetY %s couldn't be read in file %s.cal", szFileName, m_strGeomFileNameNoExt.c_str());
else
m_pModel->m_vModelOffset.y = fValue;
}
else
if (!stricmp (szDirective, "ModelOffsetZ"))
{
float fValue;
if (sscanf (szFileName, "%f", &fValue) != 1)
g_GetLog ()->LogToFile("\003Warning:directive ModelOffsetZ %s couldn't be read in file %s.cal", szFileName, m_strGeomFileNameNoExt.c_str());
else
m_pModel->m_vModelOffset.z = fValue;
}
else
if (!stricmp(szDirective, "AutoUnload"))
{
switch (CryStringUtils::toYesNoType(szFileName))
{
case CryStringUtils::nYNT_Yes:
nAnimFlags &= ~GlobalAnimation::FLAGS_DISABLE_AUTO_UNLOAD;
break;
case CryStringUtils::nYNT_No:
nAnimFlags |= GlobalAnimation::FLAGS_DISABLE_AUTO_UNLOAD;
break;
default:
g_GetLog()->LogWarning ("\003invalid option for AutoUnload directive (must be yes or no) in file %s.cal", m_strGeomFileNameNoExt.c_str());
break;
}
}
else
if (!stricmp(szDirective, "DelayLoad"))
{
switch (CryStringUtils::toYesNoType(szFileName))
{
case CryStringUtils::nYNT_Yes:
nAnimFlags &= ~GlobalAnimation::FLAGS_DISABLE_DELAY_LOAD;
break;
case CryStringUtils::nYNT_No:
nAnimFlags |= GlobalAnimation::FLAGS_DISABLE_DELAY_LOAD;
break;
default:
g_GetLog()->LogWarning ("\003invalid option for DelayLoad directive (must be yes or no) in file %s.cal", m_strGeomFileNameNoExt.c_str());
break;
}
}
else
g_GetLog()->LogWarning ("\003Unknown directive %s", szDirective);
continue;
}
arrAnimFiles.push_back(SAnimFile (strAnimDirName + "\\" + szFileName, szAnimName,nAnimFlags));
}
if (arrAnimFiles.empty())
return false;
}
return loadAnimationArray (arrAnimFiles);
}
// loads the CCG (including all the LODs in it)
bool CryModelLoader::loadCCG()
{
CCFMemReader Reader (&m_arrBufferCCG[0], (unsigned)m_arrBufferCCG.size());
if (Reader.IsEnd())
return false;
string strDirName = GetParentDirectory(m_strGeomFileNameNoExt).c_str();
string strAnimDirName = GetParentDirectory(strDirName, 2) + "\\animations";
if (!m_pModel->initFromCCG(strDirName, strAnimDirName, Reader, m_fScale))
return false;
// when we load CCG, we always have the init pose initialized,
// otherwise we fail to load
m_bBoneInitPosInitialized = true;
return true;
}
// loads the geometry files (LOD files, starting with the main one)
bool CryModelLoader::loadCGFs()
{
unsigned numLODs = (unsigned)m_arrLodFiles.size();
m_pModel->m_arrGeomInfo.reinit (numLODs);
CryModelGeometryLoader GeomLoader;
unsigned i;
for (i = 0; i < numLODs; ++i)
{
if (!GeomLoader.load (m_pModel, m_arrLodFiles[i], i, m_fScale))
{
if (i)
g_GetLog()->LogWarning ("\003Modes LOD %d can't be loaded. Please reexport the LOD file. Animated object will not be loaded.", i);
return false;
}
if (i == 0)
m_bBoneInitPosInitialized = GeomLoader.hasBoneInfoInitPos();
}
if (g_GetCVars()->ca_NoMtlSorting())
return true;
m_pModel->deleteUnusedMaterials();
unsigned numMtls = (unsigned)m_pModel->m_arrMaterials.size();
// this will map from the new material id to the old material id after sorting
// the materials
std::vector<unsigned> arrMapMtlsOld, arrMapMtlsNew;
arrMapMtlsOld.resize (numMtls);
for (i = 0; i < numMtls; ++i)
arrMapMtlsOld[i] = i;
// initial (identity) mapping created, now tokenize the material names
std::vector<CMatEntityNameTokenizer> arrTokenizers;
arrTokenizers.resize (numMtls);
for (i = 0; i < numMtls; ++i)
arrTokenizers[i].tokenize (m_pModel->m_arrMaterials[i].name);
// now sort the indices to receive the correct distribution of materials
// thus we'll effectively receive perm[new]=old
std::sort (arrMapMtlsOld.begin(), arrMapMtlsOld.end(), CMatEntityIndexSort(&arrTokenizers[0], numMtls));
//std::swap (arrMapMtlsOld.front(), arrMapMtlsOld.back());
// form the permutation perm[old]=new
arrMapMtlsNew.resize (numMtls);
ConstructReversePermutation(&arrMapMtlsOld[0], &arrMapMtlsNew[0], numMtls);
// resort the material entities in the model
RemapMatEntities (&m_pModel->m_arrMaterials[0], numMtls, &arrMapMtlsOld[0]);
// now remap the material ids in the faces
for (i = 0; i < m_pModel->numLODs(); ++i)
m_pModel->getGeometryInfo(i)->remapMtlIds(&arrMapMtlsNew[0], numMtls);
return true;
}
bool CryModelLoader::loadTextures()
{
AUTO_PROFILE_SECTION(g_dTimeShaderLoad);
if (g_GetCVars()->ca_Debug())
g_GetLog()->UpdateLoadingScreen("\005 Loading shaders from %s", m_strGeomFileNameNoExt.c_str());
return true;
}
// forgets about the m_pModel (so that it doesn't get deleted upon done()),
// and returns it
CryModel* CryModelLoader::detachModel()
{
CryModel* pModel = m_pModel; // this model will be returned
m_pModel = NULL; // forget about the model
return pModel;
}
void CryModelLoader::indicateProgress(const char*szMsg)
{
if (szMsg)
g_GetLog()->UpdateLoadingScreenPlus(szMsg?szMsg:"\003.");
}