Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
-3
View File
@@ -9,12 +9,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
add_subdirectory(Physics)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common)
ly_add_target(
NAME AzFrameworkTestShared STATIC
-59
View File
@@ -1,59 +0,0 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzFramework/Engine/Engine.h>
#include <Utils/Utils.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace EngineTests
{
////////////////////////////////////////////////////////////////////////////////////////////////
class EngineTest : public ::testing::Test
{
protected:
////////////////////////////////////////////////////////////////////////////////////////////
void SetUp() override
{
}
////////////////////////////////////////////////////////////////////////////////////////////
void TearDown() override
{
}
};
////////////////////////////////////////////////////////////////////////////////////////////////
TEST_F(EngineTest, FindEngineRoot_EngineBelowExecutable_Success)
{
// We assume in this test that our engine lives above our binaries. If that's not the case we should pass in
// a valid path to a folder below the engine root to verify this functionality
auto foundRoot = AzFramework::Engine::FindEngineRoot();
EXPECT_FALSE(foundRoot.empty());
}
#if AZ_TRAIT_DISABLE_FAILED_FRAMEWORK_TESTS
TEST_F(EngineTest, DISABLED_FindEngineRoot_StartTempFolder_NoRootFound)
#else
TEST_F(EngineTest, FindEngineRoot_StartTempFolder_NoRootFound)
#endif // AZ_TRAIT_DISABLE_FAILED_FRAMEWORK_TESTS
{
UnitTest::ScopedTemporaryDirectory tempDir;
auto foundRoot = AzFramework::Engine::FindEngineRoot(tempDir.GetDirectory());
EXPECT_TRUE(foundRoot.empty());
}
} // namespace EngineTests
@@ -14,6 +14,8 @@
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
namespace UnitTest
{
void EntityOwnershipServiceTestFixture::SetUpEntityOwnershipServiceTest()
@@ -40,6 +42,9 @@ namespace UnitTest
AZ::ComponentTypeList EntityOwnershipServiceTestFixture::EntityOwnershipServiceApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList defaultRequiredComponents = AzFramework::Application::GetRequiredSystemComponents();
defaultRequiredComponents.emplace_back(azrtti_typeid<AzToolsFramework::Prefab::PrefabSystemComponent>());
auto findComponentIterator = AZStd::find(defaultRequiredComponents.begin(), defaultRequiredComponents.end(),
azrtti_typeid<AzFramework::GameEntityContextComponent>());
if (findComponentIterator != defaultRequiredComponents.end())
@@ -1,27 +0,0 @@
#
# 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.
#
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AzPhysicsTests HEADERONLY
NAMESPACE AZ
FILES_CMAKE
azphysicstests_files.cmake
INCLUDE_DIRECTORIES
INTERFACE
..
BUILD_DEPENDENCIES
INTERFACE
AZ::AzFramework
)
endif()
@@ -1,988 +0,0 @@
/*
* 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.
*
*/
namespace Physics
{
TEST_F(PhysicsComponentBusTest, SetLinearDamping_DynamicSphere_MoreDampedBodyFallsSlower)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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);
UpdateDefaultWorld(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 = GetPositionElement(sphereA, 2);
float zB = 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<float>(vA.GetLength()), static_cast<float>(vB.GetLength()));
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetLinearDampingNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative linear damping value");
auto sphere = AddSphereEntity(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);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetAngularDamping_DynamicSphere_MoreDampedBodyRotatesSlower)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f);
auto floor = AddStaticBoxEntity(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);
UpdateDefaultWorld(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<float>(angularVelocityA.GetLength()), static_cast<float>(angularVelocityB.GetLength()));
UpdateDefaultWorld(1);
}
delete floor;
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetAngularDampingNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative angular damping value");
auto sphere = AddSphereEntity(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);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, AddImpulse_DynamicSphere_AffectsTrajectory)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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 = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetLinearVelocity_DynamicSphere_AffectsTrajectory)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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 = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, AddImpulseAtWorldPoint_DynamicSphere_AffectsTrajectoryAndRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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 = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(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<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, AddAngularImpulse_DynamicSphere_AffectsRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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 = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_NEAR(GetPositionElement(sphereA, 0), xPreviousA, 1e-3f);
EXPECT_NEAR(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<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetAngularVelocity_DynamicSphere_AffectsRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(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 = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_NEAR(GetPositionElement(sphereA, 0), xPreviousA, 1e-3f);
EXPECT_NEAR(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<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, GetLinearVelocity_FallingSphere_VelocityIncreasesOverTime)
{
auto sphere = AddSphereEntity(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++)
{
UpdateDefaultWorld(1);
AZ::Vector3 velocity;
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(),
&Physics::RigidBodyRequests::GetLinearVelocity);
float speed = velocity.GetLength();
EXPECT_GT(speed, previousSpeed);
previousSpeed = speed;
}
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetSleepThreshold_RollingSpheres_LowerThresholdSphereTravelsFurther)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f);
auto floor = AddStaticBoxEntity(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);
UpdateDefaultWorld(300);
EXPECT_GT(GetPositionElement(sphereB, 0), GetPositionElement(sphereA, 0));
delete floor;
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetSleepThresholdNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative sleep threshold value");
auto sphere = AddSphereEntity(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);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetMass_Seesaw_TipsDownAtHeavierEnd)
{
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
auto pivot = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.7f), AZ::Vector3(0.4f, 1.0f, 0.4f));
auto seesaw = AddBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.95f), AZ::Vector3(20.0f, 1.0f, 0.1f));
auto boxA = AddBoxEntity(AZ::Vector3(-9.0f, 0.0f, 1.5f), AZ::Vector3::CreateOne());
auto boxB = AddBoxEntity(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);
UpdateDefaultWorld(30);
EXPECT_GT(1.5f, GetPositionElement(boxA, 2));
EXPECT_LT(1.5f, 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);
UpdateDefaultWorld(60);
EXPECT_LT(1.5f, GetPositionElement(boxA, 2));
EXPECT_GT(1.5f, GetPositionElement(boxB, 2));
delete floor;
delete pivot;
delete seesaw;
delete boxA;
delete boxB;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Sphere_ValidExtents)
{
AZ::Vector3 spherePosition(2.0f, -3.0f, 1.0f);
auto sphere = AddSphereEntity(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()));
delete sphere;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Box_ValidExtents)
{
AZ::Vector3 boxPosition(2.0f, -3.0f, 1.0f);
AZ::Vector3 boxDimensions(3.0f, 4.0f, 5.0f);
auto box = AddBoxEntity(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));
delete box;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Capsule_ValidExtents)
{
AZ::Vector3 capsulePosition(1.0f, -3.0f, 5.0f);
float capsuleHeight = 2.0f;
float capsuleRadius = 0.3f;
auto capsule = AddCapsuleEntity(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));
delete capsule;
}
TEST_F(PhysicsComponentBusTest, ForceAwakeForceAsleep_DynamicSphere_SleepStateCorrect)
{
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
auto boxA = AddBoxEntity(AZ::Vector3(-5.0f, 0.0f, 1.0f), AZ::Vector3::CreateOne());
auto boxB = AddBoxEntity(AZ::Vector3(5.0f, 0.0f, 100.0f), AZ::Vector3::CreateOne());
UpdateDefaultWorld(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);
UpdateDefaultWorld(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);
UpdateDefaultWorld(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);
delete boxA;
delete boxB;
delete floor;
}
TEST_F(PhysicsComponentBusTest, DisableEnablePhysics_DynamicSphere)
{
using namespace AzFramework;
auto sphere = AddSphereEntity(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++)
{
UpdateDefaultWorld(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++)
{
UpdateDefaultWorld(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++)
{
UpdateDefaultWorld(1);
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity);
float speed = velocity.GetLength();
EXPECT_GT(speed, previousSpeed);
previousSpeed = speed;
}
delete sphere;
}
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> shape;
SystemRequestBus::BroadcastResult(shape, &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> shape;
SystemRequestBus::BroadcastResult(shape, &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> shape;
SystemRequestBus::BroadcastResult(shape, &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
AZStd::unique_ptr<AZ::Entity> box = AZStd::unique_ptr<AZ::Entity>(AddBoxEntity(AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)));
AZ::Aabb boxAABB;
Physics::WorldBodyRequestBus::EventResult(boxAABB, box->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16)));
AZStd::unique_ptr<AZ::Entity> sphere = AZStd::unique_ptr<AZ::Entity>(AddSphereEntity(AZ::Vector3(-100, 0, 0), 16));
AZ::Aabb sphereAABB;
Physics::WorldBodyRequestBus::EventResult(sphereAABB, sphere->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16)));
AZStd::unique_ptr<AZ::Entity> capsule = AZStd::unique_ptr<AZ::Entity>(AddCapsuleEntity(AZ::Vector3(100, 0, 0), 128, 16));
AZ::Aabb capsuleAABB;
Physics::WorldBodyRequestBus::EventResult(capsuleAABB, capsule->GetId(), &Physics::WorldBodyRequests::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
AZStd::unique_ptr<AZ::Entity> box = AZStd::unique_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)));
AZ::Aabb boxAABB;
Physics::WorldBodyRequestBus::EventResult(boxAABB, box->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16)));
AZStd::unique_ptr<AZ::Entity> sphere = AZStd::unique_ptr<AZ::Entity>(AddStaticSphereEntity(AZ::Vector3(-100, 0, 0), 16));
AZ::Aabb sphereAABB;
Physics::WorldBodyRequestBus::EventResult(sphereAABB, sphere->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16)));
AZStd::unique_ptr<AZ::Entity> capsule = AZStd::unique_ptr<AZ::Entity>(AddStaticCapsuleEntity(AZ::Vector3(100, 0, 0), 128, 16));
AZ::Aabb capsuleAABB;
Physics::WorldBodyRequestBus::EventResult(capsuleAABB, capsule->GetId(), &Physics::WorldBodyRequests::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<AZStd::unique_ptr<AZ::Entity>(const AZ::Vector3&)>;
void CheckDisableEnablePhysics(AZStd::vector<CreateEntityFunc> entityCreations)
{
// Fake Pointer for filling result to make sure that m_body has changed
Physics::WorldBody* fakeBody = (Physics::WorldBody*)(0x1234);
int i = 0;
for (CreateEntityFunc entityCreation : entityCreations)
{
AZ::Vector3 entityPos = AZ::Vector3(128.f * i, 0, 0);
AZStd::unique_ptr<AZ::Entity> entity = entityCreation(entityPos);
Physics::RayCastHit hit;
Physics::RayCastRequest request;
request.m_start = entityPos + AZ::Vector3(0, 0, 100);
request.m_direction = AZ::Vector3(0, 0, -1);
request.m_distance = 200.f;
Physics::WorldBodyRequestBus::Event(entity->GetId(), &Physics::WorldBodyRequests::DisablePhysics);
bool enabled = true;
Physics::WorldBodyRequestBus::EventResult(enabled, entity->GetId(), &Physics::WorldBodyRequests::IsPhysicsEnabled);
EXPECT_FALSE(enabled);
hit.m_body = fakeBody;
Physics::WorldRequestBus::BroadcastResult(hit, &Physics::WorldRequests::RayCast, request);
EXPECT_FALSE(hit);
Physics::WorldBodyRequestBus::Event(entity->GetId(), &Physics::WorldBodyRequests::EnablePhysics);
enabled = false;
Physics::WorldBodyRequestBus::EventResult(enabled, entity->GetId(), &Physics::WorldBodyRequests::IsPhysicsEnabled);
EXPECT_TRUE(enabled);
hit.m_body = nullptr;
Physics::WorldRequestBus::BroadcastResult(hit, &Physics::WorldRequests::RayCast, request);
EXPECT_TRUE(hit && hit.m_body->GetEntityId() == entity->GetId());
++i;
}
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_StaticRigidBody)
{
AZStd::vector<CreateEntityFunc> entityCreations =
{
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticBoxEntity(position, AZ::Vector3(32, 32, 32))); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticSphereEntity(position, 16)); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticCapsuleEntity(position, 16, 16)); }
};
CheckDisableEnablePhysics(entityCreations);
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_RigidBody)
{
AZStd::vector<CreateEntityFunc> entityCreations =
{
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddBoxEntity(position, AZ::Vector3(32, 32, 32))); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddSphereEntity(position, 16)); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddCapsuleEntity(position, 16, 16)); }
};
CheckDisableEnablePhysics(entityCreations);
}
TEST_F(PhysicsComponentBusTest, WorldBodyRayCast_CastAgainstStaticBox_ReturnsHit)
{
AZStd::unique_ptr<AZ::Entity> staticBoxEntity(AddStaticBoxEntity(AZ::Vector3(0.0f), AZ::Vector3(10.f, 10.f, 10.f)));
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;
Physics::RayCastHit hit;
Physics::WorldBodyRequestBus::EventResult(hit, staticBoxEntity->GetId(), &Physics::WorldBodyRequests::RayCast, request);
EXPECT_TRUE(hit);
bool hitIncludeSphereEntity = (hit.m_body->GetEntityId() == staticBoxEntity->GetId());
EXPECT_TRUE(hitIncludeSphereEntity);
}
using RayCastFunc = AZStd::function<Physics::RayCastHit(AZ::EntityId, const Physics::RayCastRequest&)>;
class PhysicsRigidBodyRayBusTest
: public Physics::GenericPhysicsInterfaceTest
, public ::testing::WithParamInterface<RayCastFunc>
{
};
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstNothing_ReturnsNoHit)
{
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();
RayCastHit hit = rayCastFunction(AZ::EntityId(), request);
EXPECT_FALSE(hit);
}
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstSphere_ReturnsHit)
{
AZStd::unique_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(0.0f), 10.0f));
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();
RayCastHit hit = rayCastFunction(sphereEntity->GetId(), request);
EXPECT_TRUE(hit);
bool hitsIncludeSphereEntity = (hit.m_body->GetEntityId() == 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);
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();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludeEntity = (result.m_body->GetEntityId() == 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<AZ::Entity> shapeWithTwoBoxes(AddMultiShapeEntity(config));
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithTwoBoxes->GetId(), &PhysX::ColliderComponentRequests::GetShapes);
// Upper box part z=50 (-x to +x)
{
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();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[0].get());
EXPECT_TRUE(hitIncludesShape);
}
// Lower box part z=-10 (-x to +x)
{
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();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == 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
{
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();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == 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
{
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();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == 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<AZ::Entity> shapeWithOneBox(AddMultiShapeEntity(config));
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithOneBox->GetId(), &PhysX::ColliderComponentRequests::GetShapes);
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();
RayCastHit result = rayCastFunction(shapeWithOneBox->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithOneBox->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[0].get());
EXPECT_TRUE(hitIncludesShape);
}
static const RayCastFunc RigidBodyRaycastEBusCall = [](AZ::EntityId entityId, const Physics::RayCastRequest& request)
{
Physics::RayCastHit ret;
Physics::RigidBodyRequestBus::EventResult(ret, entityId, &Physics::RigidBodyRequests::RayCast, request);
return ret;
};
static const RayCastFunc WorldBodyRaycastEBusCall = [](AZ::EntityId entityId, const Physics::RayCastRequest& request)
{
Physics::RayCastHit ret;
Physics::WorldBodyRequestBus::EventResult(ret, entityId, &Physics::WorldBodyRequests::RayCast, request);
return ret;
};
INSTANTIATE_TEST_CASE_P(, PhysicsRigidBodyRayBusTest,
::testing::Values(RigidBodyRaycastEBusCall, WorldBodyRaycastEBusCall),
// Provide nice names for the tests runs
[](const testing::TestParamInfo<PhysicsRigidBodyRayBusTest::ParamType>& info)
{
const char* name = "";
switch (info.index)
{
case 0:
name = "RigidBodyRequestBus";
break;
case 1:
name = "WorldBodyRequestBus";
break;
}
return name;
});
} // namespace Physics
@@ -1,919 +0,0 @@
/*
* 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 <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/RigidBodyBus.h>
#include <AzFramework/Physics/World.h>
#include <PhysX/ColliderComponentBus.h>
namespace Physics
{
static auto GetEntityInRayCastHitCallBack = [](AZ::EntityId entityId)
{
return [entityId](const RayCastHit& hit)
{
return hit.m_body->GetEntityId() == entityId;
};
};
TEST_F(GenericPhysicsInterfaceTest, World_CreateNewWorld_ReturnsNewWorld)
{
EXPECT_TRUE(CreateTestWorld() != nullptr);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstNothing_ReturnsNoHits)
{
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;
RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::RayCast, request);
EXPECT_FALSE(hit);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstSphere_ReturnsHits)
{
auto sphereEntity = AddSphereEntity(AZ::Vector3(0.0f), 10.0f);
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;
RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::RayCast, request);
EXPECT_TRUE(hit);
bool hitsIncludeSphereEntity = (hit.m_body->GetEntityId() == sphereEntity->GetId());
EXPECT_TRUE(hitsIncludeSphereEntity);
delete sphereEntity;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstSphere_ReturnsCorrectShapeAndMaterial)
{
auto sphereEntity = AZStd::shared_ptr<AZ::Entity>(AddSphereEntity(AZ::Vector3(0.0f), 10.0f));
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;
RayCastHit result;
WorldRequestBus::BroadcastResult(result, &WorldRequests::RayCast, request);
ASSERT_TRUE(result);
RigidBody* rigidBody;
Physics::RigidBodyRequestBus::EventResult(rigidBody, sphereEntity->GetId(), &RigidBodyRequestBus::Events::GetRigidBody);
ASSERT_NE(rigidBody->GetShape(0), nullptr);
ASSERT_NE(result.m_material, nullptr);
ASSERT_EQ(result.m_shape, rigidBody->GetShape(0).get());
ASSERT_EQ(result.m_material, rigidBody->GetShape(0).get()->GetMaterial().get());
const AZStd::string& typeName = result.m_material->GetSurfaceTypeName();
ASSERT_EQ(typeName, AZStd::string("Default"));
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstStaticObject_ReturnsHits)
{
auto boxEntity = AZStd::shared_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(10.0f, 10.0f, 10.0f)));
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;
RayCastHit result;
WorldRequestBus::BroadcastResult(result, &WorldRequests::RayCast, request);
EXPECT_TRUE(result);
bool hitsIncludeEntity = (result.m_body->GetEntityId() == boxEntity->GetId());
EXPECT_TRUE(hitsIncludeEntity);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstFilteredSpheres_ReturnsHits)
{
auto entity1 = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f, CollisionLayer(0));
auto entity2 = AddCapsuleEntity(AZ::Vector3(0.0f, 0.0f, 20.0f), 10.0f, 2.0f, CollisionLayer(1));
auto entity3 = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f), CollisionLayer(2));
CollisionGroup group = CollisionGroup::All;
group.SetLayer(CollisionLayer(0), true);
group.SetLayer(CollisionLayer(1), false);
group.SetLayer(CollisionLayer(2), true);
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_distance = 200.0f;
request.m_collisionGroup = group;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_TRUE(hits.size() == 2);
EXPECT_TRUE(hits[1].m_body->GetEntityId() == entity1->GetId());
EXPECT_TRUE(hits[0].m_body->GetEntityId() == entity3->GetId());
delete entity1;
delete entity2;
delete entity3;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstStaticOnly_ReturnsStaticBox)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::Static;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 1);
ASSERT_EQ(hits[0].m_body->GetEntityId(), staticBox->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstDynamicOnly_ReturnsDynamicSphere)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::Dynamic;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 1);
ASSERT_EQ(hits[0].m_body->GetEntityId(), dynamicSphere->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstStaticAndDynamic_ReturnsBothObjects)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::StaticAndDynamic;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 2);
ASSERT_EQ(hits[0].m_body->GetEntityId(), staticBox->GetId());
ASSERT_EQ(hits[1].m_body->GetEntityId(), dynamicSphere->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstMultipleTouchAndBlockHits_ReturnsClosestBlockAndTouches)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(20.0f, 0.0f, 0.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(40.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto blockingSphere = AddSphereEntity(AZ::Vector3(60.0f, 0.0f, 0.0f), 5.0f);
auto blockingBox = AddStaticBoxEntity(AZ::Vector3(80.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto farSphere = AddSphereEntity(AZ::Vector3(120.0f, 0.0f, 0.0f), 10.0f);
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_queryType = Physics::QueryType::StaticAndDynamic;
request.m_filterCallback = [&](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
if (body->GetEntityId() == blockingBox->GetId() || body->GetEntityId() == blockingSphere->GetId())
{
return QueryHitType::Block;
}
return QueryHitType::Touch;
};
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 3);
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(dynamicSphere->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(staticBox->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(blockingSphere->GetId())));
delete dynamicSphere;
delete staticBox;
delete blockingSphere;
delete blockingBox;
delete farSphere;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_CastAgainstNothing_ReturnsNoHits)
{
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.0f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_FALSE(hit);
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_CastAgainstSphere_ReturnsHits)
{
auto sphereEntity = AddSphereEntity(AZ::Vector3(0.0f), 10.0f);
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.0f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_TRUE(hit);
EXPECT_EQ(hit.m_body->GetEntityId(), sphereEntity->GetId());
// clear up scene
delete sphereEntity;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_SphereCastAgainstStaticObject_ReturnsHits)
{
auto boxEntity = AZStd::shared_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(1.0f, 1.0f, 1.0f)));
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_TRUE(hit);
EXPECT_EQ(hit.m_body->GetEntityId(), boxEntity->GetId());
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_SphereCastAgainstFilteredObjects_ReturnsHits)
{
auto entity1 = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f, CollisionLayer(0));
auto entity2 = AddCapsuleEntity(AZ::Vector3(0.0f, 0.0f, 20.0f), 10.0f, 2.0f, CollisionLayer(1));
auto entity3 = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f), CollisionLayer(2));
CollisionGroup group = CollisionGroup::All;
group.SetLayer(CollisionLayer(0), true);
group.SetLayer(CollisionLayer(1), false);
group.SetLayer(CollisionLayer(2), true);
AZStd::vector<Physics::RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::SphereCastMultiple,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)),
AZ::Vector3(0.0f, 0.0f, 1.0f),
200.0f, Physics::QueryType::StaticAndDynamic,
group,
nullptr
);
ASSERT_TRUE(hits.size() == 2);
EXPECT_TRUE(hits[1].m_body->GetEntityId() == entity1->GetId());
EXPECT_TRUE(hits[0].m_body->GetEntityId() == entity3->GetId());
delete entity1;
delete entity2;
delete entity3;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_AgainstMultipleTouchAndBlockHits_ReturnsClosestBlockAndTouches)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(20.0f, 0.0f, 0.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(40.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto blockingSphere = AddSphereEntity(AZ::Vector3(60.0f, 0.0f, 0.0f), 5.0f);
auto blockingBox = AddStaticBoxEntity(AZ::Vector3(80.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto farSphere = AddSphereEntity(AZ::Vector3(120.0f, 0.0f, 0.0f), 10.0f);
AZStd::vector<Physics::RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::SphereCastMultiple,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
200.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
[&](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
if (body->GetEntityId() == blockingBox->GetId() || body->GetEntityId() == blockingSphere->GetId())
{
return QueryHitType::Block;
}
return QueryHitType::Touch;
}
);
ASSERT_EQ(hits.size(), 3);
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(dynamicSphere->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(staticBox->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(blockingSphere->GetId())));
delete dynamicSphere;
delete staticBox;
delete blockingSphere;
delete blockingBox;
delete farSphere;
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjects_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
BoxShapeConfiguration overlapShape;
overlapShape.m_dimensions = AZ::Vector3(3.0f);
OverlapRequest request;
request.m_pose = AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f));
request.m_shapeConfiguration = &overlapShape;
AZStd::vector<OverlapHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::Overlap, request);
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjectsUseFriendlyFunctions_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
AZStd::shared_ptr<World> defaultWorld;
DefaultWorldBus::BroadcastResult(defaultWorld, &DefaultWorldRequests::GetDefaultWorld);
{
AZStd::vector<OverlapHit> hits = defaultWorld->OverlapBox(AZ::Vector3(3.0f), AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)));
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
{
AZStd::vector<OverlapHit> hits = defaultWorld->OverlapSphere(3.0f, AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)));
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjectsUseFriendlyFunctionsCustomFiltering_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
AZStd::shared_ptr<World> defaultWorld;
DefaultWorldBus::BroadcastResult(defaultWorld, &DefaultWorldRequests::GetDefaultWorld);
// Here we do an overlap test that covers all objects in the scene
// However we provide a custom filtering function that filters out a specific entity
{
AZ::EntityId entityIdToFilterOut = capsuleEntity->GetId();
AZStd::vector<Physics::OverlapHit> hits = defaultWorld->OverlapCapsule(100.0f, 30.0f, AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)),
[entityIdToFilterOut](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
return body->GetEntityId() != entityIdToFilterOut;
});
EXPECT_EQ(hits.size(), 2);
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[entityIdToFilterOut](const OverlapHit& hit) { return hit.m_body->GetEntityId() == entityIdToFilterOut; }));
}
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjects_ReturnsFilteredHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f, CollisionLayer(0)));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddStaticBoxEntity(AZ::Vector3(12.0f, 0.0f, 0.0f), AZ::Vector3(1.0f), CollisionLayer(1)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(14.0f, 0.0f, 0.0f), 3.0f, 1.0f, CollisionLayer(2)));
BoxShapeConfiguration overlapShape;
overlapShape.m_dimensions = AZ::Vector3(1.0f);
OverlapRequest request;
request.m_pose = AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f));
request.m_shapeConfiguration = &overlapShape;
request.m_collisionGroup = CollisionGroup::All;
request.m_collisionGroup.SetLayer(CollisionLayer(0), false); // Filter out the sphere
request.m_collisionGroup.SetLayer(CollisionLayer(1), true);
request.m_collisionGroup.SetLayer(CollisionLayer(2), true);
AZStd::vector<OverlapHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::Overlap, request);
EXPECT_EQ(hits.size(), 2);
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[sphereEntity](const OverlapHit& hit)
{
// Make sure the sphere was not included
return hit.m_body->GetEntityId() == sphereEntity->GetId();
}));
}
TEST_F(GenericPhysicsInterfaceTest, Gravity_DynamicBody_BodyFalls)
{
auto world = CreateTestWorld();
auto rigidBody = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 100.0f));
UpdateWorld(world.get(), 1.0f / 60.f, 60);
// expect velocity to be -gt and distance fallen to be 1/2gt^2, but allow quite a lot of tolerance
// due to potential differences in back end integration schemes etc.
EXPECT_NEAR(rigidBody->GetLinearVelocity().GetZ(), -10.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetTransform().GetTranslation().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetCenterOfMassWorld().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetPosition().GetZ(), 95.0f, 0.5f);
}
TEST_F(GenericPhysicsInterfaceTest, World_SplitSimulation_BodyFallsTheSameInBothWorlds)
{
auto worldA = CreateTestWorld();
auto worldB = CreateTestWorld();
AZ::Vector3 initialPosition(0.0f, 0.0f, 100.0f);
auto rigidBodyA = AddUnitBoxToWorld(worldA.get(), initialPosition);
auto rigidBodyB = AddUnitBoxToWorld(worldB.get(), initialPosition);
Physics::WorldConfiguration worldConfiguration;
float deltaTime = worldConfiguration.m_fixedTimeStep;
AZ::u32 numSteps = 60;
UpdateWorld(worldA.get(), deltaTime, numSteps);
UpdateWorldSplitSim(worldB.get(), deltaTime, numSteps);
// expect velocity to be -gt and distance fallen to be 1/2gt^2, but allow quite a lot of tolerance
// due to potential differences in back end integration schemes etc.
EXPECT_NEAR(rigidBodyA->GetLinearVelocity().GetZ(), -10.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetTransform().GetTranslation().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetCenterOfMassWorld().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetPosition().GetZ(), 95.0f, 0.5f);
// Verify simulation results are the same
EXPECT_TRUE(rigidBodyA->GetLinearVelocity().IsClose(rigidBodyB->GetLinearVelocity()));
EXPECT_TRUE(rigidBodyA->GetTransform().GetTranslation().IsClose(rigidBodyB->GetTransform().GetTranslation()));
EXPECT_TRUE(rigidBodyA->GetCenterOfMassWorld().IsClose(rigidBodyB->GetCenterOfMassWorld()));
EXPECT_TRUE(rigidBodyA->GetPosition().IsClose(rigidBodyB->GetPosition()));
}
TEST_F(GenericPhysicsInterfaceTest, IncreaseMass_StaggeredTowerOfBoxes_TowerOverbalances)
{
auto world = CreateTestWorld();
// make a tower of boxes which is staggered but should still balance if all the blocks are the same mass
auto boxA = AddStaticUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 0.5f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.3f, 0.0f, 1.5f));
auto boxC = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.6f, 0.0f, 2.5f));
// check that the tower balances
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_NEAR(2.5f, boxC->GetPosition().GetZ(), 0.01f);
// increasing the mass of the top block in the tower should overbalance it
boxC->SetMass(5.0f);
EXPECT_NEAR(1.0f, boxB->GetMass(), 0.01f);
EXPECT_NEAR(1.0f, boxB->GetInverseMass(), 0.01f);
EXPECT_NEAR(5.0f, boxC->GetMass(), 0.01f);
EXPECT_NEAR(0.2f, boxC->GetInverseMass(), 0.01f);
boxB->ForceAwake();
boxC->ForceAwake();
UpdateWorld(world.get(), 1.0f / 60.0f, 300);
EXPECT_GT(0.0f, static_cast<float>(boxC->GetPosition().GetZ()));
}
TEST_F(GenericPhysicsInterfaceTest, GetCenterOfMass_FallingBody_CenterOfMassCorrectDuringFall)
{
auto world = CreateTestWorld();
auto boxStatic = AddStaticUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 0.0f));
auto boxDynamic = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 2.0f));
auto tolerance = 1e-3f;
EXPECT_TRUE(boxDynamic->GetCenterOfMassWorld().IsClose(AZ::Vector3(0.0f, 0.0f, 2.0f), tolerance));
EXPECT_TRUE(boxDynamic->GetCenterOfMassLocal().IsClose(AZ::Vector3(0.0f, 0.0f, 0.0f), tolerance));
UpdateWorld(world.get(), 1.0f / 60.0f, 300);
EXPECT_NEAR(static_cast<float>(boxDynamic->GetCenterOfMassWorld().GetZ()), 1.0f, 1e-3f);
EXPECT_TRUE(boxDynamic->GetCenterOfMassLocal().IsClose(AZ::Vector3(0.0f, 0.0f, 0.0f), tolerance));
}
TEST_F(GenericPhysicsInterfaceTest, SetLinearVelocity_DynamicBox_AffectsTrajectory)
{
auto world = CreateTestWorld();
auto boxA = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, -5.0f, 10.0f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 5.0f, 10.0f));
boxA->SetLinearVelocity(AZ::Vector3(10.0f, 0.0f, 0.0f));
for (int i = 1; i < 10; i++)
{
float xPreviousA = boxA->GetPosition().GetX();
float xPreviousB = boxB->GetPosition().GetX();
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
EXPECT_GT(static_cast<float>(boxA->GetPosition().GetX()), xPreviousA);
EXPECT_NEAR(boxB->GetPosition().GetX(), xPreviousB, 1e-3f);
}
}
TEST_F(GenericPhysicsInterfaceTest, ApplyLinearImpulse_DynamicBox_AffectsTrajectory)
{
auto world = CreateTestWorld();
auto boxA = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 100.0f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 10.0f, 100.0f));
boxA->ApplyLinearImpulse(AZ::Vector3(10.0f, 0.0f, 0.0f));
for (int i = 1; i < 10; i++)
{
float xPreviousA = boxA->GetPosition().GetX();
float xPreviousB = boxB->GetPosition().GetX();
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
EXPECT_GT(static_cast<float>(boxA->GetPosition().GetX()), xPreviousA);
EXPECT_NEAR(boxB->GetPosition().GetX(), xPreviousB, 1e-3f);
}
}
// allow a more generous tolerance on tests involving objects in contact, since the way physics engines normally
// handle multiple contacts between objects can lead to slight imbalances in contact forces
static constexpr float ContactTestTolerance = 0.01f;
TEST_F(GenericPhysicsInterfaceTest, GetAngularVelocity_DynamicCapsuleOnSlope_GainsAngularVelocity)
{
auto world = CreateTestWorld();
AZ::Transform slopeTransform = AZ::Transform::CreateRotationY(0.1f);
auto slope = AddStaticFloorToWorld(world.get(), slopeTransform);
auto capsule = AddCapsuleToWorld(world.get(), slopeTransform.TransformPoint(AZ::Vector3::CreateAxisZ()));
// the capsule should roll down the slope, picking up angular velocity parallel to the Y axis
float angularVelocityMagnitude = capsule->GetAngularVelocity().GetLength();
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
angularVelocityMagnitude = capsule->GetAngularVelocity().GetLength();
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
auto angularVelocity = capsule->GetAngularVelocity();
EXPECT_TRUE(angularVelocity.IsPerpendicular(AZ::Vector3::CreateAxisX(), ContactTestTolerance));
EXPECT_TRUE(angularVelocity.IsPerpendicular(AZ::Vector3::CreateAxisZ(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetAngularVelocity().GetLength() > angularVelocityMagnitude);
angularVelocityMagnitude = angularVelocity.GetLength();
}
}
TEST_F(GenericPhysicsInterfaceTest, SetAngularVelocity_DynamicCapsule_StartsRolling)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
// capsule should remain stationary
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsule->GetPosition().IsClose(AZ::Vector3::CreateAxisZ(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetLinearVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
}
// apply an angular velocity and it should start rolling
auto angularVelocity = AZ::Vector3::CreateAxisY(10.0f);
capsule->SetAngularVelocity(angularVelocity);
EXPECT_TRUE(capsule->GetAngularVelocity().IsClose(angularVelocity));
for (int i = 0; i < 60; i++)
{
float xPrevious = capsule->GetPosition().GetX();
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsule->GetPosition().GetX() > xPrevious);
}
}
TEST_F(GenericPhysicsInterfaceTest, GetLinearVelocityAtWorldPoint_FallingRotatingCapsule_EdgeVelocitiesCorrect)
{
auto world = CreateTestWorld();
// create dynamic capsule and start it falling and rotating
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
float angularVelocityMagnitude = 1.0f;
capsule->SetAngularVelocity(AZ::Vector3::CreateAxisY(angularVelocityMagnitude));
capsule->SetAngularDamping(0.0f);
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// check the velocities at some points on the rim of the capsule are as expected
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
auto position = capsule->GetPosition();
float fallingSpeed = capsule->GetLinearVelocity().GetZ();
float radius = 0.5f;
AZ::Vector3 z = AZ::Vector3::CreateAxisZ(radius);
AZ::Vector3 x = AZ::Vector3::CreateAxisX(radius);
auto v1 = capsule->GetLinearVelocityAtWorldPoint(position - z);
auto v2 = capsule->GetLinearVelocityAtWorldPoint(position - x);
auto v3 = capsule->GetLinearVelocityAtWorldPoint(position + x);
EXPECT_TRUE(v1.IsClose(AZ::Vector3(-radius * angularVelocityMagnitude, 0.0f, fallingSpeed)));
EXPECT_TRUE(v2.IsClose(AZ::Vector3(0.0f, 0.0f, fallingSpeed + radius * angularVelocityMagnitude)));
EXPECT_TRUE(v3.IsClose(AZ::Vector3(0.0f, 0.0f, fallingSpeed - radius * angularVelocityMagnitude)));
}
}
TEST_F(GenericPhysicsInterfaceTest, GetPosition_RollingCapsule_OrientationCorrect)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
// create dynamic capsule and start it rolling
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
capsule->SetLinearVelocity(AZ::Vector3::CreateAxisX(5.0f));
capsule->SetAngularVelocity(AZ::Vector3::CreateAxisY(10.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// check the capsule orientation evolves as expected
for (int i = 0; i < 60; i++)
{
auto orientationPrevious = capsule->GetOrientation();
float xPrevious = capsule->GetPosition().GetX();
world->Update(1.0f / 60.0f);
float angle = 2.0f * (capsule->GetPosition().GetX() - xPrevious);
EXPECT_TRUE(capsule->GetOrientation().IsClose(orientationPrevious * AZ::Quaternion::CreateRotationY(angle)));
}
}
TEST_F(GenericPhysicsInterfaceTest, OffCenterImpulse_DynamicCapsule_StartsRotating)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
AZ::Vector3 posA(0.0f, -5.0f, 1.0f);
AZ::Vector3 posB(0.0f, 0.0f, 1.0f);
AZ::Vector3 posC(0.0f, 5.0f, 1.0f);
auto capsuleA = AddCapsuleToWorld(world.get(), posA);
auto capsuleB = AddCapsuleToWorld(world.get(), posB);
auto capsuleC = AddCapsuleToWorld(world.get(), posC);
// all the capsules should be stationary initially
for (int i = 0; i < 10; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsuleA->GetPosition().IsClose(posA));
EXPECT_TRUE(capsuleA->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleB->GetPosition().IsClose(posB));
EXPECT_TRUE(capsuleB->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleC->GetPosition().IsClose(posC));
EXPECT_TRUE(capsuleC->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
}
// apply off-center impulses to capsule A and C, and an impulse through the center of B
AZ::Vector3 impulse(0.0f, 0.0f, 10.0f);
capsuleA->ApplyLinearImpulseAtWorldPoint(impulse, posA + AZ::Vector3::CreateAxisX(0.5f));
capsuleB->ApplyLinearImpulseAtWorldPoint(impulse, posB);
capsuleC->ApplyLinearImpulseAtWorldPoint(impulse, posC + AZ::Vector3::CreateAxisX(-0.5f));
// A and C should be rotating in opposite directions, B should still have 0 angular velocity
for (int i = 0; i < 30; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsuleA->GetAngularVelocity().GetY() < 0.0f);
EXPECT_TRUE(capsuleB->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleC->GetAngularVelocity().GetY() > 0.0f);
}
}
TEST_F(GenericPhysicsInterfaceTest, ApplyAngularImpulse_DynamicSphere_StartsRotating)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
AZStd::shared_ptr<RigidBody> spheres[3];
for (int i = 0; i < 3; i++)
{
spheres[i] = AddSphereToWorld(world.get(), AZ::Vector3(0.0f, -5.0f + 5.0f * i, 1.0f));
}
// all the spheres should start stationary
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
for (int i = 0; i < 3; i++)
{
EXPECT_TRUE(spheres[i]->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero()));
}
// apply angular impulses and they should gain angular velocity parallel to the impulse direction
AZ::Vector3 impulses[3] = { AZ::Vector3(2.0f, 4.0f, 0.0f), AZ::Vector3(-3.0f, 1.0f, 0.0f),
AZ::Vector3(-2.0f, 3.0f, 0.0f) };
for (int i = 0; i < 3; i++)
{
spheres[i]->ApplyAngularImpulse(impulses[i]);
}
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
for (int i = 0; i < 3; i++)
{
auto angVel = spheres[i]->GetAngularVelocity();
EXPECT_TRUE(angVel.GetProjected(impulses[i]).IsClose(angVel, 0.1f));
}
}
TEST_F(GenericPhysicsInterfaceTest, StartAsleep_FallingBox_DoesNotFall)
{
auto world = CreateTestWorld();
// Box should start asleep
RigidBodyConfiguration config;
config.m_startAsleep = true;
// Create rigid body
AZStd::shared_ptr<RigidBody> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateRigidBody, config);
world->AddBody(*box);
UpdateWorld(world.get(), 1.0f / 60.0f, 100);
// Check the box is still at 0 and hasn't dropped
EXPECT_NEAR(0.0f, box->GetPosition().GetZ(), 0.01f);
}
TEST_F(GenericPhysicsInterfaceTest, ForceAsleep_FallingBox_BecomesStationary)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto box = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 10.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_TRUE(box->IsAwake());
auto pos = box->GetPosition();
box->ForceAsleep();
EXPECT_FALSE(box->IsAwake());
UpdateWorld(world.get(), 1.0f / 60.0f, 30);
EXPECT_FALSE(box->IsAwake());
// the box should be asleep so it shouldn't have moved
EXPECT_TRUE(box->GetPosition().IsClose(pos));
}
TEST_F(GenericPhysicsInterfaceTest, ForceAwake_SleepingBox_SleepStateCorrect)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto box = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 1.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_FALSE(box->IsAwake());
box->ForceAwake();
EXPECT_TRUE(box->IsAwake());
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// the box should have gone back to sleep
EXPECT_FALSE(box->IsAwake());
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Box_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posBox(0.0f, 0.0f, 0.0f);
auto box = AddUnitBoxToWorld(world.get(), posBox);
EXPECT_TRUE(box->GetAabb().GetMin().IsClose(posBox - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(box->GetAabb().GetMax().IsClose(posBox + 0.5f * AZ::Vector3::CreateOne()));
// rotate the box and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
box->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posBox));
AZ::Vector3 boxExtent(sqrtf(0.5f), sqrtf(0.5f), 0.5f);
EXPECT_TRUE(box->GetAabb().GetMin().IsClose(posBox - boxExtent));
EXPECT_TRUE(box->GetAabb().GetMax().IsClose(posBox + boxExtent));
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Sphere_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posSphere(0.0f, 0.0f, 0.0f);
auto sphere = AddSphereToWorld(world.get(), posSphere);
EXPECT_TRUE(sphere->GetAabb().GetMin().IsClose(posSphere - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphere->GetAabb().GetMax().IsClose(posSphere + 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);
sphere->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posSphere));
EXPECT_TRUE(sphere->GetAabb().GetMin().IsClose(posSphere - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphere->GetAabb().GetMax().IsClose(posSphere + 0.5f * AZ::Vector3::CreateOne()));
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Capsule_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posCapsule(0.0f, 0.0f, 0.0f);
auto capsule = AddCapsuleToWorld(world.get(), posCapsule);
EXPECT_TRUE(capsule->GetAabb().GetMin().IsClose(posCapsule - AZ::Vector3(0.5f, 1.0f, 0.5f)));
EXPECT_TRUE(capsule->GetAabb().GetMax().IsClose(posCapsule + AZ::Vector3(0.5f, 1.0f, 0.5f)));
// rotate the bodies and check the bounding boxes are still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
capsule->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posCapsule));
AZ::Vector3 capsuleExtent(0.5f + sqrt(0.125f), 0.5f + sqrt(0.125f), 0.5f);
EXPECT_TRUE(capsule->GetAabb().GetMin().IsClose(posCapsule - capsuleExtent));
EXPECT_TRUE(capsule->GetAabb().GetMax().IsClose(posCapsule + capsuleExtent));
}
TEST_F(GenericPhysicsInterfaceTest, Materials_BoxesSharingDefaultMaterial_JumpingSameHeight)
{
auto world = CreateTestWorld();
auto boxA = AddStaticFloorToWorld(world.get());
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(1.0f, 0.0f, 10.0f));
auto boxC = AddUnitBoxToWorld(world.get(), AZ::Vector3(-1.0f, 0.0f, 10.0f));
auto material = boxC->GetShape(0)->GetMaterial();
material->SetRestitution(1.0f);
UpdateWorld(world.get(), 1.0f / 60.0f, 150);
// boxB and boxC should have the same material (default)
// so they should both bounce high
EXPECT_NEAR(boxB->GetPosition().GetZ(), boxC->GetPosition().GetZ(), 0.5f);
}
TEST_F(GenericPhysicsInterfaceTest, World_GetNativePtrByWorldName_ReturnsNativePtr)
{
void* validNativePtr = nullptr;
WorldRequestBus::EventResult(validNativePtr, Physics::DefaultPhysicsWorldId, &WorldRequests::GetNativePointer);
EXPECT_TRUE(validNativePtr != nullptr);
void* invalidNativePtr = nullptr;
WorldRequestBus::EventResult(invalidNativePtr, AZ_CRC("Bad World Name"), &WorldRequests::GetNativePointer);
EXPECT_TRUE(invalidNativePtr == nullptr);
}
TEST_F(GenericPhysicsInterfaceTest, Collider_ColliderTag_IsSetFromConfiguration)
{
const AZStd::string colliderTagName = "ColliderTestTag";
Physics::ColliderConfiguration colliderConfig;
colliderConfig.m_tag = colliderTagName;
Physics::SphereShapeConfiguration shapeConfig;
AZStd::shared_ptr<Physics::Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfig);
EXPECT_EQ(shape->GetTag(), AZ::Crc32(colliderTagName));
}
} // namespace Physics
-168
View File
@@ -1,168 +0,0 @@
/*
* 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.
*
*/
#pragma once
#include <AzTest/AzTest.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/World.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Configuration/SystemConfiguration.h>
namespace Physics
{
class GenericPhysicsFixture
: protected Physics::DefaultWorldBus::Handler
{
public:
// Helper functions for setting up test worlds using API only
// These can be implemented here as they should not require any gem specific functions
AZStd::shared_ptr<Physics::World> CreateTestWorld();
void DestroyTestScene();
void SetUpInternal();
void TearDownInternal();
// Helper functions for setting up entities used in tests
// These need to be implemented in the gem as they may require gem specific components etc.
AZ::Entity* AddSphereEntity(const AZ::Vector3& position, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddBoxEntity(const AZ::Vector3& position, const AZ::Vector3& dimensions, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddCapsuleEntity(const AZ::Vector3& position, const float height, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticSphereEntity(const AZ::Vector3& position, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticBoxEntity(const AZ::Vector3& position, const AZ::Vector3& dimensions, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticCapsuleEntity(const AZ::Vector3& position, const float height, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
// Helper function for creating multishape entity
struct MultiShapeConfig
{
AZ::Vector3 m_position; // Position of entity
AZ::Vector3 m_rotation = AZ::Vector3(0.f, 0.f, 0.f); // Euler rotation of entity in radians
AzPhysics::CollisionLayer m_layer = AzPhysics::CollisionLayer::Default; // Collision layer
struct ShapeList
{
struct ShapeData
{
struct Box
{
AZ::Vector3 m_extent;
};
struct Sphere
{
float m_radius;
};
struct Capsule
{
float m_height;
float m_radius;
};
AZ::Vector3 m_offset;
AZStd::variant<AZStd::monostate, Box, Sphere, Capsule> m_data;
};
void AddBox(AZ::Vector3 extent, AZ::Vector3 offset)
{
ShapeData box;
ShapeData::Box boxData{ extent };
box.m_data = boxData;
box.m_offset = offset;
m_shapesData.push_back(box);
}
void AddSphere(float radius, AZ::Vector3 offset)
{
ShapeData sphere;
ShapeData::Sphere sphereData{ radius };
sphere.m_data = sphereData;
sphere.m_offset = offset;
m_shapesData.push_back(sphere);
}
void AddCapsule(float height, float radius, AZ::Vector3 offset)
{
ShapeData capsule;
ShapeData::Capsule capsuleData{ height, radius };
capsule.m_data = capsuleData;
capsule.m_offset = offset;
m_shapesData.push_back(capsule);
}
AZStd::vector<ShapeData> m_shapesData;
};
ShapeList m_shapes;
};
AZStd::unique_ptr<AZ::Entity> AddMultiShapeEntity(const MultiShapeConfig& config);
protected:
// DefaultWorldBus
AZStd::shared_ptr<World> GetDefaultWorld();
AzPhysics::Scene* m_defaultScene = nullptr;
AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle;
};
/// Class to contain tests which any implementation of the AzFramework::Physics API should pass
/// Each gem which implements the common physics API can run the generic API tests by:
/// - including this header file and the appropriate .inl files.
/// - deriving from AZ::Test::ITestEnvironment and extending the environment functions to set up the gem system component etc.
/// - implementing the helper functions required for the tests using gem specific components etc.
/// - adding a AZ_UNIT_TEST_HOOK with the derived environment class
class GenericPhysicsInterfaceTest
: protected GenericPhysicsFixture
, public testing::Test
{
public:
void SetUp() override
{
SetUpInternal();
}
void TearDown() override
{
TearDownInternal();
DestroyTestScene(); //cleanup any physics scene if created.
}
};
class PhysicsComponentBusTest
: public GenericPhysicsInterfaceTest
{
void SetUp() override
{
GenericPhysicsInterfaceTest::SetUp();
}
void TearDown() override
{
GenericPhysicsInterfaceTest::TearDown();
}
};
// helper functions
AZStd::shared_ptr<RigidBodyStatic> AddStaticFloorToWorld(World* world, const AZ::Transform& transform = AZ::Transform::CreateIdentity());
AZStd::shared_ptr<RigidBodyStatic> AddStaticUnitBoxToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddUnitBoxToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddSphereToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddCapsuleToWorld(World* world, const AZ::Vector3& position);
void UpdateScene(AzPhysics::Scene* scene, float timeStep, AZ::u32 numSteps);
float GetPositionElement(AZ::Entity* entity, int element);
} // namespace Physics
@@ -1,149 +0,0 @@
/*
* 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 "PhysicsTests.h"
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzCore/Component/TransformBus.h>
namespace Physics
{
// helper functions
AZStd::shared_ptr<World> GenericPhysicsFixture::CreateTestWorld()
{
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
AzPhysics::SceneConfiguration sceneConfiguration = physicsSystem->GetDefaultSceneConfiguration();
sceneConfiguration.m_legacyId = AZ_CRC_CE("testWorld");
sceneConfiguration.m_legacyConfiguration.m_gravity = AZ::Vector3(0.0f, 0.0f, -10.0f);
m_testSceneHandle = physicsSystem->AddScene(sceneConfiguration);
m_defaultScene = physicsSystem->GetScene(m_testSceneHandle);
return m_defaultScene->GetLegacyWorld();
}
return nullptr;
}
void GenericPhysicsFixture::DestroyTestScene()
{
//clean up the test scene
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
physicsSystem->RemoveScene(m_testSceneHandle);
}
}
AZStd::shared_ptr<RigidBodyStatic> AddStaticFloorToWorld(World* world, const AZ::Transform& transform)
{
WorldBodyConfiguration rigidBodySettings;
AZStd::shared_ptr<RigidBodyStatic> floor;
SystemRequestBus::BroadcastResult(floor, &SystemRequests::CreateStaticRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(20.0f, 20.0f, 1.0f));
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
floor->AddShape(shape);
world->AddBody(*floor);
floor->SetTransform(transform);
return floor;
}
AZStd::shared_ptr<RigidBodyStatic> AddStaticUnitBoxToWorld(World* world, const AZ::Vector3& position)
{
WorldBodyConfiguration rigidBodySettings;
rigidBodySettings.m_position = position;
AZStd::shared_ptr<RigidBodyStatic> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateStaticRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
box->AddShape(shape);
world->AddBody(*box);
return box;
}
AZStd::shared_ptr<RigidBody> AddUnitBoxToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
rigidBodySettings.m_linearDamping = 0.0f;
AZStd::shared_ptr<RigidBody> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
box->AddShape(shape);
world->AddBody(*box.get());
box->SetTransform(AZ::Transform::CreateTranslation(position));
return box;
}
AZStd::shared_ptr<RigidBody> AddSphereToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
rigidBodySettings.m_linearDamping = 0.0f;
AZStd::shared_ptr<RigidBody> sphere;
SystemRequestBus::BroadcastResult(sphere, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::SphereShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
sphere->AddShape(shape);
world->AddBody(*sphere.get());
sphere->SetTransform(AZ::Transform::CreateTranslation(position));
return sphere;
}
AZStd::shared_ptr<RigidBody> AddCapsuleToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
AZStd::shared_ptr<RigidBody> capsule = nullptr;
SystemRequestBus::BroadcastResult(capsule, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
colliderConfig.m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi);
Physics::CapsuleShapeConfiguration shapeConfig(2.0f, 0.5f);
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfig);
capsule->AddShape(shape);
world->AddBody(*capsule.get());
capsule->SetTransform(AZ::Transform::CreateTranslation(position));
return capsule;
}
void UpdateScene(AzPhysics::Scene* scene, float timeStep, AZ::u32 numSteps)
{
for (AZ::u32 i = 0; i < numSteps; i++)
{
scene->StartSimulation(timeStep);
scene->FinishSimulation();
}
}
float GetPositionElement(AZ::Entity* entity, int element)
{
AZ::Transform transform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(transform, entity->GetId(), &AZ::TransformInterface::GetWorldTM);
return transform.GetTranslation().GetElement(element);
}
} // namespace Physics
@@ -1,17 +0,0 @@
#
# 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.
#
set(FILES
PhysicsTests.h
PhysicsTests.inl
PhysicsGenericInterfaceTests.inl
PhysicsComponentBusTests.inl
)
-243
View File
@@ -1,243 +0,0 @@
/*
* 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 <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include "EntityTestbed.h"
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
class EntityScriptTest
: public EntityTestbed
{
public:
ScriptContext* m_scriptContext;
~EntityScriptTest()
{
}
void OnDestroy() override
{
delete m_scriptContext;
m_scriptContext = nullptr;
}
void run()
{
int argc = 0;
char* argv = nullptr;
Run(argc, &argv);
}
void OnReflect(AZ::SerializeContext& context, AZ::Entity& systemEntity) override
{
(void)context;
(void)systemEntity;
}
void OnSetup() override
{
m_scriptContext = aznew AZ::ScriptContext();
auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (catalogBus)
{
// Register asset types the asset DB should query our catalog for.
catalogBus->AddAssetType(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
// Build the catalog (scan).
catalogBus->AddExtension(".lua");
}
}
void OnEntityAdded(AZ::Entity& entity) override
{
// Add your components.
entity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
entity.Activate();
}
};
TEST_F(EntityScriptTest, DISABLED_Test)
{
run();
}
class ScriptComponentTest
: public ::testing::Test
{
static int mySubValue;
static int myReloadValue;
public:
void run()
{
{
ComponentApplication app;
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 100 * 1024 * 1024;
//appDesc.m_recordsMode = AllocationRecords::RECORD_FULL;
//appDesc.m_stackRecordLevels = 20;
Entity* systemEntity = app.Create(appDesc);
systemEntity->CreateComponent<MemoryComponent>();
systemEntity->CreateComponent("{CAE3A025-FAC9-4537-B39E-0A800A2326DF}"); // JobManager component
systemEntity->CreateComponent<StreamerComponent>();
systemEntity->CreateComponent<AssetManagerComponent>();
systemEntity->CreateComponent("{A316662A-6C3E-43E6-BC61-4B375D0D83B4}"); // Usersettings component
systemEntity->CreateComponent<ScriptSystemComponent>();
systemEntity->Init();
systemEntity->Activate();
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
ScriptComponent::CreateDescriptor(); // descriptor is deleted by app
ScriptContext* scriptContext = nullptr;
EBUS_EVENT_RESULT(scriptContext, ScriptSystemRequestBus, GetContext, DefaultScriptContextId);
BehaviorContext* behaviorContext = nullptr;
EBUS_EVENT_RESULT(behaviorContext, AZ::ComponentApplicationBus, GetBehaviorContext);
// make sure script instances don't can read only share data, but don't modify the source table
{
Data::Asset<ScriptAsset> scriptAsset = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
AZStd::string script = "test = {\
--[[test with no properties table as this should work too!]]\
state = {\
mysubstate = {\
mysubvalue = 2,\
},\
myvalue = 0,\
},\
}\
function test:OnActivate()\
self.state.mysubstate.mysubvalue = 5\
end\
return test;";
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), script.begin(), script.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset);
app.Tick();
app.TickSystem();
Entity* entity1 = aznew Entity();
entity1->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity1->Init();
entity1->Activate();
Entity* entity2 = aznew Entity();
entity2->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity2->Init();
entity2->Activate();
behaviorContext->Property("globalMySubValue", BehaviorValueProperty(&mySubValue));
scriptContext->Execute("globalMySubValue = test.state.mysubstate.mysubvalue", "Read my subvalue");
AZ_TEST_ASSERT(mySubValue == 2); // we should not have changed test. table but the instance table of each component.
delete entity1;
delete entity2;
scriptAsset.Reset();
}
// Test script reload
{
behaviorContext->Property("myReloadValue", BehaviorValueProperty(&myReloadValue));
Data::Asset<ScriptAsset> scriptAsset1 = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
AZStd::string script1 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 1\
end\
function testReload:OnDeactivate()\
myReloadValue = 0\
end\
return testReload;";
scriptAsset1.Get()->m_scriptBuffer.insert(scriptAsset1.Get()->m_scriptBuffer.begin(), script1.begin(), script1.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset1);
app.Tick();
app.TickSystem(); // flush assets etc.
Entity* entity = aznew Entity();
entity->CreateComponent<ScriptComponent>()->SetScript(scriptAsset1);
entity->Init();
entity->Activate();
// test value, it should set during activation of the first script
AZ_TEST_ASSERT(myReloadValue == 1);
AZStd::string script2 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 5\
end\
return testReload";
// modify the asset
Data::Asset<ScriptAsset> scriptAsset2(aznew ScriptAsset(scriptAsset1.GetId()), AZ::Data::AssetLoadBehavior::Default);
scriptAsset2.Get()->m_scriptBuffer.insert(scriptAsset2.Get()->m_scriptBuffer.begin(), script2.begin(), script2.end());
// When reloading script assets from files, ScriptSystemComponent would clear old script caches automatically in the
// function `ScriptSystemComponent::LoadAssetData()`. But here we are changing script directly in memory, therefore we
// need to clear old cache manually.
AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequestBus::Events::ClearAssetReferences, scriptAsset1.GetId());
// trigger reload
Data::AssetManager::Instance().ReloadAssetFromData(scriptAsset2);
// ReloadAssetFromData is (now) a queued event
// Need to tick subsystems here to receive reload event.
app.Tick();
app.TickSystem();
// test value with the reloaded value
EXPECT_EQ(5, myReloadValue);
delete entity;
scriptAsset1.Reset();
scriptAsset2.Reset();
}
app.Destroy();
//////////////////////////////////////////////////////////////////////////
}
}
};
TEST_F(ScriptComponentTest, ScriptComponentTestExecution)
{
run();
}
int ScriptComponentTest::mySubValue = 0;
int ScriptComponentTest::myReloadValue = 0;
} // namespace UnitTest
@@ -0,0 +1,290 @@
/*
* 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 <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include "EntityTestbed.h"
extern "C" {
# include <Lua/lualib.h>
# include <Lua/lauxlib.h>
}
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
// Global Properties used for Testing
int mySubValue = 0;
int myReloadValue = 0;
class ScriptComponentTest
: public testing::Test
{
public:
AZ_TYPE_INFO(ScriptComponentTest, "{85CDBD49-70FF-416A-8154-B5525EDD30D4}");
void SetUp() override
{
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 100 * 1024 * 1024;
//appDesc.m_recordsMode = AllocationRecords::RECORD_FULL;
//appDesc.m_stackRecordLevels = 20;
Entity* systemEntity = m_app.Create(appDesc);
systemEntity->CreateComponent<MemoryComponent>();
systemEntity->CreateComponent("{CAE3A025-FAC9-4537-B39E-0A800A2326DF}"); // JobManager component
systemEntity->CreateComponent<StreamerComponent>();
systemEntity->CreateComponent<AssetManagerComponent>();
systemEntity->CreateComponent("{A316662A-6C3E-43E6-BC61-4B375D0D83B4}"); // Usersettings component
systemEntity->CreateComponent<ScriptSystemComponent>();
systemEntity->Init();
systemEntity->Activate();
EBUS_EVENT_RESULT(m_scriptContext, ScriptSystemRequestBus, GetContext, DefaultScriptContextId);
EBUS_EVENT_RESULT(m_behaviorContext, AZ::ComponentApplicationBus, GetBehaviorContext);
EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
NetBindable::Reflect(m_serializeContext);
AzToolsFramework::Components::ScriptEditorComponent::CreateDescriptor(); // descriptor is deleted by app
AzToolsFramework::Components::ScriptEditorComponent::Reflect(m_serializeContext);
ScriptComponent::CreateDescriptor(); // descriptor is deleted by app
ScriptComponent::Reflect(m_serializeContext);
}
void TearDown() override
{
m_app.Destroy();
}
Data::Asset<ScriptAsset> CreateAndLoadScriptAsset(const AZStd::string& script)
{
Data::Asset<ScriptAsset> scriptAsset = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), script.begin(), script.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset);
m_app.Tick();
m_app.TickSystem(); // flush assets etc.
return scriptAsset;
}
static ScriptComponent* BuildGameEntity(const Data::Asset<ScriptAsset>& scriptAsset, Entity& gameEntity)
{
// We first setup the ScriptEditorComponent.
// After a script asset is loaded the ScriptEditorComponent builds the properties table.
// BuildGameEntity() hands off the properties table to the game runtime ScriptComponent.
Entity editorEntity;
auto* scriptEditorComponent = editorEntity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
scriptEditorComponent->SetScript(scriptAsset);
editorEntity.Init();
editorEntity.Activate();
scriptEditorComponent->BuildGameEntity(&gameEntity);
auto* scriptComponent = gameEntity.FindComponent<ScriptComponent>();
return scriptComponent;
}
static void OverwriteScriptBuffer(Data::Asset<ScriptAsset> scriptAsset, AZStd::string newScript)
{
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), newScript.begin(), newScript.end());
}
ComponentApplication m_app;
ScriptContext* m_scriptContext = nullptr;
BehaviorContext* m_behaviorContext = nullptr;
SerializeContext* m_serializeContext = nullptr;
};
TEST_F(ScriptComponentTest, ScriptInstancesCanReadButDontModifySourceTable)
{
// make sure script instances don't can read only share data, but don't modify the source table
const AZStd::string script = "test = {\
--[[test with no properties table as this should work too!]]\
state = {\
mysubstate = {\
mysubvalue = 2,\
},\
myvalue = 0,\
},\
}\
function test:OnActivate()\
self.state.mysubstate.mysubvalue = 5\
end\
return test;";
Data::Asset<ScriptAsset> scriptAsset = CreateAndLoadScriptAsset(script);
auto* entity1 = aznew Entity();
entity1->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity1->Init();
entity1->Activate();
auto* entity2 = aznew Entity();
entity2->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity2->Init();
entity2->Activate();
m_behaviorContext->Property("globalMySubValue", BehaviorValueProperty(&mySubValue));
m_scriptContext->Execute("globalMySubValue = test.state.mysubstate.mysubvalue", "Read my subvalue");
AZ_TEST_ASSERT(mySubValue == 2); // we should not have changed test. table but the instance table of each component.
delete entity1;
delete entity2;
}
TEST_F(ScriptComponentTest, ScriptReloads)
{
// Test script reload
m_behaviorContext->Property("myReloadValue", BehaviorValueProperty(&myReloadValue));
const AZStd::string script1 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 1\
end\
function testReload:OnDeactivate()\
myReloadValue = 0\
end\
return testReload;";
Data::Asset<ScriptAsset> scriptAsset1 = CreateAndLoadScriptAsset(script1);
auto* entity = aznew Entity();
entity->CreateComponent<ScriptComponent>()->SetScript(scriptAsset1);
entity->Init();
entity->Activate();
// test value, it should set during activation of the first script
AZ_TEST_ASSERT(myReloadValue == 1);
const AZStd::string script2 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 5\
end\
return testReload";
// modify the asset
Data::Asset<ScriptAsset> scriptAsset2(aznew ScriptAsset(scriptAsset1.GetId()), AZ::Data::AssetLoadBehavior::Default);
OverwriteScriptBuffer(scriptAsset2, script2);
// When reloading script assets from files, ScriptSystemComponent would clear old script caches automatically in the
// function `ScriptSystemComponent::LoadAssetData()`. But here we are changing script directly in memory, therefore we
// need to clear old cache manually.
AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequestBus::Events::ClearAssetReferences, scriptAsset1.GetId());
// trigger reload
Data::AssetManager::Instance().ReloadAssetFromData(scriptAsset2);
// ReloadAssetFromData is (now) a queued event
// Need to tick subsystems here to receive reload event.
m_app.Tick();
m_app.TickSystem();
// test value with the reloaded value
EXPECT_EQ(5, myReloadValue);
delete entity;
}
TEST_F(ScriptComponentTest, LuaPropertiesAreDiscovered)
{
const AZStd::string script = "local test = {\
Properties = {\
myNum = { default = 2 },\
},\
}\
function test:OnActivate()\
self.Properties.myNum = 5\
end\
return test";
const Data::Asset<ScriptAsset> scriptAsset = CreateAndLoadScriptAsset(script);
Entity editorEntity, gameEntity;
auto* scriptComponent = BuildGameEntity(scriptAsset, gameEntity);
EXPECT_NE(scriptComponent->GetScriptProperty("myNum"), nullptr);
}
TEST_F(ScriptComponentTest, UpdateNetSynchedProperty)
{
// Make sure altering a netsynched property in script only affects the single entity instance
const AZStd::string script = "local test = {\
Properties = {\
myNetSynchedNum = { default = 41, netSynched ={} },\
doUpdate = { default = false },\
},\
}\
function test:OnActivate()\
self.tickBusHandler = TickBus.Connect(self, self.entityId)\
end\
function test:OnDeactivate()\
self.tickBusHandler:Disconnect()\
end\
function test:OnTick(deltaTime, timePoint)\
if self.Properties.doUpdate then\
self.Properties.myNetSynchedNum = self.Properties.myNetSynchedNum+1\
end\
end\
return test";
const Data::Asset<ScriptAsset> scriptAsset = CreateAndLoadScriptAsset(script);
Entity entity1, entity2;
ScriptComponent* scriptComponentInstance1 = BuildGameEntity(scriptAsset, entity1);
ScriptComponent* scriptComponentInstance2 = BuildGameEntity(scriptAsset, entity2);
// Change the value of entity1's doUpdate to true.
// This way entity1's myNetSynchedNum should be incremented during OnTick
auto* doUpdateScriptProperty = azrtti_cast<ScriptPropertyBoolean*>(scriptComponentInstance1->GetScriptProperty("doUpdate"));
ASSERT_NE(doUpdateScriptProperty, nullptr);
doUpdateScriptProperty->m_value = true;
entity1.Init();
entity2.Init();
entity1.Activate();
entity2.Activate();
// Tick in order to call OnTick in our lua script.
m_app.Tick();
m_app.TickSystem();
// Ensure Entity1's myNetSynchedNum updated, but not Entity2
auto* netSynchedProperty1 = scriptComponentInstance1->GetNetworkedScriptProperty("myNetSynchedNum");
auto* netSynchedProperty2 = scriptComponentInstance2->GetNetworkedScriptProperty("myNetSynchedNum");
ASSERT_NE(netSynchedProperty1, nullptr);
ASSERT_NE(netSynchedProperty2, nullptr);
auto* num1 = azrtti_cast<const ScriptPropertyNumber*>(netSynchedProperty1);
auto* num2 = azrtti_cast<const ScriptPropertyNumber*>(netSynchedProperty2);
ASSERT_NE(num1, nullptr);
ASSERT_NE(num2, nullptr);
EXPECT_EQ(num1->m_value, 42);
EXPECT_EQ(num2->m_value, 41);
}
} // namespace UnitTest
@@ -0,0 +1,85 @@
/*
* 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 <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
#include "EntityTestbed.h"
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
class EntityScriptTest
: public EntityTestbed
{
public:
ScriptContext* m_scriptContext;
~EntityScriptTest()
{
}
void OnDestroy() override
{
delete m_scriptContext;
m_scriptContext = nullptr;
}
void run()
{
int argc = 0;
char* argv = nullptr;
Run(argc, &argv);
}
void OnReflect(AZ::SerializeContext& context, AZ::Entity& systemEntity) override
{
(void)context;
(void)systemEntity;
}
void OnSetup() override
{
m_scriptContext = aznew AZ::ScriptContext();
auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (catalogBus)
{
// Register asset types the asset DB should query our catalog for.
catalogBus->AddAssetType(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
// Build the catalog (scan).
catalogBus->AddExtension(".lua");
}
}
void OnEntityAdded(AZ::Entity& entity) override
{
// Add your components.
entity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
entity.Activate();
}
};
TEST_F(EntityScriptTest, DISABLED_Test)
{
run();
}
} // namespace UnitTest
@@ -17,7 +17,6 @@ set(FILES
BinToTextEncode.cpp
ComponentAddRemove.cpp
ComponentAdapterTests.cpp
EngineTests.cpp
EntityContext.cpp
EntityTestbed.h
FileFunc.cpp
@@ -32,7 +31,8 @@ set(FILES
OctreePerformanceTests.cpp
OctreeTests.cpp
Slices.cpp
Script.cpp
Script/ScriptComponentTests.cpp
Script/ScriptEntityTests.cpp
AssetCatalog.cpp
AssetProcessorConnection.cpp
NetBindingSystemImplTest.cpp