FabricCookerTest.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AZTestShared/Math/MathTestHelpers.h>
  9. #include <AzCore/Interface/Interface.h>
  10. #include <AzCore/UnitTest/UnitTest.h>
  11. #include <UnitTestHelper.h>
  12. #include <TriangleInputHelper.h>
  13. #include <NvCloth/IFabricCooker.h>
  14. #include <System/SystemComponent.h>
  15. namespace NvCloth
  16. {
  17. namespace Internal
  18. {
  19. FabricId ComputeFabricId(
  20. const AZStd::vector<SimParticleFormat>& particles,
  21. const AZStd::vector<SimIndexType>& indices,
  22. const AZ::Vector3& fabricGravity,
  23. bool useGeodesicTether);
  24. void CopyCookedData(FabricCookedData::InternalCookedData& azCookedData, const nv::cloth::CookedData& nvCookedData);
  25. AZStd::optional<FabricCookedData> Cook(
  26. const AZStd::vector<SimParticleFormat>& particles,
  27. const AZStd::vector<SimIndexType>& indices,
  28. const AZ::Vector3& fabricGravity,
  29. bool useGeodesicTether);
  30. void WeldVertices(
  31. const AZStd::vector<SimParticleFormat>& particles,
  32. const AZStd::vector<SimIndexType>& indices,
  33. AZStd::vector<SimParticleFormat>& weldedParticles,
  34. AZStd::vector<SimIndexType>& weldedIndices,
  35. AZStd::vector<int>& remappedVertices,
  36. float weldingDistance = AZ::Constants::FloatEpsilon);
  37. void RemoveStaticTriangles(
  38. const AZStd::vector<SimParticleFormat>& particles,
  39. const AZStd::vector<SimIndexType>& indices,
  40. AZStd::vector<SimParticleFormat>& simplifiedParticles,
  41. AZStd::vector<SimIndexType>& simplifiedIndices,
  42. AZStd::vector<int>& remappedVertices);
  43. } // namespace Internal
  44. } // namespace NvCloth
  45. namespace UnitTest
  46. {
  47. TEST(NvClothSystem, FactoryCooker_ComputeFabricIdWithNoData_IsValid)
  48. {
  49. NvCloth::FabricId fabricId = NvCloth::Internal::ComputeFabricId({}, {}, {}, false);
  50. EXPECT_TRUE(fabricId.IsValid());
  51. }
  52. TEST(NvClothSystem, FactoryCooker_ComputeFabricIdWithData_IsValid)
  53. {
  54. const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
  55. NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
  56. NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
  57. NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
  58. }};
  59. const AZStd::vector<NvCloth::SimIndexType> indices = {{
  60. 0, 1, 2
  61. }};
  62. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  63. const bool useGeodesicTether = true;
  64. NvCloth::FabricId fabricId = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
  65. EXPECT_TRUE(fabricId.IsValid());
  66. }
  67. TEST(NvClothSystem, FactoryCooker_ComputeFabricIdsWithDifferentGravityParameter_ResultInDifferentIDs)
  68. {
  69. const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
  70. NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
  71. NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
  72. NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
  73. } };
  74. const AZStd::vector<NvCloth::SimIndexType> indices = {{
  75. 0, 1, 2
  76. }};
  77. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  78. const bool useGeodesicTether = true;
  79. NvCloth::FabricId fabricId1 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
  80. NvCloth::FabricId fabricId2 = NvCloth::Internal::ComputeFabricId(particles, indices, 0.5f * gravity, useGeodesicTether);
  81. EXPECT_TRUE(fabricId1.IsValid());
  82. EXPECT_TRUE(fabricId2.IsValid());
  83. EXPECT_NE(fabricId1, fabricId2);
  84. }
  85. TEST(NvClothSystem, FactoryCooker_ComputeFabricIdsWithDifferentUseGeodesicTetherParameter_ResultInDifferentIDs)
  86. {
  87. const AZStd::vector<NvCloth::SimParticleFormat> particles = {{
  88. NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
  89. NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
  90. NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
  91. }};
  92. const AZStd::vector<NvCloth::SimIndexType> indices = {{
  93. 0, 1, 2
  94. }};
  95. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  96. const bool useGeodesicTether = true;
  97. NvCloth::FabricId fabricId1 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, useGeodesicTether);
  98. NvCloth::FabricId fabricId2 = NvCloth::Internal::ComputeFabricId(particles, indices, gravity, !useGeodesicTether);
  99. EXPECT_TRUE(fabricId1.IsValid());
  100. EXPECT_TRUE(fabricId2.IsValid());
  101. EXPECT_NE(fabricId1, fabricId2);
  102. }
  103. TEST(NvClothSystem, FactoryCooker_CopyInternalCookedDataEmpty_CopiedDataIsEmpty)
  104. {
  105. nv::cloth::CookedData nvCookedData;
  106. nvCookedData.mNumParticles = 0;
  107. NvCloth::FabricCookedData::InternalCookedData azCookedData;
  108. NvCloth::Internal::CopyCookedData(azCookedData, nvCookedData);
  109. EXPECT_EQ(azCookedData.m_numParticles, 0);
  110. EXPECT_TRUE(azCookedData.m_phaseIndices.empty());
  111. EXPECT_TRUE(azCookedData.m_phaseTypes.empty());
  112. EXPECT_TRUE(azCookedData.m_sets.empty());
  113. EXPECT_TRUE(azCookedData.m_restValues.empty());
  114. EXPECT_TRUE(azCookedData.m_stiffnessValues.empty());
  115. EXPECT_TRUE(azCookedData.m_indices.empty());
  116. EXPECT_TRUE(azCookedData.m_anchors.empty());
  117. EXPECT_TRUE(azCookedData.m_tetherLengths.empty());
  118. EXPECT_TRUE(azCookedData.m_triangles.empty());
  119. ExpectEq(azCookedData, nvCookedData);
  120. }
  121. TEST(NvClothSystem, FactoryCooker_CopyInternalCookedData_CopiedDataMatchesSource)
  122. {
  123. nv::cloth::CookedData nvCookedData;
  124. nvCookedData.mNumParticles = 0;
  125. NvCloth::FabricCookedData::InternalCookedData azCookedData;
  126. NvCloth::Internal::CopyCookedData(azCookedData, nvCookedData);
  127. ExpectEq(azCookedData, nvCookedData);
  128. }
  129. TEST(NvClothSystem, FactoryCooker_CookEmptyMesh_ReturnsNoData)
  130. {
  131. AZ_TEST_START_TRACE_SUPPRESSION;
  132. EXPECT_TRUE(NvCloth::SystemComponent::CheckLastClothError());
  133. AZStd::optional<NvCloth::FabricCookedData> fabricCookedData = NvCloth::Internal::Cook({}, {}, {}, false);
  134. NvCloth::SystemComponent::ResetLastClothError(); // Reset the nvcloth error left in SystemComponent
  135. AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Expect 1 error
  136. EXPECT_FALSE(fabricCookedData.has_value());
  137. }
  138. TEST(NvClothSystem, FactoryCooker_CookWithIncorrectIndices_ReturnsNoData)
  139. {
  140. const AZStd::vector<NvCloth::SimIndexType> incorrectIndices = {{ 0, 1 }}; // Use incorrect number of indices for a triangle (multiple of 3)
  141. AZ_TEST_START_TRACE_SUPPRESSION;
  142. EXPECT_TRUE(NvCloth::SystemComponent::CheckLastClothError());
  143. AZStd::optional<NvCloth::FabricCookedData> fabricCookedData = NvCloth::Internal::Cook({}, incorrectIndices, {}, false);
  144. NvCloth::SystemComponent::ResetLastClothError(); // Reset the nvcloth error left in SystemComponent
  145. AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Expect 1 error
  146. EXPECT_FALSE(fabricCookedData.has_value());
  147. }
  148. TEST(NvClothSystem, FactoryCooker_CookTriangle_CooksDataCorrectly)
  149. {
  150. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  151. NvCloth::SimParticleFormat(-1.0f, 0.0f, 0.0f, 1.0f),
  152. NvCloth::SimParticleFormat(1.0f, 0.0f, 0.0f, 1.0f),
  153. NvCloth::SimParticleFormat(0.0f, 1.0f, 0.0f, 1.0f),
  154. }};
  155. const AZStd::vector<NvCloth::SimIndexType> indices = { {0, 1, 2} };
  156. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  157. const bool useGeodesicTether = true;
  158. AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
  159. NvCloth::Internal::Cook(vertices, indices, gravity, useGeodesicTether);
  160. EXPECT_TRUE(fabricCookedData.has_value());
  161. EXPECT_TRUE(fabricCookedData->m_id.IsValid());
  162. EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), vertices));
  163. EXPECT_EQ(fabricCookedData->m_indices, indices);
  164. EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
  165. EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
  166. EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, vertices.size());
  167. }
  168. TEST(NvClothSystem, FactoryCooker_CookTriangleAllStatic_CooksDataCorrectly)
  169. {
  170. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  171. NvCloth::SimParticleFormat(-1.0f, 0.0f, 0.0f, 0.0f),
  172. NvCloth::SimParticleFormat(1.0f, 0.0f, 0.0f, 0.0f),
  173. NvCloth::SimParticleFormat(0.0f, 1.0f, 0.0f, 0.0f),
  174. }};
  175. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
  176. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  177. const bool useGeodesicTether = true;
  178. AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
  179. NvCloth::Internal::Cook(vertices, indices, gravity, useGeodesicTether);
  180. EXPECT_TRUE(fabricCookedData.has_value());
  181. EXPECT_TRUE(fabricCookedData->m_id.IsValid());
  182. EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), vertices));
  183. EXPECT_EQ(fabricCookedData->m_indices, indices);
  184. EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
  185. EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
  186. EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, vertices.size());
  187. }
  188. TEST(NvClothSystem, FactoryCooker_CookMesh_CooksDataCorrectly)
  189. {
  190. const float width = 1.0f;
  191. const float height = 1.0f;
  192. const AZ::u32 segmentsX = 10;
  193. const AZ::u32 segmentsY = 10;
  194. const AZ::Vector3 gravity(0.0f, 0.0f, -9.8f);
  195. const bool useGeodesicTether = true;
  196. const TriangleInput planeXY = CreatePlane(width, height, segmentsX, segmentsY);
  197. AZStd::optional<NvCloth::FabricCookedData> fabricCookedData =
  198. NvCloth::Internal::Cook(planeXY.m_vertices, planeXY.m_indices, gravity, useGeodesicTether);
  199. EXPECT_TRUE(fabricCookedData.has_value());
  200. EXPECT_TRUE(fabricCookedData->m_id.IsValid());
  201. EXPECT_THAT(fabricCookedData->m_particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), planeXY.m_vertices));
  202. EXPECT_EQ(fabricCookedData->m_indices, planeXY.m_indices);
  203. EXPECT_THAT(fabricCookedData->m_gravity, IsCloseTolerance(gravity, Tolerance));
  204. EXPECT_EQ(fabricCookedData->m_useGeodesicTether, useGeodesicTether);
  205. EXPECT_EQ(fabricCookedData->m_internalData.m_numParticles, planeXY.m_vertices.size());
  206. }
  207. TEST(NvClothSystem, FactoryCooker_WeldVerticesEmptyMesh_ReturnsEmptyData)
  208. {
  209. AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
  210. AZStd::vector<NvCloth::SimIndexType> weldedIndices;
  211. AZStd::vector<int> remappedVertices;
  212. NvCloth::Internal::WeldVertices({}, {}, weldedVertices, weldedIndices, remappedVertices);
  213. EXPECT_TRUE(weldedVertices.empty());
  214. EXPECT_TRUE(weldedIndices.empty());
  215. EXPECT_TRUE(remappedVertices.empty());
  216. }
  217. TEST(NvClothSystem, FactoryCooker_WeldVerticesTriangle_KeepsLowestInverseMass)
  218. {
  219. const AZ::Vector3 vertexPosition(100.2f, 300.2f, -30.62f);
  220. const size_t expectedSizeAfterWelding = 1;
  221. const float lowestInverseMass = 0.2f;
  222. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  223. NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, 1.0f),
  224. NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, lowestInverseMass), // This vertex has the lowest inverse mass
  225. NvCloth::SimParticleFormat::CreateFromVector3AndFloat(vertexPosition, 0.5f)
  226. }};
  227. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
  228. AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
  229. AZStd::vector<NvCloth::SimIndexType> weldedIndices;
  230. AZStd::vector<int> remappedVertices;
  231. NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
  232. ASSERT_EQ(weldedVertices.size(), expectedSizeAfterWelding);
  233. EXPECT_THAT(weldedVertices[0].GetAsVector3(), IsCloseTolerance(vertexPosition, Tolerance));
  234. EXPECT_NEAR(weldedVertices[0].GetW(), lowestInverseMass, Tolerance);
  235. }
  236. TEST(NvClothSystem, FactoryCooker_WeldVerticesSquareWithDuplicatedVertices_DuplicatedVerticesAreRemoved)
  237. {
  238. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  239. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 1.0f),
  240. NvCloth::SimParticleFormat( 1.0f, 1.0f, 0.0f, 1.0f),
  241. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
  242. NvCloth::SimParticleFormat( 1.0f, 1.0f, 0.0f, 1.0f), // Duplicated vertex
  243. NvCloth::SimParticleFormat( 1.0f,-1.0f, 0.0f, 1.0f),
  244. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f) // Duplicated vertex
  245. }};
  246. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
  247. const size_t expectedSizeAfterWelding = vertices.size() - 2;
  248. AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
  249. AZStd::vector<NvCloth::SimIndexType> weldedIndices;
  250. AZStd::vector<int> remappedVertices;
  251. NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
  252. ASSERT_EQ(weldedVertices.size(), expectedSizeAfterWelding);
  253. ASSERT_EQ(weldedIndices.size(), indices.size());
  254. ASSERT_EQ(remappedVertices.size(), vertices.size());
  255. for (size_t i = 0; i < remappedVertices.size(); ++i)
  256. {
  257. EXPECT_GE(remappedVertices[i], 0);
  258. EXPECT_LT(remappedVertices[i], weldedVertices.size());
  259. EXPECT_THAT(weldedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  260. }
  261. for (size_t i = 0; i < weldedIndices.size(); ++i)
  262. {
  263. EXPECT_GE(weldedIndices[i], 0);
  264. EXPECT_LT(weldedIndices[i], weldedVertices.size());
  265. EXPECT_EQ(weldedIndices[i], remappedVertices[indices[i]]);
  266. EXPECT_THAT(weldedVertices[weldedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
  267. }
  268. }
  269. TEST(NvClothSystem, FactoryCooker_WeldVerticesTrianglesWithoutDuplicatedVertices_ResultIsTheSame)
  270. {
  271. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  272. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 1.0f),
  273. NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 1.0f),
  274. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
  275. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  276. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 1.0f),
  277. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
  278. }};
  279. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
  280. AZStd::vector<NvCloth::SimParticleFormat> weldedVertices;
  281. AZStd::vector<NvCloth::SimIndexType> weldedIndices;
  282. AZStd::vector<int> remappedVertices;
  283. NvCloth::Internal::WeldVertices(vertices, indices, weldedVertices, weldedIndices, remappedVertices);
  284. // The result after calling WeldVertices is expected to have the same size.
  285. // The vertices inside will be reordered though due to the welding process.
  286. ASSERT_EQ(weldedVertices.size(), vertices.size());
  287. ASSERT_EQ(weldedIndices.size(), indices.size());
  288. ASSERT_EQ(remappedVertices.size(), vertices.size());
  289. for (size_t i = 0; i < remappedVertices.size(); ++i)
  290. {
  291. EXPECT_GE(remappedVertices[i], 0);
  292. EXPECT_LT(remappedVertices[i], weldedVertices.size());
  293. EXPECT_THAT(weldedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  294. }
  295. for (size_t i = 0; i < weldedIndices.size(); ++i)
  296. {
  297. EXPECT_GE(weldedIndices[i], 0);
  298. EXPECT_LT(weldedIndices[i], weldedVertices.size());
  299. EXPECT_EQ(weldedIndices[i], remappedVertices[indices[i]]);
  300. EXPECT_THAT(weldedVertices[weldedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
  301. }
  302. }
  303. TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesEmptyMesh_ReturnsEmptyData)
  304. {
  305. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  306. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  307. AZStd::vector<int> remappedVertices;
  308. NvCloth::Internal::RemoveStaticTriangles({}, {}, simplifiedVertices, simplifiedIndices, remappedVertices);
  309. EXPECT_TRUE(simplifiedVertices.empty());
  310. EXPECT_TRUE(simplifiedIndices.empty());
  311. EXPECT_TRUE(remappedVertices.empty());
  312. }
  313. TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithOneStaticTriangle_RemovesAllVerticesAndIndices)
  314. {
  315. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  316. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f),
  317. NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f),
  318. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f)
  319. }};
  320. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2 }};
  321. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  322. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  323. AZStd::vector<int> remappedVertices;
  324. NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
  325. EXPECT_TRUE(simplifiedVertices.empty());
  326. EXPECT_TRUE(simplifiedIndices.empty());
  327. EXPECT_EQ(remappedVertices.size(), vertices.size());
  328. for (const auto& remappedVertex : remappedVertices)
  329. {
  330. EXPECT_LT(remappedVertex, 0); // Remapping must be negative, meaning vertex has been removed.
  331. }
  332. }
  333. TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithStaticTriangles_StaticTriangleAndVerticesAreRemoved)
  334. {
  335. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  336. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
  337. NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
  338. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
  339. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  340. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
  341. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
  342. }};
  343. 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
  344. const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 3;
  345. const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
  346. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  347. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  348. AZStd::vector<int> remappedVertices;
  349. NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
  350. ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
  351. ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
  352. ASSERT_EQ(remappedVertices.size(), vertices.size());
  353. for (size_t i = 0; i < remappedVertices.size(); ++i)
  354. {
  355. // The first 3 vertices should have been removed, so the remapping should be negative
  356. if (i < 3)
  357. {
  358. EXPECT_LT(remappedVertices[i], 0);
  359. }
  360. else
  361. {
  362. EXPECT_GE(remappedVertices[i], 0);
  363. EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
  364. EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  365. }
  366. }
  367. for (size_t i = 0; i < simplifiedIndices.size(); ++i)
  368. {
  369. EXPECT_GE(simplifiedIndices[i], 0);
  370. EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
  371. }
  372. for (size_t i = 0; i < indices.size(); ++i)
  373. {
  374. int remappedVertex = remappedVertices[indices[i]];
  375. if (remappedVertex >= 0) // If the vertex has not been removed
  376. {
  377. EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
  378. }
  379. }
  380. }
  381. TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithStaticTrianglesSharedVertices_StaticTriangleAndVerticesAreRemoved)
  382. {
  383. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  384. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
  385. 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
  386. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
  387. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  388. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
  389. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
  390. }};
  391. 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
  392. const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 2;
  393. const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
  394. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  395. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  396. AZStd::vector<int> remappedVertices;
  397. NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
  398. ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
  399. ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
  400. ASSERT_EQ(remappedVertices.size(), vertices.size());
  401. for (size_t i = 0; i < remappedVertices.size(); ++i)
  402. {
  403. // The first and third vertex should have been removed, so the remapping should be negative.
  404. if (i == 0 || i == 2)
  405. {
  406. EXPECT_LT(remappedVertices[i], 0);
  407. }
  408. else
  409. {
  410. EXPECT_GE(remappedVertices[i], 0);
  411. EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
  412. EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  413. }
  414. }
  415. for (size_t i = 0; i < simplifiedIndices.size(); ++i)
  416. {
  417. EXPECT_GE(simplifiedIndices[i], 0);
  418. EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
  419. }
  420. for (size_t i = 0; i < indices.size(); ++i)
  421. {
  422. int remappedVertex = remappedVertices[indices[i]];
  423. if (remappedVertex >= 0) // If the vertex has not been removed
  424. {
  425. EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
  426. }
  427. }
  428. }
  429. TEST(NvClothSystem, FactoryCooker_RemoveStaticTrianglesWithNonStaticTriangles_ResultIsTheSame)
  430. {
  431. const AZStd::vector<NvCloth::SimParticleFormat> vertices = {{
  432. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f),
  433. NvCloth::SimParticleFormat(1.0f, 1.0f, 0.0f, 1.0f),
  434. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 1.0f),
  435. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  436. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
  437. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f)
  438. }};
  439. const AZStd::vector<NvCloth::SimIndexType> indices = {{ 0, 1, 2, 3, 4, 5 }};
  440. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  441. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  442. AZStd::vector<int> remappedVertices;
  443. NvCloth::Internal::RemoveStaticTriangles(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices);
  444. // The result after calling RemoveStaticTriangles is expected to have the same size.
  445. // The vertices will be reordered though due to the processing during simplification.
  446. ASSERT_EQ(simplifiedVertices.size(), vertices.size());
  447. ASSERT_EQ(simplifiedIndices.size(), indices.size());
  448. ASSERT_EQ(remappedVertices.size(), vertices.size());
  449. for (size_t i = 0; i < remappedVertices.size(); ++i)
  450. {
  451. EXPECT_GE(remappedVertices[i], 0);
  452. EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
  453. EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  454. }
  455. for (size_t i = 0; i < simplifiedIndices.size(); ++i)
  456. {
  457. EXPECT_GE(simplifiedIndices[i], 0);
  458. EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
  459. EXPECT_EQ(simplifiedIndices[i], remappedVertices[indices[i]]);
  460. EXPECT_THAT(simplifiedVertices[simplifiedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
  461. }
  462. }
  463. TEST(NvClothSystem, FactoryCooker_SimplifiyMeshWithDuplicatedVerticesAndStaticTriangles_DuplicatedVerticesAndStaticTrianglesAreRemoved)
  464. {
  465. const AZStd::vector<NvCloth::SimParticleFormat> vertices = { {
  466. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will be removed
  467. 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
  468. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will be removed
  469. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  470. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
  471. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f),
  472. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f), // Duplicated vertex
  473. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f) // Duplicated vertex
  474. } };
  475. 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
  476. const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 4;
  477. const size_t expectedIndicesSizeAfterSimplification = indices.size() - 3; // 1 triangles less is 3 indices less.
  478. const bool removeStaticTriangles = true;
  479. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  480. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  481. AZStd::vector<int> remappedVertices;
  482. AZ::Interface<NvCloth::IFabricCooker>::Get()->SimplifyMesh(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices, removeStaticTriangles);
  483. ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
  484. ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
  485. ASSERT_EQ(remappedVertices.size(), vertices.size());
  486. for (size_t i = 0; i < remappedVertices.size(); ++i)
  487. {
  488. // The first and third vertex should have been removed, so the remapping should be negative.
  489. if (i == 0 || i == 2)
  490. {
  491. EXPECT_LT(remappedVertices[i], 0);
  492. }
  493. else
  494. {
  495. EXPECT_GE(remappedVertices[i], 0);
  496. EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
  497. EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  498. }
  499. }
  500. for (size_t i = 0; i < simplifiedIndices.size(); ++i)
  501. {
  502. EXPECT_GE(simplifiedIndices[i], 0);
  503. EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
  504. }
  505. for (size_t i = 0; i < indices.size(); ++i)
  506. {
  507. int remappedVertex = remappedVertices[indices[i]];
  508. if (remappedVertex >= 0) // If the vertex has not been removed
  509. {
  510. EXPECT_THAT(simplifiedVertices[remappedVertex], IsCloseTolerance(vertices[indices[i]], Tolerance));
  511. }
  512. }
  513. }
  514. TEST(NvClothSystem, FactoryCooker_SimplifiyMeshWithoutRemovingStaticTriangles_DuplicatedVerticesRemovedAndStaticTrianglesRemain)
  515. {
  516. const AZStd::vector<NvCloth::SimParticleFormat> vertices = { {
  517. NvCloth::SimParticleFormat(-1.0f, 1.0f, 0.0f, 0.0f), // This static vertex will remain because we won't remove static triangles
  518. 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
  519. NvCloth::SimParticleFormat(-1.0f,-1.0f, 0.0f, 0.0f), // This static vertex will remain because we won't remove static triangles
  520. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f),
  521. NvCloth::SimParticleFormat(1.0f,-1.0f, 1.0f, 0.0f),
  522. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f),
  523. NvCloth::SimParticleFormat(1.0f, 1.0f, 1.0f, 1.0f), // Duplicated vertex
  524. NvCloth::SimParticleFormat(-1.0f,-1.0f, 1.0f, 1.0f) // Duplicated vertex
  525. } };
  526. 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
  527. const size_t expectedVerticesSizeAfterSimplification = vertices.size() - 2;
  528. const size_t expectedIndicesSizeAfterSimplification = indices.size();
  529. const bool removeStaticTriangles = false;
  530. AZStd::vector<NvCloth::SimParticleFormat> simplifiedVertices;
  531. AZStd::vector<NvCloth::SimIndexType> simplifiedIndices;
  532. AZStd::vector<int> remappedVertices;
  533. AZ::Interface<NvCloth::IFabricCooker>::Get()->SimplifyMesh(vertices, indices, simplifiedVertices, simplifiedIndices, remappedVertices, removeStaticTriangles);
  534. ASSERT_EQ(simplifiedVertices.size(), expectedVerticesSizeAfterSimplification);
  535. ASSERT_EQ(simplifiedIndices.size(), expectedIndicesSizeAfterSimplification);
  536. ASSERT_EQ(remappedVertices.size(), vertices.size());
  537. for (size_t i = 0; i < remappedVertices.size(); ++i)
  538. {
  539. EXPECT_GE(remappedVertices[i], 0);
  540. EXPECT_LT(remappedVertices[i], simplifiedVertices.size());
  541. EXPECT_THAT(simplifiedVertices[remappedVertices[i]], IsCloseTolerance(vertices[i], Tolerance));
  542. }
  543. for (size_t i = 0; i < simplifiedIndices.size(); ++i)
  544. {
  545. EXPECT_GE(simplifiedIndices[i], 0);
  546. EXPECT_LT(simplifiedIndices[i], simplifiedVertices.size());
  547. EXPECT_EQ(simplifiedIndices[i], remappedVertices[indices[i]]);
  548. EXPECT_THAT(simplifiedVertices[simplifiedIndices[i]], IsCloseTolerance(vertices[indices[i]], Tolerance));
  549. }
  550. }
  551. } // namespace UnitTest