You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/RC/ResourceCompilerPC/StatCGFCompiler.cpp

1261 lines
44 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "ResourceCompilerPC_precompiled.h"
#include "ConvertContext.h"
#include "IConfig.h"
#include "StatCGFCompiler.h"
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include "../CryEngine/Cry3DEngine/CGF/CGFLoader.h"
#include "CGF/CGFSaver.h"
#include "CryVersion.h"
#include "StaticObjectCompiler.h"
#include "FileUtil.h"
#include "MathHelpers.h"
#include "StringHelpers.h"
#include "UpToDateFileHelpers.h"
AZ::ComponentTypeList CGFToolApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents();
auto removed = AZStd::remove_if(components.begin(), components.end(),
[](const AZ::Uuid& id) -> bool
{
return id == azrtti_typeid<AzFramework::TargetManagementComponent>()
|| id == azrtti_typeid<AzToolsFramework::PerforceComponent>()
|| id == azrtti_typeid<AZ::UserSettingsComponent>()
|| id == azrtti_typeid<AzToolsFramework::AssetBrowser::AssetBrowserComponent>();
});
components.erase(removed, components.end());
return components;
}
void CGFToolApplication::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
{
AzToolsFramework::ToolsApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("statcgfcompiler");
}
/////////////////////////////////////////////////////////////////////////
CStatCGFCompiler::CStatCGFCompiler()
{
//MessageBox(NULL, "Pres OK baton ->", "Ok?", MB_OK);
m_refCount = 1;
}
//////////////////////////////////////////////////////////////////////////
CStatCGFCompiler::~CStatCGFCompiler()
{
}
//////////////////////////////////////////////////////////////////////////
string CStatCGFCompiler::GetOutputFileNameOnly() const
{
string sourceFileFinal = m_CC.m_config->GetAsString("overwritefilename", m_CC.m_sourceFileNameOnly.c_str(), m_CC.m_sourceFileNameOnly.c_str());
if (StringHelpers::EqualsIgnoreCase(PathHelpers::FindExtension(sourceFileFinal), "i_cgf"))
{
sourceFileFinal = PathHelpers::ReplaceExtension(sourceFileFinal, "cgf");
}
const string ext = PathHelpers::FindExtension(sourceFileFinal);
if (StringHelpers::EqualsIgnoreCase(ext, "cgf") || StringHelpers::EqualsIgnoreCase(ext, "cga"))
{
if (m_CC.m_config->GetAsBool("StripNonMesh", false, true))
{
sourceFileFinal += "m";
}
}
return sourceFileFinal;
}
//////////////////////////////////////////////////////////////////////////
AZStd::string CStatCGFCompiler::GetDependencyAbsolutePath(const AZStd::string& fileName) const
{
AZStd::string cgfSourcePath = PathHelpers::GetDirectory(m_CC.GetSourcePath().c_str()).c_str();
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePathKeepCase, cgfSourcePath);
// register the absolute path of the file as the dependency here, as the asset catalog will take care
// of resolving it to its proper scan folder and path relative to that scan folder.
AZStd::string absoluteFilePath = fileName.c_str();
if (!cgfSourcePath.empty())
{
AzFramework::StringFunc::AssetDatabasePath::Join(cgfSourcePath.c_str(), fileName.c_str(), absoluteFilePath);
AzFramework::StringFunc::AssetDatabasePath::Normalize(absoluteFilePath);
}
return absoluteFilePath;
}
//////////////////////////////////////////////////////////////////////////
string CStatCGFCompiler::GetOutputPath() const
{
return PathHelpers::Join(m_CC.GetOutputFolder(), GetOutputFileNameOnly());
}
//////////////////////////////////////////////////////////////////////////
void CStatCGFCompiler::Release()
{
if (--m_refCount <= 0)
{
delete this;
}
}
//////////////////////////////////////////////////////////////////////////
ICompiler* CStatCGFCompiler::CreateCompiler()
{
// Only ever return one compiler, since we don't support multithreading. Since
// the compiler is just this object, we can tell whether we have already returned
// a compiler by checking the ref count.
if (m_refCount >= 2)
{
return 0;
}
// Until we support multithreading for this convertor, the compiler and the
// convertor may as well just be the same object.
++m_refCount;
return this;
}
//////////////////////////////////////////////////////////////////////////
static string getTextMatrix(const Matrix34& M)
{
string text = "";
char buff[200];
azsnprintf(buff, sizeof(buff), "axisX(%g %g %g)", M.m00, M.m10, M.m20);
text += buff;
azsnprintf(buff, sizeof(buff), " axisY(%g %g %g)", M.m01, M.m11, M.m21);
text += buff;
azsnprintf(buff, sizeof(buff), " axisZ(%g %g %g)", M.m02, M.m12, M.m22);
text += buff;
azsnprintf(buff, sizeof(buff), " trans(%g %g %g)", M.m03, M.m13, M.m23);
text += buff;
return text;
}
static const char* getTextNodeType(CNodeCGF::ENodeType const type)
{
const char* text;
switch (type)
{
case CNodeCGF::NODE_HELPER:
text = "NODE_HELPER";
break;
case CNodeCGF::NODE_LIGHT:
text = "NODE_LIGHT";
break;
case CNodeCGF::NODE_MESH:
text = "NODE_MESH";
break;
default:
text = "***UNKNOWN***";
break;
}
return text;
}
static const char* getTextHelperType(HelperTypes const type)
{
const char* text;
switch (type)
{
case HP_POINT:
text = "HP_POINT";
break;
case HP_DUMMY:
text = "HP_DUMMY";
break;
case HP_XREF:
text = "HP_XREF";
break;
case HP_CAMERA:
text = "HP_CAMERA";
break;
case HP_GEOMETRY:
text = "HP_GEOMETRY";
break;
default:
text = "*** UNKNOWN ***";
break;
}
return text;
}
static string getTextPhysicalizeType(int const type)
{
string text;
switch (type)
{
case PHYS_GEOM_TYPE_NONE:
text += "PHYS_GEOM_TYPE_NONE";
break;
case PHYS_GEOM_TYPE_DEFAULT:
text += "PHYS_GEOM_TYPE_DEFAULT";
break;
case PHYS_GEOM_TYPE_NO_COLLIDE:
text += "PHYS_GEOM_TYPE_NO_COLLIDE";
break;
case PHYS_GEOM_TYPE_OBSTRUCT:
text += "PHYS_GEOM_TYPE_OBSTRUCT";
break;
case PHYS_GEOM_TYPE_DEFAULT_PROXY:
text += "PHYS_GEOM_TYPE_DEFAULT_PROXY";
break;
default:
{
text += "*** UNKNOWN ***";
char buff[50];
azsnprintf(buff, sizeof(buff), " (0x%x)", type);
text += buff;
}
break;
}
return text;
}
static string getTextPhysicalizeFlags(int const flags)
{
string text;
switch (flags)
{
case 0:
text += "none";
break;
case CNodeCGF::ePhysicsalizeFlag_MeshNotNeeded:
text += "MeshNotNeeded";
break;
default:
{
text += "*** UNKNOWN ***";
char buff[50];
azsnprintf(buff, sizeof(buff), " (0x%x)", flags);
text += buff;
}
break;
}
return text;
}
bool CStatCGFCompiler::DebugDumpCGF(const char* sourceFileName, const char* outputFilePath)
{
RCLog("Dumping geometry file %s...", sourceFileName);
class Listener
: public ILoaderCGFListener
{
public:
Listener()
{
}
virtual void Warning(const char* format)
{
RCLogWarning("%s", format);
}
virtual void Error(const char* format)
{
RCLogError("%s", format);
}
};
Listener listener;
CChunkFile chunkFile;
CLoaderCGF cgfLoader;
CContentCGF* pCGF = cgfLoader.LoadCGF(sourceFileName, chunkFile, &listener);
if (!pCGF)
{
RCLogError("Dump: Failed to load geometry file %s - %s", sourceFileName, cgfLoader.GetLastError());
return false;
}
if (pCGF->GetConsoleFormat())
{
RCLogError("Dump: Cannot dump geometry file %s because it's in console format.", sourceFileName);
delete pCGF;
return false;
}
string dumpFilename = outputFilePath;
dumpFilename += ".dump";
char resolvedPath[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath(dumpFilename.c_str(), resolvedPath, AZ_MAX_PATH_LEN);
FILE* f = nullptr;
azfopen(&f, resolvedPath, "wt");
if (f == 0)
{
RCLogError("Dump: Cannot create dump file %s.", resolvedPath);
delete pCGF;
return false;
}
fprintf(f, "<<< Dump of '%s' >>>\n", sourceFileName);
fprintf(f, "\n");
fprintf(f, "---------------------------------------------\n");
fprintf(f, "material#: %d\n", pCGF->GetMaterialCount());
fprintf(f, "\n");
std::vector<CMesh*> meshes;
for (int materialIdx = 0; materialIdx < pCGF->GetMaterialCount(); ++materialIdx)
{
fprintf(f, "material[%d]:\n", materialIdx);
const CMaterialCGF* pMat = pCGF->GetMaterial(materialIdx);
if (pMat == 0)
{
fprintf(f, "\t" "***NULL MATERIAL***");
continue;
}
fprintf(f, "\t" "name: '%s'\n", pMat->name);
fprintf(f, "\t" "nPhysicalizeType: %s\n", getTextPhysicalizeType(pMat->nPhysicalizeType).c_str());
fprintf(f, "\t" "subMaterials[]: size=%d, capacity=%d\n", pMat->subMaterials.size(), pMat->subMaterials.capacity());
for (int i = 0; i < pMat->subMaterials.size(); ++i)
{
CMaterialCGF* const pSubMat = pMat->subMaterials[i];
fprintf(f, "\t\t" "subMaterials[%d]: ", i);
if (pSubMat == 0)
{
fprintf(f, "NULL");
}
else
{
// Trying to find pSubMat in our materials
bool found = false;
for (int j = 0; j < pCGF->GetMaterialCount(); ++j)
{
if (pSubMat == pCGF->GetMaterial(j))
{
found = true;
fprintf(f, "material[%d] '%s'", j, pSubMat->name);
break;
}
}
if (!found)
{
fprintf(f, "*** not in material[] *** '%s'", pSubMat->name);
}
}
fprintf(f, "\n");
}
fprintf(f, "\t" "internals:\n");
fprintf(f, "\t\t" "nChunkId: %d\n", pMat->nChunkId);
fprintf(f, "\n");
}
fprintf(f, "---------------------------------------------\n");
{
CMaterialCGF* pMat = pCGF->GetCommonMaterial();
fprintf(f, "\t" "pCommonMaterial: ");
if (pMat == 0)
{
fprintf(f, "NULL");
}
else
{
// Trying to find commonmaterial in our materials
bool found = false;
for (int j = 0; j < pCGF->GetMaterialCount(); ++j)
{
if (pMat == pCGF->GetMaterial(j))
{
found = true;
fprintf(f, "material[%d] '%s'", j, pMat->name);
break;
}
}
if (!found)
{
fprintf(f, "*** not in material[] *** '%s'", pMat->name);
}
}
fprintf(f, "\n");
}
{
//const CPhysicalizeInfoCGF* pPhys = pCGF->GetPhysicalizeInfo();
fprintf(f, "\t" "PhysicalizeInfo: (not printed yet)\n");
}
{
//CExportInfoCGF* pExport = pCGF->GetExportInfo();
fprintf(f, "\t" "ExportInfo: (not printed yet)\n");
}
{
//CSkinningInfo* pSkin = pCGF->GetSkinningInfo();
fprintf(f, "\t" "SkinningInfo: (not printed yet)\n");
}
{
//SFoliageInfoCGF* pSkin = pCGF->GetFoliageInfo();
fprintf(f, "\t" "FoliageInfo: (not printed yet)\n");
}
fprintf(f, "---------------------------------------------\n");
fprintf(f, "node#: %d\n", pCGF->GetNodeCount());
fprintf(f, "\n");
for (int nodeIdx = 0; nodeIdx < pCGF->GetNodeCount(); ++nodeIdx)
{
fprintf(f, "node[%d]:\n", nodeIdx);
const CNodeCGF* pNode = pCGF->GetNode(nodeIdx);
if (pNode == 0)
{
fprintf(f, "\t" "***NULL NODE***");
continue;
}
fprintf(f, "\t" "type: %s\n", getTextNodeType(pNode->type));
fprintf(f, "\t" "name: '%s'\n", pNode->name);
fprintf(f, "\t" "properties: '%s'\n", pNode->properties.c_str());
fprintf(f, "\t" "localTM: %s\n", getTextMatrix(pNode->localTM).c_str());
fprintf(f, "\t" "worldTM: %s\n", getTextMatrix(pNode->worldTM).c_str());
fprintf(f, "\t" "pParent: ");
if (pNode->pParent == 0)
{
fprintf(f, "NULL");
}
else
{
// Trying to find pParent node in our nodes
bool found = false;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
if (pNode->pParent == pCGF->GetNode(i))
{
found = true;
fprintf(f, "node[%d] '%s'", i, pNode->pParent->name);
break;
}
}
if (!found)
{
fprintf(f, "*** not in node[] *** '%s'", pNode->pParent->name);
}
}
fprintf(f, "\n");
fprintf(f, "\t" "pSharedMesh: ");
if (pNode->pSharedMesh == 0)
{
fprintf(f, "NULL");
}
else
{
// Trying to find pSharedMesh node in our nodes
bool found = false;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
if (pNode->pSharedMesh == pCGF->GetNode(i))
{
found = true;
fprintf(f, "node[%d] '%s'", i, pNode->pSharedMesh->name);
break;
}
}
if (!found)
{
fprintf(f, "*** not in node[] *** '%s'", pNode->pSharedMesh->name);
}
}
fprintf(f, "\n");
//if (pNode->type == CNodeCGF::NODE_MESH)
{
fprintf(f, "\t" "pMesh: ");
CMesh* const pMesh = pNode->pMesh;
if (pMesh == 0)
{
fprintf(f, "NULL");
}
else
{
unsigned int i;
for (i = 0; i < meshes.size(); ++i)
{
if (meshes[i] == pMesh)
{
break;
}
}
if (i >= meshes.size())
{
meshes.push_back(pMesh);
i = meshes.size() - 1;
}
fprintf(f, "\t" "mesh[%d]", i);
}
fprintf(f, "\n");
}
if (pNode->type == CNodeCGF::NODE_HELPER)
{
fprintf(f, "\t" "helperType: %s\n", getTextHelperType(pNode->helperType));
fprintf(f, "\t" "helperSize: %g %g %g\n", pNode->helperSize.x, pNode->helperSize.y, pNode->helperSize.z);
}
fprintf(f, "\t" "pMaterial: ");
if (pNode->pMaterial == 0)
{
fprintf(f, "NULL");
}
else
{
// Trying to find pMaterial in our materials
bool found = false;
for (int i = 0; i < pCGF->GetMaterialCount(); ++i)
{
if (pNode->pMaterial == pCGF->GetMaterial(i))
{
found = true;
fprintf(f, "material[%d] '%s'", i, pNode->pMaterial->name);
break;
}
}
if (!found)
{
fprintf(f, "*** not in material[] *** '%s'", pNode->pMaterial->name);
}
}
fprintf(f, "\n");
fprintf(f, "\t" "nPhysicalizeFlags: %s\n", getTextPhysicalizeFlags(pNode->nPhysicalizeFlags).c_str());
{
bool bIsEmpty = true;
for (int i = 0; i < 4; ++i)
{
const bool bSlotIsEmpty = (pNode->physicalGeomData[i].size() == 0) && (pNode->physicalGeomData[i].capacity() == 0);
if (!bSlotIsEmpty)
{
bIsEmpty = false;
break;
}
}
if (bIsEmpty)
{
fprintf(f, "\t" "physicalGeomData[]: empty\n");
}
else
{
for (int i = 0; i < 4; ++i)
{
const bool bSlotIsEmpty = (pNode->physicalGeomData[i].size() == 0) && (pNode->physicalGeomData[i].capacity() == 0);
if (!bSlotIsEmpty)
{
fprintf(f, "\t" "physicalGeomData[%d]: size=%zu, capacity=%zu\n", i, pNode->physicalGeomData[i].size(), pNode->physicalGeomData[i].capacity());
}
}
}
}
fprintf(f, "\t" "internals:\n");
fprintf(f, "\t\t" "nChunkId: %d\n", pNode->nChunkId);
fprintf(f, "\t\t" "nParentChunkId: %d\n", pNode->nParentChunkId);
fprintf(f, "\t\t" "nObjectChunkId: %d\n", pNode->nObjectChunkId);
fprintf(f, "\t\t" "pos_controller_id: %d\n", pNode->pos_cont_id);
fprintf(f, "\t\t" "rot_controller_id: %d\n", pNode->rot_cont_id);
fprintf(f, "\t\t" "scl_controller_id: %d\n", pNode->scl_cont_id);
fprintf(f, "\t" "bIdentityMatrix: %s\n", (pNode->bIdentityMatrix ? "true" : "false"));
fprintf(f, "\t" "bPhysicsProxy: %s\n", (pNode->bPhysicsProxy ? "true" : "false"));
fprintf(f, "\n");
}
fprintf(f, "---------------------------------------------\n");
bool hasError = false;
fprintf(f, "mesh#: %zu\n", meshes.size());
fprintf(f, "\n");
for (int meshIdx = 0; meshIdx < meshes.size(); ++meshIdx)
{
fprintf(f, "mesh[%d]:\n", meshIdx);
CMesh* const pMesh = meshes[meshIdx];
const char* pErrorDescription = 0;
bool const bMeshValid = pMesh->Validate(&pErrorDescription);
if (!bMeshValid)
{
hasError = true;
fprintf(f, "\t\t" "** ERROR **: mesh is invalid (%s)\n", pErrorDescription);
}
fprintf(f, "\t" "numFaces: %d\n", pMesh->GetFaceCount());
fprintf(f, "\t" "numVertices: %d\n", pMesh->GetVertexCount());
fprintf(f, "\t" "nCoorCount: %d\n", pMesh->GetTexCoordCount());
fprintf(f, "\t" "nIndexCount: %d\n", pMesh->GetIndexCount());
fprintf(f,
"\t" "bbox: min(%g %g %g) max(%g %g %g)\n",
pMesh->m_bbox.min.x, pMesh->m_bbox.min.y, pMesh->m_bbox.min.z,
pMesh->m_bbox.max.x, pMesh->m_bbox.max.y, pMesh->m_bbox.max.z);
fprintf(f, "\t" "texMappingDensity: %g\n", pMesh->m_texMappingDensity);
fprintf(f, "\t" "subsets[]: size=%d, capacity=%d\n", pMesh->m_subsets.size(), pMesh->m_subsets.capacity());
for (int i = 0; i < pMesh->m_subsets.size(); ++i)
{
SMeshSubset& subset = pMesh->m_subsets[i];
fprintf(f, "\t" "subset[%d]:\n", i);
fprintf(f, "\t\t" "center: %g %g %g\n", subset.vCenter.x, subset.vCenter.y, subset.vCenter.z);
fprintf(f, "\t\t" "radius: %g\n", subset.fRadius);
fprintf(f, "\t\t" "nFirstIndexId: %d\n", subset.nFirstIndexId);
fprintf(f, "\t\t" "nNumIndices: %d\n", subset.nNumIndices);
fprintf(f, "\t\t" "nFirstVertId: %d\n", subset.nFirstVertId);
fprintf(f, "\t\t" "nNumVerts: %d\n", subset.nNumVerts);
fprintf(f, "\t\t" "nMatID: %d\n", subset.nMatID);
fprintf(f, "\t\t" "nMatFlags: 0x%08x\n", subset.nMatFlags);
fprintf(f, "\t\t" "nPhysicalizeType: %s\n", getTextPhysicalizeType(subset.nPhysicalizeType).c_str());
if (!bMeshValid)
{
// Check indices
if (subset.nNumIndices > 0)
{
const vtx_idx* const pIndices = pMesh->m_pIndices;
if (pIndices == 0)
{
fprintf(f, "\t\t" "pIndices: NULL ** ERROR **\n");
break;
}
if (subset.nNumIndices % 3)
{
fprintf(f, "\t\t" "nNumIndices is not multiplication of 3 ** ERROR **\n");
break;
}
if (subset.nFirstIndexId < 0)
{
fprintf(f, "\t\t" "nFirstIndexId < 0 ** ERROR **\n");
break;
}
if (subset.nFirstIndexId + subset.nNumIndices > pMesh->GetIndexCount())
{
fprintf(f, "\t\t" "nIndices + numIndices > mesh's nIndexCount ** ERROR **\n");
break;
}
if (subset.nNumVerts <= 0)
{
fprintf(f, "\t\t" "nNumVerts <= 0 ** ERROR **\n");
break;
}
if (subset.nFirstVertId < 0)
{
fprintf(f, "\t\t" "nFirstVertId < 0 ** ERROR **\n");
break;
}
if (subset.nFirstVertId + subset.nNumVerts > pMesh->GetVertexCount())
{
fprintf(f, "\t\t" "nFirstVertId + nNumVerts > mesh's numVertices ** ERROR **\n");
break;
}
for (int ii = 0; ii < subset.nNumIndices; ++ii)
{
const int vertexIndex = pIndices[subset.nFirstIndexId + ii];
const int nLastVertId = subset.nFirstVertId + subset.nNumVerts - 1;
if ((vertexIndex < subset.nFirstVertId) || (vertexIndex > nLastVertId))
{
fprintf(f, "\t\t" "%ith index refers to vertex %i (outside of [%i;%i]) ** ERROR **\n", ii, vertexIndex, subset.nFirstIndexId, nLastVertId);
break;
}
}
}
}
}
fprintf(f, "\n");
}
if (hasError)
{
fprintf(f, "** ATTENTION! At least one mesh has fatal errors in geometry. Search for '** ERROR **' above\n");
}
fprintf(f, "<<< End of dump of '%s' >>>\n", sourceFileName);
fclose(f);
f = 0;
delete pCGF;
pCGF = 0;
RCLog("Finished dumping geometry file %s.", sourceFileName);
return true;
}
bool debugValidateCGF(CContentCGF* pCGF, const char* a_filename)
{
RCLog("Validating geometry file %s...", a_filename);
if (!pCGF)
{
RCLogError("Validate: Failed to load geometry file %s", a_filename);
return false;
}
if (pCGF->GetConsoleFormat())
{
RCLogError("Validate: Cannot validate geometry file %s because it's in console format.", a_filename);
return false;
}
const char* pErrorDescription = 0;
if (!pCGF->ValidateMeshes(&pErrorDescription))
{
RCLogError("Validate: Geometry file %s is damaged (%s).", a_filename, pErrorDescription);
return false;
}
RCLog("Validate: Geometry file %s is ok.", a_filename);
return true;
}
#if defined(AZ_PLATFORM_WINDOWS)
#include <conio.h>
#endif
//////////////////////////////////////////////////////////////////////////
bool CStatCGFCompiler::Process()
{
// Register the AssetBuilderSDK structures needed later on.
CGFToolApplication application;
AZ::ComponentApplication::Descriptor descriptor;
application.Start(descriptor);
AssetBuilderSDK::InitializeSerializationContext();
AssetBuilderSDK::ProcessJobResponse response;
// We want to query the watch folder here for cases where we have to build a material relative path dependency.
// This is necessary for cases where the source assets lie outside of the game project directory
AZStd::string sourceWatchFolder = m_CC.m_config->GetAsString("watchfolder", "", "").c_str();
if (sourceWatchFolder.empty())
{
// Fallbacks just in case watchfolder is not set for some reason
sourceWatchFolder = m_CC.m_config->GetAsString("sourceroot", "", "").c_str();
if (sourceWatchFolder.empty())
{
sourceWatchFolder = m_CC.m_config->GetAsString("project-path", "", "").c_str();
}
}
bool compileSuccess = CompileCGF(response, sourceWatchFolder);
return WriteResponse(m_CC.GetOutputFolder(), response, compileSuccess);
}
//////////////////////////////////////////////////////////////////////////
bool CStatCGFCompiler::CompileCGF(AssetBuilderSDK::ProcessJobResponse& response, AZStd::string assetRoot, AZStd::string gameFolder)
{
#if defined(AZ_PLATFORM_WINDOWS)
// _EM_INVALID is used to avoid Floating Point Exception inside CryPhysics
MathHelpers::AutoFloatingPointExceptions autoFpe(~(_EM_INEXACT | _EM_UNDERFLOW | _EM_INVALID));
#endif
// If no game project was specified, then query it from the Settings Registry
if (gameFolder.empty())
{
gameFolder = AZ::Utils::GetProjectName();
}
// If no asset root was specified, then query it from the application
if (assetRoot.empty())
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(assetRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
}
}
const string sourceFile = m_CC.GetSourcePath();
const string outputFile = GetOutputPath();
#if !defined(AZ_PLATFORM_LINUX) && !defined(AZ_PLATFORM_APPLE) // Exception handling not enabled on linux/mac builds
try
#endif
{
const bool bStripMeshData = m_CC.m_config->HasKey("StripMesh");
const bool bStripNonMeshData = m_CC.m_config->GetAsBool("StripNonMesh", false, true);
const bool bCompactVertexStreams = m_CC.m_config->GetAsBool("CompactVertexStreams", false, true);
// Confetti: Nicholas Baldwin
const bool bOptimizePVRStripify = m_CC.m_config->GetAsInt("OptimizedPrimitiveType", 0, 0) == 1;
const bool bComputeSubsetTexelDensity = m_CC.m_config->GetAsBool("ComputeSubsetTexelDensity", false, true);
const bool bSplitLods = m_CC.m_config->GetAsBool("SplitLODs", false, true);
if (m_CC.m_config->GetAsBool("debugdump", false, true))
{
// Write a debug .dump file into the cache
DebugDumpCGF(sourceFile.c_str(), GetOutputPath());
}
class Listener
: public ILoaderCGFListener
{
public:
Listener()
: m_bLoadingErrors(false)
, m_bLoadingWarnings(false)
{
}
virtual void Warning(const char* format)
{
RCLogWarning("%s", format);
m_bLoadingWarnings = true;
}
virtual void Error(const char* format)
{
RCLogError("%s", format);
m_bLoadingErrors = true;
}
public:
bool m_bLoadingErrors;
bool m_bLoadingWarnings;
};
if (m_CC.m_pRC->GetVerbosityLevel() > 2)
{
RCLog("Loading CGF file %s", sourceFile.c_str());
}
Listener listener;
CChunkFile chunkFile;
CLoaderCGF cgfLoader;
CContentCGF* const pCGF = cgfLoader.LoadCGF(sourceFile, chunkFile, &listener);
if (m_CC.m_pRC->GetVerbosityLevel() > 2)
{
RCLog("Loaded CGF file %s", sourceFile.c_str());
}
if (!pCGF || listener.m_bLoadingErrors)
{
RCLogError("%s: Failed to load geometry file %s: %s", __FUNCTION__, sourceFile.c_str(), cgfLoader.GetLastError());
delete pCGF;
return false;
}
//////////////////////////////////////////////////////////////////////////
// Validate mesh, but abort compilation if validation fails
if (m_CC.m_config->GetAsBool("debugvalidate", false, true))
{
bool isValid = debugValidateCGF(pCGF, sourceFile.c_str());
if (!isValid)
{
delete pCGF;
return false;
}
}
//////////////////////////////////////////////////////////////////////////
const bool bConsole = !m_CC.m_pRC->GetPlatformInfo(m_CC.m_platform)->HasName("pc");
const bool bNeedEndianSwap = (m_CC.m_pRC->GetPlatformInfo(m_CC.m_platform)->bBigEndian != SYSTEM_IS_BIG_ENDIAN);
const bool bUseQuaternions = m_CC.m_config->GetAsBool("qtangents", false, true);
bool bStorePositionsAsF16;
{
const char* const optionName = "vertexPositionFormat";
const string s = m_CC.m_config->GetAsString(optionName, "f32", "f32");
if (StringHelpers::EqualsIgnoreCase(s, "f32"))
{
bStorePositionsAsF16 = false;
}
else if (StringHelpers::EqualsIgnoreCase(s, "f16"))
{
bStorePositionsAsF16 = true;
}
else if (StringHelpers::EqualsIgnoreCase(s, "exporter"))
{
bStorePositionsAsF16 = !pCGF->GetExportInfo()->bWantF32Vertices;
}
else
{
RCLogError("Unknown value of '%s': '%s'. Valid values are: 'f32', 'f16', 'exporter'.", optionName, s.c_str());
delete pCGF;
return false;
}
}
bool bStoreIndicesAsU16;
{
const char* const optionName = "vertexIndexFormat";
const string s = m_CC.m_config->GetAsString(optionName, "u32", "u32");
if (StringHelpers::EqualsIgnoreCase(s, "u32"))
{
bStoreIndicesAsU16 = false;
}
else if (StringHelpers::EqualsIgnoreCase(s, "u16"))
{
bStoreIndicesAsU16 = true;
}
else
{
RCLogError("Unknown value of '%s': '%s'. Valid values are: 'u32', 'u16'.", optionName, s.c_str());
delete pCGF;
return false;
}
}
// Delete Node and Mesh chunks from CGF chunk file.
{
if (m_CC.m_pRC->GetVerbosityLevel() > 2)
{
RCLog("Deleting old chunks");
}
DeleteOldChunks(pCGF, chunkFile);
}
CStaticObjectCompiler statCgfCompiler(bConsole, m_CC.m_pRC->GetVerbosityLevel());
statCgfCompiler.SetSplitLods(bSplitLods);
// Confetti: Nicholas Baldwin
statCgfCompiler.SetOptimizeStripify(bOptimizePVRStripify);
if (m_CC.m_pRC->GetVerbosityLevel() > 2)
{
RCLog("Making compilied CGF");
}
CContentCGF* const pCompiledCGF = statCgfCompiler.MakeCompiledCGF(pCGF, m_CC.m_bForceRecompiling);
if (!pCompiledCGF)
{
RCLogError(
"Failed to process geometry file %s. Try to re-export the file. If it not helps - contact an RC programmer.",
sourceFile.c_str());
delete pCGF;
return false;
}
// Check that we didn't have internal failures in data processing
{
if (m_CC.m_pRC->GetVerbosityLevel() > 2)
{
RCLog("Validating meshes");
}
const char* pErrorDescription = 0;
if (!pCompiledCGF->ValidateMeshes(&pErrorDescription))
{
RCLogError(
"Failed to process geometry in file %s (%s). Try to re-export the file. If it not helps - contact an RC programmer.",
sourceFile.c_str(),
pErrorDescription);
delete pCGF;
return false;
}
}
{
const SFileVersion& fv = m_CC.m_pRC->GetFileVersion();
pCompiledCGF->GetExportInfo()->rc_version[0] = fv.v[0];
pCompiledCGF->GetExportInfo()->rc_version[1] = fv.v[1];
pCompiledCGF->GetExportInfo()->rc_version[2] = fv.v[2];
pCompiledCGF->GetExportInfo()->rc_version[3] = fv.v[3];
StringHelpers::SafeCopyPadZeros(
pCompiledCGF->GetExportInfo()->rc_version_string,
sizeof(pCompiledCGF->GetExportInfo()->rc_version_string),
StringHelpers::Format(" RCVer:%d.%d ", fv.v[2], fv.v[1]).c_str());
}
// Write modified content to the output chunk file
{
CSaverCGF cgfSaver(chunkFile);
cgfSaver.SetMeshDataSaving(!bStripMeshData);
cgfSaver.SetNonMeshDataSaving(!bStripNonMeshData);
cgfSaver.SetSavePhysicsMeshes(!bStripNonMeshData);
cgfSaver.SetVertexStreamCompacting(bCompactVertexStreams);
cgfSaver.SetSubsetTexelDensityComputing(bComputeSubsetTexelDensity);
if (bStripNonMeshData)
{
// Start from a blank slate for stripped cgfs
chunkFile.Clear();
}
cgfSaver.SaveContent(pCompiledCGF, bNeedEndianSwap, bStorePositionsAsF16, bUseQuaternions, bStoreIndicesAsU16);
#if defined(AZ_PLATFORM_WINDOWS)
SetFileAttributes(outputFile, FILE_ATTRIBUTE_ARCHIVE);
#endif
if (!chunkFile.Write(outputFile))
{
RCLogError(
"Failed to process geometry file %s: %s. Try to re-export the file. If it not helps - contact an RC programmer.",
sourceFile.c_str(), chunkFile.GetLastError());
delete pCGF;
return false;
}
m_CC.m_pRC->AddInputOutputFilePair(m_CC.GetSourcePath(), GetOutputPath());
}
// Add base CGF product
static AZ::Data::AssetType meshAssetType("{C2869E3B-DDA0-4E01-8FE3-6770D788866B}"); // from MeshAsset.h
{
// Add our base job product.
// This accepts either the full path or the path relative to the Asset Processor temp directory, which is just the filename in this case
AssetBuilderSDK::JobProduct baseJobProduct(m_CC.m_sourceFileNameOnly.c_str(), meshAssetType, 0); // sub-id 0
// Add material product dependencies
{
CMaterialCGF* commonMaterial = pCGF->GetCommonMaterial();
if (commonMaterial != nullptr)
{
// Append .mtl to the material name
AZStd::string materialName = AZStd::string::format("%s.mtl", commonMaterial->name);
// Check if our material name is just a name, or if it contains a path
if (strstr(materialName.c_str(), "/") || strstr(materialName.c_str(), "\\"))
{
// The material path is already relative to devroot, for example "automatedtesting/materials/foo.mtl"
// We need to convert this to the cache path AP generates. In this case, cut off the game project name.
AZStd::string materialRelativePath = materialName;
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePath, gameFolder);
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathRelative, materialRelativePath, gameFolder.c_str());
baseJobProduct.m_pathDependencies.emplace(materialRelativePath, AssetBuilderSDK::ProductPathDependencyType::ProductFile);
}
else
{
// In this case, we just have the name of the material. The material is assumed to be in the same directory as the CGF.
// Now we need to generate that directory...
baseJobProduct.m_pathDependencies.emplace(GetDependencyAbsolutePath(materialName), AssetBuilderSDK::ProductPathDependencyType::SourceFile);
}
}
baseJobProduct.m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
}
response.m_outputProducts.push_back(baseJobProduct);
}
bool bHaveSplitLods = false;
if (bSplitLods)
{
std::vector<string> unusedFiles;
// Save split LODs
for (int lodIndex = 1; lodIndex < CStaticObjectCompiler::MAX_LOD_COUNT; ++lodIndex)
{
string lodFileName = PathHelpers::RemoveExtension(GetOutputFileNameOnly());
lodFileName += "_lod";
lodFileName += '0' + lodIndex;
lodFileName += '.';
lodFileName += PathHelpers::FindExtension(outputFile);
string lodFullPath = PathHelpers::Join(m_CC.GetOutputFolder(), lodFileName);
CContentCGF* const pLodCgf = statCgfCompiler.m_pLODs[lodIndex];
if (!pLodCgf)
{
if (FileUtil::FileExists(lodFullPath.c_str()))
{
unusedFiles.push_back(lodFullPath);
}
continue;
}
// Check that we didn't have internal failures in data processing
{
const char* pErrorDescription = 0;
if (!pLodCgf->ValidateMeshes(&pErrorDescription))
{
RCLogError(
"Failed to process geometry of LOD %i in file %s (%s). Try to re-export the file. If it not helps - contact an RC programmer.",
lodIndex,
sourceFile.c_str(),
pErrorDescription);
delete pCGF;
return false;
}
}
// Set pParent to 0 for every node (for details read big comment block
// above in the end of CStaticObjectCompiler::SplitLODs(), above AddNode()
// call).
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
pNode->pParent = 0;
}
// Save LOD content to the LOD chunk file
{
CChunkFile lodChunkFile;
CSaverCGF lodCgfSaver(lodChunkFile);
lodCgfSaver.SetMeshDataSaving(!bStripMeshData);
lodCgfSaver.SetNonMeshDataSaving(!bStripNonMeshData);
lodCgfSaver.SetSavePhysicsMeshes(!bStripNonMeshData);
lodCgfSaver.SetVertexStreamCompacting(bCompactVertexStreams);
lodCgfSaver.SetSubsetTexelDensityComputing(bComputeSubsetTexelDensity);
lodCgfSaver.SaveContent(pLodCgf, bNeedEndianSwap, bStorePositionsAsF16, bUseQuaternions, bStoreIndicesAsU16);
#if defined(AZ_PLATFORM_WINDOWS)
SetFileAttributes(lodFullPath, FILE_ATTRIBUTE_ARCHIVE);
#endif
lodChunkFile.Write(lodFullPath);
// Each LOD writes to a CGF which is an output product
static const AZ::Data::AssetType staticMeshLodsAssetType("{9AAE4926-CB6A-4C60-9948-A1A22F51DB23}");
AssetBuilderSDK::JobProduct lodJobProduct(lodFileName.c_str(), staticMeshLodsAssetType, lodIndex); // sub-id is the lod index
response.m_outputProducts.push_back(lodJobProduct);
response.m_outputProducts.at(0).m_pathDependencies.emplace(
GetDependencyAbsolutePath(lodFileName.c_str()),
AssetBuilderSDK::ProductPathDependencyType::ProductFile);
response.m_outputProducts.at(0).m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
m_CC.m_pRC->AddInputOutputFilePair(m_CC.GetSourcePath(), lodFullPath);
}
bHaveSplitLods = true;
}
for (size_t i = 0; i < unusedFiles.size(); ++i)
{
m_CC.m_pRC->MarkOutputFileForRemoval(unusedFiles[i]);
}
}
if (!UpToDateFileHelpers::SetMatchingFileTime(GetOutputPath(), m_CC.GetSourcePath()))
{
return false;
}
m_CC.m_pRC->AddInputOutputFilePair(m_CC.GetSourcePath(), GetOutputPath());
delete pCGF;
}
#if !defined(AZ_PLATFORM_LINUX) && !defined(AZ_PLATFORM_APPLE) // Exception handling not enabled on linux/mac builds
catch (char*)
{
RCLogError("Unexpected failure in processing %s to %s.", sourceFile.c_str(), outputFile.c_str());
return false;
}
#endif // !defined(AZ_PLATFORM_LINUX)
return true;
}
//////////////////////////////////////////////////////////////////////////
void CStatCGFCompiler::DeleteOldChunks(CContentCGF* pCGF, CChunkFile& chunkFile)
{
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode->nChunkId)
{
chunkFile.DeleteChunkById(pNode->nChunkId); // Delete chunk of node.
// Check if light node, for light nodes we not change light object.
if (pNode->nObjectChunkId && pNode->type != CNodeCGF::NODE_LIGHT)
{
chunkFile.DeleteChunkById(pNode->nObjectChunkId); // Delete chunk of mesh.
}
pNode->nObjectChunkId = 0;
}
}
chunkFile.DeleteChunksByType(ChunkType_ExportFlags);
chunkFile.DeleteChunksByType(ChunkType_MtlName);
chunkFile.DeleteChunksByType(ChunkType_Mesh);
chunkFile.DeleteChunksByType(ChunkType_MeshSubsets);
chunkFile.DeleteChunksByType(ChunkType_DataStream);
chunkFile.DeleteChunksByType(ChunkType_MeshPhysicsData);
chunkFile.DeleteChunksByType(ChunkType_BreakablePhysics);
chunkFile.DeleteChunksByType(ChunkType_FoliageInfo);
chunkFile.DeleteChunksByType(ChunkType_FaceMap);
chunkFile.DeleteChunksByType(ChunkType_VertAnim);
chunkFile.DeleteChunksByType(ChunkType_SceneProps);
}
//////////////////////////////////////////////////////////////////////////
bool CStatCGFCompiler::IsLodFile(const string& filename) const
{
string file = filename;
file.MakeLower();
const char* const s = strstr(file.c_str(), "_lod");
return (s && s[4] >= '0' && s[4] <= '9' && s[5] == '.');
}
//////////////////////////////////////////////////////////////////////////
bool CStatCGFCompiler::WriteResponse(const char* cacheFolder, AssetBuilderSDK::ProcessJobResponse& response, bool success) const
{
AZStd::string responseFilePath;
AzFramework::StringFunc::Path::ConstructFull(cacheFolder, AssetBuilderSDK::s_processJobResponseFileName, responseFilePath);
response.m_requiresSubIdGeneration = false;
response.m_resultCode = success ? AssetBuilderSDK::ProcessJobResult_Success : AssetBuilderSDK::ProcessJobResult_Failed;
bool result = AZ::Utils::SaveObjectToFile(responseFilePath, AZ::DataStream::StreamType::ST_XML, &response);
if (!result)
{
AZ_Error("CStatCGFCompiler", false, "Unable to save ProcessJobResponse file to %s.\n", responseFilePath.c_str());
}
return result && success;
}