/* * 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 namespace PhysX { class PhysicsComponentBusTest : public GenericPhysicsInterfaceTest { }; TEST_F(PhysicsComponentBusTest, SetLinearDamping_DynamicSphere_MoreDampedBodyFallsSlower) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, 0.1f); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, 0.2f); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 60); float dampingA, dampingB; Physics::RigidBodyRequestBus::EventResult(dampingA, sphereA->GetId(), &Physics::RigidBodyRequests::GetLinearDamping); Physics::RigidBodyRequestBus::EventResult(dampingB, sphereB->GetId(), &Physics::RigidBodyRequests::GetLinearDamping); EXPECT_NEAR(dampingA, 0.1f, 1e-3f); EXPECT_NEAR(dampingB, 0.2f, 1e-3f); float zA = TestUtils::GetPositionElement(sphereA, 2); float zB = TestUtils::GetPositionElement(sphereB, 2); EXPECT_GT(zB, zA); AZ::Vector3 vA = AZ::Vector3::CreateZero(); AZ::Vector3 vB = AZ::Vector3::CreateZero(); Physics::RigidBodyRequestBus::EventResult(vA, sphereA->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); Physics::RigidBodyRequestBus::EventResult(vB, sphereB->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); EXPECT_GT(static_cast(vA.GetLength()), static_cast(vB.GetLength())); } TEST_F(PhysicsComponentBusTest, SetLinearDampingNegative_DynamicSphere_NegativeValueRejected) { UnitTest::ErrorHandler errorHandler("Negative linear damping value"); EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), 0.5f); float damping = 0.0f, initialDamping = 0.0f; Physics::RigidBodyRequestBus::EventResult(initialDamping, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearDamping); // a negative damping value should be rejected and the damping should remain at its previous value Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, -0.1f); Physics::RigidBodyRequestBus::EventResult(damping, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearDamping); EXPECT_NEAR(damping, initialDamping, 1e-3f); EXPECT_TRUE(errorHandler.GetWarningCount() > 0); } TEST_F(PhysicsComponentBusTest, SetAngularDamping_DynamicSphere_MoreDampedBodyRotatesSlower) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f); EntityPtr floor = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f)); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetAngularDamping, 0.1f); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::SetAngularDamping, 0.2f); float dampingA, dampingB; Physics::RigidBodyRequestBus::EventResult(dampingA, sphereA->GetId(), &Physics::RigidBodyRequests::GetAngularDamping); Physics::RigidBodyRequestBus::EventResult(dampingB, sphereB->GetId(), &Physics::RigidBodyRequests::GetAngularDamping); EXPECT_NEAR(dampingA, 0.1f, 1e-3f); EXPECT_NEAR(dampingB, 0.2f, 1e-3f); AZ::Vector3 impulse(10.0f, 0.0f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::ApplyLinearImpulse, impulse); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::ApplyLinearImpulse, impulse); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); auto angularVelocityA = AZ::Vector3::CreateZero(); auto angularVelocityB = AZ::Vector3::CreateZero(); for (int timestep = 0; timestep < 10; timestep++) { Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); EXPECT_GT(static_cast(angularVelocityA.GetLength()), static_cast(angularVelocityB.GetLength())); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); } } TEST_F(PhysicsComponentBusTest, SetAngularDampingNegative_DynamicSphere_NegativeValueRejected) { UnitTest::ErrorHandler errorHandler("Negative angular damping value"); EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), 0.5f); float damping = 0.0f, initialDamping = 0.0f; Physics::RigidBodyRequestBus::EventResult(initialDamping, sphere->GetId(), &Physics::RigidBodyRequests::GetAngularDamping); // a negative damping value should be rejected and the damping should remain at its previous value Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetAngularDamping, -0.1f); Physics::RigidBodyRequestBus::EventResult(damping, sphere->GetId(), &Physics::RigidBodyRequests::GetAngularDamping); EXPECT_NEAR(damping, initialDamping, 1e-3f); EXPECT_TRUE(errorHandler.GetWarningCount() > 0); } TEST_F(PhysicsComponentBusTest, AddImpulse_DynamicSphere_AffectsTrajectory) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); AZ::Vector3 impulse(10.0f, 0.0f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::ApplyLinearImpulse, impulse); for (int i = 1; i < 10; i++) { float xPreviousA = TestUtils::GetPositionElement(sphereA, 0); float xPreviousB = TestUtils::GetPositionElement(sphereB, 0); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); EXPECT_GT(TestUtils::GetPositionElement(sphereA, 0), xPreviousA); EXPECT_NEAR(TestUtils::GetPositionElement(sphereB, 0), xPreviousB, 1e-3f); } } TEST_F(PhysicsComponentBusTest, SetLinearVelocity_DynamicSphere_AffectsTrajectory) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); AZ::Vector3 velocity(10.0f, 0.0f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetLinearVelocity, velocity); for (int i = 1; i < 10; i++) { float xPreviousA = TestUtils::GetPositionElement(sphereA, 0); float xPreviousB = TestUtils::GetPositionElement(sphereB, 0); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); EXPECT_GT(TestUtils::GetPositionElement(sphereA, 0), xPreviousA); EXPECT_NEAR(TestUtils::GetPositionElement(sphereB, 0), xPreviousB, 1e-3f); } } TEST_F(PhysicsComponentBusTest, AddImpulseAtWorldPoint_DynamicSphere_AffectsTrajectoryAndRotation) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); AZ::Vector3 impulse(10.0f, 0.0f, 0.0f); AZ::Vector3 worldPoint(0.0f, -5.0f, 0.25f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::ApplyLinearImpulseAtWorldPoint, impulse, worldPoint); AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero(); AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero(); for (int i = 1; i < 10; i++) { float xPreviousA = TestUtils::GetPositionElement(sphereA, 0); float xPreviousB = TestUtils::GetPositionElement(sphereB, 0); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); EXPECT_GT(TestUtils::GetPositionElement(sphereA, 0), xPreviousA); EXPECT_NEAR(TestUtils::GetPositionElement(sphereB, 0), xPreviousB, 1e-3f); Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero())); EXPECT_NEAR(static_cast(angularVelocityA.GetX()), 0.0f, 1e-3f); EXPECT_NEAR(static_cast(angularVelocityA.GetZ()), 0.0f, 1e-3f); EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero())); } } TEST_F(PhysicsComponentBusTest, AddAngularImpulse_DynamicSphere_AffectsRotation) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); AZ::Vector3 angularImpulse(0.0f, 10.0f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::ApplyAngularImpulse, angularImpulse); for (int i = 1; i < 10; i++) { float xPreviousA = TestUtils::GetPositionElement(sphereA, 0); float xPreviousB = TestUtils::GetPositionElement(sphereB, 0); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); EXPECT_NEAR(TestUtils::GetPositionElement(sphereA, 0), xPreviousA, 1e-3f); EXPECT_NEAR(TestUtils::GetPositionElement(sphereB, 0), xPreviousB, 1e-3f); AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero(); AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero(); Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero())); EXPECT_NEAR(static_cast(angularVelocityA.GetX()), 0.0f, 1e-3f); EXPECT_NEAR(static_cast(angularVelocityA.GetZ()), 0.0f, 1e-3f); EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero())); } } TEST_F(PhysicsComponentBusTest, SetAngularVelocity_DynamicSphere_AffectsRotation) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f); AZ::Vector3 angularVelocity(0.0f, 10.0f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetAngularVelocity, angularVelocity); for (int i = 1; i < 10; i++) { float xPreviousA = TestUtils::GetPositionElement(sphereA, 0); float xPreviousB = TestUtils::GetPositionElement(sphereB, 0); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 10); EXPECT_NEAR(TestUtils::GetPositionElement(sphereA, 0), xPreviousA, 1e-3f); EXPECT_NEAR(TestUtils::GetPositionElement(sphereB, 0), xPreviousB, 1e-3f); AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero(); AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero(); Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity); EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero())); EXPECT_NEAR(static_cast(angularVelocityA.GetX()), 0.0f, 1e-3f); EXPECT_NEAR(static_cast(angularVelocityA.GetZ()), 0.0f, 1e-3f); EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero())); } } TEST_F(PhysicsComponentBusTest, GetLinearVelocity_FallingSphere_VelocityIncreasesOverTime) { EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 0.0f, 0.0f), 0.5f); Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, 0.0f); float previousSpeed = 0.0f; for (int timestep = 0; timestep < 60; timestep++) { TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); AZ::Vector3 velocity; Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); float speed = velocity.GetLength(); EXPECT_GT(speed, previousSpeed); previousSpeed = speed; } } TEST_F(PhysicsComponentBusTest, SetSleepThreshold_RollingSpheres_LowerThresholdSphereTravelsFurther) { EntityPtr sphereA = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f); EntityPtr sphereB = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f); EntityPtr floor = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f)); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetAngularDamping, 0.75f); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::SetAngularDamping, 0.75f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::SetSleepThreshold, 1.0f); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::SetSleepThreshold, 0.5f); float sleepThresholdA, sleepThresholdB; Physics::RigidBodyRequestBus::EventResult(sleepThresholdA, sphereA->GetId(), &Physics::RigidBodyRequests::GetSleepThreshold); Physics::RigidBodyRequestBus::EventResult(sleepThresholdB, sphereB->GetId(), &Physics::RigidBodyRequests::GetSleepThreshold); EXPECT_NEAR(sleepThresholdA, 1.0f, 1e-3f); EXPECT_NEAR(sleepThresholdB, 0.5f, 1e-3f); AZ::Vector3 impulse(0.0f, 0.1f, 0.0f); Physics::RigidBodyRequestBus::Event(sphereA->GetId(), &Physics::RigidBodyRequests::ApplyAngularImpulse, impulse); Physics::RigidBodyRequestBus::Event(sphereB->GetId(), &Physics::RigidBodyRequests::ApplyAngularImpulse, impulse); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 300); EXPECT_GT(TestUtils::GetPositionElement(sphereB, 0), TestUtils::GetPositionElement(sphereA, 0)); } TEST_F(PhysicsComponentBusTest, SetSleepThresholdNegative_DynamicSphere_NegativeValueRejected) { UnitTest::ErrorHandler errorHandler("Negative sleep threshold value"); EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f); float threshold = 0.0f, initialThreshold = 0.0f; Physics::RigidBodyRequestBus::EventResult(initialThreshold, sphere->GetId(), &Physics::RigidBodyRequests::GetSleepThreshold); Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetSleepThreshold, -0.5f); Physics::RigidBodyRequestBus::EventResult(threshold, sphere->GetId(), &Physics::RigidBodyRequests::GetSleepThreshold); EXPECT_NEAR(threshold, initialThreshold, 1e-3f); EXPECT_TRUE(errorHandler.GetWarningCount() > 0); } TEST_F(PhysicsComponentBusTest, SetMass_Seesaw_TipsDownAtHeavierEnd) { EntityPtr floor = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f)); EntityPtr pivot = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3(0.0f, 0.0f, 0.7f), AZ::Vector3(0.4f, 1.0f, 0.4f)); EntityPtr seesaw = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(0.0f, 0.0f, 0.95f), AZ::Vector3(20.0f, 1.0f, 0.1f)); EntityPtr boxA = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(-9.0f, 0.0f, 1.5f), AZ::Vector3::CreateOne()); EntityPtr boxB = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(9.0f, 0.0f, 1.5f), AZ::Vector3::CreateOne()); Physics::RigidBodyRequestBus::Event(boxA->GetId(), &Physics::RigidBodyRequests::SetMass, 5.0f); float mass = 0.0f; Physics::RigidBodyRequestBus::EventResult(mass, boxA->GetId(), &Physics::RigidBodyRequests::GetMass); EXPECT_NEAR(mass, 5.0f, 1e-3f); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 30); EXPECT_GT(1.5f, TestUtils::GetPositionElement(boxA, 2)); EXPECT_LT(1.5f, TestUtils::GetPositionElement(boxB, 2)); Physics::RigidBodyRequestBus::Event(boxB->GetId(), &Physics::RigidBodyRequests::SetMass, 20.0f); Physics::RigidBodyRequestBus::EventResult(mass, boxB->GetId(), &Physics::RigidBodyRequests::GetMass); EXPECT_NEAR(mass, 20.0f, 1e-3f); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 60); EXPECT_LT(1.5f, TestUtils::GetPositionElement(boxA, 2)); EXPECT_GT(1.5f, TestUtils::GetPositionElement(boxB, 2)); } TEST_F(PhysicsComponentBusTest, GetAabb_Sphere_ValidExtents) { AZ::Vector3 spherePosition(2.0f, -3.0f, 1.0f); EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, spherePosition, 0.5f); AZ::Aabb sphereAabb; Physics::RigidBodyRequestBus::EventResult(sphereAabb, sphere->GetId(), &Physics::RigidBodyRequests::GetAabb); EXPECT_TRUE(sphereAabb.GetMin().IsClose(spherePosition - 0.5f * AZ::Vector3::CreateOne())); EXPECT_TRUE(sphereAabb.GetMax().IsClose(spherePosition + 0.5f * AZ::Vector3::CreateOne())); // rotate the sphere and check the bounding box is still correct AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi); AZ::TransformBus::Event(sphere->GetId(), &AZ::TransformInterface::SetWorldTM, AZ::Transform::CreateFromQuaternionAndTranslation(quat, spherePosition)); sphere->Deactivate(); sphere->Activate(); Physics::RigidBodyRequestBus::EventResult(sphereAabb, sphere->GetId(), &Physics::RigidBodyRequests::GetAabb); EXPECT_TRUE(sphereAabb.GetMin().IsClose(spherePosition - 0.5f * AZ::Vector3::CreateOne())); EXPECT_TRUE(sphereAabb.GetMax().IsClose(spherePosition + 0.5f * AZ::Vector3::CreateOne())); } TEST_F(PhysicsComponentBusTest, GetAabb_Box_ValidExtents) { AZ::Vector3 boxPosition(2.0f, -3.0f, 1.0f); AZ::Vector3 boxDimensions(3.0f, 4.0f, 5.0f); EntityPtr box = TestUtils::CreateBoxEntity(m_testSceneHandle, boxPosition, boxDimensions); AZ::Aabb boxAabb; Physics::RigidBodyRequestBus::EventResult(boxAabb, box->GetId(), &Physics::RigidBodyRequests::GetAabb); EXPECT_TRUE(boxAabb.GetMin().IsClose(boxPosition - 0.5f * boxDimensions)); EXPECT_TRUE(boxAabb.GetMax().IsClose(boxPosition + 0.5f * boxDimensions)); // rotate the box and check the bounding box is still correct AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi); AZ::TransformBus::Event(box->GetId(), &AZ::TransformInterface::SetWorldTM, AZ::Transform::CreateFromQuaternionAndTranslation(quat, boxPosition)); box->Deactivate(); box->Activate(); Physics::RigidBodyRequestBus::EventResult(boxAabb, box->GetId(), &Physics::RigidBodyRequests::GetAabb); AZ::Vector3 expectedRotatedDimensions(3.5f * sqrtf(2.0f), 3.5f * sqrtf(2.0f), 5.0f); EXPECT_TRUE(boxAabb.GetMin().IsClose(boxPosition - 0.5f * expectedRotatedDimensions)); EXPECT_TRUE(boxAabb.GetMax().IsClose(boxPosition + 0.5f * expectedRotatedDimensions)); } TEST_F(PhysicsComponentBusTest, GetAabb_Capsule_ValidExtents) { AZ::Vector3 capsulePosition(1.0f, -3.0f, 5.0f); float capsuleHeight = 2.0f; float capsuleRadius = 0.3f; EntityPtr capsule = TestUtils::CreateCapsuleEntity(m_testSceneHandle, capsulePosition, capsuleHeight, capsuleRadius); AZ::Aabb capsuleAabb; Physics::RigidBodyRequestBus::EventResult(capsuleAabb, capsule->GetId(), &Physics::RigidBodyRequests::GetAabb); AZ::Vector3 expectedCapsuleHalfExtents(capsuleRadius, capsuleRadius, 0.5f * capsuleHeight); EXPECT_TRUE(capsuleAabb.GetMin().IsClose(capsulePosition - expectedCapsuleHalfExtents)); EXPECT_TRUE(capsuleAabb.GetMax().IsClose(capsulePosition + expectedCapsuleHalfExtents)); // rotate the capsule and check the bounding box is still correct AZ::Quaternion quat = AZ::Quaternion::CreateRotationY(0.25f * AZ::Constants::Pi); AZ::TransformBus::Event(capsule->GetId(), &AZ::TransformInterface::SetWorldTM, AZ::Transform::CreateFromQuaternionAndTranslation(quat, capsulePosition)); capsule->Deactivate(); capsule->Activate(); Physics::RigidBodyRequestBus::EventResult(capsuleAabb, capsule->GetId(), &Physics::RigidBodyRequests::GetAabb); float rotatedHalfHeight = 0.25f * sqrtf(2.0f) * capsuleHeight + (1.0f - 0.5f * sqrt(2.0f)) * capsuleRadius; expectedCapsuleHalfExtents = AZ::Vector3(rotatedHalfHeight, capsuleRadius, rotatedHalfHeight); EXPECT_TRUE(capsuleAabb.GetMin().IsClose(capsulePosition - expectedCapsuleHalfExtents)); EXPECT_TRUE(capsuleAabb.GetMax().IsClose(capsulePosition + expectedCapsuleHalfExtents)); } TEST_F(PhysicsComponentBusTest, ForceAwakeForceAsleep_DynamicSphere_SleepStateCorrect) { EntityPtr floor = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f)); EntityPtr boxA = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(-5.0f, 0.0f, 1.0f), AZ::Vector3::CreateOne()); EntityPtr boxB = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(5.0f, 0.0f, 100.0f), AZ::Vector3::CreateOne()); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 60); bool isAwakeA = false; bool isAwakeB = false; Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(), &Physics::RigidBodyRequests::IsAwake); Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(), &Physics::RigidBodyRequests::IsAwake); EXPECT_FALSE(isAwakeA); EXPECT_TRUE(isAwakeB); Physics::RigidBodyRequestBus::Event(boxA->GetId(), &Physics::RigidBodyRequests::ForceAwake); Physics::RigidBodyRequestBus::Event(boxB->GetId(), &Physics::RigidBodyRequests::ForceAsleep); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(), &Physics::RigidBodyRequests::IsAwake); Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(), &Physics::RigidBodyRequests::IsAwake); EXPECT_TRUE(isAwakeA); EXPECT_FALSE(isAwakeB); TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 60); Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(), &Physics::RigidBodyRequests::IsAwake); Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(), &Physics::RigidBodyRequests::IsAwake); EXPECT_FALSE(isAwakeA); EXPECT_FALSE(isAwakeB); } TEST_F(PhysicsComponentBusTest, DisableEnablePhysics_DynamicSphere) { using namespace AzFramework; EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f, 0.0f, 0.0f), 0.5f); Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, 0.0f); AZ::Vector3 velocity; float previousSpeed = 0.0f; for (int timestep = 0; timestep < 30; timestep++) { TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); previousSpeed = velocity.GetLength(); } // Disable physics Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::DisablePhysics); // Check speed is not changing for (int timestep = 0; timestep < 60; timestep++) { TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); float speed = velocity.GetLength(); EXPECT_FLOAT_EQ(speed, previousSpeed); previousSpeed = speed; } // Check physics is disabled bool physicsEnabled = true; Physics::RigidBodyRequestBus::EventResult(physicsEnabled, sphere->GetId(), &Physics::RigidBodyRequests::IsPhysicsEnabled); EXPECT_FALSE(physicsEnabled); // Enable physics Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::EnablePhysics); // Check speed is increasing for (int timestep = 0; timestep < 60; timestep++) { TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1); Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity); float speed = velocity.GetLength(); EXPECT_GT(speed, previousSpeed); previousSpeed = speed; } } TEST_F(PhysicsComponentBusTest, Shape_Box_GetAabbIsCorrect) { Physics::ColliderConfiguration colliderConfig; Physics::BoxShapeConfiguration shapeConfiguration; shapeConfiguration.m_dimensions = AZ::Vector3(20.f, 20.f, 20.f); AZStd::shared_ptr shape; Physics::SystemRequestBus::BroadcastResult(shape, &Physics::SystemRequests::CreateShape, colliderConfig, shapeConfiguration); const AZ::Aabb localAabb = shape->GetAabbLocal(); EXPECT_TRUE(localAabb.GetMin().IsClose(-shapeConfiguration.m_dimensions / 2.f) && localAabb.GetMax().IsClose(shapeConfiguration.m_dimensions / 2.f)); AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f); AZ::Transform worldTransform = AZ::Transform::Identity(); worldTransform.SetTranslation(worldOffset); const AZ::Aabb worldAabb = shape->GetAabb(worldTransform); EXPECT_TRUE(worldAabb.GetMin().IsClose((-shapeConfiguration.m_dimensions / 2.f) + worldOffset) && worldAabb.GetMax().IsClose((shapeConfiguration.m_dimensions / 2.f) + worldOffset)); } TEST_F(PhysicsComponentBusTest, Shape_Sphere_GetAabbIsCorrect) { const float radius = 20.f; Physics::ColliderConfiguration colliderConfig; Physics::SphereShapeConfiguration shapeConfiguration; shapeConfiguration.m_radius = radius; AZStd::shared_ptr shape; Physics::SystemRequestBus::BroadcastResult(shape, &Physics::SystemRequests::CreateShape, colliderConfig, shapeConfiguration); const AZ::Aabb localAabb = shape->GetAabbLocal(); EXPECT_TRUE(localAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -radius)) && localAabb.GetMax().IsClose(AZ::Vector3(radius, radius, radius))); AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f); AZ::Transform worldTransform = AZ::Transform::Identity(); worldTransform.SetTranslation(worldOffset); const AZ::Aabb worldAabb = shape->GetAabb(worldTransform); EXPECT_TRUE(worldAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -radius) + worldOffset) && worldAabb.GetMax().IsClose(AZ::Vector3(radius, radius, radius) + worldOffset)); } TEST_F(PhysicsComponentBusTest, Shape_Capsule_GetAabbIsCorrect) { const float radius = 20.f; const float height = 80.f; Physics::ColliderConfiguration colliderConfig; Physics::CapsuleShapeConfiguration shapeConfiguration; shapeConfiguration.m_radius = radius; shapeConfiguration.m_height = height; AZStd::shared_ptr shape; Physics::SystemRequestBus::BroadcastResult(shape, &Physics::SystemRequests::CreateShape, colliderConfig, shapeConfiguration); const AZ::Aabb localAabb = shape->GetAabbLocal(); EXPECT_TRUE(localAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -height / 2.f)) && localAabb.GetMax().IsClose(AZ::Vector3(radius, radius, height / 2.f))); AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f); AZ::Transform worldTransform = AZ::Transform::Identity(); worldTransform.SetTranslation(worldOffset); const AZ::Aabb worldAabb = shape->GetAabb(worldTransform); EXPECT_TRUE(worldAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -height / 2.f) + worldOffset) && worldAabb.GetMax().IsClose(AZ::Vector3(radius, radius, height / 2.f) + worldOffset)); } TEST_F(PhysicsComponentBusTest, WorldBodyBus_RigidBodyColliders_AABBAreCorrect) { // Create 3 colliders, one of each type and check that the AABB of their body is the expected EntityPtr box = TestUtils::CreateBoxEntity(m_testSceneHandle, AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)); AZ::Aabb boxAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(boxAABB, box->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16))); EntityPtr sphere = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(-100, 0, 0), 16); AZ::Aabb sphereAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(sphereAABB, sphere->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16))); EntityPtr capsule = TestUtils::CreateCapsuleEntity(m_testSceneHandle, AZ::Vector3(100, 0, 0), 128, 16); AZ::Aabb capsuleAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(capsuleAABB, capsule->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(capsuleAABB.GetMin().IsClose(AZ::Vector3(-16 +100, -16, -64)) && capsuleAABB.GetMax().IsClose(AZ::Vector3(16 +100, 16, 64))); } TEST_F(PhysicsComponentBusTest, WorldBodyBus_StaticRigidBodyColliders_AABBAreCorrect) { // Create 3 colliders, one of each type and check that the AABB of their body is the expected EntityPtr box = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)); AZ::Aabb boxAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(boxAABB, box->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16))); EntityPtr sphere = TestUtils::CreateStaticSphereEntity(m_testSceneHandle, AZ::Vector3(-100, 0, 0), 16); AZ::Aabb sphereAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(sphereAABB, sphere->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16))); EntityPtr capsule = TestUtils::CreateStaticCapsuleEntity(m_testSceneHandle, AZ::Vector3(100, 0, 0), 128, 16); AZ::Aabb capsuleAABB; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(capsuleAABB, capsule->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetAabb); EXPECT_TRUE(capsuleAABB.GetMin().IsClose(AZ::Vector3(-16 +100, -16, -64)) && capsuleAABB.GetMax().IsClose(AZ::Vector3(16 +100, 16, 64))); } using CreateEntityFunc = AZStd::function; void CheckDisableEnablePhysics(AZStd::vector entityCreations, AzPhysics::SceneHandle sceneHandle) { auto* sceneInterface = AZ::Interface::Get(); int i = 0; for (CreateEntityFunc& entityCreation : entityCreations) { AZ::Vector3 entityPos = AZ::Vector3(128.f * i, 0, 0); EntityPtr entity = entityCreation(entityPos); AzPhysics::RayCastRequest request; request.m_start = entityPos + AZ::Vector3(0, 0, 100); request.m_direction = AZ::Vector3(0, 0, -1); request.m_distance = 200.f; AzPhysics::SimulatedBodyComponentRequestsBus::Event(entity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::DisablePhysics); bool enabled = true; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(enabled, entity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::IsPhysicsEnabled); EXPECT_FALSE(enabled); AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(sceneHandle, &request); EXPECT_FALSE(result); AzPhysics::SimulatedBodyComponentRequestsBus::Event(entity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::EnablePhysics); enabled = false; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(enabled, entity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::IsPhysicsEnabled); EXPECT_TRUE(enabled); result = sceneInterface->QueryScene(sceneHandle, &request); EXPECT_TRUE(result); EXPECT_TRUE(result.m_hits.size() == 1); EXPECT_TRUE(result.m_hits[0].m_entityId == entity->GetId()); ++i; } } TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_StaticRigidBody) { AZStd::vector entityCreations = { [this](const AZ::Vector3& position) { return TestUtils::CreateStaticBoxEntity(m_testSceneHandle, position, AZ::Vector3(32, 32, 32)); }, [this](const AZ::Vector3& position) { return TestUtils::CreateStaticSphereEntity(m_testSceneHandle, position, 16); }, [this](const AZ::Vector3& position) { return TestUtils::CreateStaticCapsuleEntity(m_testSceneHandle, position, 16, 16); } }; CheckDisableEnablePhysics(entityCreations, m_testSceneHandle); } TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_RigidBody) { AZStd::vector entityCreations = { [this](const AZ::Vector3& position) { return TestUtils::CreateBoxEntity(m_testSceneHandle, position, AZ::Vector3(32, 32, 32)); }, [this](const AZ::Vector3& position) { return TestUtils::CreateSphereEntity(m_testSceneHandle, position, 16); }, [this](const AZ::Vector3& position) { return TestUtils::CreateCapsuleEntity(m_testSceneHandle, position, 16, 16); } }; CheckDisableEnablePhysics(entityCreations, m_testSceneHandle); } TEST_F(PhysicsComponentBusTest, WorldBodyRayCast_CastAgainstStaticBox_ReturnsHit) { EntityPtr staticBoxEntity = TestUtils::CreateStaticBoxEntity(m_testSceneHandle, AZ::Vector3(0.0f), AZ::Vector3(10.f, 10.f, 10.f)); AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; AzPhysics::SceneQueryHit hit; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(hit, staticBoxEntity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::RayCast, request); EXPECT_TRUE(hit); bool hitIncludeSphereEntity = (hit.m_entityId == staticBoxEntity->GetId()); EXPECT_TRUE(hitIncludeSphereEntity); } using RayCastFunc = AZStd::function; class PhysicsRigidBodyRayBusTest : public GenericPhysicsInterfaceTest , public ::testing::WithParamInterface { }; TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstNothing_ReturnsNoHit) { AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit hit = rayCastFunction(AZ::EntityId(), request); EXPECT_FALSE(hit); } TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstSphere_ReturnsHit) { EntityPtr sphereEntity = TestUtils::CreateSphereEntity(m_testSceneHandle, AZ::Vector3(0.0f), 10.0f); AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit hit = rayCastFunction(sphereEntity->GetId(), request); EXPECT_TRUE(hit); bool hitsIncludeSphereEntity = (hit.m_entityId == sphereEntity->GetId()); EXPECT_TRUE(hitsIncludeSphereEntity); } TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityWithLocalOffset_ReturnsHit) { const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f); const AZ::Vector3 box1Offset(0.0f, 0.0f, 30.0f); const AZ::Vector3 box2Offset(0.0f, 0.0f, -30.0f); MultiShapeConfig config; config.m_position = AZ::Vector3(0.0f, 100.0f, 20.0f); config.m_shapes.AddBox(boxExtent, box1Offset); config.m_shapes.AddBox(boxExtent, box2Offset); auto shapeWithTwoBoxes = AddMultiShapeEntity(config); AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 100.0f, 50.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request); EXPECT_TRUE(result); bool hitIncludeEntity = (result.m_entityId == shapeWithTwoBoxes->GetId()); EXPECT_TRUE(hitIncludeEntity); } TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityWithMultipleShapesLocalOffset_ReturnsHits) { // Entity at (0, 100, 20) with two box childs with offsets +30 and -30 in Z. // Child boxes world position centers are at (0, 100, 50) and (0, 100, -10) // 4 rays tests that should retrieves the correct boxes const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f); const AZ::Vector3 box1Offset(0.0f, 0.0f, 30.0f); const AZ::Vector3 box2Offset(0.0f, 0.0f, -30.0f); MultiShapeConfig config; config.m_position = AZ::Vector3(0.0f, 100.0f, 20.0f); config.m_shapes.AddBox(boxExtent, box1Offset); config.m_shapes.AddBox(boxExtent, box2Offset); AZStd::unique_ptr shapeWithTwoBoxes(AddMultiShapeEntity(config)); AZStd::vector> shapes; PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithTwoBoxes->GetId(), &PhysX::ColliderComponentRequests::GetShapes); // Upper box part z=50 (-x to +x) { AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 100.0f, 50.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request); EXPECT_TRUE(result); bool hitIncludesEntity = (result.m_entityId == shapeWithTwoBoxes->GetId()); EXPECT_TRUE(hitIncludesEntity); bool hitIncludesShape = (result.m_shape == shapes[0].get()); EXPECT_TRUE(hitIncludesShape); } // Lower box part z=-10 (-x to +x) { AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(-100.0f, 100.0f, -10.0f); request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request); EXPECT_TRUE(result); bool hitIncludesEntity = (result.m_entityId == shapeWithTwoBoxes->GetId()); EXPECT_TRUE(hitIncludesEntity); bool hitIncludesShape = (result.m_shape == shapes[1].get()); EXPECT_TRUE(hitIncludesShape); } // Trace Vertically from top, it should retrieve the upper box shape { AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(0.0f, 100.0f, 80.0f); request.m_direction = AZ::Vector3(0.0f, 0.0f, -1.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request); EXPECT_TRUE(result); bool hitIncludesEntity = (result.m_entityId == shapeWithTwoBoxes->GetId()); EXPECT_TRUE(hitIncludesEntity); bool hitIncludesShape = (result.m_shape == shapes[0].get()); EXPECT_TRUE(hitIncludesShape); } // Trace Vertically from bottom, it should retrieve the lower box shape { AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(0.0f, 100.0f, -80.0f); request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request); EXPECT_TRUE(result); bool hitIncludesEntity = (result.m_entityId == shapeWithTwoBoxes->GetId()); EXPECT_TRUE(hitIncludesEntity); bool hitIncludesShape = (result.m_shape == shapes[1].get()); EXPECT_TRUE(hitIncludesShape); } } TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityLocalOffsetAndRotation_ReturnsHits) { // Entity at (0,0,0) rotated by 90 degrees and child box offset (0,100,0). // World position of the child should be (-100, 0, 0). // This tests raycasts from (0, 0, 0) to (-200, 0 ,0) checking that collides with the box const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f); const AZ::Vector3 boxOffset(0.0f, 100.0f, 0.0f); MultiShapeConfig config; config.m_position = AZ::Vector3(0.0f, 0.0f, 0.0f); config.m_rotation = AZ::Vector3(0, 0, AZ::Constants::HalfPi); config.m_shapes.AddBox(boxExtent, boxOffset); AZStd::unique_ptr shapeWithOneBox(AddMultiShapeEntity(config)); AZStd::vector> shapes; PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithOneBox->GetId(), &PhysX::ColliderComponentRequests::GetShapes); AzPhysics::RayCastRequest request; request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f); request.m_direction = AZ::Vector3(-1.0f, 0.0f, 0.0f); request.m_distance = 200.0f; auto rayCastFunction = GetParam(); AzPhysics::SceneQueryHit result = rayCastFunction(shapeWithOneBox->GetId(), request); EXPECT_TRUE(result); bool hitIncludesEntity = (result.m_entityId == shapeWithOneBox->GetId()); EXPECT_TRUE(hitIncludesEntity); bool hitIncludesShape = (result.m_shape == shapes[0].get()); EXPECT_TRUE(hitIncludesShape); } static const RayCastFunc RigidBodyRaycastEBusCall = []([[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const AzPhysics::RayCastRequest& request) { AzPhysics::SceneQueryHit ret; Physics::RigidBodyRequestBus::EventResult(ret, entityId, &Physics::RigidBodyRequests::RayCast, request); return ret; }; static const RayCastFunc WorldBodyRaycastEBusCall = []([[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const AzPhysics::RayCastRequest& request) { AzPhysics::SceneQueryHit ret; AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(ret, entityId, &AzPhysics::SimulatedBodyComponentRequests::RayCast, request); return ret; }; INSTANTIATE_TEST_CASE_P(, PhysicsRigidBodyRayBusTest, ::testing::Values(RigidBodyRaycastEBusCall, WorldBodyRaycastEBusCall), // Provide nice names for the tests runs [](const testing::TestParamInfo& info) { const char* name = ""; switch (info.index) { case 0: name = "RigidBodyRequestBus"; break; case 1: name = "WorldBodyRequestBus"; break; } return name; }); } // namespace Physics