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/PhysX/Code/Tests/PhysXForceRegionTest.cpp

349 lines
14 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 <AzTest/AzTest.h>
#include <BoxColliderComponent.h>
#include <ForceRegionComponent.h>
#include <RigidBodyComponent.h>
#include <StaticRigidBodyComponent.h>
#include <PhysX/ForceRegionComponentBus.h>
#include <LmbrCentral/Shape/SplineComponentBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Physics/RigidBodyBus.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <Tests/PhysXTestCommon.h>
namespace PhysX
{
enum ForceType : AZ::u8
{
WorldSpaceForce
, LocalSpaceForce
, PointForce
, SplineFollowForce
, SimpleDragForce
, LinearDampingForce
};
class PhysXForceRegionTest
: public::testing::Test
, protected Physics::DefaultWorldBus::Handler
{
void SetUp() override
{
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
AzPhysics::SceneConfiguration sceneConfiguration = physicsSystem->GetDefaultSceneConfiguration();
sceneConfiguration.m_sceneName = AzPhysics::DefaultPhysicsSceneName;
m_testSceneHandle = physicsSystem->AddScene(sceneConfiguration);
m_defaultScene = physicsSystem->GetScene(m_testSceneHandle);
}
Physics::DefaultWorldBus::Handler::BusConnect();
}
void TearDown() override
{
Physics::DefaultWorldBus::Handler::BusDisconnect();
//Cleanup the test scene
m_defaultScene = nullptr;
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
physicsSystem->RemoveScene(m_testSceneHandle);
}
m_testSceneHandle = AzPhysics::InvalidSceneHandle;
TestUtils::ResetPhysXSystem();
}
// DefaultWorldBus
AzPhysics::SceneHandle GetDefaultSceneHandle() const override
{
return m_testSceneHandle;
}
AzPhysics::Scene* m_defaultScene = nullptr;
AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle;
public:
AzPhysics::Scene* GetTestScene() const
{
return m_defaultScene;
}
AzPhysics::SceneHandle GetTestSceneHandle() const
{
return m_testSceneHandle;
}
};
AZStd::unique_ptr<AZ::Entity> AddTestRigidBodyCollider(AZ::Vector3& position
, ForceType forceType
, AzPhysics::SceneHandle sceneHandle
, const char* name = "TestObjectEntity")
{
AZStd::unique_ptr<AZ::Entity> entity(aznew AZ::Entity(name));
AZ::TransformConfig transformConfig;
if (forceType == PointForce)
{
position.SetX(0.05f);
}
transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position);
entity->CreateComponent<AzFramework::TransformComponent>()->SetConfiguration(transformConfig);
auto colliderConfiguration = AZStd::make_shared<Physics::ColliderConfiguration>();
auto boxShapeConfiguration = AZStd::make_shared<Physics::BoxShapeConfiguration>();
auto boxColliderComponent = entity->CreateComponent<BoxColliderComponent>();
boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfiguration, boxShapeConfiguration) });
AzPhysics::RigidBodyConfiguration rigidBodyConfig;
rigidBodyConfig.m_computeMass = false;
entity->CreateComponent<PhysX::RigidBodyComponent>(rigidBodyConfig, sceneHandle);
entity->Init();
entity->Activate();
return entity;
}
namespace DefaultForceRegionParams
{
const AZ::Vector3 ForceDirection(0.0f, 0.0f, 1.0f);
const AZ::Vector3 RotationY(.0f, 90.0f, .0f);
const float ForceMagnitude = 100.0f;
const float DampingRatio = 0.0f;
const float Frequency = 1.0f;
const float TargetSpeed = 1.0f;
const float LookAhead = 0.0f;
const float DragCoefficient = 1.0f;
const float VolumeDensity = 5.0f;
const float Damping = 10.0f;
}
template<typename ColliderType>
AZStd::unique_ptr<AZ::Entity> AddForceRegion(const AZ::Vector3& position, ForceType forceType)
{
AZStd::unique_ptr<AZ::Entity> forceRegionEntity(aznew AZ::Entity("ForceRegion"));
AZ::TransformConfig transformConfig;
transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position);
forceRegionEntity->CreateComponent<AzFramework::TransformComponent>()->SetConfiguration(transformConfig);
auto colliderConfiguration = AZStd::make_shared<Physics::ColliderConfiguration>();
colliderConfiguration->m_isTrigger = true;
auto shapeConfiguration = AZStd::make_shared<typename ColliderType::Configuration>();
auto colliderComponent = forceRegionEntity->CreateComponent<ColliderType>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfiguration, shapeConfiguration) });
// We need StaticRigidBodyComponent to get shapes from collider component added to PhysX world
forceRegionEntity->CreateComponent<StaticRigidBodyComponent>();
forceRegionEntity->CreateComponent<ForceRegionComponent>();
if (forceType == SplineFollowForce)
{
forceRegionEntity->CreateComponent("{F0905297-1E24-4044-BFDA-BDE3583F1E57}");//SplineComponent
}
forceRegionEntity->Init();
forceRegionEntity->Activate();
if (forceType == WorldSpaceForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForceWorldSpace
, DefaultForceRegionParams::ForceDirection
, DefaultForceRegionParams::ForceMagnitude);
}
else if (forceType == LocalSpaceForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForceLocalSpace
, DefaultForceRegionParams::ForceDirection
, DefaultForceRegionParams::ForceMagnitude);
AZ::TransformBus::Event(forceRegionEntity->GetId()
, &AZ::TransformBus::Events::SetLocalRotation
, DefaultForceRegionParams::RotationY);
}
else if (forceType == PointForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForcePoint
, DefaultForceRegionParams::ForceMagnitude);
}
else if (forceType == SplineFollowForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForceSplineFollow
, DefaultForceRegionParams::DampingRatio
, DefaultForceRegionParams::Frequency
, DefaultForceRegionParams::TargetSpeed
, DefaultForceRegionParams::LookAhead
);
const AZStd::vector<AZ::Vector3> vertices =
{
AZ::Vector3(0.0f, 0.0f, 12.5f),
AZ::Vector3(0.25f, 0.25f, 12.0f),
AZ::Vector3(0.5f, 0.5f, 12.0f)
};
LmbrCentral::SplineComponentRequestBus::Event(forceRegionEntity->GetId()
, &LmbrCentral::SplineComponentRequestBus::Events::SetVertices, vertices);
}
else if (forceType == SimpleDragForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForceSimpleDrag
, DefaultForceRegionParams::DragCoefficient
, DefaultForceRegionParams::VolumeDensity);
}
else if (forceType == LinearDampingForce)
{
PhysX::ForceRegionRequestBus::Event(forceRegionEntity->GetId()
, &PhysX::ForceRegionRequests::AddForceLinearDamping
, DefaultForceRegionParams::Damping);
}
return forceRegionEntity;
}
template<typename ColliderType>
AZ::Vector3 TestForceVolume(AzPhysics::SceneHandle sceneHandle, ForceType forceType)
{
AZ::Vector3 velocity = AZ::Vector3::CreateZero();
AZ::Vector3 position(0.0f, 0.0f, 16.0f);
auto rigidBodyCollider = AddTestRigidBodyCollider(position, forceType, sceneHandle, "TestBox" );
auto forceRegion = AddForceRegion<ColliderType>(AZ::Vector3(0.0f, 0.0f, 12.0f), forceType);
//Run simulation for a while - bounces box once on force volume
constexpr const float deltaTime = 1.0f / 180.0f;
TestUtils::UpdateScene(sceneHandle, deltaTime, 240);
Physics::RigidBodyRequestBus::EventResult(velocity
, rigidBodyCollider->GetId()
, &Physics::RigidBodyRequestBus::Events::GetLinearVelocity);
return velocity;
}
template<typename ColliderType>
void TestAppliesSameMagnitude(AzPhysics::SceneHandle sceneHandle, ForceType forceType)
{
struct ForceRegionMagnitudeChecker
: public ForceRegionNotificationBus::Handler
{
ForceRegionMagnitudeChecker() { ForceRegionNotificationBus::Handler::BusConnect(); }
~ForceRegionMagnitudeChecker() { ForceRegionNotificationBus::Handler::BusDisconnect(); }
void OnCalculateNetForce(AZ::EntityId, AZ::EntityId, const AZ::Vector3&, float netForceMagnitude)
{
// This callback can potentially be called in every frame, so just only catch the first failure to avoid spamming
if (!m_failed)
{
const float forceRegionMaxError = 0.05f; // Force region uses fast approximation for length calculations, hence the error
const bool result = AZ::IsClose(netForceMagnitude, DefaultForceRegionParams::ForceMagnitude, forceRegionMaxError);
if (!result)
{
m_failed = true;
}
EXPECT_TRUE(result);
}
}
bool m_failed = false;
};
ForceRegionMagnitudeChecker magnitudeChecker;
TestForceVolume<ColliderType>(sceneHandle, forceType);
}
TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_EntityVelocityZPositive)
{
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), WorldSpaceForce);
// World space force direction: AZ::Vector3(0.0f, 0.0f, 1.0f)
EXPECT_TRUE(entityVelocity.GetZ() > 0.0f); // World space force causes box to bounce upwards
EXPECT_NEAR(entityVelocity.GetX(), 0.0f, AZ::Constants::FloatEpsilon);
EXPECT_NEAR(entityVelocity.GetY(), 0.0f, AZ::Constants::FloatEpsilon);
}
TEST_F(PhysXForceRegionTest, ForceRegion_LocalSpaceForce_EntityVelocityZPositive)
{
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), LocalSpaceForce);
// Local space force direction: AZ::Vector3(0.0f, 0.0f, 1.0f)
// Force region was rotated about Y-axis by 90 deg
EXPECT_TRUE(entityVelocity.GetX() > 0.0f); // Falling body should be moving in positive X direction since force region is rotated.
EXPECT_NEAR(entityVelocity.GetY(), 0.0f, AZ::Constants::FloatEpsilon);
EXPECT_TRUE(entityVelocity.GetZ() < 0.0f); // Gravity
}
TEST_F(PhysXForceRegionTest, ForceRegion_PointForce_EntityVelocityZPositive)
{
// Falling body was positioned at AZ::Vector3(0.05f, 0.0f, 16.0f)
// Force region was positioned at AZ::Vector3(0.0f, 0.0f, 12.0f)
// PointForce causes box to bounce upwards and to the right.
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), PointForce);
EXPECT_TRUE(entityVelocity.GetX() > 0.0f);
EXPECT_NEAR(entityVelocity.GetY(), 0.0f, AZ::Constants::FloatEpsilon);
EXPECT_TRUE(entityVelocity.GetZ() > 0.0f);
}
TEST_F(PhysXForceRegionTest, ForceRegion_SplineFollowForce_EntityVelocitySpecificValue)
{
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), SplineFollowForce);
// Follow spline direction towards positive X and Y.
EXPECT_TRUE(entityVelocity.GetX() > 0.0f);
EXPECT_TRUE(entityVelocity.GetY() > 0.0f);
}
TEST_F(PhysXForceRegionTest, ForceRegion_SimpleDragForce_EntityVelocitySpecificValue)
{
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), SimpleDragForce);
EXPECT_TRUE(entityVelocity.GetZ() > -12.65f); // Falling velocity should be slower than free fall velocity, which is -12.65.
EXPECT_NEAR(entityVelocity.GetX(), 0.0f, AZ::Constants::FloatEpsilon); // Dragging should not change original direction.
EXPECT_NEAR(entityVelocity.GetY(), 0.0f, AZ::Constants::FloatEpsilon); // Dragging should not change original direction.
}
TEST_F(PhysXForceRegionTest, ForceRegion_LinearDampingForce_EntityVelocitySpecificValue)
{
AZ::Vector3 entityVelocity = TestForceVolume<BoxColliderComponent>(GetTestSceneHandle(), LinearDampingForce);
EXPECT_TRUE(entityVelocity.GetZ() > -12.65f); // Falling velocity should be slower than free fall velocity, which is -12.65.
EXPECT_NEAR(entityVelocity.GetX(), 0.0f, AZ::Constants::FloatEpsilon); // Damping should not change original direction.
EXPECT_NEAR(entityVelocity.GetY(), 0.0f, AZ::Constants::FloatEpsilon); // Damping should not change original direction.
}
TEST_F(PhysXForceRegionTest, ForceRegion_PointForce_AppliesSameMagnitude)
{
TestAppliesSameMagnitude<BoxColliderComponent>(GetTestSceneHandle(), PointForce);
}
TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_AppliesSameMagnitude)
{
TestAppliesSameMagnitude<BoxColliderComponent>(GetTestSceneHandle(), WorldSpaceForce);
}
TEST_F(PhysXForceRegionTest, ForceRegion_LocalSpaceForce_AppliesSameMagnitude)
{
TestAppliesSameMagnitude<BoxColliderComponent>(GetTestSceneHandle(), LocalSpaceForce);
}
}