/* * 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 #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 , protected Physics::WorldEventHandler { void SetUp() override { m_defaultWorld = AZ::Interface::Get()->CreateWorld(Physics::DefaultPhysicsWorldId); m_defaultWorld->SetEventHandler(this); Physics::DefaultWorldBus::Handler::BusConnect(); } void TearDown() override { Physics::DefaultWorldBus::Handler::BusDisconnect(); m_defaultWorld = nullptr; } // DefaultWorldBus AZStd::shared_ptr GetDefaultWorld() override { return m_defaultWorld; } // WorldEventHandler void OnCollisionBegin(const Physics::CollisionEvent&) override //Not used in force region tests { } void OnCollisionPersist(const Physics::CollisionEvent&) override //Not used in force region tests { } void OnCollisionEnd(const Physics::CollisionEvent&) override //Not used in force region tests { } void OnTriggerEnter(const Physics::TriggerEvent& triggerEvent) override { Physics::TriggerNotificationBus::QueueEvent(triggerEvent.m_triggerBody->GetEntityId() , &Physics::TriggerNotifications::OnTriggerEnter , triggerEvent); } void OnTriggerExit(const Physics::TriggerEvent& triggerEvent) override { Physics::TriggerNotificationBus::QueueEvent(triggerEvent.m_triggerBody->GetEntityId() , &Physics::TriggerNotifications::OnTriggerExit , triggerEvent); } AZStd::shared_ptr m_defaultWorld; }; AZStd::unique_ptr AddTestRigidBodyCollider(AZ::Vector3& position , ForceType forceType , 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) }); Physics::RigidBodyConfiguration rigidBodyConfig; rigidBodyConfig.m_computeMass = false; entity->CreateComponent(rigidBodyConfig); 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(ForceType forceType) { AZ::Vector3 velocity = AZ::Vector3::CreateZero(); AZ::Vector3 position(0.0f, 0.0f, 16.0f); auto rigidBodyCollider = AddTestRigidBodyCollider(position, forceType, "TestBox"); auto forceRegion = AddForceRegion(AZ::Vector3(0.0f, 0.0f, 12.0f), forceType); AZStd::shared_ptr world; Physics::DefaultWorldBus::BroadcastResult(world, &Physics::DefaultWorldRequests::GetDefaultWorld); //Run simulation for a while - bounces box once on force volume float deltaTime = 1.0f / 180.0f; for (int timeStep = 0; timeStep < 240; timeStep++) { world->Update(deltaTime); } Physics::RigidBodyRequestBus::EventResult(velocity , rigidBodyCollider->GetId() , &Physics::RigidBodyRequestBus::Events::GetLinearVelocity); return velocity; } template void TestAppliesSameMagnitude(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(forceType); } TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_EntityVelocityZPositive) { AZ::Vector3 entityVelocity = TestForceVolume(WorldSpaceForce); // World space force direction: AZ::Vector3(0.0f, 0.0f, 1.0f) EXPECT_TRUE(entityVelocity.GetZ().IsGreaterThan(0.0f)); // World space force causes box to bounce upwards EXPECT_TRUE(entityVelocity.GetX().IsClose(0.0f)); EXPECT_TRUE(entityVelocity.GetY().IsClose(0.0f)); } TEST_F(PhysXForceRegionTest, ForceRegion_LocalSpaceForce_EntityVelocityZPositive) { AZ::Vector3 entityVelocity = TestForceVolume(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().IsGreaterThan(0.0f)); // Falling body should be moving in positive X direction since force region is rotated. EXPECT_TRUE(entityVelocity.GetY().IsClose(0.0f)); EXPECT_TRUE(entityVelocity.GetZ().IsLessThan(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(PointForce); EXPECT_TRUE(entityVelocity.GetX().IsGreaterThan(0.0f)); EXPECT_TRUE(entityVelocity.GetY().IsClose(0.0f)); EXPECT_TRUE(entityVelocity.GetZ().IsGreaterThan(0.0f)); } TEST_F(PhysXForceRegionTest, ForceRegion_SplineFollowForce_EntityVelocitySpecificValue) { AZ::Vector3 entityVelocity = TestForceVolume(SplineFollowForce); // Follow spline direction towards positive X and Y. EXPECT_TRUE(entityVelocity.GetX().IsGreaterThan(0.0f)); EXPECT_TRUE(entityVelocity.GetY().IsGreaterThan(0.0f)); } TEST_F(PhysXForceRegionTest, ForceRegion_SimpleDragForce_EntityVelocitySpecificValue) { AZ::Vector3 entityVelocity = TestForceVolume(SimpleDragForce); EXPECT_TRUE(entityVelocity.GetZ().IsGreaterThan(-12.65f)); // Falling velocity should be slower than free fall velocity, which is -12.65. EXPECT_TRUE(entityVelocity.GetX().IsClose(0.0f)); // Dragging should not change original direction. EXPECT_TRUE(entityVelocity.GetY().IsClose(0.0f)); // Dragging should not change original direction. } TEST_F(PhysXForceRegionTest, ForceRegion_LinearDampingForce_EntityVelocitySpecificValue) { AZ::Vector3 entityVelocity = TestForceVolume(LinearDampingForce); EXPECT_TRUE(entityVelocity.GetZ().IsGreaterThan(-12.65f)); // Falling velocity should be slower than free fall velocity, which is -12.65. EXPECT_TRUE(entityVelocity.GetX().IsClose(0.0f)); // Damping should not change original direction. EXPECT_TRUE(entityVelocity.GetY().IsClose(0.0f)); // Damping should not change original direction. } TEST_F(PhysXForceRegionTest, ForceRegion_PointForce_AppliesSameMagnitude) { TestAppliesSameMagnitude(PointForce); } TEST_F(PhysXForceRegionTest, ForceRegion_WorldSpaceForce_AppliesSameMagnitude) { TestAppliesSameMagnitude(WorldSpaceForce); } TEST_F(PhysXForceRegionTest, ForceRegion_LocalSpaceForce_AppliesSameMagnitude) { TestAppliesSameMagnitude(LocalSpaceForce); } }