You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/NvCloth/Code/Tests/System/FabricCookerTest.cpp

654 lines
31 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 <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <UnitTestHelper.h>
#include <TriangleInputHelper.h>
#include <NvCloth/IFabricCooker.h>
#include <System/SystemComponent.h>
namespace NvCloth
{
namespace Internal
{
FabricId ComputeFabricId(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity,
bool useGeodesicTether);
void CopyCookedData(FabricCookedData::InternalCookedData& azCookedData, const nv::cloth::CookedData& nvCookedData);
AZStd::optional<FabricCookedData> Cook(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity,
bool useGeodesicTether);
void WeldVertices(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& weldedParticles,
AZStd::vector<SimIndexType>& weldedIndices,
AZStd::vector<int>& remappedVertices,
float weldingDistance = AZ::Constants::FloatEpsilon);
void RemoveStaticTriangles(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& simplifiedParticles,
AZStd::vector<SimIndexType>& simplifiedIndices,
AZStd::vector<int>& remappedVertices);
} // namespace Internal
} // namespace NvCloth
namespace UnitTest
{
TEST(NvClothSystem, FactoryCooker_ComputeFabricIdWithNoData_IsValid)
{
NvCloth::FabricId fabricId = NvCloth::Internal::ComputeFabricId({}, {}, {}, false);
EXPECT_TRUE(fabricId.IsValid());
}
TEST(NvClothSystem, FactoryCooker_ComputeFabricIdWithData_IsValid)
{
const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{
0, 1, 2
}};
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
NvCloth::FabricId fabricId = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
EXPECT_TRUE(fabricId.IsValid());
}
TEST(NvClothSystem, FactoryCooker_ComputeFabricIdsWithDifferentGravityParameter_ResultInDifferentIDs)
{
const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
} };
const AZStd::vector<NvCloth::SimIndexType> indices = {{
0, 1, 2
}};
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
NvCloth::FabricId fabricId1 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
NvCloth::FabricId fabricId2 = NvCloth::Internal::ComputeFabricId(particles, indices, 0.5f * gravity, useGeodesicTether);
EXPECT_TRUE(fabricId1.IsValid());
EXPECT_TRUE(fabricId2.IsValid());
EXPECT_NE(fabricId1, fabricId2);
}
TEST(NvClothSystem, FactoryCooker_ComputeFabricIdsWithDifferentUseGeodesicTetherParameter_ResultInDifferentIDs)
{
const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{
0, 1, 2
}};
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
NvCloth::FabricId fabricId1 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
NvCloth::FabricId fabricId2 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, !useGeodesicTether);
EXPECT_TRUE(fabricId1.IsValid());
EXPECT_TRUE(fabricId2.IsValid());
EXPECT_NE(fabricId1, fabricId2);
}
TEST(NvClothSystem, FactoryCooker_CopyInternalCookedDataEmpty_CopiedDataIsEmpty)
{
nv::cloth::CookedData nvCookedData;
nvCookedData.mNumParticles = 0;
NvCloth::FabricCookedData::InternalCookedData azCookedData;
NvCloth::Internal::CopyCookedData(azCookedData, nvCookedData);
EXPECT_EQ(azCookedData.m_numParticles, 0);
EXPECT_TRUE(azCookedData.m_phaseIndices.empty());
EXPECT_TRUE(azCookedData.m_phaseTypes.empty());
EXPECT_TRUE(azCookedData.m_sets.empty());
EXPECT_TRUE(azCookedData.m_restValues.empty());
EXPECT_TRUE(azCookedData.m_stiffnessValues.empty());
EXPECT_TRUE(azCookedData.m_indices.empty());
EXPECT_TRUE(azCookedData.m_anchors.empty());
EXPECT_TRUE(azCookedData.m_tetherLengths.empty());
EXPECT_TRUE(azCookedData.m_triangles.empty());
ExpectEq(azCookedData, nvCookedData);
}
TEST(NvClothSystem, FactoryCooker_CopyInternalCookedData_CopiedDataMatchesSource)
{
const AZ::u32 data[] = { 0, 2, 45, 64, 125 };
const size_t numDataElements = sizeof(data) / sizeof(data[0]);
nv::cloth::CookedData nvCookedData;
nvCookedData.mNumParticles = 0;
NvCloth::FabricCookedData::InternalCookedData azCookedData;
NvCloth::Internal::CopyCookedData(azCookedData, nvCookedData);
ExpectEq(azCookedData, nvCookedData);
}
TEST(NvClothSystem, FactoryCooker_CookEmptyMesh_ReturnsNoData)
{
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_TRUE(NvCloth::SystemComponent::CheckLastClothError());
AZStd::optional<NvCloth::FabricCookedData> fabricCookedData = NvCloth::Internal::Cook({}, {}, {}, false);
NvCloth::SystemComponent::ResetLastClothError(); // Reset the nvcloth error left in SystemComponent
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Expect 1 error
EXPECT_FALSE(fabricCookedData.has_value());
}
TEST(NvClothSystem, FactoryCooker_CookWithIncorrectIndices_ReturnsNoData)
{
const AZStd::vector<NvCloth::SimIndexType> incorrectIndices = {{ 0, 1 }}; // Use incorrect number of indices for a triangle (multiple of 3)
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_TRUE(NvCloth::SystemComponent::CheckLastClothError());
AZStd::optional<NvCloth::FabricCookedData> fabricCookedData = NvCloth::Internal::Cook({}, incorrectIndices, {}, false);
NvCloth::SystemComponent::ResetLastClothError(); // Reset the nvcloth error left in SystemComponent
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Expect 1 error
EXPECT_FALSE(fabricCookedData.has_value());
}
TEST(NvClothSystem, FactoryCooker_CookTriangle_CooksDataCorrectly)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 0.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 0.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(0.0f, 1.0f, 0.0f, 1.0f),
}};
const AZStd::vector<NvCloth::SimIndexType> indices = { {0, 1, 2} };
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
NvCloth::Internal::Cook(vertices, indices, gravity, useGeodesicTether);
EXPECT_TRUE(fabricCookedData.has_value());
EXPECT_TRUE(fabricCookedData->m_id.IsValid());
EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), vertices));
EXPECT_EQ(fabricCookedData->m_indices, indices);
EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, vertices.size());
}
TEST(NvClothSystem, FactoryCooker_CookTriangleAllStatic_CooksDataCorrectly)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 0.0f, 0.0f, 0.0f),
NvCloth::SimParticleFormat(1.0f, 0.0f, 0.0f, 0.0f),
NvCloth::SimParticleFormat(0.0f, 1.0f, 0.0f, 0.0f),
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
NvCloth::Internal::Cook(vertices, indices, gravity, useGeodesicTether);
EXPECT_TRUE(fabricCookedData.has_value());
EXPECT_TRUE(fabricCookedData->m_id.IsValid());
EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), vertices));
EXPECT_EQ(fabricCookedData->m_indices, indices);
EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, vertices.size());
}
TEST(NvClothSystem, FactoryCooker_CookMesh_CooksDataCorrectly)
{
const float width = 1.0f;
const float height = 1.0f;
const AZ::u32 segmentsX = 10;
const AZ::u32 segmentsY = 10;
const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
const bool useGeodesicTether = true;
const TriangleInput planeXY = CreatePlane(width, height, segmentsX, segmentsY);
AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
NvCloth::Internal::Cook(planeXY.m_vertices, planeXY.m_indices, gravity, useGeodesicTether);
EXPECT_TRUE(fabricCookedData.has_value());
EXPECT_TRUE(fabricCookedData->m_id.IsValid());
EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), planeXY.m_vertices));
EXPECT_EQ(fabricCookedData->m_indices, planeXY.m_indices);
EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, planeXY.m_vertices.size());
}
TEST(NvClothSystem, FactoryCooker_WeldVerticesEmptyMesh_ReturnsEmptyData)
{
AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
AZStd::vector<NvCloth::SimIndexType> weldedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::WeldVertices({}, {}, weldedVertices, weldedIndices, remappedVertices);
EXPECT_TRUE(weldedVertices.empty());
EXPECT_TRUE(weldedIndices.empty());
EXPECT_TRUE(remappedVertices.empty());
}
TEST(NvClothSystem, FactoryCooker_WeldVerticesTriangle_KeepsLowestInverseMass)
{
const AZ::Vector3 vertexPosition(100.2f, 300.2f, -30.62f);
const size_t expectedSizeAfterWelding = 1;
const float lowestInverseMass = 0.2f;
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, lowestInverseMass), // This vertex has the lowest inverse mass
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, 0.5f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
AZStd::vector<NvCloth::SimIndexType> weldedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
ASSERT_EQ(weldedVertices.size(), expectedSizeAfterWelding);
EXPECT_THAT(weldedVertices[0].GetAsVector3(), IsCloseTolerance(vertexPosition, Tolerance));
EXPECT_NEAR(weldedVertices[0].GetW(), lowestInverseMass, Tolerance);
}
TEST(NvClothSystem, FactoryCooker_WeldVerticesSquareWithDuplicatedVertices_DuplicatedVerticesAreRemoved)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat( 1.0f, 1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat( 1.0f, 1.0f, 0.0f, 1.0f), // Duplicated vertex
NvCloth::SimParticleFormat( 1.0f,-1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f) // Duplicated vertex
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
const size_t expectedSizeAfterWelding = vertices.size() - 2;
AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
AZStd::vector<NvCloth::SimIndexType> weldedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
ASSERT_EQ(weldedVertices.size(), expectedSizeAfterWelding);
ASSERT_EQ(weldedIndices.size(), indices.size());
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], weldedVertices.size());
EXPECT_THAT(weldedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
for (size_t i = 0; i < weldedIndices.size(); ++i)
{
EXPECT_GE(weldedIndices[i], 0);
EXPECT_LT(weldedIndices[i], weldedVertices.size());
EXPECT_EQ(weldedIndices[i], remappedVertices[indices[i]]);
EXPECT_THAT(weldedVertices[weldedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
TEST(NvClothSystem, FactoryCooker_WeldVerticesTrianglesWithoutDuplicatedVertices_ResultIsTheSame)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
AZStd::vector<NvCloth::SimIndexType> weldedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
// The result after calling WeldVertices is expected to have the same size.
// The vertices inside will be reordered though due to the welding process.
ASSERT_EQ(weldedVertices.size(), vertices.size());
ASSERT_EQ(weldedIndices.size(), indices.size());
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], weldedVertices.size());
EXPECT_THAT(weldedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
for (size_t i = 0; i < weldedIndices.size(); ++i)
{
EXPECT_GE(weldedIndices[i], 0);
EXPECT_LT(weldedIndices[i], weldedVertices.size());
EXPECT_EQ(weldedIndices[i], remappedVertices[indices[i]]);
EXPECT_THAT(weldedVertices[weldedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesEmptyMesh_ReturnsEmptyData)
{
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::RemoveStaticTriangles({}, {}, simplifiedVertices, simplifiedIndices, remappedVertices);
EXPECT_TRUE(simplifiedVertices.empty());
EXPECT_TRUE(simplifiedIndices.empty());
EXPECT_TRUE(remappedVertices.empty());
}
TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithOneStaticTriangle_RemovesAllVerticesAndIndices)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
EXPECT_TRUE(simplifiedVertices.empty());
EXPECT_TRUE(simplifiedIndices.empty());
EXPECT_EQ(remappedVertices.size(), vertices.size());
for (const auto& remappedVertex : remappedVertices)
{
EXPECT_LT(remappedVertex, 0); // Remapping must be negative, meaning vertex has been removed.
}
}
TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithStaticTriangles_StaticTriangleAndVerticesAreRemoved)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }}; // First triangle from the triplet 0,1,2 uses all static vertices and will be removed
const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 3;
const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
// The first 3 vertices should have been removed, so the remapping should be negative
if (i < 3)
{
EXPECT_LT(remappedVertices[i], 0);
}
else
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
}
for (size_t i = 0; i < simplifiedIndices.size(); ++i)
{
EXPECT_GE(simplifiedIndices[i], 0);
EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
}
for (size_t i = 0; i < indices.size(); ++i)
{
int remappedVertex = remappedVertices[indices[i]];
if (remappedVertex >= 0) // If the vertex has not been removed
{
EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
}
TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithStaticTrianglesSharedVertices_StaticTriangleAndVerticesAreRemoved)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will remain because it's also used in the third triangle too
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 3, 4, 5, 0, 1, 2, 3, 1, 5 }}; // Second triangle from the triplet 0,1,2 uses all static vertices and will be removed
const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 2;
const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
// The first and third vertex should have been removed, so the remapping should be negative.
if (i == 0 || i == 2)
{
EXPECT_LT(remappedVertices[i], 0);
}
else
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
}
for (size_t i = 0; i < simplifiedIndices.size(); ++i)
{
EXPECT_GE(simplifiedIndices[i], 0);
EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
}
for (size_t i = 0; i < indices.size(); ++i)
{
int remappedVertex = remappedVertices[indices[i]];
if (remappedVertex >= 0) // If the vertex has not been removed
{
EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
}
TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithNonStaticTriangles_ResultIsTheSame)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
// The result after calling RemoveStaticTriangles is expected to have the same size.
// The vertices will be reordered though due to the processing during simplification.
ASSERT_EQ(simplifiedVertices.size(), vertices.size());
ASSERT_EQ(simplifiedIndices.size(), indices.size());
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
for (size_t i = 0; i < simplifiedIndices.size(); ++i)
{
EXPECT_GE(simplifiedIndices[i], 0);
EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
EXPECT_EQ(simplifiedIndices[i], remappedVertices[indices[i]]);
EXPECT_THAT(simplifiedVertices[simplifiedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
TEST(NvClothSystem, FactoryCooker_SimplifiyMeshWithDuplicatedVerticesAndStaticTriangles_DuplicatedVerticesAndStaticTrianglesAreRemoved)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = { {
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will remain because it's also used in the third triangle too
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f), // Duplicated vertex
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f) // Duplicated vertex
} };
const AZStd::vector<NvCloth::SimIndexType> indices = {{ 3, 4, 5, 0, 1, 2, 6, 1, 7 }}; // Second triangle from the triplet 0,1,2 uses all static vertices and will be removed
const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 4;
const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
const bool removeStaticTriangles = true;
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
AZ::Interface<NvCloth::IFabricCooker>::Get()->SimplifyMesh(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices, removeStaticTriangles);
ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
// The first and third vertex should have been removed, so the remapping should be negative.
if (i == 0 || i == 2)
{
EXPECT_LT(remappedVertices[i], 0);
}
else
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
}
for (size_t i = 0; i < simplifiedIndices.size(); ++i)
{
EXPECT_GE(simplifiedIndices[i], 0);
EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
}
for (size_t i = 0; i < indices.size(); ++i)
{
int remappedVertex = remappedVertices[indices[i]];
if (remappedVertex >= 0) // If the vertex has not been removed
{
EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
}
TEST(NvClothSystem, FactoryCooker_SimplifiyMeshWithoutRemovingStaticTriangles_DuplicatedVerticesRemovedAndStaticTrianglesRemain)
{
const AZStd::vector<NvCloth::SimParticleFormat> vertices = { {
NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will remain because we won't remove static triangles
NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will remain because it's also used in the third triangle too
NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will remain because we won't remove static triangles
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f),
NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f), // Duplicated vertex
NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f) // Duplicated vertex
} };
const AZStd::vector<NvCloth::SimIndexType> indices = { { 3, 4, 5, 0, 1, 2, 6, 1, 7 } }; // Second triangle from the triplet 0,1,2 uses all static vertices and will be removed
const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 2;
const size_t expectedIndicesSizeAfterSimplification = indices.size();
const bool removeStaticTriangles = false;
AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
AZStd::vector<int> remappedVertices;
AZ::Interface<NvCloth::IFabricCooker>::Get()->SimplifyMesh(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices, removeStaticTriangles);
ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
ASSERT_EQ(remappedVertices.size(), vertices.size());
for (size_t i = 0; i < remappedVertices.size(); ++i)
{
EXPECT_GE(remappedVertices[i], 0);
EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
}
for (size_t i = 0; i < simplifiedIndices.size(); ++i)
{
EXPECT_GE(simplifiedIndices[i], 0);
EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
EXPECT_EQ(simplifiedIndices[i], remappedVertices[indices[i]]);
EXPECT_THAT(simplifiedVertices[simplifiedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
}
}
} // namespace UnitTest