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.
o3de/Gems/NvCloth/Code/Tests/Components/ClothComponentMesh/ActorClothCollidersTest.cpp

376 lines
18 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 <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->SetActorAsset(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->SetActorAsset(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->SetActorAsset(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();
ASSERT_EQ(sphereColliders.size(), 1);
ASSERT_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->SetActorAsset(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());
ASSERT_EQ(nativeSpheres.size(), 2); // Each capsule produces 2 spheres
ASSERT_EQ(capsuleColliders.size(), 1);
ASSERT_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->SetActorAsset(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->SetActorAsset(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->SetActorAsset(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->SetActorAsset(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->SetActorAsset(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();
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