24740b3f86
The cloth rule stores the name of a mesh node that is used to retrieve cloth data from. However, at asset processing time, the model builder switches things to look for the optimized version of a mesh. The cloth rule was not doing this, so it would return the cloth data for the unoptimized mesh. This resulted in the final mesh having some data from the optimized mesh and cloth data from the non-optimized mesh. This changes the cloth rule to use the optimized version of a mesh, if it exists, and fall back to the unoptimized mesh when it does not exist. This closes issue 2454. Signed-off-by: Chris Burel <burelc@amazon.com>
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 += 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(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 = numSkinInfluences * sizeof(uint16_t);
|
|
meshView.m_skinJointIndicesView = RHI::BufferViewDescriptor::CreateRaw(0, jointIndicesSizeInBytes);
|
|
meshView.m_skinWeightsView = RHI::BufferViewDescriptor::CreateTyped(0, numSkinInfluences, SkinWeightFormat);
|
|
}
|
|
|
|
if (!mesh.m_morphTargetVertexData.empty())
|
|
{
|
|
const size_t numTotalVertices = mesh.m_morphTargetVertexData.size();
|
|
meshView.m_morphTargetVertexDataView = RHI::BufferViewDescriptor::CreateStructured(0, 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=*/numPrevSkinInfluences * sizeof(uint16_t), numNewSkinInfluences * sizeof(uint16_t));
|
|
meshView.m_skinWeightsView = RHI::BufferViewDescriptor::CreateTyped(/*elementOffset=*/numPrevSkinInfluences, 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=*/numPrevVertexDeltas, 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 = 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
|