NvClothTest.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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 <AzTest/AzTest.h>
  9. #include <AzCore/Interface/Interface.h>
  10. #include <AzCore/Math/Transform.h>
  11. #include <AzCore/Component/TickBus.h>
  12. #include <NvCloth/IClothSystem.h>
  13. #include <NvCloth/ICloth.h>
  14. #include <NvCloth/IClothConfigurator.h>
  15. #include <NvCloth/IFabricCooker.h>
  16. #include <TriangleInputHelper.h>
  17. namespace UnitTest
  18. {
  19. //! Sets up a cloth and colliders for each test case.
  20. class NvClothTestFixture
  21. : public ::testing::Test
  22. {
  23. protected:
  24. // ::testing::Test overrides ...
  25. void SetUp() override;
  26. void TearDown() override;
  27. //! Sends tick events to make cloth simulation happen.
  28. //! Returns the position of cloth particles at tickBefore, continues ticking till tickAfter.
  29. void TickClothSimulation(const AZ::u32 tickBefore,
  30. const AZ::u32 tickAfter,
  31. AZStd::vector<NvCloth::SimParticleFormat>& particlesBefore);
  32. NvCloth::ICloth* m_cloth = nullptr;
  33. NvCloth::ICloth::PreSimulationEvent::Handler m_preSimulationEventHandler;
  34. NvCloth::ICloth::PostSimulationEvent::Handler m_postSimulationEventHandler;
  35. bool m_postSimulationEventInvoked = false;
  36. private:
  37. bool CreateCloth();
  38. void DestroyCloth();
  39. // ICloth notifications
  40. void OnPreSimulation(NvCloth::ClothId clothId, float deltaTime);
  41. void OnPostSimulation(NvCloth::ClothId clothId,
  42. float deltaTime,
  43. const AZStd::vector<NvCloth::SimParticleFormat>& updatedParticles);
  44. AZ::Transform m_clothTransform = AZ::Transform::CreateIdentity();
  45. AZStd::vector<AZ::Vector4> m_sphereColliders;
  46. };
  47. void NvClothTestFixture::SetUp()
  48. {
  49. m_preSimulationEventHandler = NvCloth::ICloth::PreSimulationEvent::Handler(
  50. [this](NvCloth::ClothId clothId, float deltaTime)
  51. {
  52. this->OnPreSimulation(clothId, deltaTime);
  53. });
  54. m_postSimulationEventHandler = NvCloth::ICloth::PostSimulationEvent::Handler(
  55. [this](NvCloth::ClothId clothId, float deltaTime, const AZStd::vector<NvCloth::SimParticleFormat>& updatedParticles)
  56. {
  57. this->OnPostSimulation(clothId, deltaTime, updatedParticles);
  58. });
  59. bool clothCreated = CreateCloth();
  60. ASSERT_TRUE(clothCreated);
  61. }
  62. void NvClothTestFixture::TearDown()
  63. {
  64. DestroyCloth();
  65. }
  66. void NvClothTestFixture::TickClothSimulation(const AZ::u32 tickBefore,
  67. const AZ::u32 tickAfter,
  68. AZStd::vector<NvCloth::SimParticleFormat>& particlesBefore)
  69. {
  70. const float timeOneFrameSeconds = 0.016f; //approx 60 fps
  71. for (AZ::u32 tickCount = 0; tickCount < tickAfter; ++tickCount)
  72. {
  73. AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick,
  74. timeOneFrameSeconds,
  75. AZ::ScriptTimePoint(AZStd::chrono::steady_clock::now()));
  76. if (tickCount == tickBefore)
  77. {
  78. particlesBefore = m_cloth->GetParticles();
  79. }
  80. }
  81. }
  82. bool NvClothTestFixture::CreateCloth()
  83. {
  84. const float width = 2.0f;
  85. const float height = 2.0f;
  86. const AZ::u32 segmentsX = 10;
  87. const AZ::u32 segmentsY = 10;
  88. const TriangleInput planeXY = CreatePlane(width, height, segmentsX, segmentsY);
  89. // Cook Fabric
  90. AZStd::optional<NvCloth::FabricCookedData> cookedData = AZ::Interface<NvCloth::IFabricCooker>::Get()->CookFabric(planeXY.m_vertices, planeXY.m_indices);
  91. if (!cookedData)
  92. {
  93. return false;
  94. }
  95. // Create cloth instance
  96. m_cloth = AZ::Interface<NvCloth::IClothSystem>::Get()->CreateCloth(planeXY.m_vertices, *cookedData);
  97. if (!m_cloth)
  98. {
  99. return false;
  100. }
  101. m_sphereColliders.emplace_back(512.0f, 512.0f, 35.0f, 1.0f);
  102. m_clothTransform.SetTranslation(AZ::Vector3(512.0f, 519.0f, 35.0f));
  103. m_cloth->GetClothConfigurator()->SetTransform(m_clothTransform);
  104. m_cloth->GetClothConfigurator()->ClearInertia();
  105. // Add cloth to default solver to be simulated
  106. AZ::Interface<NvCloth::IClothSystem>::Get()->AddCloth(m_cloth);
  107. return true;
  108. }
  109. void NvClothTestFixture::DestroyCloth()
  110. {
  111. if (m_cloth)
  112. {
  113. AZ::Interface<NvCloth::IClothSystem>::Get()->RemoveCloth(m_cloth);
  114. AZ::Interface<NvCloth::IClothSystem>::Get()->DestroyCloth(m_cloth);
  115. }
  116. }
  117. void NvClothTestFixture::OnPreSimulation([[maybe_unused]] NvCloth::ClothId clothId, float deltaTime)
  118. {
  119. m_cloth->GetClothConfigurator()->SetTransform(m_clothTransform);
  120. constexpr float velocity = 1.0f;
  121. for (auto& sphere : m_sphereColliders)
  122. {
  123. sphere.SetY(sphere.GetY() + velocity * deltaTime);
  124. }
  125. auto clothInverseTransform = m_clothTransform.GetInverse();
  126. auto sphereColliders = m_sphereColliders;
  127. for (auto& sphere : sphereColliders)
  128. {
  129. sphere.Set(clothInverseTransform.TransformPoint(sphere.GetAsVector3()), sphere.GetW());
  130. }
  131. m_cloth->GetClothConfigurator()->SetSphereColliders(sphereColliders);
  132. }
  133. void NvClothTestFixture::OnPostSimulation(
  134. NvCloth::ClothId clothId, float deltaTime,
  135. const AZStd::vector<NvCloth::SimParticleFormat>& updatedParticles)
  136. {
  137. AZ_UNUSED(clothId);
  138. AZ_UNUSED(deltaTime);
  139. AZ_UNUSED(updatedParticles);
  140. m_postSimulationEventInvoked = true;
  141. }
  142. //! Smallest Z and largest Y coordinates for a list of particles before, and a list of particles after simulation for some time.
  143. struct ParticleBounds
  144. {
  145. float m_beforeSmallestZ = std::numeric_limits<float>::max();
  146. float m_beforeLargestY = -std::numeric_limits<float>::max();
  147. float m_afterSmallestZ = std::numeric_limits<float>::max();
  148. float m_afterLargestY = -std::numeric_limits<float>::max();
  149. };
  150. static ParticleBounds GetBeforeAndAfterParticleBounds(const AZStd::vector<NvCloth::SimParticleFormat>& particlesBefore,
  151. const AZStd::vector<NvCloth::SimParticleFormat>& particlesAfter)
  152. {
  153. assert(particlesBefore.size() == particlesAfter.size());
  154. ParticleBounds beforeAndAfterParticleBounds;
  155. for (size_t particleIndex = 0; particleIndex < particlesBefore.size(); ++particleIndex)
  156. {
  157. if (particlesBefore[particleIndex].GetZ() < beforeAndAfterParticleBounds.m_beforeSmallestZ)
  158. {
  159. beforeAndAfterParticleBounds.m_beforeSmallestZ = particlesBefore[particleIndex].GetZ();
  160. }
  161. if (particlesBefore[particleIndex].GetY() > beforeAndAfterParticleBounds.m_beforeLargestY)
  162. {
  163. beforeAndAfterParticleBounds.m_beforeLargestY = particlesBefore[particleIndex].GetY();
  164. }
  165. if (particlesAfter[particleIndex].GetZ() < beforeAndAfterParticleBounds.m_afterSmallestZ)
  166. {
  167. beforeAndAfterParticleBounds.m_afterSmallestZ = particlesAfter[particleIndex].GetZ();
  168. }
  169. if (particlesAfter[particleIndex].GetY() > beforeAndAfterParticleBounds.m_afterLargestY)
  170. {
  171. beforeAndAfterParticleBounds.m_afterLargestY = particlesAfter[particleIndex].GetY();
  172. }
  173. }
  174. return beforeAndAfterParticleBounds;
  175. }
  176. //! Tests that basic cloth simulation works.
  177. TEST_F(NvClothTestFixture, Cloth_NoCollision_FallWithGravity)
  178. {
  179. const AZ::u32 tickBefore = 150;
  180. const AZ::u32 tickAfter = 300;
  181. AZStd::vector<NvCloth::SimParticleFormat> particlesBefore;
  182. TickClothSimulation(tickBefore, tickAfter, particlesBefore);
  183. ParticleBounds particleBounds = GetBeforeAndAfterParticleBounds(particlesBefore,
  184. m_cloth->GetParticles());
  185. // Cloth was extended horizontally in the y-direction earlier.
  186. // If cloth fell with gravity, its y-extent should be smaller later,
  187. // and its z-extent should go lower to a smaller Z value later.
  188. ASSERT_TRUE((particleBounds.m_afterLargestY < particleBounds.m_beforeLargestY) &&
  189. (particleBounds.m_afterSmallestZ < particleBounds.m_beforeSmallestZ));
  190. }
  191. //! Tests that collision works and pre/post simulation events work.
  192. TEST_F(NvClothTestFixture, Cloth_Collision_CollidedWithPrePostSimEvents)
  193. {
  194. m_cloth->ConnectPreSimulationEventHandler(m_preSimulationEventHandler); // The pre-simulation callback moves the sphere collider towards the cloth every tick.
  195. m_cloth->ConnectPostSimulationEventHandler(m_postSimulationEventHandler);
  196. const AZ::u32 tickBefore = 150;
  197. const AZ::u32 tickAfter = 320;
  198. AZStd::vector<NvCloth::SimParticleFormat> particlesBefore;
  199. TickClothSimulation(tickBefore, tickAfter, particlesBefore);
  200. ParticleBounds particleBounds = GetBeforeAndAfterParticleBounds(particlesBefore,
  201. m_cloth->GetParticles());
  202. // Cloth starts extended horizontally (along Y-axis). Simulation makes it swing down with gravity (as tested with the other unit test).
  203. // Then the sphere collider collides with the cloth and pushes it back up. So it is again extended in the Y-direction and
  204. // at about the same vertical height (Z-coord) as before.
  205. const float threshold = 0.25f;
  206. EXPECT_TRUE(AZ::IsClose(particleBounds.m_beforeSmallestZ , -0.97f, threshold));
  207. EXPECT_TRUE(AZ::IsClose(particleBounds.m_beforeLargestY, 0.76f, threshold));
  208. EXPECT_TRUE(AZ::IsClose(particleBounds.m_afterSmallestZ, -1.1f, threshold));
  209. EXPECT_TRUE(AZ::IsClose(particleBounds.m_afterLargestY, 0.72f, threshold));
  210. ASSERT_TRUE((fabsf(particleBounds.m_afterLargestY - particleBounds.m_beforeLargestY) < threshold) &&
  211. (fabsf(particleBounds.m_afterSmallestZ - particleBounds.m_beforeSmallestZ) < threshold));
  212. // Check that post simulation event was invoked.
  213. ASSERT_TRUE(m_postSimulationEventInvoked);
  214. m_preSimulationEventHandler.Disconnect();
  215. m_postSimulationEventHandler.Disconnect();
  216. }
  217. } // namespace UnitTest