/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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::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::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 AddTestRigidBodyCollider(AZ::Vector3& position , ForceType forceType , AzPhysics::SceneHandle sceneHandle , const char* name = "TestObjectEntity") { AZStd::unique_ptr entity(aznew AZ::Entity(name)); AZ::TransformConfig transformConfig; if (forceType == PointForce) { position.SetX(0.05f); } transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position); entity->CreateComponent()->SetConfiguration(transformConfig); auto colliderConfiguration = AZStd::make_shared(); auto boxShapeConfiguration = AZStd::make_shared(); auto boxColliderComponent = entity->CreateComponent(); boxColliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfiguration, boxShapeConfiguration) }); AzPhysics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_computeMass = false; entity->CreateComponent(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 AZStd::unique_ptr AddForceRegion(const AZ::Vector3& position, ForceType forceType) { AZStd::unique_ptr forceRegionEntity(aznew AZ::Entity("ForceRegion")); AZ::TransformConfig transformConfig; transformConfig.m_worldTransform = AZ::Transform::CreateTranslation(position); forceRegionEntity->CreateComponent()->SetConfiguration(transformConfig); auto colliderConfiguration = AZStd::make_shared(); colliderConfiguration->m_isTrigger = true; auto shapeConfiguration = AZStd::make_shared(); auto colliderComponent = forceRegionEntity->CreateComponent(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfiguration, shapeConfiguration) }); // We need StaticRigidBodyComponent to get shapes from collider component added to PhysX world forceRegionEntity->CreateComponent(); forceRegionEntity->CreateComponent(); 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 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 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(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 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(sceneHandle, forceType); } TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_EntityVelocityZPositive) { AZ::Vector3 entityVelocity = TestForceVolume(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(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(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(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(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(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(GetTestSceneHandle(), PointForce); } TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_AppliesSameMagnitude) { TestAppliesSameMagnitude(GetTestSceneHandle(), WorldSpaceForce); } TEST_F(PhysXForceRegionTest, ForceRegion_LocalSpaceForce_AppliesSameMagnitude) { TestAppliesSameMagnitude(GetTestSceneHandle(), LocalSpaceForce); } }