Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,381 @@
/*
* 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 <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/Component/Entity.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Components/ClothComponentMesh/ActorClothColliders.h>
#include <UnitTestHelper.h>
#include <ActorHelper.h>
#include <Integration/Components/ActorComponent.h>
namespace NvCloth
{
namespace Internal
{
extern const size_t NvClothMaxNumSphereColliders;
extern const size_t NvClothMaxNumCapsuleColliders;
}
}
namespace UnitTest
{
//! Fixture to setup entity with actor component.
class NvClothActorClothColliders
: public ::testing::Test
{
protected:
// ::testing::Test overrides ...
void SetUp() override;
void TearDown() override;
EMotionFX::Integration::ActorComponent* m_actorComponent = nullptr;
private:
AZStd::unique_ptr<AZ::Entity> m_entity;
};
void NvClothActorClothColliders::SetUp()
{
m_entity = AZStd::make_unique<AZ::Entity>();
m_entity->CreateComponent<AzFramework::TransformComponent>();
m_actorComponent = m_entity->CreateComponent<EMotionFX::Integration::ActorComponent>();
m_entity->Init();
m_entity->Activate();
}
void NvClothActorClothColliders::TearDown()
{
m_entity->Deactivate();
m_actorComponent = nullptr;
m_entity.reset();
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_DefaultConstruct_ReturnsEmptyData)
{
AZ::EntityId entityId;
NvCloth::ActorClothColliders actorClothColliders(entityId);
EXPECT_TRUE(actorClothColliders.GetSphereColliders().empty());
EXPECT_TRUE(actorClothColliders.GetSpheres().empty());
EXPECT_TRUE(actorClothColliders.GetCapsuleColliders().empty());
EXPECT_TRUE(actorClothColliders.GetCapsuleIndices().empty());
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithInvalidEntityId_ReturnsNull)
{
AZ::EntityId entityId;
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(entityId);
EXPECT_TRUE(actorClothColliders.get() == nullptr);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithNoClothColliders_ReturnsNull)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
EXPECT_TRUE(actorClothColliders.get() == nullptr);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithBoxClothCollider_ReturnsNull)
{
const char* const jointRootName = "joint_root";
{
const auto collider = CreateBoxCollider(jointRootName, AZ::Vector3(0.2f, 0.3f, 0.47f));
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName);
actor->AddClothCollider(collider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
// ActorClothColliders only supports spheres or capsules, other shapes will not be taken into account.
// Since there is no colliders added then it'll return null.
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
EXPECT_TRUE(actorClothColliders.get() == nullptr);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithSphereClothCollider_ReturnsValidConstraints)
{
const char* const jointRootName = "joint_root";
const float radius = 2.3f;
const AZ::Transform colliderOffet = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(65.0f)), AZ::Vector3(-0.5f, 3.0f, 6.0f));
const AZ::Transform jointTransform = AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 53.0f, -65.0f));
{
const auto collider = CreateSphereCollider(jointRootName, radius, colliderOffet);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName, jointTransform);
actor->AddClothCollider(collider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
EXPECT_TRUE(actorClothColliders.get() != nullptr);
const AZStd::vector<NvCloth::SphereCollider>& sphereColliders = actorClothColliders->GetSphereColliders();
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
const AZStd::vector<NvCloth::CapsuleCollider>& capsuleColliders = actorClothColliders->GetCapsuleColliders();
const AZStd::vector<uint32_t>& nativeCapsuleIndices = actorClothColliders->GetCapsuleIndices();
EXPECT_EQ(sphereColliders.size(), 1);
EXPECT_EQ(nativeSpheres.size(), 1);
EXPECT_TRUE(capsuleColliders.empty());
EXPECT_TRUE(nativeCapsuleIndices.empty());
EXPECT_NEAR(sphereColliders[0].m_radius, radius, Tolerance);
EXPECT_EQ(sphereColliders[0].m_nvSphereIndex, 0);
EXPECT_EQ(sphereColliders[0].m_jointIndex, 0);
EXPECT_THAT(sphereColliders[0].m_offsetTransform, IsCloseTolerance(colliderOffet, Tolerance));
EXPECT_THAT(sphereColliders[0].m_currentModelSpaceTransform, IsCloseTolerance(jointTransform * colliderOffet, Tolerance));
EXPECT_THAT(nativeSpheres[0].GetAsVector3(), IsCloseTolerance((jointTransform * colliderOffet).GetTranslation(), Tolerance));
EXPECT_NEAR(nativeSpheres[0].GetW(), radius, Tolerance);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithCapsuleClothCollider_ReturnsValidConstraints)
{
const char* const jointRootName = "joint_root";
const float height = 4.7f;
const float radius = 1.2f;
const AZ::Transform colliderOffet = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(65.0f)), AZ::Vector3(-0.5f, 3.0f, 6.0f));
const AZ::Transform jointTransform = AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 53.0f, -65.0f));
{
const auto collider = CreateCapsuleCollider(jointRootName, height, radius, colliderOffet);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName, jointTransform);
actor->AddClothCollider(collider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
EXPECT_TRUE(actorClothColliders.get() != nullptr);
const AZStd::vector<NvCloth::SphereCollider>& sphereColliders = actorClothColliders->GetSphereColliders();
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
const AZStd::vector<NvCloth::CapsuleCollider>& capsuleColliders = actorClothColliders->GetCapsuleColliders();
const AZStd::vector<uint32_t>& nativeCapsuleIndices = actorClothColliders->GetCapsuleIndices();
EXPECT_TRUE(sphereColliders.empty());
EXPECT_EQ(nativeSpheres.size(), 2); // Each capsule produces 2 spheres
EXPECT_EQ(capsuleColliders.size(), 1);
EXPECT_EQ(nativeCapsuleIndices.size(), 2); // Each capsule is 2 indices
EXPECT_NEAR(capsuleColliders[0].m_height, height, Tolerance);
EXPECT_NEAR(capsuleColliders[0].m_radius, radius, Tolerance);
EXPECT_EQ(capsuleColliders[0].m_capsuleIndex, 0);
EXPECT_EQ(capsuleColliders[0].m_sphereAIndex, 0);
EXPECT_EQ(capsuleColliders[0].m_sphereBIndex, 1);
EXPECT_EQ(capsuleColliders[0].m_jointIndex, 0);
EXPECT_THAT(capsuleColliders[0].m_offsetTransform, IsCloseTolerance(colliderOffet, Tolerance));
EXPECT_THAT(capsuleColliders[0].m_currentModelSpaceTransform, IsCloseTolerance(jointTransform * colliderOffet, Tolerance));
EXPECT_THAT(nativeSpheres[0], IsCloseTolerance(AZ::Vector4(1.5f, 54.9577f, -58.514f, radius), Tolerance));
EXPECT_THAT(nativeSpheres[1], IsCloseTolerance(AZ::Vector4(1.5f, 57.0423f, -59.486f, radius), Tolerance));
EXPECT_NEAR(nativeSpheres[0].GetAsVector3().GetDistance(nativeSpheres[1].GetAsVector3()), height - 2.0f * radius, Tolerance);
EXPECT_EQ(nativeCapsuleIndices[0], 0);
EXPECT_EQ(nativeCapsuleIndices[1], 1);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithSphereAndCapsuleClothColliders_ReturnsValidConstraints)
{
const char* const jointRootName = "joint_root";
{
const auto sphereCollider = CreateSphereCollider(jointRootName, 0.2f);
const auto capsuleCollider = CreateCapsuleCollider(jointRootName, 2.0f, 0.75f);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName);
actor->AddClothCollider(sphereCollider);
actor->AddClothCollider(capsuleCollider);
actor->AddClothCollider(sphereCollider);
actor->AddClothCollider(sphereCollider);
actor->AddClothCollider(capsuleCollider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
const AZStd::vector<NvCloth::SphereCollider>& sphereColliders = actorClothColliders->GetSphereColliders();
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
const AZStd::vector<NvCloth::CapsuleCollider>& capsuleColliders = actorClothColliders->GetCapsuleColliders();
const AZStd::vector<uint32_t>& nativeCapsuleIndices = actorClothColliders->GetCapsuleIndices();
EXPECT_EQ(sphereColliders.size(), 3);
EXPECT_EQ(nativeSpheres.size(), 3 + 2*2); // 3 spheres + 2 capsules (2 spheres per capsule)
EXPECT_EQ(capsuleColliders.size(), 2);
EXPECT_EQ(nativeCapsuleIndices.size(), 2*2); // 2 capsules (2 indices per capsule)
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorSurpassingMaxNumberOfSpheres_ConstructsUpToMaxNumberOfSpheres)
{
const char* const jointRootName = "joint_root";
{
const auto sphereCollider = CreateSphereCollider(jointRootName, 0.2f);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName);
for (size_t i = 0; i < NvCloth::Internal::NvClothMaxNumSphereColliders * 2; ++i)
{
actor->AddClothCollider(sphereCollider);
}
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
const AZStd::vector<NvCloth::SphereCollider>& sphereColliders = actorClothColliders->GetSphereColliders();
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
EXPECT_EQ(sphereColliders.size(), NvCloth::Internal::NvClothMaxNumSphereColliders);
EXPECT_EQ(nativeSpheres.size(), NvCloth::Internal::NvClothMaxNumSphereColliders);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorSurpassingMaxNumberOfCapsules_ConstructsUpToMaxNumberOfCapsules)
{
// Since each capsule has its own unique two spheres, the maximum number of
// spheres is reached by the time half of maximum number of capsules is reached.
const size_t maxNumberOfCapsules = NvCloth::Internal::NvClothMaxNumCapsuleColliders / 2;
const char* const jointRootName = "joint_root";
{
const auto capsuleCollider = CreateCapsuleCollider(jointRootName, 2.0f, 0.75f);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName);
for (size_t i = 0; i < maxNumberOfCapsules * 2; ++i)
{
actor->AddClothCollider(capsuleCollider);
}
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
const AZStd::vector<NvCloth::CapsuleCollider>& capsuleColliders = actorClothColliders->GetCapsuleColliders();
const AZStd::vector<uint32_t>& nativeCapsuleIndices = actorClothColliders->GetCapsuleIndices();
EXPECT_EQ(nativeSpheres.size(), maxNumberOfCapsules * 2);
EXPECT_EQ(capsuleColliders.size(), maxNumberOfCapsules);
EXPECT_EQ(nativeCapsuleIndices.size(), maxNumberOfCapsules * 2);
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_CreateWithActorWithNoSpaceForAnotherCapsule_CapsuleIsNotAdded)
{
const char* const jointRootName = "joint_root";
{
const auto sphereCollider = CreateSphereCollider(jointRootName, 0.2f);
const auto capsuleCollider = CreateCapsuleCollider(jointRootName, 2.0f, 0.75f);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName);
for (size_t i = 0; i < NvCloth::Internal::NvClothMaxNumSphereColliders - 1; ++i)
{
actor->AddClothCollider(sphereCollider);
}
actor->AddClothCollider(capsuleCollider); // This last capsule will not fit because it cannot add 2 additional spheres
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
EXPECT_TRUE(actorClothColliders->GetCapsuleColliders().empty());
EXPECT_TRUE(actorClothColliders->GetCapsuleIndices().empty());
}
TEST_F(NvClothActorClothColliders, ActorClothColliders_Update_ReturnsUpdatedConstraints)
{
const char* const jointRootName = "joint_root";
const char* const jointChildName = "joint_child";
const float height = 12.3f;
const float radius = 2.3f;
const AZ::Transform sphereColliderOffet = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(65.0f)), AZ::Vector3(-0.5f, 3.0f, 6.0f));
const AZ::Transform capsuleColliderOffet = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-5.0f)), AZ::Vector3(2.5f, 6.0f, -4.0f));
const AZ::Transform jointRootTransform = AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 53.0f, -65.0f));
const AZ::Transform jointChildTransform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(36.0f)), AZ::Vector3(3.0f, -2.3f, 16.0f));
{
const auto sphereCollider = CreateSphereCollider(jointRootName, radius, sphereColliderOffet);
const auto capsuleCollider = CreateCapsuleCollider(jointChildName, height, radius, capsuleColliderOffet);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(jointRootName, jointRootTransform);
actor->AddJoint(jointChildName, jointChildTransform, jointRootName);
actor->AddClothCollider(sphereCollider);
actor->AddClothCollider(capsuleCollider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothColliders> actorClothColliders = NvCloth::ActorClothColliders::Create(m_actorComponent->GetEntityId());
// Update actor instance's joints transforms
const AZ::Transform newJointRootTransform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(-32.0f)), AZ::Vector3(2.5f, -6.0f, 0.2f));
const AZ::Transform newJointChildTransform = AZ::Transform::CreateTranslation(AZ::Vector3(-2.0f, 3.0f, 0.0f));
EMotionFX::Pose* currentPose = m_actorComponent->GetActorInstance()->GetTransformData()->GetCurrentPose();
currentPose->SetLocalSpaceTransform(0, newJointRootTransform);
currentPose->SetLocalSpaceTransform(1, newJointChildTransform);
actorClothColliders->Update();
const AZStd::vector<NvCloth::SphereCollider>& sphereColliders = actorClothColliders->GetSphereColliders();
const AZStd::vector<AZ::Vector4>& nativeSpheres = actorClothColliders->GetSpheres();
const AZStd::vector<NvCloth::CapsuleCollider>& capsuleColliders = actorClothColliders->GetCapsuleColliders();
const AZStd::vector<uint32_t>& nativeCapsuleIndices = actorClothColliders->GetCapsuleIndices();
EXPECT_THAT(sphereColliders[0].m_offsetTransform, IsCloseTolerance(sphereColliderOffet, Tolerance));
EXPECT_THAT(sphereColliders[0].m_currentModelSpaceTransform, IsCloseTolerance(newJointRootTransform * sphereColliderOffet, Tolerance));
EXPECT_THAT(nativeSpheres[0].GetAsVector3(), IsCloseTolerance((newJointRootTransform * sphereColliderOffet).GetTranslation(), Tolerance));
EXPECT_THAT(capsuleColliders[0].m_offsetTransform, IsCloseTolerance(capsuleColliderOffet, Tolerance));
EXPECT_THAT(capsuleColliders[0].m_currentModelSpaceTransform, IsCloseTolerance(newJointRootTransform * newJointChildTransform * capsuleColliderOffet, Tolerance));
EXPECT_THAT(nativeSpheres[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(7.87111f, 1.65204f, 0.0353498f), Tolerance));
EXPECT_THAT(nativeSpheres[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(7.51548f, 1.08291f, -7.63535), Tolerance));
}
} // namespace UnitTest
@@ -0,0 +1,307 @@
/*
* 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 <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/Component/Entity.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Components/ClothComponentMesh/ActorClothSkinning.h>
#include <UnitTestHelper.h>
#include <ActorHelper.h>
#include <Integration/Components/ActorComponent.h>
namespace UnitTest
{
//! Fixture to setup entity with actor component and the tests data.
class NvClothActorClothSkinning
: public ::testing::Test
{
public:
const AZStd::string MeshNodeName = "cloth_mesh_node";
const AZStd::vector<AZ::Vector3> MeshVertices = {{
AZ::Vector3(-1.0f, 0.0f, 0.0f),
AZ::Vector3(1.0f, 0.0f, 0.0f),
AZ::Vector3(0.0f, 1.0f, 0.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> MeshIndices = {{
0, 1, 2
}};
const AZStd::vector<VertexSkinInfluences> MeshSkinningInfo = {{
VertexSkinInfluences{ SkinInfluence(0, 1.0f) },
VertexSkinInfluences{ SkinInfluence(0, 1.0f) },
VertexSkinInfluences{ SkinInfluence(0, 1.0f) }
}};
const AZStd::vector<int> MeshRemappedVertices = {{
0, 1, 2
}};
const AZ::u32 LodLevel = 0;
protected:
// ::testing::Test overrides ...
void SetUp() override;
void TearDown() override;
EMotionFX::Integration::ActorComponent* m_actorComponent = nullptr;
private:
AZStd::unique_ptr<AZ::Entity> m_entity;
};
void NvClothActorClothSkinning::SetUp()
{
m_entity = AZStd::make_unique<AZ::Entity>();
m_entity->CreateComponent<AzFramework::TransformComponent>();
m_actorComponent = m_entity->CreateComponent<EMotionFX::Integration::ActorComponent>();
m_entity->Init();
m_entity->Activate();
}
void NvClothActorClothSkinning::TearDown()
{
m_entity->Deactivate();
m_actorComponent = nullptr;
m_entity.reset();
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_CreateWithNoData_ReturnsNull)
{
AZ::EntityId entityId;
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(entityId, "", 0, {});
EXPECT_TRUE(actorClothSkinning.get() == nullptr);
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_CreateWithDataButWithInvalidEntityId_ReturnsNull)
{
AZ::EntityId entityId;
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(entityId, MeshNodeName, MeshRemappedVertices.size(), MeshRemappedVertices);
EXPECT_TRUE(actorClothSkinning.get() == nullptr);
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_CreateWithEmptyActor_ReturnsNull)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(m_actorComponent->GetEntityId(), "", 0, {});
EXPECT_TRUE(actorClothSkinning.get() == nullptr);
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_CreateWithActorWhoseMeshHasNoSkinningInfo_ReturnsNull)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(m_actorComponent->GetEntityId(), MeshNodeName, MeshVertices.size(), MeshRemappedVertices);
EXPECT_TRUE(actorClothSkinning.get() == nullptr);
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_CreateWithActor_ReturnsValidInstance)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(m_actorComponent->GetEntityId(), MeshNodeName, MeshVertices.size(), MeshRemappedVertices);
EXPECT_TRUE(actorClothSkinning.get() != nullptr);
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_UpdateAndApplyLinearSkinning_ModifiesVertices)
{
const AZ::Transform meshNodeTransform = AZ::Transform::CreateRotationY(AZ::DegToRad(90.0f));
EMotionFX::Integration::ActorComponent::Configuration actorConfig;
actorConfig.m_skinningMethod = EMotionFX::Integration::SkinningMethod::Linear;
auto entity = AZStd::make_unique<AZ::Entity>();
entity->CreateComponent<AzFramework::TransformComponent>();
auto* actorComponent = entity->CreateComponent<EMotionFX::Integration::ActorComponent>(&actorConfig);
entity->Init();
entity->Activate();
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName, meshNodeTransform);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo));
actor->FinishSetup();
actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(actorComponent->GetEntityId(), MeshNodeName, MeshVertices.size(), MeshRemappedVertices);
const AZStd::vector<NvCloth::SimParticleFormat> clothParticles = {{
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[0], 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[1], 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[2], 1.0f),
}};
AZStd::vector<NvCloth::SimParticleFormat> skinnedClothParticles(clothParticles.size(), NvCloth::SimParticleFormat(0.0f, 0.0f, 0.0f, 1.0f));
actorClothSkinning->UpdateSkinning();
actorClothSkinning->ApplySkinning(clothParticles, skinnedClothParticles);
EXPECT_THAT(skinnedClothParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothParticles));
// Update actor instance's joints transforms
const AZ::Transform newMeshNodeTransform = AZ::Transform::CreateRotationY(AZ::DegToRad(180.0f));
EMotionFX::Pose* currentPose = actorComponent->GetActorInstance()->GetTransformData()->GetCurrentPose();
currentPose->SetLocalSpaceTransform(0, newMeshNodeTransform);
actorComponent->GetActorInstance()->UpdateSkinningMatrices();
AZStd::vector<NvCloth::SimParticleFormat> newSkinnedClothParticles(clothParticles.size(), NvCloth::SimParticleFormat(0.0f, 0.0f, 0.0f, 1.0f));
actorClothSkinning->UpdateSkinning();
actorClothSkinning->ApplySkinning(clothParticles, newSkinnedClothParticles);
const AZ::Transform diffTransform = AZ::Transform::CreateRotationY(AZ::DegToRad(90.0f));
const AZStd::vector<NvCloth::SimParticleFormat> clothParticlesResult = {{
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(diffTransform.TransformPoint(MeshVertices[0]), 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(diffTransform.TransformPoint(MeshVertices[1]), 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(diffTransform.TransformPoint(MeshVertices[2]), 1.0f),
}};
EXPECT_THAT(newSkinnedClothParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothParticlesResult));
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_UpdateAndApplyDualQuatSkinning_ModifiesVertices)
{
const AZStd::string rootNodeName = "root_node";
const AZStd::string meshNodeName = "cloth_mesh_node";
const AZStd::vector<VertexSkinInfluences> meshSkinningInfo = {{
VertexSkinInfluences{ SkinInfluence(1, 0.75f), SkinInfluence(0, 0.25f) },
VertexSkinInfluences{ SkinInfluence(1, 0.75f), SkinInfluence(0, 0.25f) },
VertexSkinInfluences{ SkinInfluence(1, 0.75f), SkinInfluence(0, 0.25f) }
}};
const AZ::Transform rootNodeTransform = AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 53.0f, -65.0f));
const AZ::Transform meshNodeTransform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(36.0f)), AZ::Vector3(3.0f, -2.3f, 16.0f));
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(rootNodeName, rootNodeTransform);
auto meshNodeIndex = actor->AddJoint(meshNodeName, meshNodeTransform, rootNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, meshSkinningInfo));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(m_actorComponent->GetEntityId(), meshNodeName, MeshVertices.size(), MeshRemappedVertices);
const AZStd::vector<NvCloth::SimParticleFormat> clothParticles = {{
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[0], 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[1], 1.0f),
NvCloth::SimParticleFormat::CreateFromVector3AndFloat(MeshVertices[2], 1.0f),
}};
AZStd::vector<NvCloth::SimParticleFormat> skinnedClothParticles(clothParticles.size(), NvCloth::SimParticleFormat(0.0f, 0.0f, 0.0f, 1.0f));
actorClothSkinning->UpdateSkinning();
actorClothSkinning->ApplySkinning(clothParticles, skinnedClothParticles);
EXPECT_THAT(skinnedClothParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothParticles));
// Update actor instance's joints transforms
const AZ::Transform newJointRootTransform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(-32.0f)), AZ::Vector3(2.5f, -6.0f, 0.2f));
const AZ::Transform newJointChildTransform = AZ::Transform::CreateTranslation(AZ::Vector3(-2.0f, 3.0f, 0.0f));
EMotionFX::Pose* currentPose = m_actorComponent->GetActorInstance()->GetTransformData()->GetCurrentPose();
currentPose->SetLocalSpaceTransform(0, newJointRootTransform);
currentPose->SetLocalSpaceTransform(1, newJointChildTransform);
m_actorComponent->GetActorInstance()->UpdateSkinningMatrices();
AZStd::vector<NvCloth::SimParticleFormat> newSkinnedClothParticles(clothParticles.size(), NvCloth::SimParticleFormat(0.0f, 0.0f, 0.0f, 1.0f));
actorClothSkinning->UpdateSkinning();
actorClothSkinning->ApplySkinning(clothParticles, newSkinnedClothParticles);
const AZStd::vector<NvCloth::SimParticleFormat> clothParticlesResult = {{
NvCloth::SimParticleFormat(-48.4177f, -31.9446f, 45.2279f, 1.0f),
NvCloth::SimParticleFormat(-46.9087f, -32.8876f, 46.1409f, 1.0f),
NvCloth::SimParticleFormat(-47.1333f, -31.568f, 45.6844f, 1.0f),
}};
EXPECT_THAT(newSkinnedClothParticles, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothParticlesResult));
}
TEST_F(NvClothActorClothSkinning, ActorClothSkinning_UpdateActorVisibility_ReturnsExpectedValues)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
AZStd::unique_ptr<NvCloth::ActorClothSkinning> actorClothSkinning =
NvCloth::ActorClothSkinning::Create(m_actorComponent->GetEntityId(), MeshNodeName, MeshVertices.size(), MeshRemappedVertices);
EXPECT_FALSE(actorClothSkinning->IsActorVisible());
EXPECT_FALSE(actorClothSkinning->WasActorVisible());
m_actorComponent->GetActorInstance()->SetIsVisible(true);
actorClothSkinning->UpdateActorVisibility();
EXPECT_TRUE(actorClothSkinning->IsActorVisible());
EXPECT_FALSE(actorClothSkinning->WasActorVisible());
actorClothSkinning->UpdateActorVisibility();
EXPECT_TRUE(actorClothSkinning->IsActorVisible());
EXPECT_TRUE(actorClothSkinning->WasActorVisible());
m_actorComponent->GetActorInstance()->SetIsVisible(false);
actorClothSkinning->UpdateActorVisibility();
EXPECT_FALSE(actorClothSkinning->IsActorVisible());
EXPECT_TRUE(actorClothSkinning->WasActorVisible());
actorClothSkinning->UpdateActorVisibility();
EXPECT_FALSE(actorClothSkinning->IsActorVisible());
EXPECT_FALSE(actorClothSkinning->WasActorVisible());
}
} // namespace UnitTest
@@ -0,0 +1,467 @@
/*
* 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 <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzCore/Component/Entity.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Components/ClothComponentMesh/ClothComponentMesh.h>
#include <UnitTestHelper.h>
#include <CryRenderMeshStub.h>
#include <ActorHelper.h>
#include <Integration/Components/ActorComponent.h>
namespace UnitTest
{
//! Fixture to setup entity with actor component and the tests data.
class NvClothComponentMesh
: public ::testing::Test
{
public:
const AZStd::string MeshNodeName = "cloth_node";
const AZStd::vector<AZ::Vector3> MeshVertices = {{
AZ::Vector3(-1.0f, 0.0f, 0.0f),
AZ::Vector3(1.0f, 0.0f, 0.0f),
AZ::Vector3(0.0f, 1.0f, 0.0f)
}};
const AZStd::vector<NvCloth::SimIndexType> MeshIndices = {{
0, 1, 2
}};
const AZStd::vector<VertexSkinInfluences> MeshSkinningInfo = {{
VertexSkinInfluences{ SkinInfluence(0, 1.0f) },
VertexSkinInfluences{ SkinInfluence(0, 1.0f) },
VertexSkinInfluences{ SkinInfluence(0, 1.0f) }
}};
const AZStd::vector<AZ::Vector2> MeshUVs = {{
AZ::Vector2(0.0f, 0.0f),
AZ::Vector2(1.0f, 0.0f),
AZ::Vector2(0.5f, 1.0f)
}};
// [inverse mass, motion constrain radius, backstop offset, backstop radius]
const AZStd::vector<AZ::Color> MeshClothData = {{
AZ::Color(0.75f, 0.6f, 0.5f, 0.1f),
AZ::Color(1.0f, 0.16f, 0.1f, 1.0f),
AZ::Color(0.25f, 1.0f, 0.9f, 0.5f)
}};
const AZ::u32 LodLevel = 0;
protected:
// ::testing::Test overrides ...
void SetUp() override;
void TearDown() override;
EMotionFX::Integration::ActorComponent* m_actorComponent = nullptr;
private:
AZStd::unique_ptr<AZ::Entity> m_entity;
};
void NvClothComponentMesh::SetUp()
{
m_entity = AZStd::make_unique<AZ::Entity>();
m_entity->CreateComponent<AzFramework::TransformComponent>();
m_actorComponent = m_entity->CreateComponent<EMotionFX::Integration::ActorComponent>();
m_entity->Init();
m_entity->Activate();
}
void NvClothComponentMesh::TearDown()
{
m_entity->Deactivate();
m_actorComponent = nullptr;
m_entity.reset();
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_DefaultConstructor_ReturnsEmptyRenderData)
{
AZ::EntityId entityId;
NvCloth::ClothComponentMesh clothComponentMesh(entityId, {});
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_TRUE(renderData.m_particles.empty());
EXPECT_TRUE(renderData.m_tangents.empty());
EXPECT_TRUE(renderData.m_bitangents.empty());
EXPECT_TRUE(renderData.m_normals.empty());
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_InitWithEmptyActor_ReturnsEmptyRenderData)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), {});
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_TRUE(renderData.m_particles.empty());
EXPECT_TRUE(renderData.m_tangents.empty());
EXPECT_TRUE(renderData.m_bitangents.empty());
EXPECT_TRUE(renderData.m_normals.empty());
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_InitWithActorWithNoMesh_ReturnsEmptyRenderData)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
actor->AddJoint(MeshNodeName);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_TRUE(renderData.m_particles.empty());
EXPECT_TRUE(renderData.m_tangents.empty());
EXPECT_TRUE(renderData.m_bitangents.empty());
EXPECT_TRUE(renderData.m_normals.empty());
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_InitWithEntityActorWithNoClothData_TriggersError)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
AZ_TEST_START_TRACE_SUPPRESSION;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Expect 1 error
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_InitWithEntityActor_ReturnsValidRenderData)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_EQ(renderData.m_particles.size(), MeshVertices.size());
EXPECT_EQ(renderData.m_particles.size(), MeshClothData.size());
for (size_t i = 0; i < renderData.m_particles.size(); ++i)
{
EXPECT_THAT(renderData.m_particles[i].GetAsVector3(), IsCloseTolerance(MeshVertices[i], Tolerance));
EXPECT_NEAR(renderData.m_particles[i].GetW(), MeshClothData[i].GetR(), ToleranceU8);
}
EXPECT_THAT(renderData.m_tangents, ::testing::Each(IsCloseTolerance(AZ::Vector3::CreateAxisX(), Tolerance)));
EXPECT_THAT(renderData.m_bitangents, ::testing::Each(IsCloseTolerance(AZ::Vector3::CreateAxisY(), Tolerance)));
EXPECT_THAT(renderData.m_normals, ::testing::Each(IsCloseTolerance(AZ::Vector3::CreateAxisZ(), Tolerance)));
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_TickClothSystem_RunningSimulationVerticesGoDown)
{
{
const float height = 4.7f;
const float radius = 1.2f;
const auto collider = CreateCapsuleCollider(MeshNodeName, height, radius);
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->AddClothCollider(collider);
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
const AZStd::vector<NvCloth::SimParticleFormat> particlesBefore = clothComponentMesh.GetRenderData().m_particles;
// Ticking Cloth System updates all its solvers
for (size_t i = 0; i < 300.0f; ++i)
{
const float deltaTimeSim = 1.0f / 60.0f;
AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick,
deltaTimeSim,
AZ::ScriptTimePoint(AZStd::chrono::system_clock::now()));
}
const AZStd::vector<NvCloth::SimParticleFormat> particlesAfter = clothComponentMesh.GetRenderData().m_particles;
EXPECT_EQ(particlesAfter.size(), particlesBefore.size());
for (size_t i = 0; i < particlesAfter.size(); ++i)
{
EXPECT_LT(particlesAfter[i].GetZ(), particlesBefore[i].GetZ());
}
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_UpdateConfigurationInvalidEntity_ReturnEmptyRenderData)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
AZ::EntityId newEntityId;
clothComponentMesh.UpdateConfiguration(newEntityId, clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_TRUE(renderData.m_particles.empty());
EXPECT_TRUE(renderData.m_tangents.empty());
EXPECT_TRUE(renderData.m_bitangents.empty());
EXPECT_TRUE(renderData.m_normals.empty());
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_UpdateConfigurationDifferentEntity_ReturnsRenderDataFromNewEntity)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
const AZStd::vector<AZ::Vector3> newMeshVertices = {{
AZ::Vector3(-2.3f, 0.0f, 0.0f),
AZ::Vector3(4.0f, 0.0f, 0.0f),
AZ::Vector3(0.0f, -1.0f, 0.0f)
}};
auto newEntity = AZStd::make_unique<AZ::Entity>();
newEntity->CreateComponent<AzFramework::TransformComponent>();
auto* newActorComponent = newEntity->CreateComponent<EMotionFX::Integration::ActorComponent>();
newEntity->Init();
newEntity->Activate();
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test2");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, newMeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
newActorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
clothComponentMesh.UpdateConfiguration(newActorComponent->GetEntityId(), clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_EQ(renderData.m_particles.size(), newMeshVertices.size());
EXPECT_EQ(renderData.m_particles.size(), MeshClothData.size());
for (size_t i = 0; i < renderData.m_particles.size(); ++i)
{
EXPECT_THAT(renderData.m_particles[i].GetAsVector3(), IsCloseTolerance(newMeshVertices[i], Tolerance));
EXPECT_NEAR(renderData.m_particles[i].GetW(), MeshClothData[i].GetR(), ToleranceU8);
}
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_UpdateConfigurationInvalidMeshNode_ReturnEmptyRenderData)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
clothConfig.m_meshNode = "unknown_cloth_mesh_node";
clothComponentMesh.UpdateConfiguration(m_actorComponent->GetEntityId(), clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_TRUE(renderData.m_particles.empty());
EXPECT_TRUE(renderData.m_tangents.empty());
EXPECT_TRUE(renderData.m_bitangents.empty());
EXPECT_TRUE(renderData.m_normals.empty());
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_UpdateConfigurationNewMeshNode_ReturnsRenderDataFromNewMeshNode)
{
const AZStd::string meshNode2Name = "cloth_node_2";
const AZStd::vector<AZ::Vector3> mesh2Vertices = {{
AZ::Vector3(-2.3f, 0.0f, 0.0f),
AZ::Vector3(4.0f, 0.0f, 0.0f),
AZ::Vector3(0.0f, -1.0f, 0.0f)
}};
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
auto meshNode2Index = actor->AddJoint(meshNode2Name);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->SetMesh(LodLevel, meshNode2Index, CreateEMotionFXMesh(meshNode2Index, mesh2Vertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
clothConfig.m_meshNode = meshNode2Name;
clothComponentMesh.UpdateConfiguration(m_actorComponent->GetEntityId(), clothConfig);
const auto& renderData = clothComponentMesh.GetRenderData();
EXPECT_EQ(renderData.m_particles.size(), mesh2Vertices.size());
EXPECT_EQ(renderData.m_particles.size(), MeshClothData.size());
for (size_t i = 0; i < renderData.m_particles.size(); ++i)
{
EXPECT_THAT(renderData.m_particles[i].GetAsVector3(), IsCloseTolerance(mesh2Vertices[i], Tolerance));
EXPECT_NEAR(renderData.m_particles[i].GetW(), MeshClothData[i].GetR(), ToleranceU8);
}
}
TEST_F(NvClothComponentMesh, ClothComponentMesh_UpdateConfigurationInvertingGravity_RunningSimulationVerticesGoUp)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
const AZStd::vector<NvCloth::SimParticleFormat> particlesBefore = clothComponentMesh.GetRenderData().m_particles;
clothConfig.m_gravityScale = -1.0f;
clothComponentMesh.UpdateConfiguration(m_actorComponent->GetEntityId(), clothConfig);
// Ticking Cloth System updates all its solvers
for (size_t i = 0; i < 300.0f; ++i)
{
const float deltaTimeSim = 1.0f / 60.0f;
AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick,
deltaTimeSim,
AZ::ScriptTimePoint(AZStd::chrono::system_clock::now()));
}
const AZStd::vector<NvCloth::SimParticleFormat> particlesAfter = clothComponentMesh.GetRenderData().m_particles;
EXPECT_EQ(particlesAfter.size(), particlesBefore.size());
for (size_t i = 0; i < particlesAfter.size(); ++i)
{
EXPECT_GT(particlesAfter[i].GetZ(), particlesBefore[i].GetZ());
}
}
// [TODO LYN-1887] Revisit when Cloth Component Mesh works with Atom models
TEST_F(NvClothComponentMesh, DISABLED_ClothComponentMesh_ModifyMesh_RenderMeshIsUpdated)
{
{
auto actor = AZStd::make_unique<ActorHelper>("actor_test");
auto meshNodeIndex = actor->AddJoint(MeshNodeName);
actor->SetMesh(LodLevel, meshNodeIndex, CreateEMotionFXMesh(meshNodeIndex, MeshVertices, MeshIndices, MeshSkinningInfo, MeshUVs, MeshClothData));
actor->FinishSetup();
m_actorComponent->OnAssetReady(CreateAssetFromActor(AZStd::move(actor)));
}
NvCloth::ClothConfiguration clothConfig;
clothConfig.m_meshNode = MeshNodeName;
NvCloth::ClothComponentMesh clothComponentMesh(m_actorComponent->GetEntityId(), clothConfig);
// Ticking Cloth System updates all its solvers
for (size_t i = 0; i < 300.0f; ++i)
{
const float deltaTimeSim = 1.0f / 60.0f;
AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick,
deltaTimeSim,
AZ::ScriptTimePoint(AZStd::chrono::system_clock::now()));
}
CryRenderMeshStub renderMesh(MeshVertices);
/*LmbrCentral::MeshModificationNotificationBus::Event(
m_actorComponent->GetEntityId(),
&LmbrCentral::MeshModificationNotificationBus::Events::ModifyMesh,
LodLevel,
0,
&renderMesh);*/
const AZStd::vector<NvCloth::SimParticleFormat>& clothParticles = clothComponentMesh.GetRenderData().m_particles;
const AZStd::vector<Vec3>& renderMeshPositions = renderMesh.m_positions;
EXPECT_EQ(renderMeshPositions.size(), clothParticles.size());
for (size_t i = 0; i < renderMeshPositions.size(); ++i)
{
EXPECT_THAT(LYVec3ToAZVec3(renderMeshPositions[i]), IsCloseTolerance(clothParticles[i].GetAsVector3(), Tolerance));
}
}
} // namespace UnitTest
@@ -0,0 +1,297 @@
/*
* 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 <AzTest/AzTest.h>
#include <Components/ClothComponentMesh/ClothConstraints.h>
#include <UnitTestHelper.h>
namespace UnitTest
{
//! Fixture to hold the tests data.
class NvClothConstraints
: public ::testing::Test
{
public:
const AZStd::vector<float> MeshMotionConstraintsData = {{
1.0f, 0.5f, 0.0f
}};
const float MotionConstraintsMaxDistance = 3.0f;
const AZStd::vector<AZ::Vector2> MeshBackstopOffsetAndRadiusData = {{
AZ::Vector2(1.0f, 1.0f),
AZ::Vector2(0.0f, 0.5f),
AZ::Vector2(-1.0f, 0.1f)
}};
const float BackstopMaxRadius = 3.0f;
const float BackstopMaxBackOffset = 2.0f;
const float BackstopMaxFrontOffset = 5.0f;
const AZStd::vector<int> MeshRemappedVertices = {{
0, 1, 2
}};
const AZStd::vector<NvCloth::SimParticleFormat> SimulationParticles = {{
NvCloth::SimParticleFormat(1.0f,0.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,0.0f),
NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
}};
const AZStd::vector<NvCloth::SimIndexType> SimulationIndices = {{
0, 1, 2
}};
};
TEST_F(NvClothConstraints, ClothConstraints_DefaultConstruct_ReturnsEmptyData)
{
NvCloth::ClothConstraints clothConstraints;
EXPECT_TRUE(clothConstraints.GetMotionConstraints().empty());
EXPECT_TRUE(clothConstraints.GetSeparationConstraints().empty());
clothConstraints.CalculateConstraints({}, {});
EXPECT_TRUE(clothConstraints.GetMotionConstraints().empty());
EXPECT_TRUE(clothConstraints.GetSeparationConstraints().empty());
clothConstraints.CalculateConstraints(SimulationParticles, SimulationIndices);
EXPECT_TRUE(clothConstraints.GetMotionConstraints().empty());
EXPECT_TRUE(clothConstraints.GetSeparationConstraints().empty());
}
TEST_F(NvClothConstraints, ClothConstraints_CreateWithNoInfo_ReturnsEmptyData)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
{},
0.0f,
{},
0.0f,
0.0f,
0.0f,
{},
{},
{});
EXPECT_TRUE(clothConstraints->GetMotionConstraints().empty());
EXPECT_TRUE(clothConstraints->GetSeparationConstraints().empty());
}
TEST_F(NvClothConstraints, ClothConstraints_CreateWithMotionConstraintsInfo_ReturnsValidMotionConstraints)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
MeshMotionConstraintsData,
MotionConstraintsMaxDistance,
{},
0.0f,
0.0f,
0.0f,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const AZStd::vector<AZ::Vector4>& motionConstraints = clothConstraints->GetMotionConstraints();
EXPECT_TRUE(motionConstraints.size() == SimulationParticles.size());
EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(SimulationParticles[0].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(SimulationParticles[1].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(SimulationParticles[2].GetAsVector3(), Tolerance));
EXPECT_NEAR(motionConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(motionConstraints[1].GetW(), 0.0f, Tolerance);
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
}
TEST_F(NvClothConstraints, ClothConstraints_SetMotionConstraintMaxDistance_UpdatesMotionsConstraints)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
MeshMotionConstraintsData,
MotionConstraintsMaxDistance,
{},
0.0f,
0.0f,
0.0f,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const float newMotionConstraintsMaxDistance = 6.0f;
clothConstraints->SetMotionConstraintMaxDistance(newMotionConstraintsMaxDistance);
const AZStd::vector<AZ::Vector4>& motionConstraints = clothConstraints->GetMotionConstraints();
EXPECT_TRUE(motionConstraints.size() == SimulationParticles.size());
EXPECT_NEAR(motionConstraints[0].GetW(), 6.0f, Tolerance);
EXPECT_NEAR(motionConstraints[1].GetW(), 0.0f, Tolerance);
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
}
TEST_F(NvClothConstraints, ClothConstraints_CreateWithBackstopInfo_ReturnsValidSeparationConstraints)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
{},
0.0f,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
EXPECT_TRUE(separationConstraints.size() == SimulationParticles.size());
EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance);
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.88675f, -2.88675f, -2.88675f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-0.866025f, 0.133975f, -0.866025f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(3.05996f, 3.05996f, 4.05996f), Tolerance));
}
TEST_F(NvClothConstraints, ClothConstraints_SetBackstopMaxRadius_UpdatesSeparationConstraints)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
{},
0.0f,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const float newBackstopMaxRadius = 6.0f;
clothConstraints->SetBackstopMaxRadius(newBackstopMaxRadius);
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
EXPECT_TRUE(separationConstraints.size() == SimulationParticles.size());
EXPECT_NEAR(separationConstraints[0].GetW(), 6.0f, Tolerance);
EXPECT_NEAR(separationConstraints[1].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(separationConstraints[2].GetW(), 0.6f, Tolerance);
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-3.6188f, -4.6188f, -4.6188f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.73205f, -0.732051f, -1.73205f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(3.23316f, 3.23316f, 4.23316f), Tolerance));
}
TEST_F(NvClothConstraints, ClothConstraints_SetBackstopMaxOffsets_UpdatesSeparationConstraints)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
{},
0.0f,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const float newBackstopMaxBackOffset = -4.0f;
const float newBackstopMaxFrontOffset = 3.0f;
clothConstraints->SetBackstopMaxOffsets(newBackstopMaxBackOffset, newBackstopMaxFrontOffset);
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
EXPECT_TRUE(separationConstraints.size() == SimulationParticles.size());
EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance);
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(5.04145f, 4.04145f, 4.04145f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-0.866025f, 0.133975f, -0.866025f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(1.90526f, 1.90526f, 2.90526f), Tolerance));
}
TEST_F(NvClothConstraints, ClothConstraints_CalculateConstraintsWithEmptyData_ConstraintsRemainUnchanged)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
MeshMotionConstraintsData,
MotionConstraintsMaxDistance,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const AZStd::vector<AZ::Vector4> motionConstraints = clothConstraints->GetMotionConstraints();
const AZStd::vector<AZ::Vector4> separationConstraints = clothConstraints->GetSeparationConstraints();
clothConstraints->CalculateConstraints({}, {});
EXPECT_THAT(motionConstraints, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothConstraints->GetMotionConstraints()));
EXPECT_THAT(separationConstraints, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothConstraints->GetSeparationConstraints()));
}
TEST_F(NvClothConstraints, ClothConstraints_CalculateConstraintsWithSameData_ConstraintsRemainUnchanged)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
MeshMotionConstraintsData,
MotionConstraintsMaxDistance,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const AZStd::vector<AZ::Vector4> motionConstraints = clothConstraints->GetMotionConstraints();
const AZStd::vector<AZ::Vector4> separationConstraints = clothConstraints->GetSeparationConstraints();
clothConstraints->CalculateConstraints(SimulationParticles, SimulationIndices);
EXPECT_THAT(motionConstraints, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothConstraints->GetMotionConstraints()));
EXPECT_THAT(separationConstraints, ::testing::Pointwise(ContainerIsCloseTolerance(Tolerance), clothConstraints->GetSeparationConstraints()));
}
TEST_F(NvClothConstraints, ClothConstraints_CalculateConstraintsWithNewParticles_ContraintsAreModified)
{
AZStd::unique_ptr<NvCloth::ClothConstraints> clothConstraints = NvCloth::ClothConstraints::Create(
MeshMotionConstraintsData,
MotionConstraintsMaxDistance,
MeshBackstopOffsetAndRadiusData,
BackstopMaxRadius,
BackstopMaxBackOffset,
BackstopMaxFrontOffset,
SimulationParticles,
SimulationIndices,
MeshRemappedVertices);
const AZStd::vector<NvCloth::SimParticleFormat> newParticles = {{
NvCloth::SimParticleFormat(0.0f,0.0f,1.0f,1.0f),
NvCloth::SimParticleFormat(0.0f,1.0f,0.0f,1.0f),
NvCloth::SimParticleFormat(1.0f,0.0f,1.0f,0.0f),
}};
clothConstraints->CalculateConstraints(newParticles, SimulationIndices);
const AZStd::vector<AZ::Vector4>& motionConstraints = clothConstraints->GetMotionConstraints();
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
EXPECT_TRUE(motionConstraints.size() == newParticles.size());
EXPECT_NEAR(motionConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(motionConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
EXPECT_TRUE(separationConstraints.size() == newParticles.size());
EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance);
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-3.03902f, 2.80752f, 3.80752f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.41659f, 0.651243f, -0.348757f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(6.15313f, -0.876132f, 0.123868f), Tolerance));
}
} // namespace UnitTest