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/ResourceCompilerScene/Common/MaterialExporter.cpp

371 lines
17 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.
*
*/
#include <Cry_Geo.h>
#include <IIndexedMesh.h>
#include <CGFContent.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <GFxFramework/MaterialIO/Material.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IMaterialRule.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMaterialData.h>
#include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneCore/Export/MtlMaterialExporter.h>
#include <RC/ResourceCompilerScene/Common/CommonExportContexts.h>
#include <RC/ResourceCompilerScene/Common/MaterialExporter.h>
#include <SceneAPI/SceneCore/Containers/RuleContainer.h>
namespace AZ
{
namespace RC
{
namespace SceneEvents = AZ::SceneAPI::Events;
namespace SceneDataTypes = AZ::SceneAPI::DataTypes;
namespace SceneContainers = AZ::SceneAPI::Containers;
namespace SceneViews = AZ::SceneAPI::Containers::Views;
MaterialExporter::MaterialExporter()
: SceneAPI::SceneCore::RCExportingComponent()
, m_cachedGroup(nullptr)
, m_exportMaterial(true)
{
m_physMaterialNames[PHYS_GEOM_TYPE_DEFAULT_PROXY] = GFxFramework::MaterialExport::g_stringPhysicsNoDraw;
BindToCall(&MaterialExporter::ConfigureContainer);
BindToCall(&MaterialExporter::ProcessNode);
BindToCall(&MaterialExporter::PatchMesh);
}
void MaterialExporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MaterialExporter, SceneAPI::SceneCore::RCExportingComponent>()->Version(1);
}
}
SceneEvents::ProcessingResult MaterialExporter::ConfigureContainer(ContainerExportContext& context)
{
switch (context.m_phase)
{
case Phase::Construction:
{
if (!context.m_group.GetRuleContainerConst().FindFirstByType<SceneDataTypes::IMaterialRule>())
{
m_exportMaterial = false;
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Skipping material processing due to material rule not being present.");
return SceneEvents::ProcessingResult::Ignored;
}
if (!LoadMaterialFile(context))
{
m_exportMaterial = false;
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Unable to read MTL file for processing meshes.");
return SceneEvents::ProcessingResult::Failure;
}
m_cachedGroup = &(context.m_group);
SetupGlobalMaterial(context);
return SceneEvents::ProcessingResult::Success;
}
case Phase::Finalizing:
if (!m_exportMaterial)
{
Reset();
return SceneEvents::ProcessingResult::Ignored;
}
PatchSubmeshes(context);
CreateSubMaterials(context);
Reset();
return SceneEvents::ProcessingResult::Success;
default:
return SceneEvents::ProcessingResult::Ignored;
}
}
SceneEvents::ProcessingResult MaterialExporter::ProcessNode(NodeExportContext& context)
{
if (context.m_phase == Phase::Filling && m_exportMaterial)
{
AssignCommonMaterial(context);
return SceneEvents::ProcessingResult::Success;
}
else
{
return SceneEvents::ProcessingResult::Ignored;
}
}
SceneEvents::ProcessingResult MaterialExporter::PatchMesh(MeshNodeExportContext& context)
{
if (context.m_phase == Phase::Filling && m_exportMaterial)
{
return PatchMaterials(context);
}
else
{
return SceneEvents::ProcessingResult::Ignored;
}
}
bool MaterialExporter::LoadMaterialFile(ContainerExportContext& context)
{
// Load the material from the source first. If there's no source material a temporary material should have been
// created in the cache by the MaterialExporterComponent in SceneCore.
m_materialGroup = AZStd::make_shared<GFxFramework::MaterialGroup>();
bool fileRead = false;
AZStd::string materialPath = context.m_scene.GetSourceFilename();
AzFramework::StringFunc::Path::ReplaceExtension(materialPath, GFxFramework::MaterialExport::g_mtlExtension);
AZ_TraceContext("Material source file path", materialPath);
//get if we need to upate materials in source folder
const AZ::SceneAPI::Containers::RuleContainer& rules = context.m_group.GetRuleContainerConst();
AZStd::shared_ptr<const SceneDataTypes::IMaterialRule> materialRule = rules.FindFirstByType<SceneDataTypes::IMaterialRule>();
bool updateMaterials = materialRule->UpdateMaterials();
//if the source material exist and we won't need to update material later then we load the material from source folder
if (AZ::IO::SystemFile::Exists(materialPath.c_str()) && !updateMaterials)
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Using source material file for linking to meshes.");
fileRead = m_materialGroup->ReadMtlFile(materialPath.c_str());
}
else
{
materialPath = SceneAPI::Utilities::FileUtilities::CreateOutputFileName(
context.m_scene.GetName(), context.m_outputDirectory, GFxFramework::MaterialExport::g_dccMaterialExtension);
AZ_TraceContext("Material cache file path", materialPath);
if (AZ::IO::SystemFile::Exists(materialPath.c_str()))
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Using cached material file for linking to meshes.");
fileRead = m_materialGroup->ReadMtlFile(materialPath.c_str());
}
}
if (!fileRead)
{
m_materialGroup.reset();
}
return fileRead;
}
void MaterialExporter::SetupGlobalMaterial(ContainerExportContext& context)
{
AZ_Assert(m_cachedGroup == &context.m_group, "ContainerExportContext doesn't belong to chain of previously called MeshGroupExportContext.");
CMaterialCGF* rootMaterial = context.m_container.GetCommonMaterial();
if (!rootMaterial)
{
rootMaterial = new CMaterialCGF();
rootMaterial->nPhysicalizeType = PHYS_GEOM_TYPE_NONE;
azstrcpy(rootMaterial->name, sizeof(rootMaterial->name), context.m_scene.GetName().c_str());
context.m_container.SetCommonMaterial(rootMaterial);
}
}
void MaterialExporter::AssignCommonMaterial(NodeExportContext& context)
{
AZ_Assert(m_cachedGroup == &context.m_group, "MeshNodeExportContext doesn't belong to chain of previously called MeshGroupExportContext.");
CMaterialCGF* rootMaterial = context.m_container.GetCommonMaterial();
AZ_Assert(rootMaterial, "Previously assigned root material has been deleted.");
context.m_node.pMaterial = rootMaterial;
}
SceneAPI::Events::ProcessingResult MaterialExporter::PatchMaterials(MeshNodeExportContext& context)
{
AZ_Assert(m_cachedGroup == &context.m_group, "MeshNodeExportContext doesn't belong to chain of previously\
called MeshGroupExportContext.");
AZStd::vector<size_t> relocationTable;
SceneEvents::ProcessingResult result = BuildRelocationTable(relocationTable, context);
if (result == SceneEvents::ProcessingResult::Failure)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Material mapping error, mesh generation failed. \
Change FBX Setting's \"Update Materials\" to true or modify the associated material file(.mtl) to fix the issue.");
return result;
}
if (relocationTable.empty())
{
// If the relocationTable is empty no materials were assigned to any of the
// selected meshes. In this case simply leave the subsets as assigned
// so users can later manually add materials if needed.
return SceneEvents::ProcessingResult::Ignored;
}
if (context.m_container.GetExportInfo()->bMergeAllNodes)
{
// Due to a bug which cases subsets to not merge correctly (see PatchSubmeshes for more details) use the global
// table so far to patch the subset index in the face info instead. This way they will be assigned to the
// eventual global subset stored in the first mesh.
int faceCount = context.m_mesh.GetFaceCount();
for (int i = 0; i < faceCount; ++i)
{
context.m_mesh.m_pFaces[i].nSubset = relocationTable[context.m_mesh.m_pFaces[i].nSubset];
}
}
else
{
for (SMeshSubset& subset : context.m_mesh.m_subsets)
{
subset.nMatID = relocationTable[subset.nMatID];
}
}
return SceneEvents::ProcessingResult::Success;
}
void MaterialExporter::PatchSubmeshes(ContainerExportContext& context)
{
// Due to a bug in the merging process of the Compiler it will always take the number of subsets of the first mesh
// it finds. This causes files with more materials than the first model to not merge properly and ultimately cause
// the entire export to fail. (See CGFNodeMerger::MergeNodes for more details.) The work-around for now is to fill
// the first mesh up with placeholder subsets and adjust the subset indices in the face info.
AZ_Assert(m_cachedGroup == &context.m_group, "ContainerExportContext doesn't belong to chain of previously called MeshGroupExportContext.");
if (context.m_container.GetExportInfo()->bMergeAllNodes)
{
CMesh* firstMesh = nullptr;
int nodeCount = context.m_container.GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
CNodeCGF* node = context.m_container.GetNode(i);
if (node->pMesh && !node->bPhysicsProxy && node->type == CNodeCGF::NODE_MESH)
{
firstMesh = node->pMesh;
break;
}
}
if (firstMesh)
{
int subsetCount = firstMesh->GetSubSetCount();
size_t materialCount = m_materialGroup->GetMaterialCount();
for (int i = 0; i < subsetCount; ++i)
{
AZ_Assert(firstMesh->m_subsets[i].nMatID == i, "Materials addition order broken. (%i vs. %i)", firstMesh->m_subsets[i].nMatID, i);
}
for (size_t i = subsetCount; i < materialCount; ++i)
{
SMeshSubset meshSubset;
meshSubset.nMatID = i;
firstMesh->m_subsets.push_back(meshSubset);
}
}
}
}
SceneAPI::Events::ProcessingResult MaterialExporter::BuildRelocationTable(AZStd::vector<size_t>& table, MeshNodeExportContext& context)
{
SceneEvents::ProcessingResultCombiner result;
auto physicalizeType = context.m_physicalizeType;
if ((physicalizeType == PHYS_GEOM_TYPE_DEFAULT_PROXY) || (physicalizeType == PHYS_GEOM_TYPE_NO_COLLIDE))
{
table.push_back(m_materialGroup->FindMaterialIndex(GFxFramework::MaterialExport::g_stringPhysicsNoDraw));
}
else
{
const SceneContainers::SceneGraph& graph = context.m_scene.GetGraph();
auto view = SceneViews::MakeSceneGraphChildView<SceneViews::AcceptEndPointsOnly>(
graph, context.m_nodeIndex, graph.GetContentStorage().begin(), true);
for (auto it = view.begin(); it != view.end(); ++it)
{
if ((*it) && (*it)->RTTI_IsTypeOf(SceneDataTypes::IMaterialData::TYPEINFO_Uuid()))
{
AZStd::string nodeName = graph.GetNodeName(graph.ConvertToNodeIndex(it.GetHierarchyIterator())).GetName();
size_t index = m_materialGroup->FindMaterialIndex(nodeName);
if (index == GFxFramework::MaterialExport::g_materialNotFound)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Unable to find material named %s in mtl file while building FBX to Lumberyard material index table.", nodeName.c_str());
result += SceneEvents::ProcessingResult::Failure;
}
table.push_back(index);
}
}
}
return result.GetResult();
}
void MaterialExporter::CreateSubMaterials(ContainerExportContext& context)
{
AZ_Assert(m_cachedGroup == &context.m_group, "MeshNodeExportContext doesn't belong to chain of previously called MeshGroupExportContext.");
CMaterialCGF* rootMaterial = context.m_container.GetCommonMaterial();
if (!rootMaterial)
{
AZ_Assert(rootMaterial, "Previously assigned root material has been deleted.");
return;
}
// Create sub-materials stored in root material. Sub-materials will be used to assign physical types
// to subsets stored in meshes when mesh gets compiled later on.
rootMaterial->subMaterials.resize(m_materialGroup->GetMaterialCount(), nullptr);
for (size_t i = 0; i < m_materialGroup->GetMaterialCount(); ++i)
{
CMaterialCGF* materialCGF = new CMaterialCGF();
AZStd::shared_ptr<const GFxFramework::IMaterial> material = m_materialGroup->GetMaterial(i);
if (material)
{
azstrncpy(materialCGF->name, sizeof(materialCGF->name), material->GetName().c_str(), sizeof(materialCGF->name));
int materialFlags = material->GetMaterialFlags();
//MTL_FLAG_NODRAW_TOUCHBENDING and MTL_FLAG_NODRAW are mutually exclusive.
const int errorMask = AZ::GFxFramework::EMaterialFlags::MTL_FLAG_NODRAW_TOUCHBENDING |
AZ::GFxFramework::EMaterialFlags::MTL_FLAG_NODRAW;
AZ_Assert((materialFlags & errorMask) != errorMask, "A physics material can not be NODRAW and NODRAW_TOUCHBENDING at the the same time.");
if (materialFlags & AZ::GFxFramework::EMaterialFlags::MTL_FLAG_NODRAW_TOUCHBENDING)
{
materialCGF->nPhysicalizeType = PHYS_GEOM_TYPE_NO_COLLIDE;
}
else if (materialFlags & AZ::GFxFramework::EMaterialFlags::MTL_FLAG_NODRAW)
{
materialCGF->nPhysicalizeType = PHYS_GEOM_TYPE_DEFAULT_PROXY;
}
else
{
materialCGF->nPhysicalizeType = PHYS_GEOM_TYPE_NONE;
}
rootMaterial->subMaterials[i] = materialCGF;
}
}
}
void MaterialExporter::Reset()
{
m_materialGroup = nullptr;
m_exportMaterial = true;
}
} // namespace RC
} // namespace AZ