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.
2190 lines
104 KiB
C++
2190 lines
104 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <Model/ModelAssetBuilderComponent.h>
|
|
#include <Model/MaterialAssetBuilderComponent.h>
|
|
#include <Model/MorphTargetExporter.h>
|
|
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
|
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Math/Aabb.h>
|
|
#include <AzCore/Math/Transform.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/Utils.h>
|
|
#include <AzCore/std/smart_ptr/make_shared.h>
|
|
|
|
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
|
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
|
|
#include <Atom/RPI.Reflect/Model/SkinMetaAssetCreator.h>
|
|
|
|
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
|
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
|
|
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
|
|
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
|
|
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphUpwardsIterator.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBlendShapeData.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/Rules/ICoordinateSystemRule.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/Rules/ISkinRule.h>
|
|
#include <SceneAPI/SceneCore/DataTypes/Rules/IClothRule.h>
|
|
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
|
|
#include <SceneAPI/SceneCore/Utilities/SceneGraphSelector.h>
|
|
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
|
|
#include <SceneAPI/SceneData/Groups/MeshGroup.h>
|
|
#include <SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h>
|
|
#include <SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.h>
|
|
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
|
|
|
|
/**
|
|
* DEBUG DEFINES!
|
|
* These are useful for debugging bad behavior from the builder.
|
|
* By default this builder wants to merge meshes as much as possible
|
|
* to cut down on the number of buffers it has to create. This is generally
|
|
* helpful for rendering but can make debugging difficult.
|
|
*
|
|
* If you experience artifacts from models built by this builder try
|
|
* commenting these out to disable certain merging features. This will
|
|
* produce a large volume of buffers for large models but it should be a lot
|
|
* easier to step through.
|
|
*/
|
|
#define AZ_RPI_MESHES_SHARE_COMMON_BUFFERS
|
|
|
|
namespace
|
|
{
|
|
const uint32_t IndicesPerFace = 3;
|
|
const AZ::RHI::Format IndicesFormat = AZ::RHI::Format::R32_UINT;
|
|
|
|
const uint32_t PositionFloatsPerVert = 3;
|
|
const uint32_t NormalFloatsPerVert = 3;
|
|
const uint32_t UVFloatsPerVert = 2;
|
|
const uint32_t ColorFloatsPerVert = 4;
|
|
const uint32_t TangentFloatsPerVert = 4; // The 4th channel is used to indicate handedness of the bitangent, either 1 or -1.
|
|
const uint32_t BitangentFloatsPerVert = 3;
|
|
|
|
const AZ::RHI::Format PositionFormat = AZ::RHI::Format::R32G32B32_FLOAT;
|
|
const AZ::RHI::Format NormalFormat = AZ::RHI::Format::R32G32B32_FLOAT;
|
|
const AZ::RHI::Format UVFormat = AZ::RHI::Format::R32G32_FLOAT;
|
|
const AZ::RHI::Format ColorFormat = AZ::RHI::Format::R32G32B32A32_FLOAT;
|
|
const AZ::RHI::Format TangentFormat = AZ::RHI::Format::R32G32B32A32_FLOAT; // The 4th channel is used to indicate handedness of the bitangent, either 1 or -1.
|
|
const AZ::RHI::Format BitangentFormat = AZ::RHI::Format::R32G32B32_FLOAT;
|
|
|
|
const char* ShaderSemanticName_SkinJointIndices = "SKIN_JOINTINDICES";
|
|
const char* ShaderSemanticName_SkinWeights = "SKIN_WEIGHTS";
|
|
const uint32_t DefaultSkinInfluencesPerVert = 4;
|
|
const AZ::RHI::Format SkinWeightFormat = AZ::RHI::Format::R32_FLOAT; // Single-component, 32-bit floating point per weight
|
|
|
|
// Morph targets
|
|
const char* ShaderSemanticName_MorphTargetDeltas = "MORPHTARGET_VERTEXDELTAS";
|
|
const AZ::RHI::Format MorphTargetVertexIndexFormat = AZ::RHI::Format::R32_UINT; // Single-component, 32-bit integer as vertex index
|
|
const char* ShaderSemanticName_MorphTargetPositionDeltas = "MORPHTARGET_POSITIONDELTAS";
|
|
const AZ::RHI::Format MorphTargetPositionDeltaFormat = AZ::RHI::Format::R16_UINT; // 16-bit integer per compressed position delta component
|
|
const char* ShaderSemanticName_MorphTargetNormalDeltas = "MORPHTARGET_NORMALDELTAS";
|
|
const AZ::RHI::Format MorphTargetNormalDeltaFormat = AZ::RHI::Format::R8_UINT; // 8-bit integer per compressed normal delta component
|
|
|
|
// Cloth data
|
|
const char* const ShaderSemanticName_ClothData = "CLOTH_DATA";
|
|
const uint32_t ClothDataFloatsPerVert = 4;
|
|
const AZ::RHI::Format ClothDataFormat = AZ::RHI::Format::R32G32B32A32_FLOAT;
|
|
}
|
|
|
|
namespace AZ
|
|
{
|
|
class Aabb;
|
|
|
|
namespace RPI
|
|
{
|
|
static const uint64_t s_invalidMaterialUid = 0;
|
|
|
|
void ModelAssetBuilderComponent::Reflect(ReflectContext* context)
|
|
{
|
|
if (auto* serialize = azrtti_cast<SerializeContext*>(context))
|
|
{
|
|
serialize->Class<ModelAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>()
|
|
->Version(30); // (updated to separate material slot ID from default material asset)
|
|
}
|
|
}
|
|
|
|
ModelAssetBuilderComponent::ModelAssetBuilderComponent()
|
|
: m_numSkinJointInfluencesPerVertex(DefaultSkinInfluencesPerVert)
|
|
{
|
|
BindToCall(&ModelAssetBuilderComponent::BuildModel);
|
|
}
|
|
|
|
//Supports a case-insensitive check for "lodN" or "lod_N" or "lod-N" or "lod:N" or "lod|N" or "lod#N" or "lod N" at the end of the name for the current node or an ancestor node.
|
|
//Returns -1 if no valid naming convention is found.
|
|
int GetLodIndexByNamingConvention(const char* name, size_t len)
|
|
{
|
|
//look for "lodN"
|
|
if (len >= 4)
|
|
{
|
|
const char* subStr = &name[len - 4];
|
|
if (azstrnicmp(subStr, "lod", 3) == 0)
|
|
{
|
|
const char lastLetter = name[len - 1];
|
|
if (AZStd::is_digit(lastLetter))
|
|
{
|
|
return static_cast<int>(lastLetter) - '0';
|
|
}
|
|
}
|
|
}
|
|
|
|
//look for "lod_N"
|
|
if (len >= 5)
|
|
{
|
|
const char* subStr = &name[len - 5];
|
|
if (azstrnicmp(subStr, "lod", 3) == 0)
|
|
{
|
|
if (strchr("_-:|# ", name[len - 2]))
|
|
{
|
|
const char lastLetter = name[len - 1];
|
|
if (AZStd::is_digit(lastLetter))
|
|
{
|
|
return static_cast<int>(lastLetter) - '0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
SceneAPI::Events::ProcessingResult ModelAssetBuilderComponent::BuildModel(ModelAssetBuilderContext& context)
|
|
{
|
|
{
|
|
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId("ResourcePools/DefaultVertexBufferPool.resourcepool", 0);
|
|
if (!assetIdOutcome.IsSuccess())
|
|
{
|
|
return SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
m_systemInputAssemblyBufferPoolId = assetIdOutcome.GetValue();
|
|
}
|
|
|
|
m_createdSubId.clear();
|
|
|
|
m_modelName = context.m_group.GetName();
|
|
|
|
const auto& scene = context.m_scene;
|
|
const auto& sceneGraph = scene.GetGraph();
|
|
|
|
m_sourceUuid = scene.GetSourceGuid();
|
|
|
|
auto names = sceneGraph.GetNameStorage();
|
|
auto content = sceneGraph.GetContentStorage();
|
|
|
|
// Create a downwards, breadth-first view into the scene
|
|
auto pairView = AZ::SceneAPI::Containers::Views::MakePairView(names, content);
|
|
auto view = AZ::SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<
|
|
AZ::SceneAPI::Containers::Views::BreadthFirst>(
|
|
sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true);
|
|
|
|
AZStd::vector<SourceMeshContentList> sourceMeshContentListsByLod;
|
|
|
|
AZStd::shared_ptr<const SceneAPI::DataTypes::ILodRule> lodRule = context.m_group.GetRuleContainerConst().FindFirstByType<SceneAPI::DataTypes::ILodRule>();
|
|
AZStd::vector<AZStd::vector<AZStd::string>> selectedMeshPathsByLod;
|
|
|
|
// The Atom Model builder uses the optimized versions of meshes that are
|
|
// placed in the SceneGraph during its generation phase. Users select
|
|
// meshes based on their original name, and the mesh optimizer adds the
|
|
// suffix "_optimized" to these mesh nodes in the scene graph. To target
|
|
// these nodes, first filter for the non-optimized mesh nodes, then remap
|
|
// from the non-optimized one to the optimized one. This callable is used
|
|
// to filter for mesh nodes that are not the optimized ones.
|
|
const auto isNonOptimizedMesh = [](const SceneAPI::Containers::SceneGraph& graph, SceneAPI::Containers::SceneGraph::NodeIndex& index)
|
|
{
|
|
return SceneAPI::Utilities::SceneGraphSelector::IsMesh(graph, index) &&
|
|
!AZStd::string_view{graph.GetNodeName(index).GetName(), graph.GetNodeName(index).GetNameLength()}.ends_with(SceneAPI::Utilities::OptimizedMeshSuffix);
|
|
};
|
|
|
|
if (lodRule)
|
|
{
|
|
selectedMeshPathsByLod.resize(lodRule->GetLodCount());
|
|
for (size_t lod = 0; lod < lodRule->GetLodCount(); ++lod)
|
|
{
|
|
selectedMeshPathsByLod[lod] = SceneAPI::Utilities::SceneGraphSelector::GenerateTargetNodes(sceneGraph,
|
|
lodRule->GetSceneNodeSelectionList(lod), isNonOptimizedMesh, SceneAPI::Utilities::SceneGraphSelector::RemapToOptimizedMesh);
|
|
}
|
|
}
|
|
|
|
// Gather the list of nodes in the graph that are selected as part of this
|
|
// MeshGroup defined in context.m_group, then remap to the optimized mesh
|
|
// nodes, if they exist.
|
|
AZStd::vector<AZStd::string> selectedMeshPaths = SceneAPI::Utilities::SceneGraphSelector::GenerateTargetNodes(sceneGraph,
|
|
context.m_group.GetSceneNodeSelectionList(), isNonOptimizedMesh, SceneAPI::Utilities::SceneGraphSelector::RemapToOptimizedMesh);
|
|
|
|
// Iterate over the downwards, breadth-first view into the scene.
|
|
// First we have to split the source mesh data up by lod.
|
|
for (const auto& viewIt : view)
|
|
{
|
|
if (viewIt.second != nullptr &&
|
|
azrtti_istypeof<MeshData>(viewIt.second.get()))
|
|
{
|
|
const AZStd::string meshPath(viewIt.first.GetPath(), viewIt.first.GetPathLength());
|
|
const AZStd::string meshName(viewIt.first.GetName(), viewIt.first.GetNameLength());
|
|
|
|
uint32_t lodIndex = 0; // Default to the 0th LOD if nothing is found
|
|
if (lodRule)
|
|
{
|
|
// The LodRule contains the objects for Lod1 through LodN. Objects at Lod0 are not include in the LodRule
|
|
for (size_t lod = 0; lod < selectedMeshPathsByLod.size(); ++lod)
|
|
{
|
|
AZStd::vector<AZStd::string>& paths = selectedMeshPathsByLod[lod];
|
|
const auto it = AZStd::find(paths.begin(), paths.end(), meshPath);
|
|
if (it != paths.end())
|
|
{
|
|
lodIndex = aznumeric_cast<uint32_t>(lod + 1);
|
|
break;
|
|
}
|
|
}
|
|
if (lodIndex == 0)
|
|
{
|
|
// Object was not found in the LodRule, but we still need to see if it was in the selection list
|
|
const auto selectedMeshPathsIt = AZStd::find(selectedMeshPaths.begin(), selectedMeshPaths.end(), meshPath);
|
|
if(selectedMeshPathsIt == selectedMeshPaths.end())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Skip the mesh if it's not in the MeshGroup's selected mesh list
|
|
const auto selectedMeshPathsIt = AZStd::find(selectedMeshPaths.begin(), selectedMeshPaths.end(), meshPath);
|
|
if(selectedMeshPathsIt == selectedMeshPaths.end())
|
|
{
|
|
continue;
|
|
}
|
|
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Using mesh '%s'", meshPath.c_str());
|
|
|
|
// Select the Lod that this mesh is part of
|
|
{
|
|
int lodIndexFromName = GetLodIndexByNamingConvention(meshName.c_str(), meshName.size());
|
|
if (lodIndexFromName >= 0)
|
|
{
|
|
lodIndex = aznumeric_cast<uint32_t>(lodIndexFromName);
|
|
}
|
|
else
|
|
{
|
|
// If the mesh node's name doesn't have the LOD identifier in it lets walk the parent hierarchy
|
|
// The first parent node that has the LOD identifier is the LOD this mesh will be a part of
|
|
SceneAPI::Containers::SceneGraph::NodeIndex meshNodeIndex = sceneGraph.Find(meshPath);
|
|
|
|
SceneAPI::Containers::SceneGraph::NodeIndex parentNodeIndex = sceneGraph.GetNodeParent(meshNodeIndex);
|
|
while (parentNodeIndex != sceneGraph.GetRoot())
|
|
{
|
|
const SceneAPI::Containers::SceneGraph::Name& parentNodeName = sceneGraph.GetNodeName(parentNodeIndex);
|
|
|
|
lodIndexFromName = GetLodIndexByNamingConvention(parentNodeName.GetName(), parentNodeName.GetNameLength());
|
|
if (lodIndexFromName >= 0)
|
|
{
|
|
lodIndex = aznumeric_cast<uint32_t>(lodIndexFromName);
|
|
break;
|
|
}
|
|
|
|
parentNodeIndex = sceneGraph.GetNodeParent(parentNodeIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find which LodAssetBuilder we need to add this mesh to
|
|
// If the lod is new we need to create and begin a new builder
|
|
if (lodIndex + 1 >= sourceMeshContentListsByLod.size())
|
|
{
|
|
sourceMeshContentListsByLod.resize(lodIndex + 1);
|
|
}
|
|
|
|
SourceMeshContentList& sourceMeshContentList = sourceMeshContentListsByLod[lodIndex];
|
|
|
|
// Gather mesh content
|
|
SourceMeshContent sourceMesh;
|
|
|
|
// Although the nodes used to gather mesh content are the optimized ones (when found), to make
|
|
// this process transparent for the end-asset generated, the name assigned to the source mesh
|
|
// content will not include the "_optimized" prefix.
|
|
AZStd::string_view sourceMeshName = meshName;
|
|
if (sourceMeshName.ends_with(SceneAPI::Utilities::OptimizedMeshSuffix))
|
|
{
|
|
sourceMeshName.remove_suffix(SceneAPI::Utilities::OptimizedMeshSuffix.size());
|
|
}
|
|
sourceMesh.m_name = sourceMeshName;
|
|
|
|
const auto node = sceneGraph.Find(meshPath);
|
|
sourceMesh.m_worldTransform = AZ::SceneAPI::Utilities::DetermineWorldTransform(scene, node, context.m_group.GetRuleContainerConst());
|
|
|
|
auto sibling = sceneGraph.GetNodeChild(node);
|
|
|
|
AddToMeshContent(viewIt.second, sourceMesh);
|
|
|
|
bool traversing = true;
|
|
while (traversing)
|
|
{
|
|
if (sibling.IsValid())
|
|
{
|
|
auto siblingContent = sceneGraph.GetNodeContent(sibling);
|
|
|
|
AddToMeshContent(siblingContent, sourceMesh);
|
|
|
|
sibling = sceneGraph.GetNodeSibling(sibling);
|
|
}
|
|
else
|
|
{
|
|
traversing = false;
|
|
}
|
|
}
|
|
|
|
sourceMesh.m_isMorphed = GetIsMorphed(sceneGraph, node);
|
|
|
|
// Get the cloth data (only for full mesh LOD 0).
|
|
sourceMesh.m_meshClothData = (lodIndex == 0)
|
|
? SceneAPI::DataTypes::IClothRule::FindClothData(
|
|
sceneGraph, node, sourceMesh.m_meshData->GetVertexCount(), context.m_group.GetRuleContainerConst())
|
|
: AZStd::vector<AZ::Color>{};
|
|
|
|
// We've traversed this node and all its children that hold
|
|
// relevant data We can move it into the list of content for this lod
|
|
sourceMeshContentList.emplace_back(AZStd::move(sourceMesh));
|
|
}
|
|
}
|
|
|
|
// Then in each Lod we need to group all faces by material id.
|
|
// All sub meshes with the same material id get merged
|
|
AZStd::vector<Data::Asset<ModelLodAsset>> lodAssets;
|
|
lodAssets.resize(sourceMeshContentListsByLod.size());
|
|
|
|
// Joint name to joint index map used for the skinning influences.
|
|
AZStd::unordered_map<AZStd::string, uint16_t> jointNameToIndexMap;
|
|
|
|
AZStd::string modelAssetName = GetAssetFullName(ModelAsset::TYPEINFO_Uuid());
|
|
const AZ::Data::AssetId modelAssetId = CreateAssetId(modelAssetName);
|
|
|
|
MorphTargetMetaAssetCreator morphTargetMetaCreator;
|
|
morphTargetMetaCreator.Begin(MorphTargetMetaAsset::ConstructAssetId(modelAssetId, modelAssetName));
|
|
|
|
ModelAssetCreator modelAssetCreator;
|
|
modelAssetCreator.Begin(modelAssetId);
|
|
|
|
uint32_t lodIndex = 0;
|
|
for (const SourceMeshContentList& sourceMeshContentList : sourceMeshContentListsByLod)
|
|
{
|
|
ModelLodAssetCreator lodAssetCreator;
|
|
m_lodName = AZStd::string::format("lod%d", lodIndex);
|
|
AZStd::string lodAssetName = GetAssetFullName(ModelLodAsset::TYPEINFO_Uuid());
|
|
lodAssetCreator.Begin(CreateAssetId(lodAssetName));
|
|
|
|
{
|
|
ProductMeshContentList lodMeshes = SourceMeshListToProductMeshList(context, sourceMeshContentList, jointNameToIndexMap, morphTargetMetaCreator);
|
|
|
|
PadVerticesForSkinning(lodMeshes);
|
|
|
|
// By default, we merge meshes that share the same material
|
|
bool canMergeMeshes = true;
|
|
|
|
AZStd::shared_ptr<const SceneAPI::SceneData::StaticMeshAdvancedRule> staticMeshAdvancedRule = context.m_group.GetRuleContainerConst().FindFirstByType<SceneAPI::SceneData::StaticMeshAdvancedRule>();
|
|
if (staticMeshAdvancedRule && !staticMeshAdvancedRule->MergeMeshes())
|
|
{
|
|
// If the merge meshes option is disabled in the advanced mesh rule, don't merge meshes
|
|
canMergeMeshes = false;
|
|
}
|
|
else
|
|
{
|
|
for (const SourceMeshContent& sourceMesh : sourceMeshContentList)
|
|
{
|
|
if (sourceMesh.m_isMorphed)
|
|
{
|
|
// Merging meshes shuffles around the order of the vertices, but morph targets rely on having an index that tell them which vertices to morph
|
|
// We do not merge morphed meshes so that this index is preserved and correct.
|
|
// If we keep track of the ordering changes in MergeMeshesByMaterialUid and then re-mapped the MORPHTARGET_VERTEXINDICES buffer
|
|
// we could potentially enable merging meshes that are morphed. But for now, disable merging.
|
|
canMergeMeshes = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (canMergeMeshes)
|
|
{
|
|
lodMeshes = MergeMeshesByMaterialUid(lodMeshes);
|
|
}
|
|
|
|
#if defined(AZ_RPI_MESHES_SHARE_COMMON_BUFFERS)
|
|
// We shouldn't need a mesh name for the buffer names since meshed are sharing common buffers
|
|
m_meshName = "";
|
|
ProductMeshViewList lodMeshViews;
|
|
|
|
ProductMeshContent mergedMesh;
|
|
MergeMeshesToCommonBuffers(lodMeshes, mergedMesh, lodMeshViews);
|
|
|
|
BufferAssetView indexBuffer;
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo> streamBuffers;
|
|
|
|
if (!CreateModelLodBuffers(mergedMesh, indexBuffer, streamBuffers, lodAssetCreator))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
|
|
for (const ProductMeshView& meshView : lodMeshViews)
|
|
{
|
|
if (!CreateMesh(meshView, indexBuffer, streamBuffers, modelAssetCreator, lodAssetCreator, context.m_materialsByUid))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
}
|
|
#else
|
|
uint32_t meshIndex = 0;
|
|
for (const ProductMeshContent& mesh : lodMeshes)
|
|
{
|
|
const ProductMeshView meshView = CreateViewToEntireMesh(mesh);
|
|
|
|
BufferAssetView indexBuffer;
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo> streamBuffers;
|
|
|
|
// Mesh name in ProductMeshContent could be duplicated so generate unique mesh name using index
|
|
m_meshName = AZStd::string::format("mesh%d", meshIndex++);
|
|
|
|
if (!CreateModelLodBuffers(mesh, indexBuffer, streamBuffers, lodAssetCreator))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
|
|
if (!CreateMesh(meshView, indexBuffer, streamBuffers, lodAssetCreator, context.m_materialsByUid))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (!lodAssetCreator.End(lodAssets[lodIndex]))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
lodAssets[lodIndex].SetHint(lodAssetName); // name will be used for file name when export asset
|
|
|
|
lodIndex++;
|
|
}
|
|
sourceMeshContentListsByLod.clear();
|
|
|
|
// Finalize all LOD assets
|
|
for (auto& lodAsset : lodAssets)
|
|
{
|
|
modelAssetCreator.AddLodAsset(AZStd::move(lodAsset));
|
|
}
|
|
|
|
// Finalize the model
|
|
if (!modelAssetCreator.End(context.m_outputModelAsset))
|
|
{
|
|
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
|
}
|
|
|
|
// Fill the skin meta asset
|
|
if (!jointNameToIndexMap.empty())
|
|
{
|
|
SkinMetaAssetCreator skinCreator;
|
|
skinCreator.Begin(SkinMetaAsset::ConstructAssetId(modelAssetId, modelAssetName));
|
|
|
|
skinCreator.SetJointNameToIndexMap(jointNameToIndexMap);
|
|
|
|
if (!skinCreator.End(context.m_outputSkinMetaAsset))
|
|
{
|
|
AZ_Warning(s_builderName, false, "Cannot create skin meta asset. Skinning influences won't be automatically relinked.");
|
|
}
|
|
}
|
|
|
|
// Fill the morph target meta asset
|
|
if (!morphTargetMetaCreator.IsEmpty())
|
|
{
|
|
if (!morphTargetMetaCreator.End(context.m_outputMorphTargetMetaAsset))
|
|
{
|
|
AZ_Warning(s_builderName, false, "Cannot create morph target meta asset for model asset '%s'.", modelAssetName.c_str());
|
|
}
|
|
}
|
|
|
|
context.m_outputModelAsset.SetHint(modelAssetName);
|
|
return AZ::SceneAPI::Events::ProcessingResult::Success;
|
|
}
|
|
|
|
void ModelAssetBuilderComponent::AddToMeshContent(
|
|
const AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IGraphObject>& data,
|
|
SourceMeshContent& content)
|
|
{
|
|
if (azrtti_istypeof<MeshData>(data.get()))
|
|
{
|
|
auto meshData = AZStd::static_pointer_cast<const MeshData>(data);
|
|
content.m_meshData = meshData;
|
|
}
|
|
else if (azrtti_istypeof<UVData>(data.get()))
|
|
{
|
|
auto uvData = AZStd::static_pointer_cast<const UVData>(data);
|
|
content.m_meshUVData.push_back(uvData);
|
|
}
|
|
else if (azrtti_istypeof<ColorData>(data.get()))
|
|
{
|
|
auto colorData = AZStd::static_pointer_cast<const ColorData>(data);
|
|
content.m_meshColorData.push_back(colorData);
|
|
}
|
|
else if (azrtti_istypeof<TangentData>(data.get()))
|
|
{
|
|
auto tangentData = AZStd::static_pointer_cast<const TangentData>(data);
|
|
if (!content.m_meshTangents)
|
|
{
|
|
content.m_meshTangents = tangentData;
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning(s_builderName, false,
|
|
"Found multiple tangent data sets for mesh '%s'. Only the first will be used.",
|
|
content.m_name.GetCStr());
|
|
}
|
|
}
|
|
else if (azrtti_istypeof<BitangentData>(data.get()))
|
|
{
|
|
auto bitangentData = AZStd::static_pointer_cast<const BitangentData>(data);
|
|
if (!content.m_meshBitangents)
|
|
{
|
|
content.m_meshBitangents = bitangentData;
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning(s_builderName, false,
|
|
"Found multiple bitangent data sets for mesh '%s'. Only the first will be used.",
|
|
content.m_name.GetCStr());
|
|
}
|
|
}
|
|
else if (azrtti_istypeof<MaterialData>(data.get()))
|
|
{
|
|
auto materialData = AZStd::static_pointer_cast<const MaterialData>(data);
|
|
content.m_materials.push_back(materialData->GetUniqueId());
|
|
}
|
|
else if (azrtti_istypeof<SkinData>(data.get()))
|
|
{
|
|
content.m_skinData.emplace_back(data, static_cast<const SkinData*>(data.get()));
|
|
}
|
|
}
|
|
|
|
ModelAssetBuilderComponent::ProductMeshContentList ModelAssetBuilderComponent::SourceMeshListToProductMeshList(
|
|
const ModelAssetBuilderContext& context,
|
|
const SourceMeshContentList& sourceMeshList,
|
|
AZStd::unordered_map<AZStd::string, uint16_t>& jointNameToIndexMap,
|
|
MorphTargetMetaAssetCreator& morphTargetMetaCreator)
|
|
{
|
|
ProductMeshContentList productMeshList;
|
|
|
|
using Face = SceneAPI::DataTypes::IMeshData::Face;
|
|
using FaceList = AZStd::vector<Face>;
|
|
struct UidFaceList
|
|
{
|
|
MaterialUid m_materialUid;
|
|
FaceList m_faceList;
|
|
};
|
|
using FacesByMaterialUid = AZStd::vector<UidFaceList>;
|
|
using ProductList = AZStd::vector<FacesByMaterialUid>;
|
|
|
|
ProductList productList;
|
|
productList.resize(sourceMeshList.size());
|
|
|
|
AZStd::vector<SceneAPI::DataTypes::MatrixType> meshTransforms;
|
|
meshTransforms.reserve(sourceMeshList.size());
|
|
|
|
size_t productMeshCount = 0;
|
|
|
|
MorphTargetExporter morphTargetExporter;
|
|
|
|
// Break up source data by material uid. We don't do any merging at this point,
|
|
// and we don't sort by material id at this point so that the resulting vertex data
|
|
// will have a 1-1 relationship with the source data. This ensures morph target indices
|
|
// don't need to be re-mapped, as long as the meshes aren't merged later
|
|
// We just can't output a mesh that has faces with multiple materials.
|
|
for (size_t i = 0; i < sourceMeshList.size(); ++i)
|
|
{
|
|
const SourceMeshContent& sourceMeshContent = sourceMeshList[i];
|
|
FacesByMaterialUid& productsByMaterialUid = productList[i];
|
|
|
|
meshTransforms.push_back(sourceMeshContent.m_worldTransform);
|
|
|
|
const auto& meshData = sourceMeshContent.m_meshData;
|
|
|
|
const uint32_t faceCount = meshData->GetFaceCount();
|
|
|
|
MaterialUid currentMaterialId = std::numeric_limits<MaterialUid>::max();
|
|
for (uint32_t j = 0; j < faceCount; ++j)
|
|
{
|
|
const Face& faceInfo = meshData->GetFaceInfo(j);
|
|
const MaterialUid matUid = sourceMeshContent.GetMaterialUniqueId(meshData->GetFaceMaterialId(j));
|
|
|
|
// Start a new product mesh if the material changed
|
|
if (currentMaterialId != matUid)
|
|
{
|
|
UidFaceList uidFaceList;
|
|
uidFaceList.m_materialUid = matUid;
|
|
productsByMaterialUid.push_back(uidFaceList);
|
|
currentMaterialId = matUid;
|
|
}
|
|
|
|
// Add the faceinfo to the current product mesh
|
|
UidFaceList& currentFaceList = productsByMaterialUid.back();
|
|
currentFaceList.m_faceList.push_back(faceInfo);
|
|
}
|
|
|
|
productMeshCount += productsByMaterialUid.size();
|
|
}
|
|
productMeshList.reserve(productMeshCount);
|
|
|
|
// Get the skin rule
|
|
if (const auto* skinRule = context.m_group.GetRuleContainerConst().FindFirstByType<SceneAPI::DataTypes::ISkinRule>().get())
|
|
{
|
|
m_numSkinJointInfluencesPerVertex = skinRule->GetMaxWeightsPerVertex();
|
|
m_skinWeightThreshold = skinRule->GetWeightThreshold();
|
|
}
|
|
|
|
uint32_t totalVertexCount = 0;
|
|
for (size_t i = 0; i < productList.size(); ++i)
|
|
{
|
|
const FacesByMaterialUid& productsByMaterialUid = productList[i];
|
|
const SceneAPI::DataTypes::MatrixType& meshTransform = meshTransforms[i];
|
|
|
|
const SceneAPI::DataTypes::MatrixType inverseTranspose = meshTransform.GetInverseFull().GetTranspose();
|
|
|
|
const SourceMeshContent& sourceMesh = sourceMeshList[i];
|
|
|
|
const auto& meshData = sourceMesh.m_meshData;
|
|
const auto& uvContentCollection = sourceMesh.m_meshUVData;
|
|
const size_t uvSetCount = uvContentCollection.size();
|
|
const auto& colorContentCollection = sourceMesh.m_meshColorData;
|
|
const size_t colorSetCount = colorContentCollection.size();
|
|
bool processedMorphTargets = false;
|
|
bool warnedExcessOfSkinInfluences = false;
|
|
|
|
for (const auto& it : productsByMaterialUid)
|
|
{
|
|
ProductMeshContent productMesh;
|
|
productMesh.m_name = sourceMesh.m_name;
|
|
|
|
productMesh.m_materialUid = it.m_materialUid;
|
|
|
|
const FaceList& faceInfoList = it.m_faceList;
|
|
uint32_t indexCount = static_cast<uint32_t>(faceInfoList.size()) * 3;
|
|
|
|
productMesh.m_indices.reserve(indexCount);
|
|
|
|
for (const Face& faceInfo : faceInfoList)
|
|
{
|
|
productMesh.m_indices.push_back(faceInfo.vertexIndex[0]);
|
|
productMesh.m_indices.push_back(faceInfo.vertexIndex[1]);
|
|
productMesh.m_indices.push_back(faceInfo.vertexIndex[2]);
|
|
}
|
|
|
|
// We need to both gather a collection of unique
|
|
// indices so that we don't gather duplicate vertex data
|
|
// while also correcting the collection of indices
|
|
// that we have so that they start at 0 and are contiguous.
|
|
AZStd::map<uint32_t, uint32_t> oldToNewIndices;
|
|
uint32_t newIndex = 0;
|
|
for (uint32_t& index : productMesh.m_indices)
|
|
{
|
|
if (oldToNewIndices.find(index) == oldToNewIndices.end())
|
|
{
|
|
oldToNewIndices[index] = newIndex;
|
|
newIndex++;
|
|
}
|
|
|
|
index = oldToNewIndices[index];
|
|
}
|
|
|
|
AZStd::vector<float>& positions = productMesh.m_positions;
|
|
AZStd::vector<float>& normals = productMesh.m_normals;
|
|
AZStd::vector<float>& tangents = productMesh.m_tangents;
|
|
AZStd::vector<float>& bitangents = productMesh.m_bitangents;
|
|
AZStd::vector<AZStd::vector<float>>& uvSets = productMesh.m_uvSets;
|
|
AZStd::vector<AZ::Name>& uvNames = productMesh.m_uvCustomNames;
|
|
AZStd::vector<AZStd::vector<float>>& colorSets = productMesh.m_colorSets;
|
|
AZStd::vector<AZ::Name>& colorNames = productMesh.m_colorCustomNames;
|
|
AZStd::vector<float>& clothData = productMesh.m_clothData;
|
|
|
|
const size_t vertexCount = oldToNewIndices.size();
|
|
positions.reserve(vertexCount * PositionFloatsPerVert);
|
|
normals.reserve(vertexCount * NormalFloatsPerVert);
|
|
|
|
if (sourceMesh.m_meshTangents)
|
|
{
|
|
tangents.reserve(vertexCount * TangentFloatsPerVert);
|
|
|
|
if (sourceMesh.m_meshBitangents)
|
|
{
|
|
bitangents.reserve(vertexCount * BitangentFloatsPerVert);
|
|
}
|
|
}
|
|
|
|
uvNames.reserve(uvSetCount);
|
|
for (auto& uvContent : uvContentCollection)
|
|
{
|
|
uvNames.push_back(uvContent->GetCustomName());
|
|
}
|
|
|
|
uvSets.resize(uvSetCount);
|
|
for (auto& uvSet : uvSets)
|
|
{
|
|
uvSet.reserve(vertexCount * UVFloatsPerVert);
|
|
}
|
|
|
|
colorNames.reserve(colorSetCount);
|
|
for (auto& colorContent : colorContentCollection)
|
|
{
|
|
colorNames.push_back(colorContent->GetCustomName());
|
|
}
|
|
|
|
colorSets.resize(colorSetCount);
|
|
for (auto& colorSet : colorSets)
|
|
{
|
|
colorSet.reserve(vertexCount * ColorFloatsPerVert);
|
|
}
|
|
|
|
const bool hasClothData = !sourceMesh.m_meshClothData.empty();
|
|
if (hasClothData)
|
|
{
|
|
AZ_Assert(sourceMesh.m_meshClothData.size() == vertexCount,
|
|
"Vertex Count %d does not match mesh cloth data size %d", vertexCount, sourceMesh.m_meshClothData.size());
|
|
clothData.reserve(vertexCount * ClothDataFloatsPerVert);
|
|
}
|
|
|
|
const bool hasSkinData = !sourceMesh.m_skinData.empty();
|
|
if (hasSkinData)
|
|
{
|
|
// Skinned meshes require that positions, normals, tangents, bitangents, all exist and have the same number
|
|
// of total elements. Pad buffers with missing data to make them align with positions and normals
|
|
if (!sourceMesh.m_meshTangents)
|
|
{
|
|
tangents.resize(vertexCount * TangentFloatsPerVert, 1.0f);
|
|
AZ_Warning(s_builderName, false, "Mesh '%s' is missing tangents and no defaults were generated. Skinned meshes require tangents. Dummy tangents will be inserted, which may result in rendering artifacts.", sourceMesh.m_name.GetCStr());
|
|
}
|
|
if (!sourceMesh.m_meshBitangents)
|
|
{
|
|
bitangents.resize(vertexCount * BitangentFloatsPerVert, 1.0f);
|
|
AZ_Warning(s_builderName, false, "Mesh '%s' is missing bitangents and no defaults were generated. Skinned meshes require bitangents. Dummy bitangents will be inserted, which may result in rendering artifacts.", sourceMesh.m_name.GetCStr());
|
|
}
|
|
}
|
|
|
|
for (const auto& itr : oldToNewIndices)
|
|
{
|
|
// We use the 'old' index as that properly indexes
|
|
// into the old mesh data. The 'new' index is used for properly
|
|
// indexing into this new collection that we're building here.
|
|
const uint32_t oldIndex = itr.first;
|
|
|
|
AZ::Vector3 pos = meshData->GetPosition(oldIndex);
|
|
AZ::Vector3 normal = meshData->GetNormal(oldIndex);
|
|
|
|
// Pre-multiply transform
|
|
pos = meshTransform * pos;
|
|
|
|
pos = context.m_coordSysConverter.ConvertVector3(pos);
|
|
|
|
positions.push_back(pos.GetX());
|
|
positions.push_back(pos.GetY());
|
|
positions.push_back(pos.GetZ());
|
|
|
|
// Multiply normal by inverse transpose to avoid
|
|
// incorrect values produced by non-uniformly scaled
|
|
// transforms.
|
|
normal = inverseTranspose.TransformVector(normal);
|
|
normal = context.m_coordSysConverter.ConvertVector3(normal);
|
|
normal.Normalize();
|
|
|
|
normals.push_back(normal.GetX());
|
|
normals.push_back(normal.GetY());
|
|
normals.push_back(normal.GetZ());
|
|
|
|
if (sourceMesh.m_meshTangents)
|
|
{
|
|
AZ::Vector4 tangentWithW = sourceMesh.m_meshTangents->GetTangent(oldIndex);
|
|
AZ::Vector3 tangent = tangentWithW.GetAsVector3();
|
|
float bitangentSign = tangentWithW.GetW();
|
|
|
|
tangent = meshTransform.TransformVector(tangent);
|
|
tangent = context.m_coordSysConverter.ConvertVector3(tangent);
|
|
tangent.Normalize();
|
|
|
|
tangents.push_back(tangent.GetX());
|
|
tangents.push_back(tangent.GetY());
|
|
tangents.push_back(tangent.GetZ());
|
|
tangents.push_back(bitangentSign);
|
|
|
|
if (sourceMesh.m_meshBitangents)
|
|
{
|
|
AZ::Vector3 bitangent = sourceMesh.m_meshBitangents->GetBitangent(oldIndex);
|
|
|
|
bitangent = meshTransform.TransformVector(bitangent);
|
|
bitangent = context.m_coordSysConverter.ConvertVector3(bitangent);
|
|
bitangent.Normalize();
|
|
|
|
bitangents.push_back(bitangent.GetX());
|
|
bitangents.push_back(bitangent.GetY());
|
|
bitangents.push_back(bitangent.GetZ());
|
|
}
|
|
}
|
|
|
|
// Gather UVs
|
|
for (uint32_t ii = 0; ii < uvSetCount; ++ii)
|
|
{
|
|
auto& uvs = uvSets[ii];
|
|
const auto& uvContent = uvContentCollection[ii];
|
|
|
|
AZ::Vector2 uv = uvContent->GetUV(oldIndex);
|
|
|
|
uvs.push_back(uv.GetX());
|
|
uvs.push_back(uv.GetY());
|
|
}
|
|
|
|
// Gather Colors
|
|
for (uint32_t ii = 0; ii < colorSetCount; ++ii)
|
|
{
|
|
auto& colors = colorSets[ii];
|
|
const auto& colorContent = colorContentCollection[ii];
|
|
|
|
SceneAPI::DataTypes::Color color = colorContent->GetColor(oldIndex);
|
|
|
|
colors.push_back(color.red);
|
|
colors.push_back(color.green);
|
|
colors.push_back(color.blue);
|
|
colors.push_back(color.alpha);
|
|
}
|
|
|
|
// Gather Cloth Data
|
|
if (hasClothData)
|
|
{
|
|
const AZ::Color& vertexClothData = sourceMesh.m_meshClothData[oldIndex];
|
|
|
|
clothData.push_back(vertexClothData.GetR());
|
|
clothData.push_back(vertexClothData.GetG());
|
|
clothData.push_back(vertexClothData.GetB());
|
|
clothData.push_back(vertexClothData.GetA());
|
|
}
|
|
|
|
// Gather skinning influences
|
|
if (hasSkinData)
|
|
{
|
|
// Warn about excess of skin influences once per-source mesh.
|
|
GatherVertexSkinningInfluences(sourceMesh, productMesh, jointNameToIndexMap, oldIndex, warnedExcessOfSkinInfluences);
|
|
}
|
|
}
|
|
|
|
if(!processedMorphTargets)
|
|
{
|
|
// Gather morph targets once per-source mesh.
|
|
morphTargetExporter.ProduceMorphTargets(context.m_scene, totalVertexCount, sourceMesh, productMesh, morphTargetMetaCreator, context.m_coordSysConverter);
|
|
processedMorphTargets = true;
|
|
}
|
|
|
|
totalVertexCount += static_cast<uint32_t>(vertexCount);
|
|
productMeshList.emplace_back(productMesh);
|
|
}
|
|
}
|
|
|
|
return productMeshList;
|
|
}
|
|
|
|
void ModelAssetBuilderComponent::PadVerticesForSkinning(ProductMeshContentList& productMeshList)
|
|
{
|
|
// Check if this is a skinned mesh
|
|
if (!productMeshList.empty() && !productMeshList[0].m_skinWeights.empty())
|
|
{
|
|
// First, do a pass to see if any mesh has morphed colors
|
|
bool hasMorphedColors = false;
|
|
for (ProductMeshContent& productMesh : productMeshList)
|
|
{
|
|
if (productMesh.m_hasMorphedColors)
|
|
{
|
|
hasMorphedColors = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (ProductMeshContent& productMesh : productMeshList)
|
|
{
|
|
size_t vertexCount = productMesh.m_positions.size() / PositionFloatsPerVert;
|
|
|
|
// Skinned meshes require that positions, normals, tangents, bitangents, all exist and have the same number
|
|
// of total elements. Pad buffers with missing data to make them align with positions and normals
|
|
if (productMesh.m_tangents.empty())
|
|
{
|
|
productMesh.m_tangents.resize(vertexCount * TangentFloatsPerVert, 1.0f);
|
|
AZ_Warning(s_builderName, false, "Mesh '%s' is missing tangents and no defaults were generated. Skinned meshes require tangents. Dummy tangents will be inserted, which may result in rendering artifacts.", productMesh.m_name.GetCStr());
|
|
}
|
|
if (productMesh.m_bitangents.empty())
|
|
{
|
|
productMesh.m_bitangents.resize(vertexCount * BitangentFloatsPerVert, 1.0f);
|
|
AZ_Warning(s_builderName, false, "Mesh '%s' is missing bitangents and no defaults were generated. Skinned meshes require bitangents. Dummy bitangents will be inserted, which may result in rendering artifacts.", productMesh.m_name.GetCStr());
|
|
}
|
|
|
|
// If any of the meshes have morphed colors, padd all the meshes so that the color stream is aligned with the other skinned streams
|
|
if (hasMorphedColors)
|
|
{
|
|
if (productMesh.m_colorCustomNames.empty())
|
|
{
|
|
productMesh.m_colorCustomNames.push_back(Name{ "COLOR" });
|
|
}
|
|
|
|
if (productMesh.m_colorSets.empty())
|
|
{
|
|
productMesh.m_colorSets.resize(1);
|
|
}
|
|
|
|
if (productMesh.m_colorSets[0].empty())
|
|
{
|
|
productMesh.m_colorSets[0].resize(vertexCount * ColorFloatsPerVert, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelAssetBuilderComponent::GatherVertexSkinningInfluences(
|
|
const SourceMeshContent& sourceMesh,
|
|
ProductMeshContent& productMesh,
|
|
AZStd::unordered_map<AZStd::string, uint16_t>& jointNameToIndexMap,
|
|
size_t vertexIndex,
|
|
bool& warnedExcessOfSkinInfluences) const
|
|
{
|
|
AZStd::vector<uint16_t>& skinJointIndices = productMesh.m_skinJointIndices;
|
|
AZStd::vector<float>& skinWeights = productMesh.m_skinWeights;
|
|
const auto& sourceMeshData = sourceMesh.m_meshData;
|
|
|
|
size_t numInfluencesAdded = 0;
|
|
for (const auto& skinData : sourceMesh.m_skinData)
|
|
{
|
|
const size_t numJoints = skinData->GetBoneCount();
|
|
const AZ::u32 controlPointIndex = sourceMeshData->GetControlPointIndex(static_cast<int>(vertexIndex));
|
|
const size_t numSkinInfluences = skinData->GetLinkCount(controlPointIndex);
|
|
|
|
size_t numInfluencesExcess = 0;
|
|
|
|
for (size_t influenceIndex = 0; influenceIndex < numSkinInfluences; ++influenceIndex)
|
|
{
|
|
const AZ::SceneAPI::DataTypes::ISkinWeightData::Link& link = skinData->GetLink(controlPointIndex, influenceIndex);
|
|
|
|
const float weight = link.weight;
|
|
const AZStd::string& boneName = skinData->GetBoneName(link.boneId);
|
|
|
|
// The bone id is a local bone id to the mesh. Since there could be multiple meshes, we store a global index to this asset,
|
|
// which is guaranteed to be unique. Later we will translate those indices back using the skinmetadata.
|
|
if (!jointNameToIndexMap.contains(boneName))
|
|
{
|
|
jointNameToIndexMap[boneName] = aznumeric_caster(jointNameToIndexMap.size());
|
|
}
|
|
const AZ::u16 jointIndex = jointNameToIndexMap[boneName];
|
|
|
|
// Add skin influence
|
|
if (weight > m_skinWeightThreshold)
|
|
{
|
|
if (numInfluencesAdded < m_numSkinJointInfluencesPerVertex)
|
|
{
|
|
skinJointIndices.push_back(jointIndex);
|
|
skinWeights.push_back(weight);
|
|
numInfluencesAdded++;
|
|
}
|
|
else
|
|
{
|
|
numInfluencesExcess++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numInfluencesExcess > 0)
|
|
{
|
|
AZ_Warning(s_builderName, warnedExcessOfSkinInfluences,
|
|
"Mesh %s has more skin influences (%d) than the maximum (%d). Skinning influences won't be normalized. Maximum number of skin influences can be increased with a Skin Modifier in Scene Settings.",
|
|
sourceMesh.m_name.GetCStr(),
|
|
m_numSkinJointInfluencesPerVertex + numInfluencesExcess,
|
|
m_numSkinJointInfluencesPerVertex);
|
|
warnedExcessOfSkinInfluences = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (size_t influenceIndex = numInfluencesAdded; influenceIndex < m_numSkinJointInfluencesPerVertex; ++influenceIndex)
|
|
{
|
|
skinJointIndices.push_back(0);
|
|
skinWeights.push_back(0.0f);
|
|
}
|
|
}
|
|
|
|
ModelAssetBuilderComponent::ProductMeshContentList ModelAssetBuilderComponent::MergeMeshesByMaterialUid(const ProductMeshContentList& productMeshList)
|
|
{
|
|
ProductMeshContentList finalMeshList;
|
|
{
|
|
AZStd::unordered_map<MaterialUid, ProductMeshContentList> meshesByMatUid;
|
|
|
|
// First pass to reserve memory
|
|
// This saves time with very large meshes
|
|
{
|
|
AZStd::unordered_map<MaterialUid, size_t> meshCountByMatUid;
|
|
|
|
for (const ProductMeshContent& mesh : productMeshList)
|
|
{
|
|
if (mesh.CanBeMerged())
|
|
{
|
|
meshCountByMatUid[mesh.m_materialUid]++;
|
|
}
|
|
}
|
|
|
|
for (const auto& it : meshCountByMatUid)
|
|
{
|
|
meshesByMatUid[it.first].reserve(it.second);
|
|
}
|
|
}
|
|
|
|
size_t unmergeableMeshCount = 0;
|
|
for (const ProductMeshContent& mesh : productMeshList)
|
|
{
|
|
if (mesh.CanBeMerged())
|
|
{
|
|
meshesByMatUid[mesh.m_materialUid].push_back(mesh);
|
|
}
|
|
else
|
|
{
|
|
unmergeableMeshCount++;
|
|
}
|
|
}
|
|
|
|
const size_t mergedMeshCount = meshesByMatUid.size();
|
|
finalMeshList.reserve(mergedMeshCount + unmergeableMeshCount);
|
|
|
|
// Add the merged meshes
|
|
for (const auto& it : meshesByMatUid)
|
|
{
|
|
const ProductMeshContentList& meshList = it.second;
|
|
|
|
ProductMeshContent mergedMesh = MergeMeshList(meshList, RemapIndices);
|
|
mergedMesh.m_materialUid = it.first;
|
|
ValidateStreamAlignment(mergedMesh);
|
|
|
|
finalMeshList.emplace_back(AZStd::move(mergedMesh));
|
|
}
|
|
|
|
// Add the unmergeable meshes
|
|
for (const ProductMeshContent& mesh : productMeshList)
|
|
{
|
|
if (!mesh.CanBeMerged())
|
|
{
|
|
ValidateStreamAlignment(mesh);
|
|
finalMeshList.emplace_back(mesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
return finalMeshList;
|
|
}
|
|
|
|
template<typename T>
|
|
void ModelAssetBuilderComponent::ValidateStreamSize([[maybe_unused]] size_t expectedVertexCount, [[maybe_unused]] const AZStd::vector<T>& bufferData, [[maybe_unused]] AZ::RHI::Format format, [[maybe_unused]] const char* streamName) const
|
|
{
|
|
#if defined(AZ_ENABLE_TRACING)
|
|
size_t actualVertexCount = (bufferData.size() * sizeof(T)) / RHI::GetFormatSize(format);
|
|
#endif
|
|
AZ_Error(s_builderName, expectedVertexCount == actualVertexCount, "VertexStream '%s' does not match the expected vertex count. This typically means multiple sub-meshes have mis-matched vertex stream layouts (such as one having more uv sets than the other) but are assigned the same material in the dcc tool so they were merged.", streamName);
|
|
}
|
|
|
|
void ModelAssetBuilderComponent::ValidateStreamAlignment(const ProductMeshContent& mesh) const
|
|
{
|
|
size_t expectedVertexCount = mesh.m_positions.size() * sizeof(mesh.m_positions[0]) / RHI::GetFormatSize(PositionFormat);
|
|
if (!mesh.m_normals.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_normals, NormalFormat, "NORMAL");
|
|
}
|
|
if (!mesh.m_tangents.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_tangents, TangentFormat, "TANGENT");
|
|
}
|
|
if (!mesh.m_bitangents.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_bitangents, BitangentFormat, "BITANGENT");
|
|
}
|
|
for (size_t i = 0; i < mesh.m_uvSets.size(); ++i)
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_uvSets[i], UVFormat, mesh.m_uvCustomNames[i].GetCStr());
|
|
}
|
|
for (size_t i = 0; i < mesh.m_colorSets.size(); ++i)
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_colorSets[i], ColorFormat, mesh.m_colorCustomNames[i].GetCStr());
|
|
}
|
|
if (!mesh.m_clothData.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount, mesh.m_clothData, ClothDataFormat, ShaderSemanticName_ClothData);
|
|
}
|
|
if (!mesh.m_skinJointIndices.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount * m_numSkinJointInfluencesPerVertex, mesh.m_skinJointIndices, AZ::RHI::Format::R16_UINT, ShaderSemanticName_SkinJointIndices);
|
|
}
|
|
if (!mesh.m_skinWeights.empty())
|
|
{
|
|
ValidateStreamSize(expectedVertexCount * m_numSkinJointInfluencesPerVertex, mesh.m_skinWeights, SkinWeightFormat, ShaderSemanticName_SkinWeights);
|
|
}
|
|
}
|
|
|
|
ModelAssetBuilderComponent::ProductMeshView ModelAssetBuilderComponent::CreateViewToEntireMesh(const ProductMeshContent& mesh)
|
|
{
|
|
ProductMeshView meshView;
|
|
meshView.m_name = mesh.m_name.GetStringView();
|
|
|
|
auto meshIndexCount = static_cast<uint32_t>(mesh.m_indices.size());
|
|
auto meshPositionsFloatCount = static_cast<uint32_t>(mesh.m_positions.size());
|
|
auto meshNormalsFloatCount = static_cast<uint32_t>(mesh.m_normals.size());
|
|
|
|
auto meshPositionCount = meshPositionsFloatCount / PositionFloatsPerVert;
|
|
auto meshNormalsCount = meshNormalsFloatCount / NormalFloatsPerVert;
|
|
|
|
meshView.m_indexView = RHI::BufferViewDescriptor::CreateTyped(0, meshIndexCount, IndicesFormat);
|
|
meshView.m_positionView = RHI::BufferViewDescriptor::CreateTyped(0, meshPositionCount, PositionFormat);
|
|
if (meshNormalsCount > 0)
|
|
{
|
|
meshView.m_normalView = RHI::BufferViewDescriptor::CreateTyped(0, meshNormalsCount, NormalFormat);
|
|
}
|
|
|
|
const size_t uvSetCount = mesh.m_uvSets.size();
|
|
meshView.m_uvSetViews.reserve(uvSetCount);
|
|
meshView.m_uvCustomNames.resize(uvSetCount);
|
|
meshView.m_uvCustomNames.resize(mesh.m_uvCustomNames.size());
|
|
AZ_Assert(mesh.m_uvSets.size() == mesh.m_uvCustomNames.size(), "UV set size doesn't match the number of custom uv names");
|
|
for (uint32_t uvSetIndex = 0; uvSetIndex < mesh.m_uvSets.size(); uvSetIndex++)
|
|
{
|
|
const auto& uvSet = mesh.m_uvSets[uvSetIndex];
|
|
auto uvFloatCount = static_cast<uint32_t>(uvSet.size());
|
|
auto uvCount = uvFloatCount / UVFloatsPerVert;
|
|
|
|
meshView.m_uvSetViews.push_back(RHI::BufferViewDescriptor::CreateTyped(0, uvCount, UVFormat));
|
|
meshView.m_uvCustomNames.push_back(mesh.m_uvCustomNames[uvSetIndex]);
|
|
}
|
|
|
|
meshView.m_colorSetViews.reserve(mesh.m_colorSets.size());
|
|
meshView.m_colorCustomNames.resize(mesh.m_colorCustomNames.size());
|
|
for (uint32_t colorSetIndex = 0; colorSetIndex < mesh.m_colorSets.size(); colorSetIndex++)
|
|
{
|
|
const auto& colorSet = mesh.m_colorSets[colorSetIndex];
|
|
auto colorFloatCount = static_cast<uint32_t>(colorSet.size());
|
|
auto colorCount = colorFloatCount / ColorFloatsPerVert;
|
|
|
|
meshView.m_colorSetViews.push_back(RHI::BufferViewDescriptor::CreateTyped(0, colorCount, ColorFormat));
|
|
meshView.m_colorCustomNames.push_back(mesh.m_colorCustomNames[colorSetIndex]);
|
|
}
|
|
|
|
if (!mesh.m_tangents.empty())
|
|
{
|
|
meshView.m_tangentView = RHI::BufferViewDescriptor::CreateTyped(0, meshNormalsCount, TangentFormat);
|
|
}
|
|
|
|
if (!mesh.m_bitangents.empty())
|
|
{
|
|
meshView.m_bitangentView = RHI::BufferViewDescriptor::CreateTyped(0, meshNormalsCount, BitangentFormat);
|
|
}
|
|
|
|
if (!mesh.m_skinJointIndices.empty() && !mesh.m_skinWeights.empty())
|
|
{
|
|
AZ_Assert(mesh.m_skinJointIndices.size() == mesh.m_skinWeights.size(),
|
|
"Number of skin influence joint indices (%d) should match the number of weights (%d).",
|
|
mesh.m_skinJointIndices.size(), mesh.m_skinWeights.size());
|
|
|
|
AZ_Assert(mesh.m_skinWeights.size() % m_numSkinJointInfluencesPerVertex == 0,
|
|
"The number of skin influences per vertex (%d) is not a multiple of the total number of skinning weights (%d). This means that not every vertex has exactly (%d) skinning weights and invalidates the data.",
|
|
mesh.m_skinWeights.size(), m_numSkinJointInfluencesPerVertex, m_numSkinJointInfluencesPerVertex);
|
|
const size_t numSkinInfluences = mesh.m_skinWeights.size();
|
|
|
|
uint32_t jointIndicesSizeInBytes = static_cast<uint32_t>(numSkinInfluences * sizeof(uint16_t));
|
|
meshView.m_skinJointIndicesView = RHI::BufferViewDescriptor::CreateRaw(0, jointIndicesSizeInBytes);
|
|
meshView.m_skinWeightsView = RHI::BufferViewDescriptor::CreateTyped(0, static_cast<uint32_t>(numSkinInfluences), SkinWeightFormat);
|
|
}
|
|
|
|
if (!mesh.m_morphTargetVertexData.empty())
|
|
{
|
|
const size_t numTotalVertices = mesh.m_morphTargetVertexData.size();
|
|
meshView.m_morphTargetVertexDataView = RHI::BufferViewDescriptor::CreateStructured(0, static_cast<uint32_t>(numTotalVertices), sizeof(PackedCompressedMorphTargetDelta));
|
|
}
|
|
|
|
if (!mesh.m_clothData.empty())
|
|
{
|
|
auto meshClothDataFloatCount = static_cast<uint32_t>(mesh.m_clothData.size());
|
|
AZ_Assert((meshClothDataFloatCount % ClothDataFloatsPerVert) == 0,
|
|
"Unexpected number of cloth data elements (%d), it should contain a multiple of %d elements.", meshClothDataFloatCount, ClothDataFloatsPerVert);
|
|
|
|
auto meshClothDataCount = meshClothDataFloatCount / ClothDataFloatsPerVert;
|
|
AZ_Assert(meshClothDataCount == meshPositionCount,
|
|
"Number of cloth data elements (%d) does not match the number of positions (%d) in the mesh", meshClothDataCount, meshPositionCount);
|
|
|
|
meshView.m_clothDataView = RHI::BufferViewDescriptor::CreateTyped(0, meshClothDataCount, ClothDataFormat);
|
|
}
|
|
|
|
meshView.m_materialUid = mesh.m_materialUid;
|
|
|
|
return meshView;
|
|
}
|
|
|
|
void ModelAssetBuilderComponent::MergeMeshesToCommonBuffers(
|
|
const ProductMeshContentList& lodMeshList,
|
|
ProductMeshContent& lodMeshContent,
|
|
ProductMeshViewList& meshViews)
|
|
{
|
|
meshViews.reserve(lodMeshList.size());
|
|
|
|
// We want to merge these meshes into one large
|
|
// ProductMesh. That large buffer gets set on the LOD directly
|
|
// rather than a Mesh in the LOD.
|
|
ProductMeshContentAllocInfo lodBufferInfo;
|
|
|
|
for (const ProductMeshContent& mesh : lodMeshList)
|
|
{
|
|
if (lodBufferInfo.m_uvSetFloatCounts.size() < mesh.m_uvSets.size())
|
|
{
|
|
lodBufferInfo.m_uvSetFloatCounts.resize(mesh.m_uvSets.size());
|
|
}
|
|
|
|
if (lodBufferInfo.m_colorSetFloatCounts.size() < mesh.m_colorSets.size())
|
|
{
|
|
lodBufferInfo.m_colorSetFloatCounts.resize(mesh.m_colorSets.size());
|
|
}
|
|
|
|
// Once again we save a lot of time and memory by determining what we
|
|
// need to allocate up-front
|
|
|
|
auto meshIndexCount = static_cast<uint32_t>(mesh.m_indices.size());
|
|
auto meshPositionsFloatCount = static_cast<uint32_t>(mesh.m_positions.size());
|
|
auto meshNormalsFloatCount = static_cast<uint32_t>(mesh.m_normals.size());
|
|
auto meshTangentsFloatCount = static_cast<uint32_t>(mesh.m_tangents.size());
|
|
auto meshBitangentsFloatCount = static_cast<uint32_t>(mesh.m_bitangents.size());
|
|
auto meshClothDataFloatCount = static_cast<uint32_t>(mesh.m_clothData.size());
|
|
|
|
// For each element we need to:
|
|
// record the offset for the view
|
|
// accumulate the allocation info
|
|
// fill the rest of the data for the view
|
|
|
|
ProductMeshView meshView;
|
|
meshView.m_name = mesh.m_name;
|
|
meshView.m_indexView = RHI::BufferViewDescriptor::CreateTyped(static_cast<uint32_t>(lodBufferInfo.m_indexCount), meshIndexCount, IndicesFormat);
|
|
lodBufferInfo.m_indexCount += meshIndexCount;
|
|
|
|
const uint32_t meshVertexCount = meshPositionsFloatCount / PositionFloatsPerVert;
|
|
|
|
if (!mesh.m_positions.empty())
|
|
{
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_positionsFloatCount) / PositionFloatsPerVert;
|
|
meshView.m_positionView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, PositionFormat);
|
|
lodBufferInfo.m_positionsFloatCount += meshPositionsFloatCount;
|
|
}
|
|
|
|
if (!mesh.m_normals.empty())
|
|
{
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_normalsFloatCount) / NormalFloatsPerVert;
|
|
meshView.m_normalView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, NormalFormat);
|
|
lodBufferInfo.m_normalsFloatCount += meshNormalsFloatCount;
|
|
}
|
|
|
|
if (!mesh.m_tangents.empty())
|
|
{
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_tangentsFloatCount) / TangentFloatsPerVert;
|
|
meshView.m_tangentView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, TangentFormat);
|
|
lodBufferInfo.m_tangentsFloatCount += meshTangentsFloatCount;
|
|
}
|
|
|
|
if (!mesh.m_bitangents.empty())
|
|
{
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_bitangentsFloatCount) / BitangentFloatsPerVert;
|
|
meshView.m_bitangentView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, BitangentFormat);
|
|
lodBufferInfo.m_bitangentsFloatCount += meshBitangentsFloatCount;
|
|
}
|
|
|
|
const size_t uvSetCount = mesh.m_uvSets.size();
|
|
if (uvSetCount > 0)
|
|
{
|
|
meshView.m_uvSetViews.resize(uvSetCount);
|
|
meshView.m_uvCustomNames.resize(uvSetCount);
|
|
for (size_t i = 0; i < uvSetCount; ++i)
|
|
{
|
|
meshView.m_uvCustomNames[i] = mesh.m_uvCustomNames[i];
|
|
|
|
auto& uvSetView = meshView.m_uvSetViews[i];
|
|
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_uvSetFloatCounts[i]) / UVFloatsPerVert;
|
|
uvSetView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, UVFormat);
|
|
|
|
const auto uvCount = static_cast<uint32_t>(mesh.m_uvSets[i].size());
|
|
lodBufferInfo.m_uvSetFloatCounts[i] += uvCount;
|
|
}
|
|
}
|
|
|
|
const size_t colorSetCount = mesh.m_colorSets.size();
|
|
if (colorSetCount > 0)
|
|
{
|
|
meshView.m_colorSetViews.resize(colorSetCount);
|
|
meshView.m_colorCustomNames.resize(colorSetCount);
|
|
for (size_t i = 0; i < colorSetCount; ++i)
|
|
{
|
|
meshView.m_colorCustomNames[i] = mesh.m_colorCustomNames[i];
|
|
|
|
auto& colorSetView = meshView.m_colorSetViews[i];
|
|
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_colorSetFloatCounts[i]) / ColorFloatsPerVert;
|
|
colorSetView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, ColorFormat);
|
|
|
|
const auto colorCount = static_cast<uint32_t>(mesh.m_colorSets[i].size());
|
|
lodBufferInfo.m_colorSetFloatCounts[i] += colorCount;
|
|
}
|
|
}
|
|
|
|
if (!mesh.m_clothData.empty())
|
|
{
|
|
const uint32_t elementOffset = static_cast<uint32_t>(lodBufferInfo.m_clothDataFloatCount) / ClothDataFloatsPerVert;
|
|
meshView.m_clothDataView = RHI::BufferViewDescriptor::CreateTyped(elementOffset, meshVertexCount, ClothDataFormat);
|
|
lodBufferInfo.m_clothDataFloatCount += meshClothDataFloatCount;
|
|
}
|
|
|
|
meshView.m_materialUid = mesh.m_materialUid;
|
|
|
|
if (!mesh.m_skinJointIndices.empty() && !mesh.m_skinWeights.empty())
|
|
{
|
|
AZ_Assert(mesh.m_skinJointIndices.size() == mesh.m_skinWeights.size(),
|
|
"Number of skin influence joint indices (%d) should match the number of weights (%d).",
|
|
mesh.m_skinJointIndices.size(), mesh.m_skinWeights.size());
|
|
|
|
AZ_Assert(mesh.m_skinWeights.size() % m_numSkinJointInfluencesPerVertex == 0,
|
|
"The number of skin influences per vertex (%d) is not a multiple of the total number of skinning weights (%d). This means that not every vertex has exactly (%d) skinning weights and invalidates the data.",
|
|
mesh.m_skinWeights.size(), m_numSkinJointInfluencesPerVertex, m_numSkinJointInfluencesPerVertex);
|
|
|
|
const size_t numPrevSkinInfluences = lodBufferInfo.m_skinInfluencesCount;
|
|
const size_t numNewSkinInfluences = mesh.m_skinWeights.size();
|
|
|
|
meshView.m_skinJointIndicesView = RHI::BufferViewDescriptor::CreateRaw(/*byteOffset=*/ static_cast<uint32_t>(numPrevSkinInfluences * sizeof(uint16_t)), static_cast<uint32_t>(numNewSkinInfluences * sizeof(uint16_t)));
|
|
meshView.m_skinWeightsView = RHI::BufferViewDescriptor::CreateTyped(/*elementOffset=*/ static_cast<uint32_t>(numPrevSkinInfluences), static_cast<uint32_t>(numNewSkinInfluences), SkinWeightFormat);
|
|
|
|
lodBufferInfo.m_skinInfluencesCount += numNewSkinInfluences;
|
|
}
|
|
|
|
if (!mesh.m_morphTargetVertexData.empty())
|
|
{
|
|
const size_t numPrevVertexDeltas = lodBufferInfo.m_morphTargetVertexDeltaCount;
|
|
const size_t numNewVertexDeltas = mesh.m_morphTargetVertexData.size();
|
|
|
|
meshView.m_morphTargetVertexDataView = RHI::BufferViewDescriptor::CreateStructured(/*elementOffset=*/ static_cast<uint32_t>(numPrevVertexDeltas), static_cast<uint32_t>(numNewVertexDeltas), sizeof(PackedCompressedMorphTargetDelta));
|
|
|
|
lodBufferInfo.m_morphTargetVertexDeltaCount += numNewVertexDeltas;
|
|
}
|
|
|
|
meshViews.emplace_back(AZStd::move(meshView));
|
|
}
|
|
|
|
// Now that we have the views settled, we can just merge the mesh
|
|
lodMeshContent = MergeMeshList(lodMeshList, PreserveIndices);
|
|
}
|
|
|
|
ModelAssetBuilderComponent::ProductMeshContent ModelAssetBuilderComponent::MergeMeshList(
|
|
const ProductMeshContentList& productMeshList,
|
|
IndicesOperation indicesOp)
|
|
{
|
|
ProductMeshContent mergedMesh;
|
|
|
|
// A preallocation pass for the merged mesh
|
|
{
|
|
size_t indexCount = 0;
|
|
size_t positionCount = 0;
|
|
size_t normalCount = 0;
|
|
size_t tangentCount = 0;
|
|
size_t bitangentCount = 0;
|
|
size_t clothDataCount = 0;
|
|
AZStd::vector<size_t> uvSetCounts;
|
|
AZStd::vector<size_t> colorSetCounts;
|
|
|
|
for (const ProductMeshContent& mesh : productMeshList)
|
|
{
|
|
indexCount += mesh.m_indices.size();
|
|
positionCount += mesh.m_positions.size();
|
|
normalCount += mesh.m_normals.size();
|
|
tangentCount += mesh.m_tangents.size();
|
|
bitangentCount += mesh.m_bitangents.size();
|
|
clothDataCount += mesh.m_clothData.size();
|
|
|
|
if (mesh.m_uvSets.size() > uvSetCounts.size())
|
|
{
|
|
uvSetCounts.resize(mesh.m_uvSets.size());
|
|
}
|
|
|
|
for (size_t i = 0; i < mesh.m_uvSets.size(); ++i)
|
|
{
|
|
uvSetCounts[i] += mesh.m_uvSets[i].size();
|
|
}
|
|
|
|
if (mesh.m_colorSets.size() > colorSetCounts.size())
|
|
{
|
|
colorSetCounts.resize(mesh.m_colorSets.size());
|
|
}
|
|
|
|
for (size_t i = 0; i < mesh.m_colorSets.size(); ++i)
|
|
{
|
|
colorSetCounts[i] += mesh.m_colorSets[i].size();
|
|
}
|
|
}
|
|
|
|
mergedMesh.m_indices.reserve(indexCount);
|
|
mergedMesh.m_positions.reserve(positionCount);
|
|
mergedMesh.m_normals.reserve(normalCount);
|
|
mergedMesh.m_tangents.reserve(tangentCount);
|
|
mergedMesh.m_bitangents.reserve(bitangentCount);
|
|
mergedMesh.m_clothData.reserve(clothDataCount);
|
|
|
|
mergedMesh.m_uvCustomNames.resize(uvSetCounts.size());
|
|
for (auto& mesh : productMeshList)
|
|
{
|
|
int32_t nameCount = aznumeric_cast<int32_t>(mesh.m_uvCustomNames.size());
|
|
// Backward stack, the first mesh defines the name.
|
|
for (int32_t i = nameCount - 1; i >= 0; --i)
|
|
{
|
|
mergedMesh.m_uvCustomNames[i] = mesh.m_uvCustomNames[i];
|
|
}
|
|
}
|
|
|
|
mergedMesh.m_uvSets.resize(uvSetCounts.size());
|
|
for (size_t i = 0; i < uvSetCounts.size(); ++i)
|
|
{
|
|
mergedMesh.m_uvSets[i].reserve(uvSetCounts[i]);
|
|
}
|
|
|
|
mergedMesh.m_colorCustomNames.resize(colorSetCounts.size());
|
|
for (auto& mesh : productMeshList)
|
|
{
|
|
int32_t nameCount = aznumeric_cast<int32_t>(mesh.m_colorCustomNames.size());
|
|
// Backward stack, the first mesh defines the name.
|
|
for (int32_t i = nameCount - 1; i >= 0; --i)
|
|
{
|
|
mergedMesh.m_colorCustomNames[i] = mesh.m_colorCustomNames[i];
|
|
}
|
|
}
|
|
|
|
mergedMesh.m_colorSets.resize(colorSetCounts.size());
|
|
for (size_t i = 0; i < colorSetCounts.size(); ++i)
|
|
{
|
|
mergedMesh.m_colorSets[i].reserve(colorSetCounts[i]);
|
|
}
|
|
}
|
|
|
|
uint32_t tailIndex = 0;
|
|
|
|
// Append each common mesh onto this LOD-wide mesh
|
|
for (const ProductMeshContent& mesh : productMeshList)
|
|
{
|
|
if(mergedMesh.m_name.IsEmpty())
|
|
{
|
|
mergedMesh.m_name = mesh.m_name;
|
|
}
|
|
else
|
|
{
|
|
mergedMesh.m_name = AZStd::string::format("%s+%s", mergedMesh.m_name.GetCStr(), mesh.m_name.GetCStr());
|
|
}
|
|
|
|
AZStd::vector<uint32_t> indices = mesh.m_indices;
|
|
|
|
if (indicesOp == RemapIndices)
|
|
{
|
|
/**
|
|
* Remap indices to start where the last mesh left off
|
|
* If mesh 0 has indices 0,1,2 and mesh 1 has indices 0,1,2
|
|
* we need to rescale them so that mesh 1 has indices 3,4,5
|
|
*/
|
|
uint32_t largestIndex = 0;
|
|
|
|
for (uint32_t& index : indices)
|
|
{
|
|
index += tailIndex;
|
|
|
|
if (index > largestIndex)
|
|
{
|
|
largestIndex = index;
|
|
}
|
|
}
|
|
|
|
// +1 because if the largest index is 5 we want the next index to start at 6
|
|
tailIndex = largestIndex + 1;
|
|
}
|
|
|
|
mergedMesh.m_indices.insert(
|
|
mergedMesh.m_indices.end(), indices.begin(), indices.end());
|
|
|
|
if (!mesh.m_positions.empty())
|
|
{
|
|
mergedMesh.m_positions.insert(
|
|
mergedMesh.m_positions.end(), mesh.m_positions.begin(), mesh.m_positions.end());
|
|
}
|
|
|
|
if (!mesh.m_normals.empty())
|
|
{
|
|
mergedMesh.m_normals.insert(
|
|
mergedMesh.m_normals.end(), mesh.m_normals.begin(), mesh.m_normals.end());
|
|
}
|
|
|
|
if (!mesh.m_tangents.empty())
|
|
{
|
|
mergedMesh.m_tangents.insert(
|
|
mergedMesh.m_tangents.end(), mesh.m_tangents.begin(), mesh.m_tangents.end());
|
|
}
|
|
|
|
if (!mesh.m_bitangents.empty())
|
|
{
|
|
mergedMesh.m_bitangents.insert(
|
|
mergedMesh.m_bitangents.end(), mesh.m_bitangents.begin(), mesh.m_bitangents.end());
|
|
}
|
|
|
|
const size_t uvSetCount = mesh.m_uvSets.size();
|
|
for (size_t i = 0; i < uvSetCount; ++i)
|
|
{
|
|
mergedMesh.m_uvSets[i].insert(
|
|
mergedMesh.m_uvSets[i].end(), mesh.m_uvSets[i].begin(), mesh.m_uvSets[i].end());
|
|
}
|
|
|
|
const size_t colorSetCount = mesh.m_colorSets.size();
|
|
for (size_t i = 0; i < colorSetCount; ++i)
|
|
{
|
|
mergedMesh.m_colorSets[i].insert(
|
|
mergedMesh.m_colorSets[i].end(), mesh.m_colorSets[i].begin(), mesh.m_colorSets[i].end());
|
|
}
|
|
|
|
if (!mesh.m_skinJointIndices.empty())
|
|
{
|
|
mergedMesh.m_skinJointIndices.insert(
|
|
mergedMesh.m_skinJointIndices.end(), mesh.m_skinJointIndices.begin(), mesh.m_skinJointIndices.end());
|
|
}
|
|
|
|
if (!mesh.m_skinWeights.empty())
|
|
{
|
|
mergedMesh.m_skinWeights.insert(
|
|
mergedMesh.m_skinWeights.end(), mesh.m_skinWeights.begin(), mesh.m_skinWeights.end());
|
|
}
|
|
|
|
if (!mesh.m_morphTargetVertexData.empty())
|
|
{
|
|
const auto& sourceMorphTargetData = mesh.m_morphTargetVertexData;
|
|
auto& mergedMorphTargetData = mergedMesh.m_morphTargetVertexData;
|
|
mergedMorphTargetData.insert(mergedMorphTargetData.end(), sourceMorphTargetData.begin(), sourceMorphTargetData.end());
|
|
}
|
|
|
|
if (!mesh.m_clothData.empty())
|
|
{
|
|
mergedMesh.m_clothData.insert(
|
|
mergedMesh.m_clothData.end(), mesh.m_clothData.begin(), mesh.m_clothData.end());
|
|
}
|
|
}
|
|
|
|
return mergedMesh;
|
|
}
|
|
|
|
template<typename T>
|
|
bool ModelAssetBuilderComponent::BuildStructuredStreamBuffer(
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& outStreamBuffers,
|
|
const AZStd::vector<T>& bufferData,
|
|
const RHI::ShaderSemantic& semantic,
|
|
const AZ::Name& customStreamName)
|
|
{
|
|
AZStd::string bufferName = semantic.ToString();
|
|
size_t elementCount = bufferData.size();
|
|
size_t elementSize = sizeof(T);
|
|
Outcome<Data::Asset<BufferAsset>> bufferOutcome = CreateStructuredBufferAsset(bufferData.data(), elementCount, elementSize, bufferName);
|
|
|
|
if (!bufferOutcome.IsSuccess())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build %s stream", semantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
outStreamBuffers.push_back({ semantic, customStreamName, {bufferOutcome.GetValue(), bufferOutcome.GetValue()->GetBufferViewDescriptor()} });
|
|
return true;
|
|
};
|
|
|
|
template<typename T>
|
|
bool ModelAssetBuilderComponent::BuildRawStreamBuffer(
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& outStreamBuffers,
|
|
const AZStd::vector<T>& bufferData,
|
|
const RHI::ShaderSemantic& semantic,
|
|
const AZ::Name& customStreamName)
|
|
{
|
|
AZStd::string bufferName = semantic.ToString();
|
|
size_t sizeInBytes = bufferData.size() * sizeof(T);
|
|
Outcome<Data::Asset<BufferAsset>> bufferOutcome = CreateRawBufferAsset(bufferData.data(), sizeInBytes, bufferName);
|
|
|
|
if (!bufferOutcome.IsSuccess())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build %s stream", semantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
outStreamBuffers.push_back({ semantic, customStreamName, {bufferOutcome.GetValue(), bufferOutcome.GetValue()->GetBufferViewDescriptor()} });
|
|
return true;
|
|
};
|
|
|
|
template<typename T>
|
|
bool ModelAssetBuilderComponent::BuildTypedStreamBuffer(
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& outStreamBuffers,
|
|
const AZStd::vector<T>& bufferData,
|
|
AZ::RHI::Format format,
|
|
const RHI::ShaderSemantic& semantic,
|
|
const AZ::Name& customStreamName)
|
|
{
|
|
AZStd::string bufferName = semantic.ToString();
|
|
size_t floatsPerElement = RHI::GetFormatSize(format) / sizeof(T);
|
|
Outcome<Data::Asset<BufferAsset>> bufferOutcome = CreateTypedBufferAsset(bufferData.data(), bufferData.size() / floatsPerElement, format, bufferName);
|
|
|
|
if (!bufferOutcome.IsSuccess())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build %s stream", semantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
outStreamBuffers.push_back({semantic, customStreamName, {bufferOutcome.GetValue(), bufferOutcome.GetValue()->GetBufferViewDescriptor()}});
|
|
return true;
|
|
};
|
|
|
|
template<typename T>
|
|
bool ModelAssetBuilderComponent::BuildStreamBuffer(size_t vertexCount,
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& outStreamBuffers,
|
|
const AZStd::vector<T>& bufferData,
|
|
AZ::RHI::Format format,
|
|
const RHI::ShaderSemantic& semantic,
|
|
const AZ::Name& customStreamName)
|
|
{
|
|
size_t expectedElementCount = vertexCount * RHI::GetFormatComponentCount(format);
|
|
if (expectedElementCount != bufferData.size())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build %s stream. Expected %d elements but found %d.", semantic.ToString().data(), expectedElementCount, bufferData.size());
|
|
return false;
|
|
}
|
|
|
|
AZStd::string bufferName = semantic.ToString();
|
|
Outcome<Data::Asset<BufferAsset>> bufferOutcome = CreateTypedBufferAsset(bufferData.data(), vertexCount, format, bufferName);
|
|
if (!bufferOutcome.IsSuccess())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build %s stream", semantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
outStreamBuffers.push_back({semantic, customStreamName, {bufferOutcome.GetValue(), bufferOutcome.GetValue()->GetBufferViewDescriptor()}});
|
|
return true;
|
|
};
|
|
|
|
bool ModelAssetBuilderComponent::CreateModelLodBuffers(
|
|
const ProductMeshContent& lodBufferContent,
|
|
BufferAssetView& outIndexBuffer,
|
|
AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& outStreamBuffers,
|
|
ModelLodAssetCreator& lodAssetCreator)
|
|
{
|
|
const AZStd::vector<uint32_t>& indices = lodBufferContent.m_indices;
|
|
const AZStd::vector<float>& positions = lodBufferContent.m_positions;
|
|
const AZStd::vector<float>& normals = lodBufferContent.m_normals;
|
|
const AZStd::vector<float>& tangents = lodBufferContent.m_tangents;
|
|
const AZStd::vector<float>& bitangents = lodBufferContent.m_bitangents;
|
|
const AZStd::vector<AZStd::vector<float>>& uvSets = lodBufferContent.m_uvSets;
|
|
const AZStd::vector<AZ::Name>& uvCustomNames = lodBufferContent.m_uvCustomNames;
|
|
const AZStd::vector<AZStd::vector<float>>& colorSets = lodBufferContent.m_colorSets;
|
|
const AZStd::vector<AZ::Name>& colorCustomNames = lodBufferContent.m_colorCustomNames;
|
|
const AZStd::vector<float>& clothData = lodBufferContent.m_clothData;
|
|
|
|
// Build Index Buffer ...
|
|
{
|
|
Outcome<Data::Asset<BufferAsset>> indexBufferOutcome = CreateTypedBufferAsset(indices.data(), indices.size(), IndicesFormat, "index");
|
|
if (!indexBufferOutcome.IsSuccess())
|
|
{
|
|
AZ_Error(s_builderName, false, "Failed to build index stream");
|
|
return false;
|
|
}
|
|
|
|
outIndexBuffer = { indexBufferOutcome.GetValue(), indexBufferOutcome.GetValue()->GetBufferViewDescriptor() };
|
|
}
|
|
|
|
// Build various stream buffers ...
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, positions, PositionFormat, RHI::ShaderSemantic{"POSITION"}))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, normals, NormalFormat, RHI::ShaderSemantic{"NORMAL"}))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!tangents.empty())
|
|
{
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, tangents, TangentFormat, RHI::ShaderSemantic{"TANGENT"}))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!bitangents.empty())
|
|
{
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, bitangents, BitangentFormat, RHI::ShaderSemantic{"BITANGENT"}))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < uvSets.size(); ++i)
|
|
{
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, uvSets[i], UVFormat, RHI::ShaderSemantic{"UV", i}, uvCustomNames[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < colorSets.size(); ++i)
|
|
{
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, colorSets[i], ColorFormat, RHI::ShaderSemantic{"COLOR", i}, colorCustomNames[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Skinning buffers
|
|
const AZStd::vector<uint16_t>& skinJointIndices = lodBufferContent.m_skinJointIndices;
|
|
const AZStd::vector<float>& skinWeights = lodBufferContent.m_skinWeights;
|
|
if (!skinJointIndices.empty() && !skinWeights.empty())
|
|
{
|
|
const size_t vertexCount = positions.size() / PositionFloatsPerVert;
|
|
const size_t numSkinInfluences = vertexCount * m_numSkinJointInfluencesPerVertex;
|
|
|
|
if (!BuildRawStreamBuffer<uint16_t>(outStreamBuffers, skinJointIndices, RHI::ShaderSemantic{ShaderSemanticName_SkinJointIndices}))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!BuildStreamBuffer<float>(numSkinInfluences, outStreamBuffers, skinWeights, SkinWeightFormat, RHI::ShaderSemantic{ShaderSemanticName_SkinWeights}))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Morph target buffers
|
|
const AZStd::vector<PackedCompressedMorphTargetDelta>& morphTargetVertexDeltas = lodBufferContent.m_morphTargetVertexData;
|
|
if (!morphTargetVertexDeltas.empty())
|
|
{
|
|
if (!BuildStructuredStreamBuffer<PackedCompressedMorphTargetDelta>(outStreamBuffers, morphTargetVertexDeltas,
|
|
RHI::ShaderSemantic{ ShaderSemanticName_MorphTargetDeltas }))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!clothData.empty())
|
|
{
|
|
if (!BuildTypedStreamBuffer<float>(outStreamBuffers, clothData, ClothDataFormat, RHI::ShaderSemantic{ ShaderSemanticName_ClothData }))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
lodAssetCreator.SetLodIndexBuffer(outIndexBuffer.GetBufferAsset());
|
|
|
|
for (const auto& streamBufferInfo : outStreamBuffers)
|
|
{
|
|
lodAssetCreator.AddLodStreamBuffer(streamBufferInfo.m_bufferAssetView.GetBufferAsset());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ModelAssetBuilderComponent::CreateMesh(
|
|
const ProductMeshView& meshView,
|
|
const BufferAssetView& lodIndexBuffer,
|
|
const AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& lodStreamBuffers,
|
|
ModelAssetCreator& modelAssetCreator,
|
|
ModelLodAssetCreator& lodAssetCreator,
|
|
const MaterialAssetsByUid& materialAssetsByUid)
|
|
{
|
|
lodAssetCreator.BeginMesh();
|
|
|
|
if (meshView.m_materialUid != s_invalidMaterialUid)
|
|
{
|
|
auto iter = materialAssetsByUid.find(meshView.m_materialUid);
|
|
if (iter != materialAssetsByUid.end())
|
|
{
|
|
ModelMaterialSlot materialSlot;
|
|
materialSlot.m_stableId = static_cast<AZ::RPI::ModelMaterialSlot::StableId>(meshView.m_materialUid);
|
|
materialSlot.m_displayName = iter->second.m_name;
|
|
materialSlot.m_defaultMaterialAsset = iter->second.m_asset;
|
|
|
|
modelAssetCreator.AddMaterialSlot(materialSlot);
|
|
lodAssetCreator.SetMeshMaterialSlot(materialSlot.m_stableId);
|
|
}
|
|
}
|
|
|
|
lodAssetCreator.SetMeshName(meshView.m_name);
|
|
|
|
// Set the index stream
|
|
BufferAssetView indexBufferAssetView(lodIndexBuffer.GetBufferAsset(), meshView.m_indexView);
|
|
|
|
lodAssetCreator.SetMeshIndexBuffer(AZStd::move(indexBufferAssetView));
|
|
|
|
{
|
|
// Build the mesh's Aabb
|
|
ModelLodAsset::Mesh::StreamBufferInfo positionStreamBufferInfo;
|
|
const RHI::ShaderSemantic& positionSemantic = RHI::ShaderSemantic{"POSITION"};
|
|
if (!FindStreamBufferById(lodStreamBuffers, positionSemantic, positionStreamBufferInfo))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const RHI::BufferViewDescriptor& positionBufferViewDescriptor = meshView.m_positionView;
|
|
|
|
// Calculate SubMesh's AABB from position stream
|
|
AZ::Aabb subMeshAabb = AZ::Aabb::CreateNull();
|
|
if (CalculateAABB(positionBufferViewDescriptor, *positionStreamBufferInfo.m_bufferAssetView.GetBufferAsset().Get(), subMeshAabb))
|
|
{
|
|
lodAssetCreator.SetMeshAabb(AZStd::move(subMeshAabb));
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning(s_builderName, false, "Failed to calculate AABB for Mesh");
|
|
}
|
|
|
|
// Set position buffer
|
|
BufferAssetView meshPositionBufferAssetView(
|
|
positionStreamBufferInfo.m_bufferAssetView.GetBufferAsset(),
|
|
meshView.m_positionView);
|
|
|
|
lodAssetCreator.AddMeshStreamBuffer(positionSemantic, AZ::Name(), meshPositionBufferAssetView);
|
|
}
|
|
|
|
// Set normal buffer
|
|
if (meshView.m_normalView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{"NORMAL"}, AZ::Name(), meshView.m_normalView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set UV buffers
|
|
for (size_t i = 0; i < meshView.m_uvSetViews.size(); ++i)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{"UV", i}, meshView.m_uvCustomNames[i], meshView.m_uvSetViews[i], lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set Color buffers
|
|
for (size_t i = 0; i < meshView.m_colorSetViews.size(); ++i)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{"COLOR", i}, meshView.m_colorCustomNames[i], meshView.m_colorSetViews[i], lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set Tangent/Bitangent buffer
|
|
if (meshView.m_tangentView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{"TANGENT"}, AZ::Name(), meshView.m_tangentView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (meshView.m_bitangentView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{"BITANGENT"}, AZ::Name(), meshView.m_bitangentView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set skin buffers
|
|
if (meshView.m_skinJointIndicesView.m_elementCount > 0 && meshView.m_skinWeightsView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{ShaderSemanticName_SkinJointIndices}, AZ::Name(), meshView.m_skinJointIndicesView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{ShaderSemanticName_SkinWeights}, AZ::Name(), meshView.m_skinWeightsView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set morph target buffers
|
|
if (meshView.m_morphTargetVertexDataView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{ShaderSemanticName_MorphTargetDeltas}, AZ::Name(),
|
|
meshView.m_morphTargetVertexDataView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set cloth data buffer
|
|
if (meshView.m_clothDataView.m_elementCount > 0)
|
|
{
|
|
if (!SetMeshStreamBufferById(RHI::ShaderSemantic{ ShaderSemanticName_ClothData }, AZ::Name(), meshView.m_clothDataView, lodStreamBuffers, lodAssetCreator))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
lodAssetCreator.EndMesh();
|
|
|
|
return true;
|
|
}
|
|
|
|
Outcome<Data::Asset<BufferAsset>> ModelAssetBuilderComponent::CreateTypedBufferAsset(
|
|
const void* data, const size_t elementCount, RHI::Format format, const AZStd::string& bufferName)
|
|
{
|
|
RHI::BufferViewDescriptor bufferViewDescriptor =
|
|
RHI::BufferViewDescriptor::CreateTyped(0, static_cast<uint32_t>(elementCount), format);
|
|
|
|
return CreateBufferAsset(data, bufferViewDescriptor, bufferName);
|
|
}
|
|
|
|
Outcome<Data::Asset<BufferAsset>> ModelAssetBuilderComponent::CreateStructuredBufferAsset(
|
|
const void* data, const size_t elementCount, const size_t elementSize, const AZStd::string& bufferName)
|
|
{
|
|
RHI::BufferViewDescriptor bufferViewDescriptor =
|
|
RHI::BufferViewDescriptor::CreateStructured(0, static_cast<uint32_t>(elementCount), static_cast<uint32_t>(elementSize));
|
|
|
|
return CreateBufferAsset(data, bufferViewDescriptor, bufferName);
|
|
}
|
|
|
|
Outcome<Data::Asset<BufferAsset>> ModelAssetBuilderComponent::CreateRawBufferAsset(
|
|
const void* data, const size_t totalSizeInBytes, const AZStd::string& bufferName)
|
|
{
|
|
RHI::BufferViewDescriptor bufferViewDescriptor =
|
|
RHI::BufferViewDescriptor::CreateRaw(0, static_cast<uint32_t>(totalSizeInBytes));
|
|
|
|
return CreateBufferAsset(data, bufferViewDescriptor, bufferName);
|
|
}
|
|
|
|
Outcome<Data::Asset<BufferAsset>> ModelAssetBuilderComponent::CreateBufferAsset(
|
|
const void* data, const RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName)
|
|
{
|
|
BufferAssetCreator creator;
|
|
AZStd::string bufferAssetName = GetAssetFullName(BufferAsset::TYPEINFO_Uuid(), bufferName);
|
|
creator.Begin(CreateAssetId(bufferAssetName));
|
|
|
|
RHI::BufferDescriptor bufferDescriptor;
|
|
bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::ShaderRead;
|
|
bufferDescriptor.m_byteCount = static_cast<uint64_t>(bufferViewDescriptor.m_elementSize) * static_cast<uint64_t>(bufferViewDescriptor.m_elementCount);
|
|
|
|
creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
|
|
|
|
creator.SetBufferViewDescriptor(bufferViewDescriptor);
|
|
|
|
creator.SetPoolAsset({ m_systemInputAssemblyBufferPoolId, azrtti_typeid<RPI::ResourcePoolAsset>() });
|
|
|
|
Data::Asset<BufferAsset> bufferAsset;
|
|
if (creator.End(bufferAsset))
|
|
{
|
|
bufferAsset.SetHint(bufferAssetName);
|
|
return AZ::Success(bufferAsset);
|
|
}
|
|
|
|
return AZ::Failure();
|
|
}
|
|
|
|
bool ModelAssetBuilderComponent::SetMeshStreamBufferById(
|
|
const RHI::ShaderSemantic& semantic,
|
|
const AZ::Name& customName,
|
|
const RHI::BufferViewDescriptor& bufferViewDescriptor,
|
|
const AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& lodStreamBuffers,
|
|
ModelLodAssetCreator& lodAssetCreator)
|
|
{
|
|
ModelLodAsset::Mesh::StreamBufferInfo streamBufferInfo;
|
|
|
|
if (FindStreamBufferById(lodStreamBuffers, semantic, streamBufferInfo))
|
|
{
|
|
Data::Asset<BufferAsset> bufferAsset = streamBufferInfo.m_bufferAssetView.GetBufferAsset();
|
|
|
|
lodAssetCreator.AddMeshStreamBuffer(semantic, customName, { bufferAsset, bufferViewDescriptor });
|
|
|
|
return true;
|
|
}
|
|
|
|
AZ_Error(s_builderName, false, "Failed to apply the %s buffer to the mesh", semantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
AZStd::string ModelAssetBuilderComponent::GetAssetFullName(const TypeId& assetType, const AZStd::string& bufferName)
|
|
{
|
|
AZStd::string fullName;
|
|
|
|
if (assetType == ModelAsset::TYPEINFO_Uuid())
|
|
{
|
|
fullName = m_modelName;
|
|
}
|
|
else if (assetType == ModelLodAsset::TYPEINFO_Uuid())
|
|
{
|
|
fullName = AZStd::string::format("%s_%s", m_modelName.c_str(), m_lodName.c_str());
|
|
}
|
|
else
|
|
{
|
|
if (m_meshName.empty())
|
|
{
|
|
fullName = AZStd::string::format("%s_%s_%s", m_modelName.c_str(), m_lodName.c_str(), bufferName.c_str());
|
|
}
|
|
else
|
|
{
|
|
fullName = AZStd::string::format("%s_%s_%s_%s", m_modelName.c_str(), m_lodName.c_str(), m_meshName.c_str(), bufferName.c_str());
|
|
}
|
|
}
|
|
return fullName;
|
|
}
|
|
|
|
Data::AssetId ModelAssetBuilderComponent::CreateAssetId(const AZStd::string& assetName)
|
|
{
|
|
// The sub id of any model related assets starts with the same prefix 0x10 for first 8 bits
|
|
// And it uses the name hash for the last 24 bits
|
|
static const uint32_t prefix = 0x10000000;
|
|
|
|
uint32_t productSubId;
|
|
Data::AssetId assetId;
|
|
assetId.SetInvalid();
|
|
|
|
productSubId = prefix | AZ::Crc32(assetName) & 0xffffff;
|
|
|
|
if (m_createdSubId.find(productSubId) != m_createdSubId.end())
|
|
{
|
|
AZ_Error("Mesh builder", false, "Duplicate asset sub id for asset [%s]", assetName.c_str());
|
|
return assetId;
|
|
}
|
|
|
|
m_createdSubId.insert(productSubId);
|
|
|
|
assetId.m_guid = m_sourceUuid;
|
|
assetId.m_subId = productSubId;
|
|
|
|
return assetId;
|
|
}
|
|
|
|
bool ModelAssetBuilderComponent::CalculateAABB(const RHI::BufferViewDescriptor& bufferViewDesc, const BufferAsset& bufferAsset, AZ::Aabb& aabb)
|
|
{
|
|
const uint32_t elementSize = bufferViewDesc.m_elementSize;
|
|
const uint32_t elementCount = bufferViewDesc.m_elementCount;
|
|
const uint32_t elementOffset = bufferViewDesc.m_elementOffset;
|
|
AZ_Assert(elementOffset + elementCount <= bufferAsset.GetBufferViewDescriptor().m_elementCount, "bufferViewDesc is out of range of bufferAsset");
|
|
|
|
// Position is 3 floats
|
|
if (elementSize == sizeof(float) * 3)
|
|
{
|
|
AZ_Assert(bufferViewDesc.m_elementFormat == RHI::Format::R32G32B32_FLOAT, "position buffer format does not match element size");
|
|
|
|
struct Position { float x,y,z; };
|
|
const Position* buffer = reinterpret_cast<const Position*>(&bufferAsset.GetBuffer()[0]) + elementOffset;
|
|
|
|
AZ::Vector3 vpos; //note: it seems to be fastest to reuse a local Vector3 rather than constructing new ones each loop iteration
|
|
for (uint32_t i = 0; i < elementCount; ++i)
|
|
{
|
|
vpos.Set(const_cast<float*>(reinterpret_cast<const float*>(&buffer[i])));
|
|
aabb.AddPoint(vpos);
|
|
}
|
|
}
|
|
// Position is 4 halfs
|
|
else if (elementSize == sizeof(uint16_t) * 4)
|
|
{
|
|
// Can't handle this yet since we have no way to do math on
|
|
// halfs
|
|
AZ_Error(
|
|
s_builderName, false,
|
|
"Can't calculate AABB for SubMesh; positions stored "
|
|
"in halfs not supported.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// No idea what type of position stream this is
|
|
AZ_Error(
|
|
s_builderName, false,
|
|
"Can't calculate AABB for SubMesh; can't determine "
|
|
"element type of stream.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ModelAssetBuilderComponent::MaterialUid ModelAssetBuilderComponent::SourceMeshContent::GetMaterialUniqueId(uint32_t index) const
|
|
{
|
|
if (index >= m_materials.size())
|
|
{
|
|
return s_invalidMaterialUid;
|
|
}
|
|
|
|
return m_materials[index];
|
|
}
|
|
|
|
bool ModelAssetBuilderComponent::FindStreamBufferById(
|
|
const AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferInfoList,
|
|
const RHI::ShaderSemantic& streamSemantic,
|
|
ModelLodAsset::Mesh::StreamBufferInfo& outStreamBufferInfo)
|
|
{
|
|
for (const auto& streamBufferInfo : streamBufferInfoList)
|
|
{
|
|
if (streamBufferInfo.m_semantic == streamSemantic)
|
|
{
|
|
outStreamBufferInfo = streamBufferInfo;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
AZ_Error(s_builderName, false, "Attempted to find a buffer for stream %s but failed!", streamSemantic.ToString().data());
|
|
return false;
|
|
}
|
|
|
|
bool ModelAssetBuilderComponent::GetIsMorphed(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex) const
|
|
{
|
|
// Note: In here we are checking directly in the scene graph. We are also suppose to check if user selected those morph target in blendshape rule, that work
|
|
// will be done when the mesh group support blendshape rule.
|
|
auto contentStorage = graph.GetContentStorage();
|
|
auto downwardsView = AZ::SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<AZ::SceneAPI::Containers::Views::BreadthFirst>(graph, nodeIndex, contentStorage.begin(), true);
|
|
auto filteredView = AZ::SceneAPI::Containers::Views::MakeFilterView(downwardsView, AZ::SceneAPI::Containers::DerivedTypeFilter<AZ::SceneAPI::DataTypes::IBlendShapeData>());
|
|
return (filteredView.begin() != filteredView.end());
|
|
}
|
|
|
|
SceneAPI::DataTypes::MatrixType ModelAssetBuilderComponent::GetWorldTransform(const SceneAPI::Containers::SceneGraph& sceneGraph, SceneAPI::Containers::SceneGraph::NodeIndex node)
|
|
{
|
|
// the logic here copies the logic in @AZ::RC::WorldMatrixExporter::ConcatenateMatricesUpwards
|
|
namespace SceneDataTypes = AZ::SceneAPI::DataTypes;
|
|
namespace SceneViews = AZ::SceneAPI::Containers::Views;
|
|
|
|
SceneAPI::DataTypes::MatrixType transform = SceneAPI::DataTypes::MatrixType::CreateIdentity();
|
|
|
|
const SceneAPI::Containers::SceneGraph::NodeHeader* nodeIterator = sceneGraph.ConvertToHierarchyIterator(node);
|
|
auto upwardsView = SceneViews::MakeSceneGraphUpwardsView(sceneGraph, nodeIterator, sceneGraph.GetContentStorage().cbegin(), true);
|
|
for (auto it = upwardsView.begin(); it != upwardsView.end(); ++it)
|
|
{
|
|
if (!(*it))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const SceneAPI::DataTypes::IGraphObject* nodeTemp = it->get();
|
|
const SceneDataTypes::ITransform* nodeTransform = azrtti_cast<const SceneDataTypes::ITransform*>(nodeTemp);
|
|
if (nodeTransform)
|
|
{
|
|
transform = nodeTransform->GetMatrix() * transform;
|
|
}
|
|
else
|
|
{
|
|
// If the translation is not an end point it means it's its own group as opposed to being
|
|
// a component of the parent, so only list end point children.
|
|
auto view = SceneViews::MakeSceneGraphChildView<SceneViews::AcceptEndPointsOnly>(sceneGraph, it.GetHierarchyIterator(),
|
|
sceneGraph.GetContentStorage().begin(), true);
|
|
auto result = AZStd::find_if(view.begin(), view.end(), SceneAPI::Containers::DerivedTypeFilter<SceneDataTypes::ITransform>());
|
|
if (result != view.end())
|
|
{
|
|
transform = azrtti_cast<const SceneDataTypes::ITransform*>(result->get())->GetMatrix() * transform;
|
|
}
|
|
}
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
} // namespace RPI
|
|
} // namespace AZ
|