Added dependency tree system, methods for generating dependency trees from resources, and saving/loading dependency trees to the project cache folder

This commit is contained in:
parax0 2016-07-25 02:12:30 -06:00
parent c1405bfac1
commit 9341c11ac8
30 changed files with 886 additions and 56 deletions

View File

@ -32,6 +32,10 @@ public:
inline bool HasFlag(FlagEnum Flag) const { return ((mValue & Flag) != 0); }
inline bool HasAnyFlags(TFlags Flags) const { return ((mValue & Flags) != 0); }
inline bool HasAllFlags(TFlags Flags) const { return ((mValue & Flags) == Flags); }
inline void SetFlag(FlagEnum Flag) { mValue |= Flag; }
inline void SetFlag(TFlags Flags) { mValue |= Flags; }
inline void ClearFlag(FlagEnum Flag) { mValue &= ~Flag; }
inline void ClearFlag(TFlags Flags) { mValue &= ~Flags; }
};
#define DECLARE_FLAGS(Enum, FlagTypeName) typedef TFlags<Enum> FlagTypeName;

View File

@ -212,7 +212,7 @@ public:
inline void Insert(u32 Pos, CharType Chr)
{
#ifdef _DEBUG
if (Size() <= Pos)
if (Size() < Pos)
throw std::out_of_range("Invalid position passed to TBasicString::Insert()");
#endif
mInternalString.insert(Pos, 1, Chr);
@ -221,7 +221,7 @@ public:
inline void Insert(u32 Pos, const CharType* pkStr)
{
#ifdef _DEBUG
if (Size() <= Pos)
if (Size() < Pos)
throw std::out_of_range("Invalid position passed to TBasicString::Insert()");
#endif
mInternalString.insert(Pos, pkStr);
@ -846,7 +846,10 @@ public:
static TBasicString<CharType> FromFloat(float Value, int MinDecimals = 1)
{
_TString Out = std::to_string(Value);
std::basic_stringstream<CharType> sstream;
sstream << Value;
_TString Out = sstream.str();
int NumZeroes = Out.Size() - (Out.IndexOf(LITERAL(".")) + 1);
while (Out.Back() == CHAR_LITERAL('0') && NumZeroes > MinDecimals)

View File

@ -196,7 +196,10 @@ HEADERS += \
GameProject/CResourceStore.h \
GameProject/CVirtualDirectory.h \
GameProject/CResourceEntry.h \
GameProject/CResourceIterator.h
GameProject/CResourceIterator.h \
Resource/CDependencyGroup.h \
Resource/Factory/CDependencyGroupLoader.h \
GameProject/CDependencyTree.h
# Source Files
SOURCES += \
@ -287,4 +290,6 @@ SOURCES += \
GameProject/CResourceStore.cpp \
GameProject/CVirtualDirectory.cpp \
GameProject/CResourceEntry.cpp \
GameProject/CPackage.cpp
GameProject/CPackage.cpp \
Resource/Factory/CDependencyGroupLoader.cpp \
GameProject/CDependencyTree.cpp

View File

@ -0,0 +1,320 @@
#include "CDependencyTree.h"
#include "Core/Resource/Script/CScriptLayer.h"
#include "Core/Resource/Script/CScriptObject.h"
// ************ CResourceDependency ************
EDependencyNodeType CResourceDependency::Type() const
{
return eDNT_ResourceDependency;
}
void CResourceDependency::Read(IInputStream& rFile, EUIDLength IDLength)
{
mID = CUniqueID(rFile, IDLength);
}
void CResourceDependency::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
if (IDLength == e32Bit)
rFile.WriteLong(mID.ToLong());
else
rFile.WriteLongLong(mID.ToLongLong());
}
// ************ CAnimSetDependency ************
EDependencyNodeType CAnimSetDependency::Type() const
{
return eDNT_AnimSet;
}
void CAnimSetDependency::Read(IInputStream& rFile, EUIDLength IDLength)
{
CResourceDependency::Read(rFile, IDLength);
mUsedChar = rFile.ReadLong();
}
void CAnimSetDependency::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
CResourceDependency::Write(rFile, IDLength);
rFile.WriteLong(mUsedChar);
}
// Static
CAnimSetDependency* CAnimSetDependency::BuildDependency(TCharacterProperty *pProp)
{
ASSERT(pProp && pProp->Type() == eCharacterProperty && pProp->Instance()->Area()->Game() <= eEchoes);
CAnimationParameters Params = pProp->Get();
if (!Params.ID().IsValid()) return nullptr;
CAnimSetDependency *pDepend = new CAnimSetDependency;
pDepend->SetID(Params.ID());
pDepend->SetUsedChar(Params.CharacterIndex());
return pDepend;
}
// ************ CDependencyTree ************
CDependencyTree::~CDependencyTree()
{
for (u32 iRef = 0; iRef < mReferencedResources.size(); iRef++)
delete mReferencedResources[iRef];
}
EDependencyNodeType CDependencyTree::Type() const
{
return eDNT_Root;
}
void CDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength)
{
mID = CUniqueID(rFile, IDLength);
u32 NumDepends = rFile.ReadLong();
mReferencedResources.reserve(NumDepends);
for (u32 iDep = 0; iDep < NumDepends; iDep++)
{
CResourceDependency *pDepend = new CResourceDependency;
pDepend->Read(rFile, IDLength);
mReferencedResources.push_back(pDepend);
}
}
void CDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
if (IDLength == e32Bit)
rFile.WriteLong(mID.ToLong());
else
rFile.WriteLongLong(mID.ToLongLong());
rFile.WriteLong( mReferencedResources.size() );
for (u32 iDep = 0; iDep < mReferencedResources.size(); iDep++)
mReferencedResources[iDep]->Write(rFile, IDLength);
}
u32 CDependencyTree::NumDependencies() const
{
return mReferencedResources.size();
}
bool CDependencyTree::HasDependency(const CUniqueID& rkID)
{
for (u32 iDep = 0; iDep < mReferencedResources.size(); iDep++)
{
if (mReferencedResources[iDep]->ID() == rkID)
return true;
}
return false;
}
CUniqueID CDependencyTree::DependencyByIndex(u32 Index) const
{
ASSERT(Index >= 0 && Index < mReferencedResources.size());
return mReferencedResources[Index]->ID();
}
void CDependencyTree::AddDependency(CResource *pRes)
{
if (!pRes || HasDependency(pRes->ResID())) return;
CResourceDependency *pDepend = new CResourceDependency(pRes->ResID());
mReferencedResources.push_back(pDepend);
}
void CDependencyTree::AddDependency(const CUniqueID& rkID)
{
if (!rkID.IsValid() || HasDependency(rkID)) return;
CResourceDependency *pDepend = new CResourceDependency(rkID);
mReferencedResources.push_back(pDepend);
}
// ************ CAnimSetDependencyTree ************
EDependencyNodeType CAnimSetDependencyTree::Type() const
{
return eDNT_AnimSet;
}
void CAnimSetDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength)
{
CDependencyTree::Read(rFile, IDLength);
u32 NumChars = rFile.ReadLong();
mCharacterOffsets.reserve(NumChars);
for (u32 iChar = 0; iChar < NumChars; iChar++)
mCharacterOffsets.push_back( rFile.ReadLong() );
}
void CAnimSetDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
CDependencyTree::Write(rFile, IDLength);
rFile.WriteLong(mCharacterOffsets.size());
for (u32 iChar = 0; iChar < mCharacterOffsets.size(); iChar++)
rFile.WriteLong( mCharacterOffsets[iChar] );
}
// ************ CScriptInstanceDependencyTree ************
CScriptInstanceDependencyTree::~CScriptInstanceDependencyTree()
{
for (u32 iDep = 0; iDep < mDependencies.size(); iDep++)
delete mDependencies[iDep];
}
EDependencyNodeType CScriptInstanceDependencyTree::Type() const
{
return eDNT_ScriptInstance;
}
void CScriptInstanceDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength)
{
mObjectType = rFile.ReadLong();
u32 NumDepends = rFile.ReadLong();
mDependencies.reserve(NumDepends);
for (u32 iDep = 0; iDep < NumDepends; iDep++)
{
CUniqueID ID(rFile, IDLength);
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
if (pEntry && pEntry->ResourceType() == eAnimSet && pEntry->Game() <= eEchoes)
{
CAnimSetDependency *pSet = new CAnimSetDependency();
pSet->SetID(ID);
pSet->SetUsedChar( rFile.ReadLong() );
mDependencies.push_back(pSet);
}
else
{
CResourceDependency *pRes = new CResourceDependency(ID);
mDependencies.push_back(pRes);
}
}
}
void CScriptInstanceDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
rFile.WriteLong(mObjectType);
rFile.WriteLong(mDependencies.size());
for (u32 iDep = 0; iDep < mDependencies.size(); iDep++)
mDependencies[iDep]->Write(rFile, IDLength);
}
bool CScriptInstanceDependencyTree::HasDependency(const CUniqueID& rkID)
{
if (!rkID.IsValid()) return false;
for (u32 iDep = 0; iDep < mDependencies.size(); iDep++)
{
CResourceDependency *pDep = mDependencies[iDep];
if (pDep->ID() == rkID) return true;
}
return false;
}
// Static
CScriptInstanceDependencyTree* CScriptInstanceDependencyTree::BuildTree(CScriptObject *pInstance)
{
CScriptInstanceDependencyTree *pTree = new CScriptInstanceDependencyTree();
pTree->mObjectType = pInstance->ObjectTypeID();
ParseStructDependencies(pTree, pInstance->Properties());
return pTree;
}
void CScriptInstanceDependencyTree::ParseStructDependencies(CScriptInstanceDependencyTree *pTree, CPropertyStruct *pStruct)
{
for (u32 iProp = 0; iProp < pStruct->Count(); iProp++)
{
IProperty *pProp = pStruct->PropertyByIndex(iProp);
if (pProp->Type() == eStructProperty || pProp->Type() == eArrayProperty)
ParseStructDependencies(pTree, static_cast<CPropertyStruct*>(pProp));
else if (pProp->Type() == eFileProperty)
{
CUniqueID ID = static_cast<TFileProperty*>(pProp)->Get().ID();
if (ID.IsValid() && !pTree->HasDependency(ID))
{
CResourceDependency *pDep = new CResourceDependency(ID);
pTree->mDependencies.push_back(pDep);
}
}
else if (pProp->Type() == eCharacterProperty)
{
TCharacterProperty *pChar = static_cast<TCharacterProperty*>(pProp);
CUniqueID ID = pChar->Get().ID();
if (ID.IsValid() && !pTree->HasDependency(ID))
pTree->mDependencies.push_back( CAnimSetDependency::BuildDependency(pChar) );
}
}
}
// ************ CAreaDependencyTree ************
CAreaDependencyTree::~CAreaDependencyTree()
{
for (u32 iInst = 0; iInst < mScriptInstances.size(); iInst++)
delete mScriptInstances[iInst];
}
EDependencyNodeType CAreaDependencyTree::Type() const
{
return eDNT_Area;
}
void CAreaDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength)
{
// Base dependency list contains non-script dependencies (world geometry textures + PATH/PTLA/EGMC)
CDependencyTree::Read(rFile, IDLength);
u32 NumScriptInstances = rFile.ReadLong();
mScriptInstances.reserve(NumScriptInstances);
for (u32 iInst = 0; iInst < NumScriptInstances; iInst++)
{
CScriptInstanceDependencyTree *pInst = new CScriptInstanceDependencyTree;
pInst->Read(rFile, IDLength);
mScriptInstances.push_back(pInst);
}
u32 NumLayers = rFile.ReadLong();
mLayerOffsets.reserve(NumLayers);
for (u32 iLyr = 0; iLyr < NumLayers; iLyr++)
mLayerOffsets.push_back( rFile.ReadLong() );
}
void CAreaDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const
{
CDependencyTree::Write(rFile, IDLength);
rFile.WriteLong(mScriptInstances.size());
for (u32 iInst = 0; iInst < mScriptInstances.size(); iInst++)
mScriptInstances[iInst]->Write(rFile, IDLength);
rFile.WriteLong(mLayerOffsets.size());
for (u32 iLyr = 0; iLyr < mLayerOffsets.size(); iLyr++)
rFile.WriteLong(mLayerOffsets[iLyr]);
}
void CAreaDependencyTree::AddScriptLayer(CScriptLayer *pLayer)
{
if (!pLayer) return;
mLayerOffsets.push_back(mScriptInstances.size());
for (u32 iInst = 0; iInst < pLayer->NumInstances(); iInst++)
{
CScriptInstanceDependencyTree *pTree = CScriptInstanceDependencyTree::BuildTree( pLayer->InstanceByIndex(iInst) );
ASSERT(pTree != nullptr);
if (pTree->NumDependencies() > 0)
mScriptInstances.push_back(pTree);
else
delete pTree;
}
}

View File

@ -0,0 +1,156 @@
#ifndef CDEPENDENCYTREE
#define CDEPENDENCYTREE
#include "CResourceEntry.h"
#include <FileIO/FileIO.h>
#include <Common/AssertMacro.h>
#include <Common/CUniqueID.h>
class CScriptLayer;
class CScriptObject;
class CPropertyStruct;
class TCharacterProperty;
// Group of node classes forming a tree of cached resource dependencies.
enum EDependencyNodeType
{
eDNT_Root,
eDNT_AnimSet,
eDNT_ScriptInstance,
eDNT_Area,
eDNT_ResourceDependency,
eDNT_AnimSetDependency
};
// Base class providing an interface for reading/writing to cache file and determining type.
class IDependencyNode
{
public:
virtual ~IDependencyNode() {}
virtual EDependencyNodeType Type() const = 0;
virtual void Read(IInputStream& rFile, EUIDLength IDLength) = 0;
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const = 0;
};
// Node representing a single resource dependency.
class CResourceDependency : public IDependencyNode
{
CUniqueID mID;
public:
CResourceDependency() {}
CResourceDependency(const CUniqueID& rkID) : mID(rkID) {}
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
// Accessors
inline CUniqueID ID() const { return mID; }
inline void SetID(const CUniqueID& rkID) { mID = rkID; }
};
// Node representing a single animset dependency contained in a script object. Indicates which character is being used.
class CAnimSetDependency : public CResourceDependency
{
protected:
u32 mUsedChar;
public:
CAnimSetDependency() : CResourceDependency(), mUsedChar(-1) {}
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
// Accessors
inline u32 UsedChar() const { return mUsedChar; }
inline void SetUsedChar(u32 CharIdx) { mUsedChar = CharIdx; }
// Static
static CAnimSetDependency* BuildDependency(TCharacterProperty *pProp);
};
// Tree root node, representing a resource.
class CDependencyTree : public IDependencyNode
{
protected:
CUniqueID mID;
std::vector<CResourceDependency*> mReferencedResources;
public:
CDependencyTree(const CUniqueID& rkID) : mID(rkID) {}
~CDependencyTree();
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
u32 NumDependencies() const;
bool HasDependency(const CUniqueID& rkID);
CUniqueID DependencyByIndex(u32 Index) const;
void AddDependency(const CUniqueID& rkID);
void AddDependency(CResource *pRes);
// Accessors
inline void SetID(const CUniqueID& rkID) { mID = rkID; }
inline CUniqueID ID() const { return mID; }
};
// Node representing an animset resource; allows for lookup of dependencies of a particular character in the set.
class CAnimSetDependencyTree : public CDependencyTree
{
protected:
std::vector<u32> mCharacterOffsets;
public:
CAnimSetDependencyTree(const CUniqueID& rkID) : CDependencyTree(rkID) {}
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
};
// Node representing a script object. Indicates the type of object.
class CScriptInstanceDependencyTree : public IDependencyNode
{
protected:
u32 mObjectType;
std::vector<CResourceDependency*> mDependencies;
public:
~CScriptInstanceDependencyTree();
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
bool HasDependency(const CUniqueID& rkID);
// Accessors
u32 NumDependencies() const { return mDependencies.size(); }
// Static
static CScriptInstanceDependencyTree* BuildTree(CScriptObject *pInstance);
static void ParseStructDependencies(CScriptInstanceDependencyTree *pTree, CPropertyStruct *pStruct);
};
// Node representing an area. Tracks dependencies on a per-instance basis and can separate dependencies of different script layers.
class CAreaDependencyTree : public CDependencyTree
{
protected:
std::vector<CScriptInstanceDependencyTree*> mScriptInstances;
std::vector<u32> mLayerOffsets;
public:
CAreaDependencyTree(const CUniqueID& rkID) : CDependencyTree(rkID) {}
~CAreaDependencyTree();
virtual EDependencyNodeType Type() const;
virtual void Read(IInputStream& rFile, EUIDLength IDLength);
virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const;
void AddScriptLayer(CScriptLayer *pLayer);
};
#endif // CDEPENDENCYTREE

View File

@ -1,4 +1,5 @@
#include "CGameExporter.h"
#include "Core/GameProject/CResourceIterator.h"
#include "Core/GameProject/CResourceStore.h"
#include "Core/Resource/CWorld.h"
#include "Core/Resource/Script/CMasterTemplate.h"
@ -14,6 +15,7 @@
#define SAVE_PACKAGE_DEFINITIONS 1
#define EXPORT_WORLDS 1
#define EXPORT_COOKED 1
#define EXPORT_CACHE 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
: mStore(this)
@ -476,7 +478,6 @@ void CGameExporter::ExportWorlds()
void CGameExporter::ExportCookedResources()
{
#if EXPORT_COOKED
{
SCOPED_TIMER(ExportCookedResources);
FileUtil::CreateDirectory(mCookedDir);
@ -487,7 +488,6 @@ void CGameExporter::ExportCookedResources()
ExportResource(rRes);
}
}
#endif
{
SCOPED_TIMER(SaveResourceDatabase);
#if EXPORT_COOKED
@ -495,6 +495,20 @@ void CGameExporter::ExportCookedResources()
#endif
mpProject->Save();
}
#if EXPORT_CACHE
{
SCOPED_TIMER(SaveCacheData);
for (CResourceIterator It(&mStore); It; ++It)
{
if (!It->IsTransient())
{
It->UpdateDependencies();
It->SaveCacheData();
}
}
}
#endif
}
void CGameExporter::ExportResource(SResourceInstance& rRes)
@ -520,6 +534,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
// Register resource and write to file
CResourceEntry *pEntry = mStore.RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), OutDir, OutName);
#if EXPORT_COOKED
// Cooked (todo: save raw)
TWideString OutPath = pEntry->CookedAssetPath();
FileUtil::CreateDirectory(OutPath.GetFileDirectory());
@ -530,5 +545,8 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
rRes.Exported = true;
ASSERT(pEntry->HasCookedVersion());
#else
(void) pEntry; // Prevent "unused local variable" compiler warning
#endif
}
}

View File

@ -68,6 +68,7 @@ bool CGameProject::Load(const TWideString& rkPath)
// All loaded!
mProjectRoot = rkPath.GetFileDirectory();
mProjectRoot.Replace(L"/", L"\\");
return true;
}

View File

@ -38,7 +38,9 @@ public:
, mProjectName("Unnamed Project")
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{}
{
mProjectRoot.Replace(L"/", L"\\");
}
~CGameProject();
@ -51,6 +53,7 @@ public:
inline TWideString ProjectRoot() const { return mProjectRoot; }
inline TWideString ResourceDBPath(bool Relative) const { return Relative ? mResourceDBPath : mProjectRoot + mResourceDBPath; }
inline TWideString DiscDir(bool Relative) const { return Relative ? L"Disc\\" : mProjectRoot + L"Disc\\"; }
inline TWideString CacheDir(bool Relative) const { return Relative ? L"Cache\\" : mProjectRoot + L"Cache\\"; }
inline TWideString ContentDir(bool Relative) const { return Relative ? L"Content\\" : mProjectRoot + L"Content\\"; }
inline TWideString CookedDir(bool Relative) const { return Relative ? L"Cooked\\" : mProjectRoot + L"Cooked\\"; }
inline TWideString PackagesDir(bool Relative) const { return Relative ? L"Packages\\" : mProjectRoot + L"Packages\\"; }

View File

@ -2,6 +2,7 @@
#include "CGameProject.h"
#include "CResourceStore.h"
#include "Core/Resource/CResource.h"
#include <FileIO/FileIO.h>
#include <Common/FileUtil.h>
#include <Common/TString.h>
@ -11,6 +12,7 @@
#include "Core/Resource/Factory/CAnimSetLoader.h"
#include "Core/Resource/Factory/CAreaLoader.h"
#include "Core/Resource/Factory/CCollisionLoader.h"
#include "Core/Resource/Factory/CDependencyGroupLoader.h"
#include "Core/Resource/Factory/CFontLoader.h"
#include "Core/Resource/Factory/CMaterialLoader.h"
#include "Core/Resource/Factory/CModelLoader.h"
@ -26,19 +28,20 @@
CResourceEntry::CResourceEntry(CResourceStore *pStore, const CUniqueID& rkID,
const TWideString& rkDir, const TWideString& rkFilename,
EResType Type, bool Transient /*= false*/)
: mpStore(pStore)
, mpResource(nullptr)
: mpResource(nullptr)
, mpStore(pStore)
, mpDependencies(nullptr)
, mID(rkID)
, mName(rkFilename)
, mType(Type)
, mNeedsRecook(false)
, mTransient(Transient)
, mCachedSize(-1)
, mCachedUppercaseName(rkFilename.ToUpper())
{
if (Transient) mFlags |= eREF_Transient;
mpDirectory = mpStore->GetVirtualDirectory(rkDir, Transient, true);
if (mpDirectory) mpDirectory->AddChild(L"", this);
mGame = ((mTransient || !mpStore->ActiveProject()) ? eUnknownVersion : mpStore->ActiveProject()->Game());
mGame = ((Transient || !mpStore->ActiveProject()) ? eUnknownVersion : mpStore->ActiveProject()->Game());
}
CResourceEntry::~CResourceEntry()
@ -46,6 +49,104 @@ CResourceEntry::~CResourceEntry()
if (mpResource) delete mpResource;
}
bool CResourceEntry::LoadCacheData()
{
ASSERT(!IsTransient());
TWideString Path = CacheDataPath(false);
CFileInStream File(Path.ToUTF8().ToStdString(), IOUtil::eLittleEndian);
if (!File.IsValid())
{
Log::Error("Unable to load cache data " + Path.ToUTF8() + "; couldn't open file");
return false;
}
// Header
TString Magic = File.ReadString(4);
ASSERT(Magic == "CACH");
File.Seek(0x4, SEEK_CUR); // Skip Version
mFlags = File.ReadLong() & eREF_SavedFlags;
// Dependency Tree
u32 DepsTreeSize = File.ReadLong();
if (mpDependencies)
{
delete mpDependencies;
mpDependencies = nullptr;
}
if (DepsTreeSize > 0)
{
mpDependencies = new CDependencyTree(mID);
mpDependencies->Read(File, Game() <= eEchoes ? e32Bit : e64Bit);
}
return true;
}
bool CResourceEntry::SaveCacheData()
{
ASSERT(!IsTransient());
TWideString Path = CacheDataPath(false);
TWideString Dir = Path.GetFileDirectory();
FileUtil::CreateDirectory(Dir);
CFileOutStream File(Path.ToUTF8().ToStdString(), IOUtil::eLittleEndian);
if (!File.IsValid())
{
Log::Error("Unable to save cache data " + TString(Path.GetFileName()) + "; couldn't open file");
return false;
}
// Header
File.WriteString("CACH", 4);
File.WriteLong(0); // Reserved Space (Version)
File.WriteLong(mFlags & eREF_SavedFlags);
// Dependency Tree
if (!mpDependencies) UpdateDependencies();
u32 DepsSizeOffset = File.Tell();
File.WriteLong(0);
u32 DepsStart = File.Tell();
if (mpDependencies) mpDependencies->Write(File, Game() <= eEchoes ? e32Bit : e64Bit);
u32 DepsSize = File.Tell() - DepsStart;
File.Seek(DepsSizeOffset, SEEK_SET);
File.WriteLong(DepsSize);
return true;
}
void CResourceEntry::UpdateDependencies()
{
if (mpDependencies)
{
delete mpDependencies;
mpDependencies = nullptr;
}
if (!mpResource)
Load();
if (!mpResource)
{
Log::Error("Unable to update cached dependencies; failed to load resource");
return;
}
mpDependencies = mpResource->BuildDependencyTree();
gpResourceStore->DestroyUnreferencedResources();
}
TWideString CResourceEntry::CacheDataPath(bool Relative) const
{
return mpStore->ActiveProject()->CacheDir(Relative) + mID.ToString().ToUTF16() + L".rcd";
}
bool CResourceEntry::HasRawVersion() const
{
return FileUtil::Exists(RawAssetPath());
@ -61,7 +162,7 @@ TString CResourceEntry::RawAssetPath(bool Relative) const
TWideString Ext = GetResourceRawExtension(mType, mGame).ToUTF16();
TWideString Path = mpDirectory ? mpDirectory->FullPath() : L"";
TWideString Name = mName + L"." + Ext;
return ((mTransient || Relative) ? Path + Name : mpStore->ActiveProject()->ContentDir(false) + Path + Name);
return ((IsTransient() || Relative) ? Path + Name : mpStore->ActiveProject()->ContentDir(false) + Path + Name);
}
TString CResourceEntry::CookedAssetPath(bool Relative) const
@ -69,7 +170,7 @@ TString CResourceEntry::CookedAssetPath(bool Relative) const
TWideString Ext = GetResourceCookedExtension(mType, mGame).ToUTF16();
TWideString Path = mpDirectory ? mpDirectory->FullPath() : L"";
TWideString Name = mName + L"." + Ext;
return ((mTransient || Relative) ? Path + Name : mpStore->ActiveProject()->CookedDir(false) + Path + Name);
return ((IsTransient() || Relative) ? Path + Name : mpStore->ActiveProject()->CookedDir(false) + Path + Name);
}
bool CResourceEntry::IsInDirectory(CVirtualDirectory *pDir) const
@ -101,11 +202,11 @@ u64 CResourceEntry::Size() const
bool CResourceEntry::NeedsRecook() const
{
// Assets that do not have a raw version can't be recooked since they will always just be saved cooked to begin with.
// We will recook any asset where the raw version has been updated but not recooked yet. mNeedsRecook can also be
// We will recook any asset where the raw version has been updated but not recooked yet. eREF_NeedsRecook can also be
// toggled to arbitrarily flag any asset for recook.
if (!HasRawVersion()) return false;
if (!HasCookedVersion()) return true;
if (mNeedsRecook) return true;
if (mFlags.HasFlag(eREF_NeedsRecook)) return true;
return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath()));
}
@ -152,6 +253,7 @@ CResource* CResourceEntry::Load(IInputStream& rInput)
case eAnimation: mpResource = CAnimationLoader::LoadANIM(rInput, this); break;
case eAnimSet: mpResource = CAnimSetLoader::LoadANCSOrCHAR(rInput, this); break;
case eArea: mpResource = CAreaLoader::LoadMREA(rInput, this); break;
case eDependencyGroup: mpResource = CDependencyGroupLoader::LoadDGRP(rInput, this);break;
case eDynamicCollision: mpResource = CCollisionLoader::LoadDCLN(rInput, this); break;
case eFont: mpResource = CFontLoader::LoadFONT(rInput, this); break;
case eModel: mpResource = CModelLoader::LoadCMDL(rInput, this); break;
@ -185,7 +287,7 @@ void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
// Set new directory and name
bool HasDirectory = mpDirectory != nullptr;
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, mTransient, true);
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, IsTransient(), true);
if (pNewDir != mpDirectory)
{
@ -216,9 +318,9 @@ void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
void CResourceEntry::AddToProject(const TWideString& rkDir, const TWideString& rkName)
{
if (mTransient)
if (mFlags.HasFlag(eREF_Transient))
{
mTransient = false;
mFlags.ClearFlag(eREF_Transient);
Move(rkDir, rkName);
}
@ -230,10 +332,10 @@ void CResourceEntry::AddToProject(const TWideString& rkDir, const TWideString& r
void CResourceEntry::RemoveFromProject()
{
if (!mTransient)
if (!mFlags.HasFlag(eREF_Transient))
{
TString Dir = CookedAssetPath().GetFileDirectory();
mTransient = true;
mFlags.SetFlag(eREF_Transient);
Move(Dir, mName);
}

View File

@ -4,31 +4,49 @@
#include "CVirtualDirectory.h"
#include "Core/Resource/EResType.h"
#include <Common/CUniqueID.h>
#include <Common/Flags.h>
#include <Common/types.h>
class CResource;
class CResourceStore;
class CDependencyTree;
enum EResEntryFlag
{
eREF_NeedsRecook = 0x1,
eREF_Transient = 0x2,
eREF_HasThumbnail = 0x4,
// Flags that save to the cache file
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
class CResourceEntry
{
CResourceStore *mpStore;
CResource *mpResource;
CResourceStore *mpStore;
CDependencyTree *mpDependencies;
CUniqueID mID;
EResType mType;
EGame mGame;
CVirtualDirectory *mpDirectory;
TWideString mName;
bool mNeedsRecook;
bool mTransient;
FResEntryFlags mFlags;
mutable u64 mCachedSize;
mutable TWideString mCachedUppercaseName; // This is used to speed up case-insensitive sorting.
mutable TWideString mCachedUppercaseName; // This is used to speed up case-insensitive sorting and filtering.
public:
CResourceEntry(CResourceStore *pStore, const CUniqueID& rkID,
const TWideString& rkDir, const TWideString& rkFilename,
EResType Type, bool Transient = false);
~CResourceEntry();
bool LoadCacheData();
bool SaveCacheData();
void UpdateDependencies();
TWideString CacheDataPath(bool Relative = false) const;
bool HasRawVersion() const;
bool HasCookedVersion() const;
TString RawAssetPath(bool Relative = false) const;
@ -45,17 +63,18 @@ public:
void RemoveFromProject();
// Accessors
void SetDirty() { mNeedsRecook = true; }
void SetDirty() { mFlags.SetFlag(eREF_NeedsRecook); }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline CResource* Resource() const { return mpResource; }
inline CUniqueID ID() const { return mID; }
inline EGame Game() const { return mGame; }
inline CVirtualDirectory* Directory() const { return mpDirectory; }
inline TWideString Name() const { return mName; }
inline TWideString UppercaseName() const { return mCachedUppercaseName; }
inline EResType ResourceType() const { return mType; }
inline bool IsTransient() const { return mTransient; }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline CResource* Resource() const { return mpResource; }
inline CDependencyTree* Dependencies() const { return mpDependencies; }
inline CUniqueID ID() const { return mID; }
inline EGame Game() const { return mGame; }
inline CVirtualDirectory* Directory() const { return mpDirectory; }
inline TWideString Name() const { return mName; }
inline const TWideString& UppercaseName() const { return mCachedUppercaseName; }
inline EResType ResourceType() const { return mType; }
inline bool IsTransient() const { return mFlags.HasFlag(eREF_Transient); }
protected:
CResource* InternalLoad(IInputStream& rInput);

View File

@ -11,7 +11,7 @@ class CResourceIterator
CResourceEntry *mpCurEntry;
public:
CResourceIterator(CResourceStore *pStore)
CResourceIterator(CResourceStore *pStore = gpResourceStore)
: mpStore(pStore)
, mpCurEntry(nullptr)
{

View File

@ -69,6 +69,14 @@ void CResourceStore::LoadResourceDatabase(const TString& rkPath)
pRes = pRes->NextSiblingElement("Resource");
}
}
// All resources registered - load cache data
for (auto It = mResourceEntries.begin(); It != mResourceEntries.end(); It++)
{
CResourceEntry *pEntry = It->second;
if (!pEntry->IsTransient())
pEntry->LoadCacheData();
}
}
void CResourceStore::SaveResourceDatabase(const TString& rkPath) const
@ -108,10 +116,6 @@ void CResourceStore::SaveResourceDatabase(const TString& rkPath) const
XMLElement *pName = Doc.NewElement("FileName");
pName->SetText(*pEntry->Name().ToUTF8());
pRes->LinkEndChild(pName);
XMLElement *pRecook = Doc.NewElement("NeedsRecook");
pRecook->SetText(pEntry->NeedsRecook() ? "true" : "false");
pRes->LinkEndChild(pRecook);
}
Doc.SaveFile(*rkPath);
@ -231,10 +235,7 @@ CResourceEntry* CResourceStore::RegisterTransientResource(EResType Type, const C
{
CResourceEntry *pEntry = FindEntry(rkID);
if (pEntry)
Log::Error("Attempted to register transient resource that already exists: " + rkID.ToString() + " / Dir: " + rkDir.ToUTF8() + " / Name: " + rkFileName.ToUTF8());
else
if (!pEntry)
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkFileName, Type, true);
mResourceEntries[rkID] = pEntry;

View File

@ -65,6 +65,8 @@ public:
// Accessors
inline CGameProject* ActiveProject() const { return mpProj; }
inline CVirtualDirectory* RootDirectory() const { return mpProjectRoot; }
inline u32 NumTotalResources() const { return mResourceEntries.size(); }
inline u32 NumLoadedResources() const { return mLoadedResources.size(); }
};
extern CResourceStore *gpResourceStore;

View File

@ -10,7 +10,7 @@ CGameArea::CGameArea(CResourceEntry *pEntry /*= 0*/)
, mTerrainMerged(false)
, mOriginalWorldMeshCount(0)
, mUsesCompression(false)
, mMaterialSet(nullptr)
, mpMaterialSet(nullptr)
, mpGeneratorLayer(nullptr)
, mpCollision(nullptr)
{
@ -31,6 +31,32 @@ CGameArea::~CGameArea()
delete mLightLayers[iLyr][iLight];
}
CDependencyTree* CGameArea::BuildDependencyTree() const
{
// Base dependencies
CAreaDependencyTree *pTree = new CAreaDependencyTree(ResID());
for (u32 iMat = 0; iMat < mpMaterialSet->NumMaterials(); iMat++)
{
CMaterial *pMat = mpMaterialSet->MaterialByIndex(iMat);
pTree->AddDependency(pMat->IndTexture());
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
pTree->AddDependency(pMat->Pass(iPass)->Texture());
}
pTree->AddDependency(mpPoiToWorldMap);
Log::Warning("CGameArea::FindDependencies not handling PATH/PTLA");
// Layer dependencies
for (u32 iLayer = 0; iLayer < mScriptLayers.size(); iLayer++)
pTree->AddScriptLayer(mScriptLayers[iLayer]);
pTree->AddScriptLayer(mpGeneratorLayer);
return pTree;
}
void CGameArea::AddWorldModel(CModel *pModel)
{
mWorldModels.push_back(pModel);
@ -52,7 +78,7 @@ void CGameArea::MergeTerrain()
for (u32 iSurf = 0; iSurf < SubmeshCount; iSurf++)
{
SSurface *pSurf = pMdl->GetSurface(iSurf);
CMaterial *pMat = mMaterialSet->MaterialByIndex(pSurf->MaterialID);
CMaterial *pMat = mpMaterialSet->MaterialByIndex(pSurf->MaterialID);
bool NewMat = true;
for (std::vector<CStaticModel*>::iterator it = mStaticWorldModels.begin(); it != mStaticWorldModels.end(); it++)
@ -93,7 +119,7 @@ void CGameArea::ClearTerrain()
delete mStaticWorldModels[iStatic];
mStaticWorldModels.clear();
if (mMaterialSet) delete mMaterialSet;
if (mpMaterialSet) delete mpMaterialSet;
mVertexCount = 0;
mTriangleCount = 0;

View File

@ -45,7 +45,7 @@ class CGameArea : public CResource
std::vector<SSectionNumber> mSectionNumbers;
// Geometry
CMaterialSet *mMaterialSet;
CMaterialSet *mpMaterialSet;
std::vector<CModel*> mWorldModels; // TerrainModels is the original version of each model; this is currently mainly used in the POI map editor
std::vector<CStaticModel*> mStaticWorldModels; // StaticTerrainModels is the merged terrain for faster rendering in the world editor
// Script
@ -62,6 +62,7 @@ class CGameArea : public CResource
public:
CGameArea(CResourceEntry *pEntry = 0);
~CGameArea();
CDependencyTree* BuildDependencyTree() const;
void AddWorldModel(CModel *pModel);
void MergeTerrain();

View File

@ -28,6 +28,7 @@ public:
// Accessors
inline EGame Version() const { return mGame; }
inline CUniqueID ID() const { return mCharacter.ID(); }
inline CAnimSet* AnimSet() const { return (CAnimSet*) mCharacter.Load(); }
inline u32 CharacterIndex() const { return mCharIndex; }
inline u32 AnimIndex() const { return mAnimIndex; }

View File

@ -0,0 +1,33 @@
#ifndef CDEPENDENCYGROUP
#define CDEPENDENCYGROUP
#include "CResource.h"
class CDependencyGroup : public CResource
{
DECLARE_RESOURCE_TYPE(eDependencyGroup)
std::set<CUniqueID> mDependencies;
public:
CDependencyGroup(CResourceEntry *pEntry = 0) : CResource(pEntry) {}
inline void AddDependency(const CUniqueID& rkID) { mDependencies.insert(rkID); }
inline void AddDependency(CResource *pRes) { if (pRes) mDependencies.insert(pRes->ResID()); }
inline void RemoveDependency(const CUniqueID& rkID) { mDependencies.erase(rkID); }
inline void Clear() { mDependencies.clear(); }
inline bool HasDependency(const CUniqueID& rkID) const { return mDependencies.find(rkID) != mDependencies.end(); }
inline u32 NumDependencies() const { return mDependencies.size(); }
inline CUniqueID DependencyByIndex(u32 Index) const { return *std::next(mDependencies.begin(), Index); }
CDependencyTree* BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree(ResID());
for (auto DepIt = mDependencies.begin(); DepIt != mDependencies.end(); DepIt++)
pTree->AddDependency(*DepIt);
return pTree;
}
};
#endif // CDEPENDENCYGROUP

View File

@ -22,6 +22,13 @@ inline float PtsToFloat(s32 Pt)
return 0.00208333f * Pt;
}
CDependencyTree* CFont::BuildDependencyTree() const
{
CDependencyTree *pOut = new CDependencyTree(ResID());
pOut->AddDependency(mpFontTexture);
return pOut;
}
CVector2f CFont::RenderString(const TString& rkString, CRenderer* /*pRenderer*/, float /*AspectRatio*/,
CVector2f /*Position*/, CColor FillColor, CColor StrokeColor, u32 FontSize)
{

View File

@ -60,6 +60,7 @@ class CFont : public CResource
public:
CFont(CResourceEntry *pEntry = 0);
~CFont();
CDependencyTree* BuildDependencyTree() const;
CVector2f RenderString(const TString& rkString, CRenderer *pRenderer, float AspectRatio,
CVector2f Position = CVector2f(0,0),
CColor FillColor = CColor::skWhite, CColor StrokeColor = CColor::skBlack,

View File

@ -2,6 +2,7 @@
#define CRESOURCE_H
#include "EResType.h"
#include "Core/GameProject/CDependencyTree.h"
#include "Core/GameProject/CResourceEntry.h"
#include "Core/GameProject/CResourceStore.h"
#include <Common/CFourCC.h>
@ -39,7 +40,8 @@ public:
}
virtual ~CResource() {}
virtual CDependencyTree* BuildDependencyTree() const { return new CDependencyTree(ResID()); }
inline CResourceEntry* Entry() const { return mpEntry; }
inline TString Source() const { return mpEntry ? mpEntry->CookedAssetPath(true).GetFileName() : ""; }
inline TString FullSource() const { return mpEntry ? mpEntry->CookedAssetPath(true) : ""; }

View File

@ -40,6 +40,17 @@ public:
, mCategory(eNone)
{}
CDependencyTree* BuildDependencyTree() const
{
if (Game() >= eEchoesDemo)
Log::Warning("CScan::BuildDependencyTree not handling Echoes/Corruption dependencies");
CDependencyTree *pTree = new CDependencyTree(ResID());
pTree->AddDependency(mpFrame);
pTree->AddDependency(mpStringTable);
return pTree;
}
EGame Version() const { return mVersion; }
CStringTable* ScanText() const { return mpStringTable; }
bool IsImportant() const { return mIsImportant; }

View File

@ -17,6 +17,28 @@ CWorld::~CWorld()
{
}
CDependencyTree* CWorld::BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree(ResID());
for (u32 iArea = 0; iArea < mAreas.size(); iArea++)
{
pTree->AddDependency(mAreas[iArea].FileID);
pTree->AddDependency(mAreas[iArea].pAreaName);
}
pTree->AddDependency(mpWorldName);
pTree->AddDependency(mpDarkWorldName);
pTree->AddDependency(mpSaveWorld);
pTree->AddDependency(mpDefaultSkybox);
pTree->AddDependency(mpMapWorld);
if (Game() <= ePrime)
Log::Warning("CWorld::BuildDependencyTree not handling audio groups");
return pTree;
}
void CWorld::SetAreaLayerInfo(CGameArea *pArea)
{
// The AreaIndex parameter is a placeholder until an improved world loader is implemented.

View File

@ -84,6 +84,7 @@ public:
CWorld(CResourceEntry *pEntry = 0);
~CWorld();
CDependencyTree* BuildDependencyTree() const;
void SetAreaLayerInfo(CGameArea *pArea);
// Accessors

View File

@ -73,7 +73,7 @@ void CAreaLoader::ReadGeometryPrime()
mpSectionMgr->ToSection(mGeometryBlockNum);
// Materials
mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
mpArea->mpMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
mpSectionMgr->ToNextSection();
// Geometry
@ -81,7 +81,7 @@ void CAreaLoader::ReadGeometryPrime()
for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++)
{
CModel *pModel = CModelLoader::LoadWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mMaterialSet, mVersion);
CModel *pModel = CModelLoader::LoadWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mpMaterialSet, mVersion);
FileModels.push_back(pModel);
if (mVersion <= ePrime)
@ -394,7 +394,7 @@ void CAreaLoader::ReadGeometryCorruption()
mpSectionMgr->ToSection(mGeometryBlockNum);
// Materials
mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
mpArea->mpMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
mpSectionMgr->ToNextSection();
// Geometry
@ -404,7 +404,7 @@ void CAreaLoader::ReadGeometryCorruption()
for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++)
{
CModel *pWorldModel = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mMaterialSet, CurWOBJSection, CurGPUSection, mVersion);
CModel *pWorldModel = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mpMaterialSet, CurWOBJSection, CurGPUSection, mVersion);
FileModels.push_back(pWorldModel);
CurWOBJSection += 4;

View File

@ -0,0 +1,48 @@
#include "CDependencyGroupLoader.h"
#include <Common/AssertMacro.h>
EGame CDependencyGroupLoader::VersionTest(IInputStream& rDGRP, u32 DepCount)
{
// Only difference between versions is asset ID length. Just check for EOF with 32-bit ID length.
u32 Start = rDGRP.Tell();
rDGRP.Seek(DepCount * 4, SEEK_CUR);
u32 Remaining = rDGRP.Size() - Start;
EGame Game = ePrimeDemo;
if (Remaining < 32)
{
for (u32 iRem = 0; iRem < Remaining; iRem++)
{
if (rDGRP.ReadByte() != 0xFF)
{
Game = eCorruptionProto;
break;
}
}
}
rDGRP.Seek(Start, SEEK_SET);
return Game;
}
CDependencyGroup* CDependencyGroupLoader::LoadDGRP(IInputStream& rDGRP, CResourceEntry *pEntry)
{
if (!rDGRP.IsValid()) return nullptr;
u32 NumDependencies = rDGRP.ReadLong();
EGame Game = VersionTest(rDGRP, NumDependencies);
EUIDLength IDLength = (Game < eCorruptionProto ? e32Bit : e64Bit);
CDependencyGroup *pGroup = new CDependencyGroup(pEntry);
pGroup->SetGame(Game);
for (u32 iDep = 0; iDep < NumDependencies; iDep++)
{
rDGRP.Seek(0x4, SEEK_CUR); // Skip dependency type
CUniqueID AssetID(rDGRP, IDLength);
pGroup->AddDependency(AssetID);
}
return pGroup;
}

View File

@ -0,0 +1,16 @@
#ifndef CDEPENDENCYGROUPLOADER_H
#define CDEPENDENCYGROUPLOADER_H
#include "Core/Resource/CDependencyGroup.h"
#include "Core/Resource/EGame.h"
class CDependencyGroupLoader
{
CDependencyGroupLoader() {}
static EGame VersionTest(IInputStream& rDGRP, u32 DepCount);
public:
static CDependencyGroup* LoadDGRP(IInputStream& rDGRP, CResourceEntry *pEntry);
};
#endif // CDEPENDENCYGROUPLOADER_H

View File

@ -65,7 +65,7 @@ void CMaterialLoader::ReadPrimeMatSet()
{
mpSet->mMaterials[iMat] = ReadPrimeMaterial();
mpSet->mMaterials[iMat]->mVersion = mVersion;
mpSet->mMaterials[iMat]->mName = TString("Material #") + std::to_string(iMat + 1);
mpSet->mMaterials[iMat]->mName = TString("Material #") + TString::FromInt32(iMat + 1, 0, 10);
mpFile->Seek(MatsStart + Offsets[iMat], SEEK_SET);
}
}
@ -261,7 +261,7 @@ void CMaterialLoader::ReadCorruptionMatSet()
u32 Next = mpFile->Tell() + Size;
mpSet->mMaterials[iMat] = ReadCorruptionMaterial();
mpSet->mMaterials[iMat]->mVersion = mVersion;
mpSet->mMaterials[iMat]->mName = TString("Material #") + std::to_string(iMat + 1);
mpSet->mMaterials[iMat]->mName = TString("Material #") + TString::FromInt32(iMat + 1, 0, 10);
mpFile->Seek(Next, SEEK_SET);
}
}

View File

@ -29,6 +29,31 @@ CModel::~CModel()
delete mMaterialSets[iMat];
}
CDependencyTree* CModel::BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree(ResID());
for (u32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
{
CMaterialSet *pSet = mMaterialSets[iSet];
for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
pTree->AddDependency(pMat->IndTexture());
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CMaterialPass *pPass = pMat->Pass(iPass);
pTree->AddDependency(pPass->Texture());
}
}
}
return pTree;
}
void CModel::BufferGL()
{
if (!mBuffered)

View File

@ -25,6 +25,7 @@ public:
CModel(CMaterialSet *pSet, bool OwnsMatSet);
~CModel();
CDependencyTree* BuildDependencyTree() const;
void BufferGL();
void GenerateMaterialShaders();
void ClearGLBuffer();

View File

@ -63,6 +63,7 @@ bool CScriptObject::IsEditorProperty(IProperty *pProp)
(pProp == mpRotation) ||
(pProp == mpScale) ||
(pProp == mpActive) ||
(pProp == mpLightParameters) ||
(pProp->Parent() == mpLightParameters)
);
}