diff --git a/source/graphics/ColladaManager.cpp b/source/graphics/ColladaManager.cpp new file mode 100644 index 0000000000..0b27265375 --- /dev/null +++ b/source/graphics/ColladaManager.cpp @@ -0,0 +1,236 @@ +#include "precompiled.h" + +#include "ColladaManager.h" + +#include "graphics/ModelDef.h" +#include "lib/res/file/vfs.h" +#include "lib/res/handle.h" +#include "ps/CLogger.h" +#include "ps/CStr.h" +#include "ps/CVFSFile.h" +#include "ps/DllLoader.h" + +namespace Collada +{ + #include "collada/DLL.h" +} + +namespace +{ + struct VFSOutputCB + { + VFSOutputCB(Handle hf) : hf(hf) {} + void operator() (const char* data, unsigned int length) + { + FileIOBuf buf = (FileIOBuf)data; + const ssize_t ret = vfs_io(hf, length, &buf); + // TODO: handle errors sensibly + } + + Handle hf; + }; + + void ColladaLog(int severity, const char* text) + { + LOG(severity == LOG_INFO ? NORMAL : + severity == LOG_WARNING ? WARNING : ERROR, + "collada", "%s", text); + } + + void ColladaOutput(void* cb_data, const char* data, unsigned int length) + { + VFSOutputCB* cb = static_cast(cb_data); + (*cb)(data, length); + } +} + +class CColladaManagerImpl +{ + DllLoader dll; + + void (*set_logger)(Collada::LogFn logger); + int (*convert_dae_to_pmd)(const char* dae, Collada::OutputFn pmd_writer, void* cb_data); + int (*convert_dae_to_psa)(const char* dae, Collada::OutputFn psa_writer, void* cb_data); + +public: + CColladaManagerImpl() + : dll("Collada") + { + } + + ~CColladaManagerImpl() + { + if (dll.IsLoaded()) + set_logger(NULL); // unregister the log handler + } + + bool Convert(const CStr& daeFilename, const CStr& pmdFilename, CColladaManager::FileType type) + { + // To avoid always loading the DLL when it's usually not going to be + // used (and to do the same on Linux where delay-loading won't help), + // and to avoid compile-time dependencies (because it's a minor pain + // to get all the right libraries to build the COLLADA DLL), we load + // it dynamically when it is required, instead of using the exported + // functions and binding at link-time. + if (! dll.IsLoaded()) + { + if (! dll.LoadDLL()) + { + LOG_ONCE(ERROR, "collada", "Failed to load COLLADA conversion DLL"); + return false; + } + + try + { + dll.LoadSymbol("set_logger", set_logger); + dll.LoadSymbol("convert_dae_to_pmd", convert_dae_to_pmd); + dll.LoadSymbol("convert_dae_to_psa", convert_dae_to_psa); + } + catch (PSERROR_DllLoader&) + { + LOG(ERROR, "collada", "Failed to load symbols from COLLADA conversion DLL"); + dll.Unload(); + return false; + } + + set_logger(ColladaLog); + } + + // We need to null-terminate the buffer, so do it (possibly inefficiently) + // by converting to a CStr + CStr daeData; + + { + CVFSFile daeFile; + if (daeFile.Load(daeFilename) != PSRETURN_OK) + return false; + + daeData = daeFile.GetAsString(); + + // scope closes daeFile - necessary if we don't use FILE_LONG_LIVED + } + + // Prepare the output file + + Handle hf = vfs_open(pmdFilename, FILE_WRITE|FILE_NO_AIO); + if (hf < 0) + return false; + + // Do the conversion + + VFSOutputCB cb (hf); + + switch (type) + { + case CColladaManager::PMD: convert_dae_to_pmd(daeData.c_str(), ColladaOutput, static_cast(&cb)); break; + case CColladaManager::PSA: convert_dae_to_psa(daeData.c_str(), ColladaOutput, static_cast(&cb)); break; + } + + vfs_close(hf); + + return true; + } +}; + +CColladaManager::CColladaManager() +: m(new CColladaManagerImpl()) +{ +} + +CColladaManager::~CColladaManager() +{ + delete m; +} + +CStr CColladaManager::GetLoadableFilename(const CStr &sourceName, FileType type) +{ + const char* extn = NULL; + switch (type) + { + case PMD: extn = ".pmd"; break; + case PSA: extn = ".psa"; break; + // no other alternatives + } + + /* + + If there is a .dae file: + * Calculate a hash to identify it. + * Look for a cached .pmd file matching that hash. + * If it exists, load it. Else, convert the .dae into .pmd and load it. + Otherwise, if there is a (non-cache) .pmd file: + * Load it. + Else, fail. + + The hash calculation ought to be fast, since normally (during development) + the .dae file will exist but won't have changed recently and so the cache + would be used. Hence, just hash the file's size, mtime, and the converter + version number (so updates of the converter can cause regeneration of .pmds) + instead of the file's actual contents. + + TODO (maybe): The .dae -> .pmd conversion may fail (e.g. if the .dae is + invalid or unsupported), but it may take a long time to start the conversion + then realise it's not going to work. That will delay the loading of the game + every time, which is annoying, so maybe it should cache the error message + until the .dae is updated and fixed. (Alternatively, avoid having that many + broken .daes in the game.) + + */ + + // (TODO: the comments and variable names say "pmd" but actually they can + // be "psa" too.) + + CStr dae = sourceName + ".dae"; + if (! vfs_exists(dae)) + { + // No .dae - got to use the .pmd, assuming there is one + return sourceName + extn; + } + + // There is a .dae - see if there's an up-to-date cached copy + + struct stat fileStat; + if (vfs_stat(dae, &fileStat) < 0) + { + // This shouldn't occur for any sensible reasons + LOG(ERROR, "collada", "Failed to stat DAE file '%s'", dae.c_str()); + return ""; + } + + // Build a struct of all the data we want to hash. + // (Use ints and not time_t/off_t because we don't care about overflow + // but do care about the fields not being 64-bit aligned) + // (Remove the lowest bit of mtime because some things round it to a + // resolution of 2 seconds) + struct { int version; int mtime; int size; } hashSource + = { COLLADA_CONVERTER_VERSION, (int)fileStat.st_mtime & ~1, (int)fileStat.st_size }; + cassert(sizeof(hashSource) == sizeof(int) * 3); // no padding, because that would be bad + + // Calculate the hash, convert to hex + u32 hash = fnv_hash(static_cast(&hashSource), sizeof(hashSource)); + char hashString[9]; + sprintf(hashString, "%08x", hash); + + // realDaePath is "mods/whatever/art/meshes/whatever.dae" + char realDaePath[PATH_MAX]; + vfs_realpath(dae, realDaePath); + + // cachedPmdVfsPath is "cache/mods/whatever/art/meshes/whatever_{hash}.pmd" + CStr cachedPmdVfsPath = "cache/"; + cachedPmdVfsPath += realDaePath; + // Remove the .dae extension (which will certainly be there) + cachedPmdVfsPath = cachedPmdVfsPath.substr(0, cachedPmdVfsPath.length()-4); + // Add a _hash.pmd extension + cachedPmdVfsPath += "_"; + cachedPmdVfsPath += hashString; + cachedPmdVfsPath += extn; + + // If it's not in the cache, we'll have to create it first + if (! vfs_exists(cachedPmdVfsPath)) + { + if (! m->Convert(dae, cachedPmdVfsPath, type)) + return ""; // failed to convert + } + + return cachedPmdVfsPath; +} diff --git a/source/graphics/ColladaManager.h b/source/graphics/ColladaManager.h new file mode 100644 index 0000000000..5f2ad8b782 --- /dev/null +++ b/source/graphics/ColladaManager.h @@ -0,0 +1,32 @@ +#ifndef COLLADAMANAGER_H__ +#define COLLADAMANAGER_H__ + +class CStr8; + +class CColladaManagerImpl; + +class CColladaManager +{ +public: + enum FileType { PMD, PSA }; + + CColladaManager(); + ~CColladaManager(); + + /** + * Returns the VFS path to a PMD/PSA file for the given source file. + * Performs a (cached) conversion from COLLADA if necessary. + * + * @param sourceName path and name, minus extension, of file to load. + * One of either "sourceName.pmd" or "sourceName.dae" should exist. + * + * @return full VFS path (including extension) of file to load; or empty + * string if there was a problem and it could not be loaded. + */ + CStr8 GetLoadableFilename(const CStr8& sourceName, FileType type); + +private: + CColladaManagerImpl* m; +}; + +#endif // COLLADAMANAGER_H__ diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 6dc8d5435c..1e982a7f0f 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -4,11 +4,13 @@ #include "graphics/Camera.h" #include "graphics/CinemaTrack.h" +#include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" +#include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/Unit.h" @@ -57,7 +59,9 @@ class CGameViewImpl : public CJSObject, boost::noncopyable { public: CGameViewImpl(CGame* game) - : Game(game), MeshManager(), ObjectManager(MeshManager), + : Game(game), + ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), + ObjectManager(MeshManager, SkeletonAnimManager), ViewCamera(), CullCamera(), LockCullCamera(false), @@ -79,7 +83,9 @@ public: } CGame* Game; + CColladaManager ColladaManager; CMeshManager MeshManager; + CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; /** diff --git a/source/graphics/MeshManager.cpp b/source/graphics/MeshManager.cpp index a914495cb0..d4d78b5ad0 100644 --- a/source/graphics/MeshManager.cpp +++ b/source/graphics/MeshManager.cpp @@ -2,140 +2,25 @@ #include "MeshManager.h" +#include "graphics/ColladaManager.h" #include "graphics/ModelDef.h" #include "ps/CLogger.h" #include "ps/FileUnpacker.h" // to get access to its CError -#include "ps/CVFSFile.h" -#include "ps/DllLoader.h" -#include "lib/res/file/vfs.h" - -namespace Collada -{ - #include "collada/DLL.h" -} - -#include +#include "ps/Profile.h" #define LOG_CATEGORY "mesh" -void ColladaLog(int severity, const char* text) -{ - LOG(severity==LOG_INFO ? NORMAL : severity==LOG_WARNING ? WARNING : ERROR, - "collada", "%s", text); -} +// TODO: should this cache models while they're not actively in the game? +// (Currently they'll probably be deleted when the reference count drops to 0, +// even if it's quite possible that they'll get reloaded very soon.) -struct VFSOutputCB -{ - VFSOutputCB(Handle hf) : hf(hf) {} - void operator() (const char* data, unsigned int length) - { - FileIOBuf buf = (FileIOBuf)data; - const ssize_t ret = vfs_io(hf, length, &buf); - // TODO: handle errors sensibly - } - - Handle hf; -}; - -void ColladaOutput(void* cb_data, const char* data, unsigned int length) -{ - VFSOutputCB* cb = static_cast(cb_data); - (*cb)(data, length); -} - -typedef STL_HASH_MAP, CStr_hash_compare> mesh_map; -class CMeshManagerImpl -{ - DllLoader dll; - - void (*set_logger)(Collada::LogFn logger); - int (*convert_dae_to_pmd)(const char* dae, Collada::OutputFn pmd_writer, void* cb_data); - -public: - mesh_map MeshMap; - - CMeshManagerImpl() - : dll("Collada") - { - } - - ~CMeshManagerImpl() - { - if (dll.IsLoaded()) - set_logger(NULL); // unregister the log handler - } - - CModelDefPtr Convert(const CStr& daeFilename, const CStr& pmdFilename, const CStr& name) - { - // To avoid always loading the DLL when it's usually not going to be - // used (and to do the same on Linux where delay-loading won't help), - // and to avoid compile-time dependencies (because it's a minor pain - // to get all the right libraries to build the COLLADA DLL), we load - // it dynamically when it is required, instead of using the exported - // functions and binding at link-time. - if (! dll.IsLoaded()) - { - if (! dll.LoadDLL()) - { - LOG_ONCE(ERROR, LOG_CATEGORY, "Failed to load COLLADA conversion DLL"); - return CModelDefPtr(); - } - - try - { - dll.LoadSymbol("set_logger", set_logger); - dll.LoadSymbol("convert_dae_to_pmd", convert_dae_to_pmd); - } - catch (PSERROR_DllLoader&) - { - LOG(ERROR, LOG_CATEGORY, "Failed to load symbols from COLLADA conversion DLL"); - dll.Unload(); - return CModelDefPtr(); - } - - set_logger(ColladaLog); - } - - // We need to null-terminate the buffer, so do it (possibly inefficiently) - // by converting to a CStr - CStr daeData; - - { - CVFSFile daeFile; - if (daeFile.Load(daeFilename) != PSRETURN_OK) - return CModelDefPtr(); - - daeData = daeFile.GetAsString(); - - // scope closes daeFile - necessary if we don't use FILE_LONG_LIVED - } - - // Prepare the output file - Handle hf = vfs_open(pmdFilename, FILE_WRITE|FILE_NO_AIO); - if (hf < 0) - return CModelDefPtr(); - - // Do the conversion - VFSOutputCB cb (hf); - convert_dae_to_pmd(daeData.c_str(), ColladaOutput, static_cast(&cb)); - - vfs_close(hf); - - // Now load the PMD that was just created - CModelDefPtr model (CModelDef::Load(pmdFilename, name)); - MeshMap[name] = model; - return model; - } -}; - -CMeshManager::CMeshManager() -: m(new CMeshManagerImpl()) +CMeshManager::CMeshManager(CColladaManager& colladaManager) +: m_ColladaManager(colladaManager) { } CMeshManager::~CMeshManager() { - delete m; } CModelDefPtr CMeshManager::GetMesh(const CStr& filename) @@ -148,91 +33,25 @@ CModelDefPtr CMeshManager::GetMesh(const CStr& filename) name = filename; // Find the mesh if it's already been loaded and cached - mesh_map::iterator iter = m->MeshMap.find(name); - if (iter != m->MeshMap.end() && !iter->second.expired()) + mesh_map::iterator iter = m_MeshMap.find(name); + if (iter != m_MeshMap.end() && !iter->second.expired()) return CModelDefPtr(iter->second); - /* + PROFILE( "load mesh" ); - If there is a .dae file: - * Calculate a hash to identify it. - * Look for a cached .pmd file matching that hash. - * If it exists, load it. Else, convert the .dae into .pmd and load it. - Otherwise, if there is a (non-cache) .pmd file: - * Load it. - Else, fail. + CStr pmdFilename = m_ColladaManager.GetLoadableFilename(name, CColladaManager::PMD); - The hash calculation ought to be fast, since normally (during development) - the .dae file will exist but won't have changed recently and so the cache - would be used. Hence, just hash the file's size, mtime, and the converter - version number (so updates will cause regeneration of .pmds) instead of - its contents. - - TODO (maybe): The .dae -> .pmd conversion may fail (e.g. if the .dae is - invalid or unsupported), but it may take a long time to start the conversion - then realise it's not going to work. That will delay the loading of the game - every time, which is annoying, so maybe it should cache the error messge - until the .dae is updated and fixed. (Alternatively, avoid having many - broken .daes in the game's data files.) - - */ + if (pmdFilename.empty()) + { + LOG(ERROR, LOG_CATEGORY, "Could not load mesh '%s'", filename.c_str()); + return CModelDefPtr(); + } try { - CStr dae = name+".dae"; - if (! vfs_exists(dae)) - { - // No .dae - got to use the .pmd, assuming there is one - CModelDefPtr model (CModelDef::Load(name+".pmd", name)); - m->MeshMap[name] = model; - return model; - } - - // There is a .dae - see if there's an up-to-date cached copy - - struct stat fileStat; - if (vfs_stat(dae, &fileStat) < 0) - { - // This shouldn't occur for any sensible reasons - LOG(ERROR, LOG_CATEGORY, "Failed to stat DAE file '%s'", filename.c_str()); - return CModelDefPtr(); - } - - // Build a struct of all the data we want to hash. - // (Use ints and not time_t/off_t because we don't care about overflow - // but do care about the fields not being 64-bit aligned) - struct { int version; int mtime; int size; } hashSource - = { COLLADA_CONVERTER_VERSION, fileStat.st_mtime & ~1, fileStat.st_size }; - cassert(sizeof(hashSource) == sizeof(int) * 3); // no padding, because that would be bad - // Calculate the hash, convert to hex - u32 hash = fnv_hash(static_cast(&hashSource), sizeof(hashSource)); - char hashString[9]; - sprintf(hashString, "%08x", hash); - - char realDaePath[PATH_MAX]; - vfs_realpath(dae, realDaePath); - // realDaePath is "mods/whatever/art/meshes/whatever.dae" - - CStr cachedPmdVfsPath = "cache/"; - cachedPmdVfsPath += realDaePath; - // Remove the .dae extension (which will certainly be there) - cachedPmdVfsPath = cachedPmdVfsPath.substr(0, cachedPmdVfsPath.length()-4); - // Add a _hash.pmd extension - cachedPmdVfsPath += "_"; - cachedPmdVfsPath += hashString; - cachedPmdVfsPath += ".pmd"; - - // If it's cached, load and return that copy - if (vfs_exists(cachedPmdVfsPath)) - { - CModelDefPtr model (CModelDef::Load(cachedPmdVfsPath, name)); - m->MeshMap[name] = model; - return model; - } - - // Not in the cache, so create it - - return m->Convert(dae, cachedPmdVfsPath, name); + CModelDefPtr model (CModelDef::Load(pmdFilename, name)); + m_MeshMap[name] = model; + return model; } catch (PSERROR_File&) { diff --git a/source/graphics/MeshManager.h b/source/graphics/MeshManager.h index a56735d306..cf8245ed6a 100644 --- a/source/graphics/MeshManager.h +++ b/source/graphics/MeshManager.h @@ -1,25 +1,28 @@ #ifndef __H_MESHMANAGER_H__ #define __H_MESHMANAGER_H__ +#include "ps/CStr.h" + #include +#include class CModelDef; typedef boost::shared_ptr CModelDefPtr; -class CStr8; +class CColladaManager; -class CMeshManagerImpl; - -class CMeshManager +class CMeshManager : boost::noncopyable { public: - CMeshManager(); + CMeshManager(CColladaManager& colladaManager); ~CMeshManager(); - CModelDefPtr GetMesh(const CStr8& filename); + CModelDefPtr GetMesh(const CStr& filename); private: - CMeshManagerImpl* m; + typedef STL_HASH_MAP, CStr_hash_compare> mesh_map; + mesh_map m_MeshMap; + CColladaManager& m_ColladaManager; }; #endif diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 6832af9535..f3c5a6c0a1 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -26,10 +26,11 @@ ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor -CModel::CModel() +CModel::CModel(CSkeletonAnimManager& skeletonAnimManager) : m_Parent(NULL), m_Flags(0), m_Anim(NULL), m_AnimTime(0), m_BoneMatrices(NULL), m_InverseBindBoneMatrices(NULL), - m_PositionValid(false), m_ShadingColor(1,1,1,1) + m_PositionValid(false), m_ShadingColor(1,1,1,1), + m_SkeletonAnimManager(skeletonAnimManager) { } @@ -201,7 +202,7 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result) // animation specific to this model CSkeletonAnim* CModel::BuildAnimation(const char* filename, const char* name, float speed, double actionpos, double actionpos2) { - CSkeletonAnimDef* def=g_SkelAnimMan.GetAnimation(filename); + CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(filename); if (!def) return NULL; @@ -463,7 +464,7 @@ void CModel::RemoveProp(SPropPoint* point) // Clone: return a clone of this model CModel* CModel::Clone() const { - CModel* clone = new CModel; + CModel* clone = new CModel(m_SkeletonAnimManager); clone->m_ObjectBounds = m_ObjectBounds; clone->InitModel(m_pModelDef); clone->SetTexture(m_Texture); diff --git a/source/graphics/Model.h b/source/graphics/Model.h index c9f932e3f4..22bd0ed4eb 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -20,6 +20,7 @@ struct SPropPoint; class CObjectEntry; class CSkeletonAnim; class CSkeletonAnimDef; +class CSkeletonAnimManager; #define MODELFLAG_CASTSHADOWS (1<<0) #define MODELFLAG_NOLOOPANIMATION (1<<1) @@ -27,7 +28,7 @@ class CSkeletonAnimDef; /////////////////////////////////////////////////////////////////////////////// // CModel: basically, a mesh object - holds the texturing and skinning // information for a model in game -class CModel : public CRenderableObject +class CModel : public CRenderableObject, boost::noncopyable { friend class CUnitAnimation; // HACK - we should probably move the rest of this class's animation state @@ -44,7 +45,7 @@ public: public: // constructor - CModel(); + CModel(CSkeletonAnimManager& skeletonAnimManager); // destructor ~CModel(); @@ -201,6 +202,9 @@ private: // modulating color CColor m_ShadingColor; + + // manager object which can load animations for us + CSkeletonAnimManager& m_SkeletonAnimManager; }; #endif diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index f4b08c4978..378c5b8bc9 100644 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -82,7 +82,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections // delete old model, create new delete m_Model; - m_Model = new CModel; + m_Model = new CModel(objectManager.GetSkeletonAnimManager()); m_Model->SetTexture((const char*) m_TextureName); m_Model->SetMaterial(g_MaterialManager.LoadMaterial(m_Base->m_Material)); m_Model->InitModel(modeldef); diff --git a/source/graphics/ObjectManager.cpp b/source/graphics/ObjectManager.cpp index 2d6309f977..676e1f8e06 100644 --- a/source/graphics/ObjectManager.cpp +++ b/source/graphics/ObjectManager.cpp @@ -34,8 +34,8 @@ bool operator< (const CObjectManager::ObjectKey& a, const CObjectManager::Object return a.ActorVariation < b.ActorVariation; } -CObjectManager::CObjectManager(CMeshManager& meshManager) -: m_MeshManager(meshManager) +CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager) +: m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager) { } diff --git a/source/graphics/ObjectManager.h b/source/graphics/ObjectManager.h index 9a23802bbc..a8768aa52b 100644 --- a/source/graphics/ObjectManager.h +++ b/source/graphics/ObjectManager.h @@ -10,6 +10,7 @@ class CMatrix3D; class CMeshManager; class CObjectBase; class CObjectEntry; +class CSkeletonAnimManager; /////////////////////////////////////////////////////////////////////////////////////////// // CObjectManager: manager class for all possible actor types @@ -28,10 +29,14 @@ public: public: // constructor, destructor - CObjectManager(CMeshManager& meshManager); + CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager); ~CObjectManager(); + // Provide access to the manager classes for meshes and animations - they're + // needed when objects are being created and so this seems like a convenient + // place to centralise access. CMeshManager& GetMeshManager() const { return m_MeshManager; } + CSkeletonAnimManager& GetSkeletonAnimManager() const { return m_SkeletonAnimManager; } void UnloadObjects(); @@ -49,6 +54,7 @@ public: private: CMeshManager& m_MeshManager; + CSkeletonAnimManager& m_SkeletonAnimManager; std::map m_Objects; std::map m_ObjectBases; diff --git a/source/graphics/SkeletonAnimManager.cpp b/source/graphics/SkeletonAnimManager.cpp index 45981c50f4..c8c1d83d14 100644 --- a/source/graphics/SkeletonAnimManager.cpp +++ b/source/graphics/SkeletonAnimManager.cpp @@ -8,18 +8,21 @@ #include "precompiled.h" -#include "lib/res/res.h" -#include "Model.h" -#include "ps/CLogger.h" #include "SkeletonAnimManager.h" + +#include "graphics/ColladaManager.h" +#include "graphics/Model.h" +#include "graphics/SkeletonAnimDef.h" +#include "lib/res/res.h" +#include "ps/CLogger.h" #include "ps/FileUnpacker.h" -#include #define LOG_CATEGORY "graphics" /////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimManager constructor -CSkeletonAnimManager::CSkeletonAnimManager() +CSkeletonAnimManager::CSkeletonAnimManager(CColladaManager& colladaManager) +: m_ColladaManager(colladaManager) { } @@ -28,48 +31,55 @@ CSkeletonAnimManager::CSkeletonAnimManager() CSkeletonAnimManager::~CSkeletonAnimManager() { typedef std::map::iterator Iter; - for (Iter i=m_Animations.begin();i!=m_Animations.end();++i) { + for (Iter i = m_Animations.begin(); i != m_Animations.end(); ++i) delete i->second; - } } /////////////////////////////////////////////////////////////////////////////// // GetAnimation: return a given animation by filename; return null if filename // doesn't refer to valid animation file -CSkeletonAnimDef* CSkeletonAnimManager::GetAnimation(const char* filename) +CSkeletonAnimDef* CSkeletonAnimManager::GetAnimation(const CStr& filename) { - // already loaded? - CStr fname(filename); - std::map::iterator iter=m_Animations.find(fname); - if (iter!=m_Animations.end()) { - // yes - return it + // Strip a three-letter file extension (if there is one) from the filename + CStr name; + if (filename.length() > 4 && filename[filename.length()-4] == '.') + name = filename.substr(0, filename.length()-4); + else + name = filename; + + // Find if it's already been loaded + std::map::iterator iter = m_Animations.find(name); + if (iter != m_Animations.end()) return iter->second; + + CSkeletonAnimDef* def = NULL; + + // Find the file to load + CStr psaFilename = m_ColladaManager.GetLoadableFilename(name, CColladaManager::PSA); + + if (psaFilename.empty()) + { + LOG(ERROR, LOG_CATEGORY, "Could not load animation '%s'", filename.c_str()); + def = NULL; + } + else + { + try + { + def = CSkeletonAnimDef::Load(psaFilename); + } + catch (PSERROR_File&) + { + // ignore errors (they'll be logged elsewhere) + } } - // already failed to load? - std::set::iterator setiter=m_BadAnimationFiles.find(fname); - if (setiter!=m_BadAnimationFiles.end()) { - // yes - return null - return 0; - } + if (def) + LOG(NORMAL, LOG_CATEGORY, "CSkeletonAnimManager::GetAnimation(%s): Loaded successfully", filename.c_str()); + else + LOG(ERROR, LOG_CATEGORY, "CSkeletonAnimManager::GetAnimation(%s): Failed loading, marked file as bad", filename.c_str()); - // try and load it now - CSkeletonAnimDef* def; - try { - def=CSkeletonAnimDef::Load(filename); - } catch (PSERROR_File&) { - def=0; - } - - if (!def) { - LOG(ERROR, LOG_CATEGORY, "CSkeletonAnimManager::GetAnimation(%s): Failed loading, marked file as bad", filename); - // add this file as bad - m_BadAnimationFiles.insert(fname); - return 0; - } else { - LOG(NORMAL, LOG_CATEGORY, "CSkeletonAnimManager::GetAnimation(%s): Loaded successfully", filename); - // add mapping for this file - m_Animations[fname]=def; - return def; - } + // Add to map + m_Animations[name] = def; // NULL if failed to load - we won't try loading it again + return def; } diff --git a/source/graphics/SkeletonAnimManager.h b/source/graphics/SkeletonAnimManager.h index fe6b5e01b9..3ce91a8ebb 100644 --- a/source/graphics/SkeletonAnimManager.h +++ b/source/graphics/SkeletonAnimManager.h @@ -11,34 +11,32 @@ #include #include -#include "SkeletonAnimDef.h" -#include "ps/Singleton.h" - -// access to sole CSkeletonAnimManager object -#define g_SkelAnimMan CSkeletonAnimManager::GetSingleton() +class CColladaManager; +class CSkeletonAnimDef; +class CStr8; /////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimManager : owner class of all skeleton anims - manages creation, // loading and destruction of animation data -class CSkeletonAnimManager : public Singleton +class CSkeletonAnimManager : boost::noncopyable { public: // constructor, destructor - CSkeletonAnimManager(); + CSkeletonAnimManager(CColladaManager& colladaManager); ~CSkeletonAnimManager(); // return a given animation by filename; return null if filename doesn't // refer to valid animation file - CSkeletonAnimDef* GetAnimation(const char* filename); + CSkeletonAnimDef* GetAnimation(const CStr8& filename); private: CSkeletonAnimDef* LoadAnimation(const char* filename); - // map of all known animations - std::map m_Animations; - // set of bad animation names - prevents multiple reloads of bad files - std::set m_BadAnimationFiles; + // map of all known animations. Value is NULL if it failed to load. + std::map m_Animations; + + CColladaManager& m_ColladaManager; }; #endif diff --git a/source/graphics/tests/test_MeshManager.h b/source/graphics/tests/test_MeshManager.h index a75b29b236..4b69a0a5af 100644 --- a/source/graphics/tests/test_MeshManager.h +++ b/source/graphics/tests/test_MeshManager.h @@ -6,6 +6,7 @@ #include "lib/res/file/trace.h" #include "lib/res/h_mgr.h" +#include "graphics/ColladaManager.h" #include "graphics/MeshManager.h" #include "graphics/ModelDef.h" @@ -95,6 +96,7 @@ class TestMeshManager : public CxxTest::TestSuite TS_ASSERT_OK(vfs_opt_rebuild_main_archive(MOD_PATH"/trace.txt", MOD_PATH"/test%02d.zip")); } + CColladaManager* colladaManager; CMeshManager* meshManager; public: @@ -102,12 +104,14 @@ public: void setUp() { initVfs(); - meshManager = new CMeshManager(); + colladaManager = new CColladaManager(); + meshManager = new CMeshManager(*colladaManager); } void tearDown() { delete meshManager; + delete colladaManager; deinitVfs(); } @@ -163,6 +167,18 @@ public: if (modeldef) TS_ASSERT_STR_EQUALS(modeldef->GetName(), testBase); } + void test_load_dae_caching() + { + copyFile(srcDAE, testDAE); + + CStr daeName1 = colladaManager->GetLoadableFilename(testBase, CColladaManager::PMD); + CStr daeName2 = colladaManager->GetLoadableFilename(testBase, CColladaManager::PMD); + TS_ASSERT(daeName1.length()); + TS_ASSERT_STR_EQUALS(daeName1, daeName2); + // TODO: it'd be nice to test that it isn't doing the DAE->PMD conversion + // again, but there doesn't seem to be an easy way to check that + } + void test_load_nonexistent_pmd() { CModelDefPtr modeldef = meshManager->GetMesh(testPMD); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index c3c03fa16b..fc7a4f467b 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -36,7 +36,6 @@ #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/ParticleEngine.h" -#include "graphics/SkeletonAnimManager.h" #include "graphics/TextureManager.h" #include "renderer/Renderer.h" @@ -710,9 +709,6 @@ static void InitRenderer() // create the material manager new CMaterialManager; - // create actor related stuff - new CSkeletonAnimManager; - MICROLOG(L"init renderer"); g_Renderer.Open(g_xres,g_yres,g_bpp); @@ -811,8 +807,6 @@ void Shutdown(uint flags) // destroy actor related stuff TIMER_BEGIN("shutdown actor stuff"); - delete &g_SkelAnimMan; - delete &g_MaterialManager; TIMER_END("shutdown actor stuff"); diff --git a/source/ps/Profile.cpp b/source/ps/Profile.cpp index b5af672c98..d5cf343d2d 100644 --- a/source/ps/Profile.cpp +++ b/source/ps/Profile.cpp @@ -372,6 +372,11 @@ void CProfileNode::Frame() static long get_memory_alloc_count() { #if HAVE_VC_DEBUG_ALLOC + // TODO: it's probably better to use _CrtSetAllocHook to increment a + // user-visible counter. (I didn't know that existed when I wrote this.) + + // Find the number of allocations that have ever occurred, by doing a dummy + // allocation and checking its request number static long bias = 0; // so we can subtract the allocations caused by this function void* p = malloc(1); long requestNumber = 0; @@ -381,6 +386,9 @@ static long get_memory_alloc_count() ++bias; return requestNumber - bias; #else + // TODO: support other compilers if it's easy. + // TODO: don't show this column of data when we don't have sensible values + // to display. return 0; #endif } @@ -450,7 +458,7 @@ void CProfileManager::StartScript( const char* name ) current->Call(); } -const char* CProfileManager::InternString( CStr8 intern ) +const char* CProfileManager::InternString( const CStr8& intern ) { std::map::iterator it = m_internedStrings.find( intern ); if( it != m_internedStrings.end() ) diff --git a/source/ps/Profile.h b/source/ps/Profile.h index ecbe11a8a5..df100d61a1 100644 --- a/source/ps/Profile.h +++ b/source/ps/Profile.h @@ -134,7 +134,7 @@ public: // Resets absolutely everything void StructuralReset(); - const char* InternString( CStr8 intern ); + const char* InternString( const CStr8& intern ); inline const CProfileNode* GetCurrent() { return( current ); } inline const CProfileNode* GetRoot() { return( root ); } @@ -150,11 +150,13 @@ class CProfileSample public: CProfileSample( const char* name ) { - g_Profiler.Start( name ); + if (CProfileManager::IsInitialised()) + g_Profiler.Start( name ); } ~CProfileSample() { - g_Profiler.Stop(); + if (CProfileManager::IsInitialised()) + g_Profiler.Stop(); } }; diff --git a/source/test_setup.cpp b/source/test_setup.cpp index 09138fb2e5..50198aff4b 100644 --- a/source/test_setup.cpp +++ b/source/test_setup.cpp @@ -25,7 +25,7 @@ class LeakReporter : public CxxTest::GlobalFixture // Send output to stdout as well as the debug window, so it works during // the normal build process as well as when debugging the test .exe - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE|_CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT); #endif diff --git a/source/tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp b/source/tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp index 96cc171fc7..8611a2601e 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp +++ b/source/tools/atlas/AtlasUI/CustomControls/Buttons/ToolButton.cpp @@ -71,7 +71,7 @@ void ToolButtonBar::AddToolButton(const wxString& shortLabel, const wxString& lo wxFileName iconPath (_T("tools/atlas/toolbar/")); iconPath.MakeAbsolute(Datafile::GetDataDirectory()); iconPath.SetFullName(iconPNGFilename); - wxFileInputStream fstr (iconPath.GetFullPath()); + wxFFileInputStream fstr (iconPath.GetFullPath()); if (! fstr.Ok()) { wxLogError(_("Failed to open toolbar icon file '%s'"), iconPath.GetFullPath().c_str()); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp index dc25ea13ca..e1c7835052 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp @@ -86,7 +86,7 @@ public: wxFileName iconPath (_T("tools/atlas/toolbar/")); iconPath.MakeAbsolute(Datafile::GetDataDirectory()); iconPath.SetFullName(iconPNGFilename); - wxFileInputStream fstr (iconPath.GetFullPath()); + wxFFileInputStream fstr (iconPath.GetFullPath()); if (! fstr.Ok()) { wxLogError(_("Failed to open toolbar icon file '%s'"), iconPath.GetFullPath().c_str()); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinematic/Cinematic.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinematic/Cinematic.cpp index 20e46f494e..c3c449bf26 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinematic/Cinematic.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Cinematic/Cinematic.cpp @@ -841,7 +841,7 @@ wxImage CinematicSidebar::LoadIcon(const wxString& filename) wxFileName iconPath (_T("tools/atlas/buttons/")); iconPath.MakeAbsolute(Datafile::GetDataDirectory()); iconPath.SetFullName(filename); - wxFileInputStream fstr (iconPath.GetFullPath()); + wxFFileInputStream fstr (iconPath.GetFullPath()); if (! fstr.Ok()) { wxLogError(_("Failed to open cinematic icon file '%s'"), iconPath.GetFullPath().c_str()); diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index 1d30097870..ce70b02f3f 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.cpp +++ b/source/tools/atlas/GameInterface/ActorViewer.cpp @@ -4,11 +4,13 @@ #include "View.h" +#include "graphics/ColladaManager.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnim.h" #include "graphics/SkeletonAnimDef.h" +#include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TextureEntry.h" #include "graphics/TextureManager.h" @@ -25,7 +27,8 @@ struct ActorViewerImpl : public Scene, boost::noncopyable { ActorViewerImpl() - : Unit(NULL), MeshManager(), ObjectManager(MeshManager) + : Unit(NULL), ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), + ObjectManager(MeshManager, SkeletonAnimManager) { } @@ -41,7 +44,9 @@ struct ActorViewerImpl : public Scene, boost::noncopyable CTerrain Terrain; + CColladaManager ColladaManager; CMeshManager MeshManager; + CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; // Simplistic implementation of the Scene interface