diff --git a/Gems/PhysX/Code/Tests/PhysXColliderComponentModeTests.cpp b/Gems/PhysX/Code/Tests/PhysXColliderComponentModeTests.cpp index 5722d1a397..e323110d6f 100644 --- a/Gems/PhysX/Code/Tests/PhysXColliderComponentModeTests.cpp +++ b/Gems/PhysX/Code/Tests/PhysXColliderComponentModeTests.cpp @@ -17,7 +17,9 @@ #include #include #include +#include #include +#include namespace UnitTest { @@ -437,4 +439,277 @@ namespace UnitTest EXPECT_NEAR(assetScale.GetY(), 1.0f, tolerance); EXPECT_NEAR(assetScale.GetZ(), 1.0f, tolerance); } + + class PhysXEditorColliderComponentFixture : public UnitTest::ToolsApplicationFixture + { + public: + void SetUpEditorFixtureImpl() override; + void TearDownEditorFixtureImpl() override; + void SetupTransform(const AZ::Quaternion& rotation, const AZ::Vector3& translation, float uniformScale); + void SetupCollider( + const Physics::ShapeConfiguration& shapeConfiguration, + const AZ::Quaternion& colliderRotation, + const AZ::Vector3& colliderOffset); + void SetupNonUniformScale(const AZ::Vector3& nonUniformScale); + void EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode subMode); + + AZ::Entity* m_entity = nullptr; + AZ::EntityComponentIdPair m_idPair; + }; + + void PhysXEditorColliderComponentFixture::SetUpEditorFixtureImpl() + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + + UnitTest::CreateDefaultEditorEntity("EditorColliderComponentEntity", &m_entity); + } + + void PhysXEditorColliderComponentFixture::TearDownEditorFixtureImpl() + { + AzToolsFramework::EditorEntityContextRequestBus::Broadcast( + &AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity, m_entity->GetId()); + m_entity = nullptr; + } + + void PhysXEditorColliderComponentFixture::SetupTransform( + const AZ::Quaternion& rotation, const AZ::Vector3& translation, float uniformScale) + { + const AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(rotation, translation); + AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetWorldTM, transform); + AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetLocalUniformScale, uniformScale); + } + + void PhysXEditorColliderComponentFixture::SetupCollider( + const Physics::ShapeConfiguration& shapeConfiguration, const AZ::Quaternion& colliderRotation, const AZ::Vector3& colliderOffset) + { + m_entity->Deactivate(); + auto* colliderComponent = + m_entity->CreateComponent(Physics::ColliderConfiguration(), shapeConfiguration); + m_entity->Activate(); + m_idPair = AZ::EntityComponentIdPair(m_entity->GetId(), colliderComponent->GetId()); + PhysX::EditorColliderComponentRequestBus::Event( + m_idPair, &PhysX::EditorColliderComponentRequests::SetColliderOffset, colliderOffset); + PhysX::EditorColliderComponentRequestBus::Event( + m_idPair, &PhysX::EditorColliderComponentRequests::SetColliderRotation, colliderRotation); + } + + void PhysXEditorColliderComponentFixture::SetupNonUniformScale(const AZ::Vector3& nonUniformScale) + { + m_entity->Deactivate(); + m_entity->CreateComponent(AzToolsFramework::Components::EditorNonUniformScaleComponent::RTTI_Type()); + m_entity->Activate(); + AZ::NonUniformScaleRequestBus::Event(m_entity->GetId(), &AZ::NonUniformScaleRequests::SetScale, nonUniformScale); + } + + void PhysXEditorColliderComponentFixture::EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode subMode) + { + AzToolsFramework::SelectEntity(m_entity->GetId()); + EnterComponentMode(); + PhysX::ColliderComponentModeRequestBus::Broadcast(&PhysX::ColliderComponentModeRequests::SetCurrentMode, subMode); + } + + using PhysXEditorColliderComponentManipulatorFixture = + UnitTest::IndirectCallManipulatorViewportInteractionFixtureMixin; + + // use a reasonably large tolerance because manipulator precision is limited by viewport resolution + static const float ManipulatorTolerance = 0.01f; + + TEST_F(PhysXEditorColliderComponentManipulatorFixture, OffsetManipulatorsCorrectlyLocatedRelativeToCollider) + { + const AZ::Vector3 boxDimensions(2.0f, 3.0f, 1.5f); + const AZ::Quaternion boxRotation(0.1f, 0.1f, 0.7f, 0.7f); + const AZ::Vector3 boxOffset(3.0f, 1.0f, 2.0f); + SetupCollider(Physics::BoxShapeConfiguration(boxDimensions), boxRotation, boxOffset); + const AZ::Quaternion entityRotation(0.8f, 0.2f, 0.4f, 0.4f); + const AZ::Vector3 entityTranslation(2.0f, -3.0f, 0.5f); + const float uniformScale = 2.0f; + SetupTransform(entityRotation, entityTranslation, uniformScale); + EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode::Offset); + + // the expected position of the collider centre based on the combination of entity transform and collider offset + const AZ::Vector3 expectedColliderPosition(8.8f, -2.28f, 3.54f); + + // the expected world space direction of the collider offset x-axis based on the entity transform + const AZ::Vector3 expectedXAxis(0.6f, 0.64f, 0.48f); + + // position the camera to look down at the collider from above + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(-AZ::Constants::HalfPi), expectedColliderPosition + AZ::Vector3::CreateAxisZ(10.0f))); + + // position in world space, slightly moved along the x-axis in order to grab the x translation manipulator + const AZ::Vector3 worldStart = expectedColliderPosition + 0.5f * expectedXAxis; + + // position in world space to move to + const AZ::Vector3 worldEnd = worldStart + 2.0f * expectedXAxis; + + const auto screenStart = AzFramework::WorldToScreen(worldStart, m_cameraState); + const auto screenEnd = AzFramework::WorldToScreen(worldEnd, m_cameraState); + + m_actionDispatcher + ->CameraState(m_cameraState) + // move the mouse to the position of the x offset manipulator + ->MousePosition(screenStart) + // drag to move the manipulator + ->MouseLButtonDown() + ->MousePosition(screenEnd) + ->MouseLButtonUp(); + + AZ::Vector3 newColliderOffset = AZ::Vector3::CreateZero(); + PhysX::EditorColliderComponentRequestBus::EventResult( + newColliderOffset, m_idPair, &PhysX::EditorColliderComponentRequests::GetColliderOffset); + + EXPECT_THAT(newColliderOffset, IsCloseTolerance(AZ::Vector3(4.0f, 1.0f, 2.0f), ManipulatorTolerance)); + } + + TEST_F(PhysXEditorColliderComponentManipulatorFixture, OffsetManipulatorsCorrectlyLocatedRelativeToColliderWithNonUniformScale) + { + const float capsuleRadius = 0.5f; + const float capsuleHeight = 2.0f; + const AZ::Quaternion capsuleRotation(0.2f, -0.4f, 0.8f, 0.4f); + const AZ::Vector3 capsuleOffset(-2.0f, 3.0f, -1.0f); + SetupCollider(Physics::CapsuleShapeConfiguration(capsuleHeight, capsuleRadius), capsuleRotation, capsuleOffset); + const AZ::Quaternion entityRotation(-0.1f, 0.7f, -0.7f, 0.1f); + const AZ::Vector3 entityTranslation(-1.0f, 1.0f, -2.5f); + const float uniformScale = 1.5f; + SetupTransform(entityRotation, entityTranslation, uniformScale); + const AZ::Vector3 nonUniformScale(2.0f, 0.5f, 1.5f); + SetupNonUniformScale(nonUniformScale); + EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode::Offset); + + // the expected position of the collider centre based on the combination of entity transform, collider offset and non-uniform scale + const AZ::Vector3 expectedColliderPosition(4.13f, 4.84f, -4.75f); + + // the expected world space direction of the collider offset z-axis based on the entity transform + const AZ::Vector3 expectedZAxis(0.28f, -0.96f, 0.0f); + + // position the camera to look at the collider from underneath + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi), expectedColliderPosition - AZ::Vector3::CreateAxisZ(10.0f))); + + // position in world space, slightly moved along the z-axis in order to grab the z translation manipulator + // need to go in the negative z direction because the camera angle causes the manipulator to flip + const AZ::Vector3 worldStart = expectedColliderPosition - 0.5f * expectedZAxis; + + // position in world space to move to + const AZ::Vector3 worldEnd = worldStart - 2.25f * expectedZAxis; + + const auto screenStart = AzFramework::WorldToScreen(worldStart, m_cameraState); + const auto screenEnd = AzFramework::WorldToScreen(worldEnd, m_cameraState); + + m_actionDispatcher + ->CameraState(m_cameraState) + // move the mouse to the position of the z offset manipulator + ->MousePosition(screenStart) + // drag to move the manipulator + ->MouseLButtonDown() + ->MousePosition(screenEnd) + ->MouseLButtonUp(); + + AZ::Vector3 newColliderOffset = AZ::Vector3::CreateZero(); + PhysX::EditorColliderComponentRequestBus::EventResult( + newColliderOffset, m_idPair, &PhysX::EditorColliderComponentRequests::GetColliderOffset); + + EXPECT_THAT(newColliderOffset, IsCloseTolerance(AZ::Vector3(-2.0f, 3.0f, -2.0f), ManipulatorTolerance)); + } + + TEST_F(PhysXEditorColliderComponentManipulatorFixture, BoxColliderScaleManipulatorsCorrectlyLocatedRelativeToColliderWithNonUniformScale) + { + const AZ::Vector3 boxDimensions(2.0f, 2.0f, 3.0f); + const AZ::Quaternion boxRotation(0.7f, 0.7f, -0.1f, 0.1f); + const AZ::Vector3 boxOffset(0.5f, 1.5f, 2.0f); + SetupCollider(Physics::BoxShapeConfiguration(boxDimensions), boxRotation, boxOffset); + const AZ::Quaternion entityRotation(0.2f, 0.4f, -0.4f, 0.8f); + const AZ::Vector3 entityTranslation(2.0f, -3.0f, -2.0f); + const float uniformScale = 0.5f; + SetupTransform(entityRotation, entityTranslation, uniformScale); + const AZ::Vector3 nonUniformScale(3.0f, 1.5f, 2.5f); + SetupNonUniformScale(nonUniformScale); + EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode::Dimensions); + + // the expected position of the collider centre based on the combination of entity transform, collider offset and non-uniform scale + const AZ::Vector3 expectedColliderPosition(4.37f, -4.285f, -1.1f); + + // the expected position of the y scale manipulator relative to the centre of the collider, based on collider + // rotation, entity rotation and scale, and non-uniform scale + const AZ::Vector3 scaleManipulatorYDelta(0.54f, -0.72f, -1.2f); + + // position the camera to look at the collider along the x-y diagonal + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationZ(-AZ::Constants::QuarterPi), expectedColliderPosition - AZ::Vector3(2.0f, 2.0f, 0.0f))); + + const AZ::Vector3 worldStart = expectedColliderPosition + scaleManipulatorYDelta; + const AZ::Vector3 worldEnd = worldStart + 0.1f * scaleManipulatorYDelta; + + const auto screenStart = AzFramework::WorldToScreen(worldStart, m_cameraState); + const auto screenEnd = AzFramework::WorldToScreen(worldEnd, m_cameraState); + + m_actionDispatcher + ->CameraState(m_cameraState) + // move the mouse to the position of the y scale manipulator + ->MousePosition(screenStart) + // drag to move the manipulator + ->MouseLButtonDown() + ->MousePosition(screenEnd) + ->MouseLButtonUp(); + + AZ::Vector3 newBoxDimensions = AZ::Vector3::CreateZero(); + AzToolsFramework::BoxManipulatorRequestBus::EventResult( + newBoxDimensions, m_idPair, &AzToolsFramework::BoxManipulatorRequests::GetDimensions); + + EXPECT_THAT(newBoxDimensions, IsCloseTolerance(AZ::Vector3(2.0f, 2.2f, 3.0f), ManipulatorTolerance)); + } + + TEST_F(PhysXEditorColliderComponentManipulatorFixture, SphereColliderScaleManipulatorsCorrectlyLocatedRelativeToColliderWithNonUniformScale) + { + const float sphereRadius = 1.0f; + const AZ::Quaternion sphereRotation(-0.1f, 0.7f, -0.7f, 0.1f); + const AZ::Vector3 sphereOffset(-2.0f, 1.0f, -3.0f); + SetupCollider(Physics::SphereShapeConfiguration(sphereRadius), sphereRotation, sphereOffset); + const AZ::Quaternion entityRotation(-0.4f, -0.2f, 0.4f, 0.8f); + const AZ::Vector3 entityTranslation(-1.0f, -3.0f, 3.0f); + const float uniformScale = 1.5f; + SetupTransform(entityRotation, entityTranslation, uniformScale); + const AZ::Vector3 nonUniformScale(1.5f, 0.5f, 2.0f); + SetupNonUniformScale(nonUniformScale); + EnterColliderSubMode(PhysX::ColliderComponentModeRequests::SubMode::Dimensions); + + // the expected position of the collider centre based on the combination of entity transform, collider offset and non-uniform scale + const AZ::Vector3 expectedColliderPosition(1.7f, -10.65f, -3.0f); + + // position the camera to look at the collider along the y-axis + AzFramework::SetCameraTransform( + m_cameraState, AZ::Transform::CreateTranslation(expectedColliderPosition - AZ::Vector3(0.0f, 5.0f, 0.0f))); + + // the expected position of the scale manipulator relative to the centre of the collider, based on collider + // rotation, entity scale, non-uniform scale and camera state + const AZ::Vector3 scaleManipulatorDelta(-1.1952f, -1.8036f, 0.168f); + + const AZ::Vector3 worldStart = expectedColliderPosition + scaleManipulatorDelta; + const AZ::Vector3 worldEnd = worldStart - 0.1f * scaleManipulatorDelta; + + const auto screenStart = AzFramework::WorldToScreen(worldStart, m_cameraState); + const auto screenEnd = AzFramework::WorldToScreen(worldEnd, m_cameraState); + + m_actionDispatcher + ->CameraState(m_cameraState) + // move the mouse to the position of the y scale manipulator + ->MousePosition(screenStart) + // drag to move the manipulator + ->MouseLButtonDown() + ->MousePosition(screenEnd) + ->MouseLButtonUp(); + + float newSphereRadius = 0.0f; + PhysX::EditorColliderComponentRequestBus::EventResult( + newSphereRadius, m_idPair, &PhysX::EditorColliderComponentRequests::GetSphereRadius); + + EXPECT_NEAR(newSphereRadius, 0.9f, ManipulatorTolerance); + } } // namespace UnitTest