/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include #include // NvCloth library includes #include namespace NvCloth { namespace Internal { physx::PxVec3& AsPxVec3(AZ::Vector3& azVec); const physx::PxVec3& AsPxVec3(const AZ::Vector3& azVec); physx::PxQuat& AsPxQuat(AZ::Quaternion& azQuat); const physx::PxQuat& AsPxQuat(const AZ::Quaternion& azQuat); void FastCopy(const AZStd::vector& azVector, nv::cloth::Range& nvRange); void FastCopy(const nv::cloth::Range& nvRange, AZStd::vector& azVector); void FastMove(AZStd::vector&& azVector, nv::cloth::Range& nvRange); void FastMove(nv::cloth::Range&& nvRange, AZStd::vector& azVector); } // namespace Internal } // namespace NvCloth namespace UnitTest { TEST(NvClothSystem, Cloth_AzVector3AsPxVec3_PxVec3ElementsAreTheSameAsAzVector3) { AZ::Vector3 zero = AZ::Vector3::CreateZero(); AZ::Vector3 one = AZ::Vector3::CreateOne(); AZ::Vector3 axisX = AZ::Vector3::CreateAxisX(); AZ::Vector3 axisY = AZ::Vector3::CreateAxisY(); AZ::Vector3 axisZ = AZ::Vector3::CreateAxisZ(); AZ::Vector3 vec3 = AZ::Vector3(26.0f, -462.366f, 15.384f); physx::PxVec3& pxZero = NvCloth::Internal::AsPxVec3(zero); physx::PxVec3& pxOne = NvCloth::Internal::AsPxVec3(one); physx::PxVec3& pxAxisX = NvCloth::Internal::AsPxVec3(axisX); physx::PxVec3& pxAxisY = NvCloth::Internal::AsPxVec3(axisY); physx::PxVec3& pxAxisZ = NvCloth::Internal::AsPxVec3(axisZ); physx::PxVec3& pxVec3 = NvCloth::Internal::AsPxVec3(vec3); ExpectEq(zero, pxZero); ExpectEq(one, pxOne); ExpectEq(axisX, pxAxisX); ExpectEq(axisY, pxAxisY); ExpectEq(axisZ, pxAxisZ); ExpectEq(vec3, pxVec3); } TEST(NvClothSystem, Cloth_AzVector3AsPxVec3Const_PxVec3ElementsAreTheSameAsAzVector3) { const AZ::Vector3 zero = AZ::Vector3::CreateZero(); const AZ::Vector3 one = AZ::Vector3::CreateOne(); const AZ::Vector3 axisX = AZ::Vector3::CreateAxisX(); const AZ::Vector3 axisY = AZ::Vector3::CreateAxisY(); const AZ::Vector3 axisZ = AZ::Vector3::CreateAxisZ(); const AZ::Vector3 vec3 = AZ::Vector3(26.0f, -462.366f, 15.384f); const physx::PxVec3& pxZero = NvCloth::Internal::AsPxVec3(zero); const physx::PxVec3& pxOne = NvCloth::Internal::AsPxVec3(one); const physx::PxVec3& pxAxisX = NvCloth::Internal::AsPxVec3(axisX); const physx::PxVec3& pxAxisY = NvCloth::Internal::AsPxVec3(axisY); const physx::PxVec3& pxAxisZ = NvCloth::Internal::AsPxVec3(axisZ); const physx::PxVec3& pxVec3 = NvCloth::Internal::AsPxVec3(vec3); ExpectEq(zero, pxZero); ExpectEq(one, pxOne); ExpectEq(axisX, pxAxisX); ExpectEq(axisY, pxAxisY); ExpectEq(axisZ, pxAxisZ); ExpectEq(vec3, pxVec3); } TEST(NvClothSystem, Cloth_AzQuaternionAsPxQuat_QuatElementsAreTheSameAsAzQuaternion) { AZ::Quaternion zero = AZ::Quaternion::CreateZero(); AZ::Quaternion one = AZ::Quaternion::CreateIdentity(); AZ::Quaternion rotX = AZ::Quaternion::CreateRotationX(AZ::DegToRad(26.5f)); AZ::Quaternion rotY = AZ::Quaternion::CreateRotationY(AZ::DegToRad(-196.5f)); AZ::Quaternion rotZ = AZ::Quaternion::CreateRotationZ(AZ::DegToRad(263.2f)); AZ::Quaternion quat = AZ::Quaternion(26.0f, -62.366f, 15.384f, 5.0f); physx::PxQuat& pxZero = NvCloth::Internal::AsPxQuat(zero); physx::PxQuat& pxOne = NvCloth::Internal::AsPxQuat(one); physx::PxQuat& pxRotX = NvCloth::Internal::AsPxQuat(rotX); physx::PxQuat& pxRotY = NvCloth::Internal::AsPxQuat(rotY); physx::PxQuat& pxRotZ = NvCloth::Internal::AsPxQuat(rotZ); physx::PxQuat& pxQuat = NvCloth::Internal::AsPxQuat(quat); ExpectEq(zero, pxZero); ExpectEq(one, pxOne); ExpectEq(rotX, pxRotX); ExpectEq(rotY, pxRotY); ExpectEq(rotZ, pxRotZ); ExpectEq(quat, pxQuat); } TEST(NvClothSystem, Cloth_AzQuaternionAsPxQuatConst_QuatElementsAreTheSameAsAzQuaternion) { const AZ::Quaternion zero = AZ::Quaternion::CreateZero(); const AZ::Quaternion one = AZ::Quaternion::CreateIdentity(); const AZ::Quaternion rotX = AZ::Quaternion::CreateRotationX(AZ::DegToRad(26.5f)); const AZ::Quaternion rotY = AZ::Quaternion::CreateRotationY(AZ::DegToRad(-196.5f)); const AZ::Quaternion rotZ = AZ::Quaternion::CreateRotationZ(AZ::DegToRad(263.2f)); const AZ::Quaternion quat = AZ::Quaternion(26.0f, -62.366f, 15.384f, 5.0f); const physx::PxQuat& pxZero = NvCloth::Internal::AsPxQuat(zero); const physx::PxQuat& pxOne = NvCloth::Internal::AsPxQuat(one); const physx::PxQuat& pxRotX = NvCloth::Internal::AsPxQuat(rotX); const physx::PxQuat& pxRotY = NvCloth::Internal::AsPxQuat(rotY); const physx::PxQuat& pxRotZ = NvCloth::Internal::AsPxQuat(rotZ); const physx::PxQuat& pxQuat = NvCloth::Internal::AsPxQuat(quat); ExpectEq(zero, pxZero); ExpectEq(one, pxOne); ExpectEq(rotX, pxRotX); ExpectEq(rotY, pxRotY); ExpectEq(rotZ, pxRotZ); ExpectEq(quat, pxQuat); } TEST(NvClothSystem, Cloth_FastCopy_nvRangeElmenentsAreTheSameAsAZStdVector) { const AZStd::vector azEmpty; const AZStd::vector azValues = {{ AZ::Vector4(15.0f, -692.0f, 65.0f, -15.0f), AZ::Vector4(1851.594f, 1.0f, -125.0f, 168.0f), AZ::Vector4(2384.05f, -692.0f, 41865.153f, 1567.0f), AZ::Vector4(35.02f, 2572.453f, 2465.0f, 987.0f), AZ::Vector4(-14.161f, 47.0f, 65.0f, -6358.52f) }}; nv::cloth::Vector::Type nvEmpty; nv::cloth::Vector::Type nvValues(azValues.size()); nv::cloth::Range nvEmptyRange(nvEmpty.begin(), nvEmpty.end()); nv::cloth::Range nvValuesRange(nvValues.begin(), nvValues.end()); NvCloth::Internal::FastCopy(azEmpty, nvEmptyRange); NvCloth::Internal::FastCopy(azValues, nvValuesRange); ExpectEq(azEmpty, nvEmptyRange); ExpectEq(azValues, nvValuesRange); } TEST(NvClothSystem, Cloth_FastCopy_AZStdVectorElmenentsAreTheSameAsNvRange) { nv::cloth::Vector::Type nvEmpty; nv::cloth::Vector::Type nvValues; nvValues.pushBack(physx::PxVec4(15.0f, -692.0f, 65.0f, -15.0f)); nvValues.pushBack(physx::PxVec4(1851.594f, 1.0f, -125.0f, 168.0f)); nvValues.pushBack(physx::PxVec4(2384.05f, -692.0f, 41865.153f, 1567.0f)); nvValues.pushBack(physx::PxVec4(35.02f, 2572.453f, 2465.0f, 987.0f)); nvValues.pushBack(physx::PxVec4(-14.161f, 47.0f, 65.0f, -6358.52f)); const nv::cloth::Range nvEmptyRange(nvEmpty.begin(), nvEmpty.end()); const nv::cloth::Range nvValuesRange(nvValues.begin(), nvValues.end()); AZStd::vector azEmpty; AZStd::vector azValues(nvValuesRange.size()); NvCloth::Internal::FastCopy(nvEmptyRange, azEmpty); NvCloth::Internal::FastCopy(nvValuesRange, azValues); ExpectEq(azEmpty, nvEmptyRange); ExpectEq(azValues, nvValuesRange); } TEST(NvClothSystem, Cloth_FastMove_nvRangeElmenentsAreTheSameAsAZStdVector) { const AZStd::vector azEmpty; const AZStd::vector azValues = {{ AZ::Vector4(15.0f, -692.0f, 65.0f, -15.0f), AZ::Vector4(1851.594f, 1.0f, -125.0f, 168.0f), AZ::Vector4(2384.05f, -692.0f, 41865.153f, 1567.0f), AZ::Vector4(35.02f, 2572.453f, 2465.0f, 987.0f), AZ::Vector4(-14.161f, 47.0f, 65.0f, -6358.52f) }}; nv::cloth::Vector::Type nvEmpty; nv::cloth::Vector::Type nvValues(azValues.size()); nv::cloth::Range nvEmptyRange(nvEmpty.begin(), nvEmpty.end()); nv::cloth::Range nvValuesRange(nvValues.begin(), nvValues.end()); { AZStd::vector azEmptyCopy = azEmpty; AZStd::vector azValuesCopy = azValues; NvCloth::Internal::FastMove(AZStd::move(azEmptyCopy), nvEmptyRange); NvCloth::Internal::FastMove(AZStd::move(azValuesCopy), nvValuesRange); } ExpectEq(azEmpty, nvEmptyRange); ExpectEq(azValues, nvValuesRange); } TEST(NvClothSystem, Cloth_FastMove_AZStdVectorElmenentsAreTheSameAsNvRange) { nv::cloth::Vector::Type nvEmpty; nv::cloth::Vector::Type nvValues; nvValues.pushBack(physx::PxVec4(15.0f, -692.0f, 65.0f, -15.0f)); nvValues.pushBack(physx::PxVec4(1851.594f, 1.0f, -125.0f, 168.0f)); nvValues.pushBack(physx::PxVec4(2384.05f, -692.0f, 41865.153f, 1567.0f)); nvValues.pushBack(physx::PxVec4(35.02f, 2572.453f, 2465.0f, 987.0f)); nvValues.pushBack(physx::PxVec4(-14.161f, 47.0f, 65.0f, -6358.52f)); const nv::cloth::Range nvEmptyRange(nvEmpty.begin(), nvEmpty.end()); const nv::cloth::Range nvValuesRange(nvValues.begin(), nvValues.end()); AZStd::vector azEmpty; AZStd::vector azValues(nvValuesRange.size()); { nv::cloth::Vector::Type nvEmptyCopy = nvEmpty; nv::cloth::Vector::Type nvValuesCopy = nvValues; nv::cloth::Range nvEmptyRangeCopy(nvEmptyCopy.begin(), nvEmptyCopy.end()); nv::cloth::Range nvValuesRangeCopy(nvValuesCopy.begin(), nvValuesCopy.end()); NvCloth::Internal::FastMove(AZStd::move(nvEmptyRangeCopy), azEmpty); NvCloth::Internal::FastMove(AZStd::move(nvValuesRangeCopy), azValues); } ExpectEq(azEmpty, nvEmptyRange); ExpectEq(azValues, nvValuesRange); } //! Sets up a cloth for each test case with access to its native cloth instance. //! Creating cloth using direct calls to the library, instead of using Factory, to //! be able to keep a pointer the native cloth instance. class NvClothSystemCloth : public ::testing::Test { protected: // ::testing::Test overrides ... void SetUp() override; void TearDown() override; AZStd::unique_ptr m_cloth; nv::cloth::Cloth* m_nvCloth = nullptr; // Pointer to native cloth instance of m_cloth private: void CreateFabric(); void CreateCloth(); NvCloth::NvFactoryUniquePtr m_nvFactory; AZStd::unique_ptr m_fabric; }; void NvClothSystemCloth::SetUp() { m_nvFactory = NvCloth::NvFactoryUniquePtr(NvClothCreateFactoryCPU()); CreateFabric(); CreateCloth(); } void NvClothSystemCloth::TearDown() { m_nvCloth = nullptr; m_cloth.reset(); m_fabric.reset(); m_nvFactory.reset(); } void NvClothSystemCloth::CreateFabric() { const NvCloth::FabricCookedData fabricCookedData = CreateTestFabricCookedData(); NvCloth::NvFabricUniquePtr nvFabric( m_nvFactory->createFabric( fabricCookedData.m_internalData.m_numParticles, NvCloth::ToNvRange(fabricCookedData.m_internalData.m_phaseIndices), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_sets), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_restValues), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_stiffnessValues), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_indices), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_anchors), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_tetherLengths), NvCloth::ToNvRange(fabricCookedData.m_internalData.m_triangles))); EXPECT_TRUE(nvFabric.get() != nullptr); m_fabric = AZStd::make_unique( fabricCookedData, AZStd::move(nvFabric)); } void NvClothSystemCloth::CreateCloth() { NvCloth::NvClothUniquePtr nvCloth( m_nvFactory->createCloth( NvCloth::ToPxVec4NvRange(m_fabric->m_cookedData.m_particles), *m_fabric->m_nvFabric.get())); EXPECT_TRUE(nvCloth.get() != nullptr); m_nvCloth = nvCloth.get(); m_cloth = AZStd::make_unique( NvCloth::ClothId(1), m_fabric->m_cookedData.m_particles, m_fabric.get(), AZStd::move(nvCloth)); } TEST_F(NvClothSystemCloth, Cloth_SetParticles_ParticlesAreSetToClothAndNativeCloth) { auto newParticles = m_cloth->GetParticles(); for (auto& particle : newParticles) { particle *= 2.0f; } m_cloth->SetParticles(newParticles); EXPECT_THAT(newParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), m_cloth->GetParticles())); const nv::cloth::MappedRange nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); ExpectEq(newParticles, nvClothCurrentParticles); // The inverse masses (W element) should have been copied into the previous particles inside NvCloth // to take effect for the next simulation update. const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); for (size_t i = 0; i < newParticles.size(); ++i) { EXPECT_NEAR(newParticles[i].GetW(), nvClothPreviousParticles[i].w, Tolerance); } } TEST_F(NvClothSystemCloth, Cloth_SetParticlesMove_ParticlesAreSetToClothAndNativeCloth) { auto newParticles = m_cloth->GetParticles(); for (auto& particle : newParticles) { particle *= 2.0f; } { auto newParticlesCopy = newParticles; m_cloth->SetParticles(AZStd::move(newParticlesCopy)); } EXPECT_THAT(newParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), m_cloth->GetParticles())); const nv::cloth::MappedRange nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); ExpectEq(newParticles, nvClothCurrentParticles); // The inverse masses (W element) should have been copied into the previous particles inside NvCloth // to take effect for the next simulation update. const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); for (size_t i = 0; i < newParticles.size(); ++i) { EXPECT_NEAR(newParticles[i].GetW(), nvClothPreviousParticles[i].w, Tolerance); } } TEST_F(NvClothSystemCloth, Cloth_DiscardParticleDelta_NativeClothPreviousAndCurrentParticlesAreTheSame) { m_cloth->DiscardParticleDelta(); const nv::cloth::MappedRange nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); EXPECT_EQ(nvClothCurrentParticles.size(), nvClothPreviousParticles.size()); for (size_t i = 0; i < nvClothCurrentParticles.size(); ++i) { ExpectEq(nvClothCurrentParticles[i], nvClothPreviousParticles[i]); } } TEST_F(NvClothSystemCloth, Cloth_Update_SimParticlesAreUpdated) { const AZ::Vector3 movement(6.0f, 1.0f, 3.0f); const auto previousParticles = m_cloth->GetParticles(); // Fake all particles have been moved during simulation. { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { if (particle.w != 0.0f) { particle.x += movement.GetX(); particle.y += movement.GetY(); particle.z += movement.GetZ(); } } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); const auto& particles = m_cloth->GetParticles(); for (size_t i = 0; i < particles.size(); ++i) { if (particles[i].GetW() == 0.0f) { EXPECT_THAT(particles[i].GetAsVector3(), IsCloseTolerance(previousParticles[i].GetAsVector3(), Tolerance)); } else { EXPECT_THAT(particles[i].GetAsVector3(), IsCloseTolerance(previousParticles[i].GetAsVector3() + movement, Tolerance)); } } } TEST_F(NvClothSystemCloth, Cloth_UpdateInvalidParticles_SimParticlesAreNotUpdated) { const auto previousParticles = m_cloth->GetParticles(); // Fake a particle has been set to non-finite values during simulation { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { if (particle.w != 0.0f) { particle.x = std::numeric_limits::quiet_NaN(); particle.y = std::numeric_limits::infinity(); break; } } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); const auto& particles = m_cloth->GetParticles(); EXPECT_THAT(particles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), previousParticles)); } TEST_F(NvClothSystemCloth, Cloth_UpdateInvalidParticles_NativeClothParticlesAreRestored) { // Fake a particle has been set to non-finite values during simulation { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { if (particle.w != 0.0f) { particle.x = std::numeric_limits::quiet_NaN(); particle.y = std::numeric_limits::infinity(); break; } } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); const nv::cloth::MappedRange nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); EXPECT_EQ(nvClothCurrentParticles.size(), nvClothPreviousParticles.size()); for (size_t i = 0; i < nvClothCurrentParticles.size(); ++i) { ExpectEq(nvClothCurrentParticles[i], nvClothPreviousParticles[i]); } } TEST_F(NvClothSystemCloth, Cloth_UpdateInvalidParticlesManyAttempts_NativeClothParticlesAreRestoredToInitialPositions) { const auto& initialParticles = m_cloth->GetInitialParticles(); const size_t numInvalidSimulations = 30; for (size_t i = 0; i < numInvalidSimulations; ++i) { // Fake a particle has been set to non-finite values during simulation { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { if (particle.w != 0.0f) { particle.x = std::numeric_limits::quiet_NaN(); particle.y = std::numeric_limits::infinity(); break; } } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); } const nv::cloth::MappedRange nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); EXPECT_EQ(initialParticles.size(), nvClothCurrentParticles.size()); EXPECT_EQ(initialParticles.size(), nvClothPreviousParticles.size()); for (size_t i = 0; i < nvClothCurrentParticles.size(); ++i) { ExpectEq(initialParticles[i], nvClothCurrentParticles[i]); ExpectEq(initialParticles[i], nvClothPreviousParticles[i]); } } TEST_F(NvClothSystemCloth, Cloth_CollisionAffectsStaticParticles_StaticParticlesAreModifiedDuringUpdate) { const AZ::Vector3 movement(6.0f, 1.0f, 3.0f); const auto previousParticles = m_cloth->GetParticles(); m_cloth->GetClothConfigurator()->SetCollisionAffectsStaticParticles(true); // Fake all particles have been moved during simulation, cloth contains static particles. { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { particle.x += movement.GetX(); particle.y += movement.GetY(); particle.z += movement.GetZ(); } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); const auto& particles = m_cloth->GetParticles(); for (size_t i = 0; i < particles.size(); ++i) { EXPECT_THAT(particles[i].GetAsVector3(), IsCloseTolerance(previousParticles[i].GetAsVector3() + movement, Tolerance)); } } TEST_F(NvClothSystemCloth, Cloth_CollisionDoesNotAffectStaticParticles_StaticParticlesAreNotModifiedDuringUpdate) { const AZ::Vector3 movement(6.0f, 1.0f, 3.0f); const auto previousParticles = m_cloth->GetParticles(); m_cloth->GetClothConfigurator()->SetCollisionAffectsStaticParticles(false); // Fake all particles have been moved during simulation, cloth contains static particles. { nv::cloth::MappedRange nvParticles = m_nvCloth->getCurrentParticles(); for (auto& particle : nvParticles) { particle.x += movement.GetX(); particle.y += movement.GetY(); particle.z += movement.GetZ(); } // nvcloth particles are set once nvParticles gets out of scope } m_cloth->Update(); const auto& particles = m_cloth->GetParticles(); for (size_t i = 0; i < particles.size(); ++i) { if (particles[i].GetW() == 0.0f) { EXPECT_THAT(particles[i].GetAsVector3(), IsCloseTolerance(previousParticles[i].GetAsVector3(), Tolerance)); } else { EXPECT_THAT(particles[i].GetAsVector3(), IsCloseTolerance(previousParticles[i].GetAsVector3() + movement, Tolerance)); } } } TEST_F(NvClothSystemCloth, Cloth_ClothConfigurationSetTransform_TranslationAndRotationAreAppliedToNativeCloth) { const AZ::Transform identity = AZ::Transform::CreateIdentity(); const AZ::Transform rotationX = AZ::Transform::CreateRotationX(AZ::DegToRad(35.0f)); const AZ::Transform rotationYAndTranslation = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationY(AZ::DegToRad(-135.0f)), AZ::Vector3(36.0f, 50.0f, -69.35f)); m_cloth->GetClothConfigurator()->SetTransform(identity); ExpectEq(identity.GetTranslation(), m_nvCloth->getTranslation()); ExpectEq(AZ::Quaternion::CreateFromTransform(identity), m_nvCloth->getRotation()); m_cloth->GetClothConfigurator()->SetTransform(rotationX); ExpectEq(rotationX.GetTranslation(), m_nvCloth->getTranslation()); ExpectEq(AZ::Quaternion::CreateFromTransform(rotationX), m_nvCloth->getRotation()); m_cloth->GetClothConfigurator()->SetTransform(rotationYAndTranslation); ExpectEq(rotationYAndTranslation.GetTranslation(), m_nvCloth->getTranslation()); ExpectEq(AZ::Quaternion::CreateFromTransform(rotationYAndTranslation), m_nvCloth->getRotation()); } TEST_F(NvClothSystemCloth, Cloth_ClothConfigurationSetMass_MassIsAppliedToClothSimParticlesAndNativeClothPreviousParticles) { const float globalMass = 2.0f; const auto& initialParticles = m_cloth->GetInitialParticles(); m_cloth->GetClothConfigurator()->SetMass(globalMass); const auto& particles = m_cloth->GetParticles(); for (size_t i = 0; i < initialParticles.size(); ++i) { EXPECT_NEAR(particles[i].GetW(), initialParticles[i].GetW() / globalMass, Tolerance); } // The inverse masses (W element) should have been copied into the previous particles inside NvCloth // to take effect for the next simulation update. const nv::cloth::MappedRange nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth); for (size_t i = 0; i < initialParticles.size(); ++i) { EXPECT_NEAR(nvClothPreviousParticles[i].w, initialParticles[i].GetW() / globalMass, Tolerance); } } } // namespace UnitTest