[MeshOptimizer] Determine the original vertex index based on the position (#2008)
* Determine the original vertex index based on the position The Assimp library does not expose the FBX control point indices. This change causes vertices that are close enough in their position to be considered as coming from the same control point. This allows the mesh optimizer to consider vertices with the same control point index (or "original vertex index" as it is called in the code) for deduplication. Signed-off-by: Chris Burel <burelc@amazon.com> * Use a filter view instead of reimplementing a filter view Signed-off-by: Chris Burel <burelc@amazon.com> * Don't attempt to weld similar vertices if there's blendshapes Signed-off-by: Chris Burel <burelc@amazon.com> * Add test for the mesh optimizer's ability to weld nearby vertices Signed-off-by: Chris Burel <burelc@amazon.com> * Add logging call to show mesh optimizer effect on vertex count Signed-off-by: Chris Burel <burelc@amazon.com> * Use a bunch of temporaries in order to make `position` `const` Signed-off-by: Chris Burel <burelc@amazon.com> * Supply the vertex index remapping to the optimized skin weights This ensures that the optimized skin weights use the vertex indexes from the optimized mesh Signed-off-by: Chris Burel <burelc@amazon.com>
This commit is contained in:
@@ -28,27 +28,27 @@ namespace AZ
|
||||
}
|
||||
namespace SceneData
|
||||
{
|
||||
class MeshGroup
|
||||
class SCENE_DATA_CLASS MeshGroup
|
||||
: public DataTypes::IMeshGroup
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(MeshGroup, "{07B356B7-3635-40B5-878A-FAC4EFD5AD86}", DataTypes::IMeshGroup);
|
||||
AZ_CLASS_ALLOCATOR(MeshGroup, SystemAllocator, 0)
|
||||
|
||||
MeshGroup();
|
||||
~MeshGroup() override = default;
|
||||
SCENE_DATA_API MeshGroup();
|
||||
SCENE_DATA_API ~MeshGroup() override = default;
|
||||
|
||||
const AZStd::string& GetName() const override;
|
||||
void SetName(const AZStd::string& name);
|
||||
void SetName(AZStd::string&& name) override;
|
||||
const Uuid& GetId() const override;
|
||||
void OverrideId(const Uuid& id) override;
|
||||
SCENE_DATA_API const AZStd::string& GetName() const override;
|
||||
SCENE_DATA_API void SetName(const AZStd::string& name);
|
||||
SCENE_DATA_API void SetName(AZStd::string&& name) override;
|
||||
SCENE_DATA_API const Uuid& GetId() const override;
|
||||
SCENE_DATA_API void OverrideId(const Uuid& id) override;
|
||||
|
||||
Containers::RuleContainer& GetRuleContainer() override;
|
||||
const Containers::RuleContainer& GetRuleContainerConst() const override;
|
||||
SCENE_DATA_API Containers::RuleContainer& GetRuleContainer() override;
|
||||
SCENE_DATA_API const Containers::RuleContainer& GetRuleContainerConst() const override;
|
||||
|
||||
DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override;
|
||||
const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override;
|
||||
SCENE_DATA_API DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override;
|
||||
SCENE_DATA_API const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static bool VersionConverter(SerializeContext& context, SerializeContext::DataElementNode& classElement);
|
||||
|
||||
@@ -112,6 +112,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
PRIVATE
|
||||
Gem::SceneProcessing.Editor.Static
|
||||
AZ::AzTest
|
||||
AZ::SceneData
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME Gem::SceneProcessing.Editor.Tests
|
||||
|
||||
+105
-22
@@ -96,6 +96,85 @@ namespace AZ::SceneGenerationComponents
|
||||
namespace Containers = AZ::SceneAPI::Containers;
|
||||
namespace Views = Containers::Views;
|
||||
|
||||
// @brief A class to map from a mesh's vertex index to it's welded vertex index
|
||||
//
|
||||
// When the mesh optimizer runs, it welds nearby vertices (if there are no blendshapes). This class provides a
|
||||
// constant time lookup to map from an unwelded vertex index to the welded one.
|
||||
// The welding works by rounding the vertex's position to the given position tolerance, then uses that rounded
|
||||
// Vector3 as a key into a unordered_map.
|
||||
template <class MeshDataType>
|
||||
class Vector3Map
|
||||
: private AZStd::unordered_map<AZ::Vector3, AZ::u32>
|
||||
{
|
||||
public:
|
||||
Vector3Map(const MeshDataType* meshData, bool hasBlendShapes, float positionTolerance)
|
||||
: m_meshData(meshData)
|
||||
, m_hasBlendShapes(hasBlendShapes)
|
||||
, m_positionTolerance(positionTolerance)
|
||||
, m_positionToleranceReciprocal(1.0f / positionTolerance)
|
||||
{
|
||||
}
|
||||
|
||||
using AZStd::unordered_map<AZ::Vector3, AZ::u32>::reserve;
|
||||
|
||||
AZ::u32 operator[](const AZ::u32 vertexIndex)
|
||||
{
|
||||
if (m_hasBlendShapes)
|
||||
{
|
||||
// Don't attempt to weld similar vertices if there's blendshapes
|
||||
// Welding the vertices here based on position could cause the vertices of a base shape to be welded,
|
||||
// and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between
|
||||
// the two
|
||||
return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex));
|
||||
}
|
||||
|
||||
const auto& [iter, didInsert] = try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex);
|
||||
if (didInsert)
|
||||
{
|
||||
++m_currentOriginalVertexIndex;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] AZ::u32 at(const AZ::u32 vertexIndex) const
|
||||
{
|
||||
if (m_hasBlendShapes)
|
||||
{
|
||||
// Don't attempt to weld similar vertices if there's blendshapes
|
||||
// Welding the vertices here based on position could cause the vertices of a base shape to be welded,
|
||||
// and the vertices of the blendshape to not be welded, resulting in a vertex count mismatch between
|
||||
// the two
|
||||
return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex));
|
||||
}
|
||||
|
||||
auto iter = find(GetPositionForIndex(vertexIndex));
|
||||
AZSTD_CONTAINER_ASSERT(iter != end(), "Element with key is not present");
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
AZ::Vector3 GetPositionForIndex(const AZ::u32 vertexIndex) const
|
||||
{
|
||||
// Round the vertex position so that a float comparison can be made with entires in the map
|
||||
// pos = floor( x * 10 + 0.5) * 0.1
|
||||
return AZ::Vector3(
|
||||
AZ::Simd::Vec3::Floor(
|
||||
(m_meshData->GetPosition(vertexIndex) * m_positionToleranceReciprocal + AZ::Vector3(0.5f)).GetSimdValue()
|
||||
)
|
||||
) * m_positionTolerance;
|
||||
}
|
||||
|
||||
const MeshDataType* m_meshData;
|
||||
bool m_hasBlendShapes;
|
||||
float m_positionTolerance;
|
||||
float m_positionToleranceReciprocal;
|
||||
AZ::u32 m_currentOriginalVertexIndex = 0;
|
||||
};
|
||||
|
||||
template<class MeshDataType>
|
||||
Vector3Map(const MeshDataType*) -> Vector3Map<const MeshDataType>;
|
||||
|
||||
MeshOptimizerComponent::MeshOptimizerComponent()
|
||||
{
|
||||
BindToCall(&MeshOptimizerComponent::OptimizeMeshes);
|
||||
@@ -106,7 +185,7 @@ namespace AZ::SceneGenerationComponents
|
||||
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<MeshOptimizerComponent, GenerationComponent>()->Version(2);
|
||||
serializeContext->Class<MeshOptimizerComponent, GenerationComponent>()->Version(4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +194,8 @@ namespace AZ::SceneGenerationComponents
|
||||
const MeshDataType* meshData,
|
||||
const SkinWeightDataView& skinWeights,
|
||||
AZ::u32 maxWeightsPerVertex,
|
||||
float weightThreshold)
|
||||
float weightThreshold,
|
||||
const Vector3Map<MeshDataType>& positionMap)
|
||||
{
|
||||
if (skinWeights.empty())
|
||||
{
|
||||
@@ -141,15 +221,12 @@ namespace AZ::SceneGenerationComponents
|
||||
for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex)
|
||||
{
|
||||
const ISkinWeightData::Link& link = skinData.get().GetLink(controlPointIndex, linkIndex);
|
||||
skinningInfo->AddInfluence(usedPointIndex, {aznumeric_caster(link.boneId), link.weight});
|
||||
skinningInfo->AddInfluence(positionMap.at(usedPointIndex), {aznumeric_caster(link.boneId), link.weight});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skinningInfo)
|
||||
{
|
||||
skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold);
|
||||
}
|
||||
skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold);
|
||||
|
||||
return skinningInfo;
|
||||
}
|
||||
@@ -192,17 +269,12 @@ namespace AZ::SceneGenerationComponents
|
||||
const AZStd::vector<AZStd::pair<const IMeshData*, NodeIndex>> meshes = [](const SceneGraph& graph)
|
||||
{
|
||||
AZStd::vector<AZStd::pair<const IMeshData*, NodeIndex>> meshes;
|
||||
for (auto it = graph.GetContentStorage().cbegin(); it != graph.GetContentStorage().cend(); ++it)
|
||||
const auto meshNodes = Containers::MakeDerivedFilterView<IMeshData>(graph.GetContentStorage());
|
||||
for (auto it = meshNodes.cbegin(); it != meshNodes.cend(); ++it)
|
||||
{
|
||||
// Skip anything that isn't a mesh.
|
||||
const auto* mesh = azdynamic_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(it->get());
|
||||
if (!mesh)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later.
|
||||
meshes.emplace_back(mesh, graph.ConvertToNodeIndex(it));
|
||||
// The sequential calls to GetBaseIterator unwrap the layers of FilterIterators from the MakeDerivedFilterView
|
||||
meshes.emplace_back(&(*it), graph.ConvertToNodeIndex(it.GetBaseIterator().GetBaseIterator().GetBaseIterator()));
|
||||
}
|
||||
return meshes;
|
||||
}(graph);
|
||||
@@ -287,6 +359,12 @@ namespace AZ::SceneGenerationComponents
|
||||
|
||||
auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes);
|
||||
|
||||
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Base mesh: %zu vertices, optimized mesh: %zu vertices, %0.02f%% of the original",
|
||||
mesh->GetUsedControlPointCount(),
|
||||
optimizedMesh->GetUsedControlPointCount(),
|
||||
((float)optimizedMesh->GetUsedControlPointCount() / (float)mesh->GetUsedControlPointCount()) * 100.0f
|
||||
);
|
||||
|
||||
const NodeIndex optimizedMeshNodeIndex = graph.AddChild(graph.GetNodeParent(nodeIndex), name.c_str(), AZStd::move(optimizedMesh));
|
||||
|
||||
auto addOptimizedNodes = [&graph, &optimizedMeshNodeIndex](const auto& originalNodeIndexes, auto& optimizedNodes)
|
||||
@@ -428,10 +506,9 @@ namespace AZ::SceneGenerationComponents
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector3*> bitangentLayers = makeLayersForData(bitangents);
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerColor*> vertexColorLayers = makeLayersForData(vertexColors);
|
||||
|
||||
const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType<SceneAPI::DataTypes::ISkinRule>().get();
|
||||
const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4;
|
||||
const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f;
|
||||
meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold));
|
||||
constexpr float positionTolerance = 0.0001f;
|
||||
Vector3Map positionMap(meshData, hasBlendShapes, positionTolerance);
|
||||
positionMap.reserve(vertexCount);
|
||||
|
||||
// Add the vertex data to all the layers
|
||||
const AZ::u32 faceCount = meshData->GetFaceCount();
|
||||
@@ -440,8 +517,8 @@ namespace AZ::SceneGenerationComponents
|
||||
meshBuilder.BeginPolygon(baseMesh->GetFaceMaterialId(faceIndex));
|
||||
for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex)
|
||||
{
|
||||
const int orgVertexNumber = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(vertexIndex));
|
||||
AZ_Assert(orgVertexNumber >= 0, "Invalid vertex number");
|
||||
const AZ::u32 orgVertexNumber = positionMap[vertexIndex];
|
||||
|
||||
orgVtxLayer->SetCurrentVertexValue(orgVertexNumber);
|
||||
|
||||
posLayer->SetCurrentVertexValue(meshData->GetPosition(vertexIndex));
|
||||
@@ -471,6 +548,12 @@ namespace AZ::SceneGenerationComponents
|
||||
|
||||
meshBuilder.EndPolygon();
|
||||
}
|
||||
|
||||
const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType<SceneAPI::DataTypes::ISkinRule>().get();
|
||||
const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4;
|
||||
const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f;
|
||||
meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold, positionMap));
|
||||
|
||||
meshBuilder.GenerateSubMeshVertexOrders();
|
||||
|
||||
// Create the resulting nodes
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Jobs/JobManagerComponent.h>
|
||||
#include <AzCore/Memory/MemoryComponent.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h>
|
||||
#include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
|
||||
#include <SceneAPI/SceneCore/Utilities/SceneGraphSelector.h>
|
||||
#include <SceneAPI/SceneData/GraphData/MeshData.h>
|
||||
#include <SceneAPI/SceneData/GraphData/SkinWeightData.h>
|
||||
#include <SceneAPI/SceneData/Groups/MeshGroup.h>
|
||||
#include <Generation/Components/MeshOptimizer/MeshOptimizerComponent.h>
|
||||
|
||||
#include <InitSceneAPIFixture.h>
|
||||
|
||||
namespace AZ::SceneAPI::DataTypes
|
||||
{
|
||||
void PrintTo(const ISkinWeightData::Link& link, ::std::ostream* os)
|
||||
{
|
||||
*os << '{' << link.boneId << ", " << link.weight << '}';
|
||||
}
|
||||
}
|
||||
|
||||
namespace SceneProcessing
|
||||
{
|
||||
class VertexDeduplicationFixture
|
||||
: public SceneProcessing::InitSceneAPIFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
SceneProcessing::InitSceneAPIFixture::SetUp();
|
||||
|
||||
m_systemEntity = m_app.Create({}, {});
|
||||
m_systemEntity->AddComponent(aznew AZ::MemoryComponent());
|
||||
m_systemEntity->AddComponent(aznew AZ::JobManagerComponent());
|
||||
m_systemEntity->Init();
|
||||
m_systemEntity->Activate();
|
||||
}
|
||||
void TearDown() override
|
||||
{
|
||||
m_systemEntity->Deactivate();
|
||||
SceneProcessing::InitSceneAPIFixture::TearDown();
|
||||
}
|
||||
|
||||
static AZStd::unique_ptr<AZ::SceneAPI::DataTypes::IMeshData> MakePlaneMesh()
|
||||
{
|
||||
// Create a simple plane with 2 triangles, 6 total vertices, 2 shared vertices
|
||||
// 0 --- 1
|
||||
// | / |
|
||||
// | / |
|
||||
// | / |
|
||||
// 2 --- 3
|
||||
const AZStd::array planeVertexPositions = {
|
||||
AZ::Vector3{0.0f, 0.0f, 0.0f},
|
||||
AZ::Vector3{0.0f, 0.0f, 1.0f},
|
||||
AZ::Vector3{1.0f, 0.0f, 1.0f},
|
||||
AZ::Vector3{1.0f, 0.0f, 1.0f},
|
||||
AZ::Vector3{1.0f, 0.0f, 0.0f},
|
||||
AZ::Vector3{0.0f, 0.0f, 0.0f},
|
||||
};
|
||||
|
||||
auto mesh = AZStd::make_unique<AZ::SceneData::GraphData::MeshData>();
|
||||
|
||||
int i = 0;
|
||||
for (const AZ::Vector3& position : planeVertexPositions)
|
||||
{
|
||||
mesh->AddPosition(position);
|
||||
mesh->AddNormal(AZ::Vector3::CreateAxisY());
|
||||
|
||||
// This assumes that the data coming from the import process gives a unique control point
|
||||
// index to every vertex. This follows the behavior of the AssImp library.
|
||||
mesh->SetVertexIndexToControlPointIndexMap(i, i);
|
||||
++i;
|
||||
}
|
||||
|
||||
mesh->AddFace({0, 1, 2}, 0);
|
||||
mesh->AddFace({3, 4, 5}, 0);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
static AZStd::unique_ptr<AZ::SceneData::GraphData::SkinWeightData> MakeSkinData()
|
||||
{
|
||||
auto skinWeights = AZStd::make_unique<AZ::SceneData::GraphData::SkinWeightData>();
|
||||
|
||||
skinWeights->ResizeContainerSpace(6);
|
||||
|
||||
// Add bones 0 and 1 to the skin weights
|
||||
skinWeights->GetBoneId("0");
|
||||
skinWeights->GetBoneId("1");
|
||||
|
||||
skinWeights->AppendLink(0, {/*.boneId=*/0, /*.weight=*/1});
|
||||
skinWeights->AppendLink(1, {/*.boneId=*/0, /*.weight=*/1});
|
||||
skinWeights->AppendLink(2, {/*.boneId=*/0, /*.weight=*/1});
|
||||
skinWeights->AppendLink(3, {/*.boneId=*/1, /*.weight=*/1});
|
||||
skinWeights->AppendLink(4, {/*.boneId=*/1, /*.weight=*/1});
|
||||
skinWeights->AppendLink(5, {/*.boneId=*/1, /*.weight=*/1});
|
||||
|
||||
return skinWeights;
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::ComponentApplication m_app;
|
||||
AZ::Entity* m_systemEntity;
|
||||
};
|
||||
|
||||
TEST_F(VertexDeduplicationFixture, CanDeduplicateVertices)
|
||||
{
|
||||
AZ::SceneAPI::Containers::Scene scene("testScene");
|
||||
AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
|
||||
|
||||
const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh());
|
||||
|
||||
// The original source mesh should have 6 vertices
|
||||
EXPECT_EQ(AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
|
||||
|
||||
auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
|
||||
meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh");
|
||||
scene.GetManifest().AddEntry(AZStd::move(meshGroup));
|
||||
|
||||
AZ::SceneGenerationComponents::MeshOptimizerComponent component;
|
||||
AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc");
|
||||
component.OptimizeMeshes(context);
|
||||
|
||||
AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix));
|
||||
ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh";
|
||||
|
||||
const auto& optimizedMesh = AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(optimizedNodeIndex));
|
||||
ASSERT_TRUE(optimizedMesh);
|
||||
|
||||
// The optimized mesh should have 4 vertices, the 2 shared vertices are welded together
|
||||
EXPECT_EQ(optimizedMesh->GetVertexCount(), 4);
|
||||
}
|
||||
|
||||
MATCHER(VectorOfLinksEq, "")
|
||||
{
|
||||
return testing::ExplainMatchResult(
|
||||
testing::AllOf(
|
||||
testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)),
|
||||
testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight))
|
||||
),
|
||||
testing::get<1>(arg),
|
||||
result_listener
|
||||
);
|
||||
}
|
||||
|
||||
MATCHER(VectorOfVectorOfLinksEq, "")
|
||||
{
|
||||
return testing::ExplainMatchResult(
|
||||
testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)),
|
||||
testing::get<1>(arg),
|
||||
result_listener
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesRemapSkinning)
|
||||
{
|
||||
AZ::SceneAPI::Containers::Scene scene("testScene");
|
||||
AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
|
||||
|
||||
const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh());
|
||||
const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", MakeSkinData());
|
||||
graph.MakeEndPoint(skinDataNodeIndex);
|
||||
|
||||
// The original source mesh should have 6 vertices
|
||||
EXPECT_EQ(AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
|
||||
|
||||
auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
|
||||
meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh");
|
||||
scene.GetManifest().AddEntry(AZStd::move(meshGroup));
|
||||
|
||||
AZ::SceneGenerationComponents::MeshOptimizerComponent component;
|
||||
AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc");
|
||||
component.OptimizeMeshes(context);
|
||||
|
||||
AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix));
|
||||
ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh";
|
||||
|
||||
const auto& optimizedMesh = AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(optimizedNodeIndex));
|
||||
ASSERT_TRUE(optimizedMesh);
|
||||
|
||||
AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights"));
|
||||
ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data";
|
||||
|
||||
const auto& optimizedSkinWeights = AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::ISkinWeightData>(graph.GetNodeContent(optimizedSkinDataNodeIndex));
|
||||
ASSERT_TRUE(optimizedSkinWeights);
|
||||
|
||||
const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> expectedLinks
|
||||
{
|
||||
/*0*/ { {0, 0.5f}, {1, 0.5f} },
|
||||
/*1*/ { {0, 1.0f} },
|
||||
/*2*/ { {0, 0.5f}, {1, 0.5f} },
|
||||
/*3*/ { {1, 1.0f} },
|
||||
};
|
||||
|
||||
AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> gotLinks(optimizedMesh->GetVertexCount());
|
||||
for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex)
|
||||
{
|
||||
for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex)
|
||||
{
|
||||
gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex));
|
||||
}
|
||||
}
|
||||
EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks));
|
||||
}
|
||||
} // namespace SceneProcessing
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
set(FILES
|
||||
Tests/InitSceneAPIFixture.h
|
||||
Tests/MeshBuilder/MeshOptimizerComponentTests.cpp
|
||||
Tests/MeshBuilder/MeshBuilderTests.cpp
|
||||
Tests/MeshBuilder/MeshVerticesTests.cpp
|
||||
Tests/MeshBuilder/SkinInfluencesTests.cpp
|
||||
|
||||
Reference in New Issue
Block a user