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.
621 lines
26 KiB
C++
621 lines
26 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 <UnitTestHelper.h>
|
|
#include <TriangleInputHelper.h>
|
|
|
|
#include <System/Factory.h>
|
|
#include <System/Fabric.h>
|
|
#include <System/FabricCooker.h>
|
|
#include <System/Cloth.h>
|
|
#include <System/NvTypes.h>
|
|
|
|
// NvCloth library includes
|
|
#include <NvCloth/Cloth.h>
|
|
|
|
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<AZ::Vector4>& azVector, nv::cloth::Range<physx::PxVec4>& nvRange);
|
|
void FastCopy(const nv::cloth::Range<physx::PxVec4>& nvRange, AZStd::vector<AZ::Vector4>& azVector);
|
|
void FastMove(AZStd::vector<AZ::Vector4>&& azVector, nv::cloth::Range<physx::PxVec4>& nvRange);
|
|
void FastMove(nv::cloth::Range<physx::PxVec4>&& nvRange, AZStd::vector<AZ::Vector4>& 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<AZ::Vector4> azEmpty;
|
|
const AZStd::vector<AZ::Vector4> 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<physx::PxVec4>::Type nvEmpty;
|
|
nv::cloth::Vector<physx::PxVec4>::Type nvValues(static_cast<uint32_t>(azValues.size()));
|
|
|
|
nv::cloth::Range<physx::PxVec4> nvEmptyRange(nvEmpty.begin(), nvEmpty.end());
|
|
nv::cloth::Range<physx::PxVec4> 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<physx::PxVec4>::Type nvEmpty;
|
|
nv::cloth::Vector<physx::PxVec4>::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<physx::PxVec4> nvEmptyRange(nvEmpty.begin(), nvEmpty.end());
|
|
const nv::cloth::Range<physx::PxVec4> nvValuesRange(nvValues.begin(), nvValues.end());
|
|
|
|
AZStd::vector<AZ::Vector4> azEmpty;
|
|
AZStd::vector<AZ::Vector4> 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<AZ::Vector4> azEmpty;
|
|
const AZStd::vector<AZ::Vector4> 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<physx::PxVec4>::Type nvEmpty;
|
|
nv::cloth::Vector<physx::PxVec4>::Type nvValues(static_cast<uint32_t>(azValues.size()));
|
|
|
|
nv::cloth::Range<physx::PxVec4> nvEmptyRange(nvEmpty.begin(), nvEmpty.end());
|
|
nv::cloth::Range<physx::PxVec4> nvValuesRange(nvValues.begin(), nvValues.end());
|
|
|
|
{
|
|
AZStd::vector<AZ::Vector4> azEmptyCopy = azEmpty;
|
|
AZStd::vector<AZ::Vector4> 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<physx::PxVec4>::Type nvEmpty;
|
|
nv::cloth::Vector<physx::PxVec4>::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<physx::PxVec4> nvEmptyRange(nvEmpty.begin(), nvEmpty.end());
|
|
const nv::cloth::Range<physx::PxVec4> nvValuesRange(nvValues.begin(), nvValues.end());
|
|
|
|
AZStd::vector<AZ::Vector4> azEmpty;
|
|
AZStd::vector<AZ::Vector4> azValues(nvValuesRange.size());
|
|
|
|
{
|
|
nv::cloth::Vector<physx::PxVec4>::Type nvEmptyCopy = nvEmpty;
|
|
nv::cloth::Vector<physx::PxVec4>::Type nvValuesCopy = nvValues;
|
|
|
|
nv::cloth::Range<physx::PxVec4> nvEmptyRangeCopy(nvEmptyCopy.begin(), nvEmptyCopy.end());
|
|
nv::cloth::Range<physx::PxVec4> 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<NvCloth::Cloth> 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<NvCloth::Fabric> 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<NvCloth::Fabric>(
|
|
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::Cloth>(
|
|
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<const physx::PxVec4> 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<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
for (size_t i = 0; i < newParticles.size(); ++i)
|
|
{
|
|
EXPECT_NEAR(newParticles[i].GetW(), nvClothPreviousParticles[static_cast<uint32_t>(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<const physx::PxVec4> 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<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
for (size_t i = 0; i < newParticles.size(); ++i)
|
|
{
|
|
EXPECT_NEAR(newParticles[i].GetW(), nvClothPreviousParticles[static_cast<uint32_t>(i)].w, Tolerance);
|
|
}
|
|
}
|
|
|
|
TEST_F(NvClothSystemCloth, Cloth_DiscardParticleDelta_NativeClothPreviousAndCurrentParticlesAreTheSame)
|
|
{
|
|
m_cloth->DiscardParticleDelta();
|
|
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth);
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
|
|
EXPECT_EQ(nvClothCurrentParticles.size(), nvClothPreviousParticles.size());
|
|
for (size_t i = 0; i < nvClothCurrentParticles.size(); ++i)
|
|
{
|
|
ExpectEq(nvClothCurrentParticles[static_cast<uint32_t>(i)], nvClothPreviousParticles[static_cast<uint32_t>(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<physx::PxVec4> 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<physx::PxVec4> nvParticles = m_nvCloth->getCurrentParticles();
|
|
for (auto& particle : nvParticles)
|
|
{
|
|
if (particle.w != 0.0f)
|
|
{
|
|
particle.x = std::numeric_limits<float>::quiet_NaN();
|
|
particle.y = std::numeric_limits<float>::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<physx::PxVec4> nvParticles = m_nvCloth->getCurrentParticles();
|
|
for (auto& particle : nvParticles)
|
|
{
|
|
if (particle.w != 0.0f)
|
|
{
|
|
particle.x = std::numeric_limits<float>::quiet_NaN();
|
|
particle.y = std::numeric_limits<float>::infinity();
|
|
break;
|
|
}
|
|
}
|
|
// nvcloth particles are set once nvParticles gets out of scope
|
|
}
|
|
|
|
m_cloth->Update();
|
|
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth);
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
|
|
EXPECT_EQ(nvClothCurrentParticles.size(), nvClothPreviousParticles.size());
|
|
for (size_t i = 0; i < nvClothCurrentParticles.size(); ++i)
|
|
{
|
|
ExpectEq(nvClothCurrentParticles[static_cast<uint32_t>(i)], nvClothPreviousParticles[static_cast<uint32_t>(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<physx::PxVec4> nvParticles = m_nvCloth->getCurrentParticles();
|
|
for (auto& particle : nvParticles)
|
|
{
|
|
if (particle.w != 0.0f)
|
|
{
|
|
particle.x = std::numeric_limits<float>::quiet_NaN();
|
|
particle.y = std::numeric_limits<float>::infinity();
|
|
break;
|
|
}
|
|
}
|
|
// nvcloth particles are set once nvParticles gets out of scope
|
|
}
|
|
|
|
m_cloth->Update();
|
|
}
|
|
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothCurrentParticles = nv::cloth::readCurrentParticles(*m_nvCloth);
|
|
const nv::cloth::MappedRange<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
|
|
EXPECT_EQ(initialParticles.size(), nvClothCurrentParticles.size());
|
|
EXPECT_EQ(initialParticles.size(), nvClothPreviousParticles.size());
|
|
for (size_t i = 0; i < initialParticles.size(); ++i)
|
|
{
|
|
ExpectEq(initialParticles[i], nvClothCurrentParticles[static_cast<uint32_t>(i)]);
|
|
ExpectEq(initialParticles[i], nvClothPreviousParticles[static_cast<uint32_t>(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<physx::PxVec4> 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<physx::PxVec4> 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(identity.GetRotation(), m_nvCloth->getRotation());
|
|
|
|
m_cloth->GetClothConfigurator()->SetTransform(rotationX);
|
|
|
|
ExpectEq(rotationX.GetTranslation(), m_nvCloth->getTranslation());
|
|
ExpectEq(rotationX.GetRotation(), m_nvCloth->getRotation());
|
|
|
|
m_cloth->GetClothConfigurator()->SetTransform(rotationYAndTranslation);
|
|
|
|
ExpectEq(rotationYAndTranslation.GetTranslation(), m_nvCloth->getTranslation());
|
|
ExpectEq(rotationYAndTranslation.GetRotation(), 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<const physx::PxVec4> nvClothPreviousParticles = nv::cloth::readPreviousParticles(*m_nvCloth);
|
|
for (size_t i = 0; i < initialParticles.size(); ++i)
|
|
{
|
|
EXPECT_NEAR(nvClothPreviousParticles[static_cast<uint32_t>(i)].w, initialParticles[i].GetW() / globalMass, Tolerance);
|
|
}
|
|
}
|
|
} // namespace UnitTest
|