/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include namespace UnitTest { class AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture : public ToolsApplicationFixture<> { protected: struct State { State(AZStd::unique_ptr viewportManipulatorInteraction) : m_viewportManipulatorInteraction(viewportManipulatorInteraction.release()) , m_actionDispatcher( AZStd::make_unique(*m_viewportManipulatorInteraction)) , m_linearManipulator(AzManipulatorTestFramework::CreateLinearManipulator( m_viewportManipulatorInteraction->GetManipulatorManager().GetId(), /*position=*/AZ::Vector3(0.0f, 50.0f, 0.0f), /*radius=*/m_boundsRadius)) { // default sanity check call backs m_linearManipulator->InstallLeftMouseDownCallback( [this]([[maybe_unused]] const AzToolsFramework::LinearManipulator::Action& action) { m_receivedLeftMouseDown = true; }); m_linearManipulator->InstallMouseMoveCallback( [this]([[maybe_unused]] const AzToolsFramework::LinearManipulator::Action& action) { m_receivedMouseMove = true; }); m_linearManipulator->InstallLeftMouseUpCallback( [this]([[maybe_unused]] const AzToolsFramework::LinearManipulator::Action& action) { m_receivedLeftMouseUp = true; }); } ~State() = default; // sanity flags for manipulator mouse callbacks bool m_receivedLeftMouseDown = false; bool m_receivedMouseMove = false; bool m_receivedLeftMouseUp = false; private: AZStd::unique_ptr m_viewportManipulatorInteraction; public: AZStd::unique_ptr m_actionDispatcher; AZStd::shared_ptr m_linearManipulator; }; void ConsumeViewportLeftMouseClick(State& state); void ConsumeViewportMouseMoveHover(State& state); void ConsumeViewportMouseMoveActive(State& state); void MoveManipulatorAlongAxis(State& state); protected: void SetUpEditorFixtureImpl() override { m_directState = AZStd::make_unique(AZStd::make_unique( AZStd::make_shared())); m_busState = AZStd::make_unique(AZStd::make_unique( AZStd::make_shared())); m_cameraState = AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize); } void TearDownEditorFixtureImpl() override { m_directState.reset(); m_busState.reset(); } public: AZStd::unique_ptr m_directState; AZStd::unique_ptr m_busState; AzFramework::CameraState m_cameraState; static constexpr float m_boundsRadius = 2.0f; }; void AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture::ConsumeViewportLeftMouseClick(State& state) { // given a left mouse down ray in world space // consume the mouse down and up events state.m_actionDispatcher->CameraState(m_cameraState) ->MousePosition(AzManipulatorTestFramework::GetCameraStateViewportCenter(m_cameraState)) ->MouseLButtonDown() ->Trace("Expecting left mouse button down") ->ExpectTrue(state.m_receivedLeftMouseDown) ->Trace("Not expecting left mouse button up") ->ExpectFalse(state.m_receivedLeftMouseUp) ->Trace("Expecting mouse move") // note: a zero delta mouse move is generated after every mouse up event in the // editor application - the manipulator test framework simulates this event to // ensure the tests are representative of what actually happens in the editor // therefore we do expect m_receivedMouseMove to be true after MouseLButtonDown ->ExpectTrue(state.m_receivedMouseMove) ->ExpectManipulatorBeingInteracted() ->MouseLButtonUp() ->Trace("Expecting left mouse button up") ->ExpectTrue(state.m_receivedLeftMouseDown) ->ExpectTrue(state.m_receivedLeftMouseUp) ->ExpectTrue(state.m_receivedMouseMove) ->ExpectFalse(state.m_linearManipulator->PerformingAction()) ->ExpectManipulatorNotBeingInteracted(); } void AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture::ConsumeViewportMouseMoveHover(State& state) { // given a left mouse down ray in world space // consume the mouse move event state.m_actionDispatcher->CameraState(m_cameraState) ->MousePosition(AzManipulatorTestFramework::GetCameraStateViewportCenter(m_cameraState)) ->ExpectFalse(state.m_linearManipulator->PerformingAction()) ->ExpectManipulatorNotBeingInteracted() ->ExpectFalse(state.m_receivedLeftMouseDown) ->ExpectFalse(state.m_receivedMouseMove) ->ExpectFalse(state.m_receivedLeftMouseUp); } void AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture::ConsumeViewportMouseMoveActive(State& state) { // given a left mouse down ray in world space // consume the mouse move event state.m_actionDispatcher->CameraState(m_cameraState) ->MousePosition(AzManipulatorTestFramework::GetCameraStateViewportCenter(m_cameraState)) ->MouseLButtonDown() ->ExpectTrue(state.m_linearManipulator->PerformingAction()) ->ExpectManipulatorBeingInteracted() ->MouseLButtonUp() ->ExpectTrue(state.m_receivedLeftMouseDown) ->ExpectTrue(state.m_receivedMouseMove) ->ExpectTrue(state.m_receivedLeftMouseUp); } void AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture::MoveManipulatorAlongAxis(State& state) { // the initial starting position of the manipulator (in front of the camera) const auto initialPositionWorld = state.m_linearManipulator->GetLocalPosition(); // where the manipulator should end up (in front and to the left of the camera) const auto finalPositionWorld = AZ::Vector3(-10.0f, 50.0f, 0.0f); // perspective scale factor for manipulator distance to camera const auto scaledRadiusBound = AzToolsFramework::CalculateScreenToWorldMultiplier(initialPositionWorld, m_cameraState) * m_boundsRadius; // vector from camera to manipulator const auto vectorToInitialPositionWorld = (initialPositionWorld - m_cameraState.m_position).GetNormalized(); // adjusted final world position taking into account the manipulator position relative to the camera const auto finalPositionWorldAdjusted = finalPositionWorld - (vectorToInitialPositionWorld * scaledRadiusBound); // calculate the position in screen space of the initial position of the manipulator const auto initialPositionScreen = AzFramework::WorldToScreen(initialPositionWorld, m_cameraState); // calculate the position in screen space of the final position of the manipulator const auto finalPositionScreen = AzFramework::WorldToScreen(finalPositionWorldAdjusted, m_cameraState); AZ::Vector3 movementAlongAxis = AZ::Vector3::CreateZero(); state.m_linearManipulator->InstallMouseMoveCallback( [&movementAlongAxis](const AzToolsFramework::LinearManipulator::Action& action) { movementAlongAxis = action.LocalPosition(); }); state.m_actionDispatcher->CameraState(m_cameraState) ->MousePosition(initialPositionScreen) ->MouseLButtonDown() ->ExpectTrue(state.m_linearManipulator->PerformingAction()) ->ExpectManipulatorBeingInteracted() ->MousePosition(finalPositionScreen) ->MouseLButtonUp() ->ExpectTrue(state.m_receivedLeftMouseDown) ->ExpectTrue(state.m_receivedLeftMouseUp) ->ExpectTrue(movementAlongAxis.IsClose(finalPositionWorld, 0.01f)); } TEST_F(AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture, ConsumeViewportLeftMouseClick) { ConsumeViewportLeftMouseClick(*m_directState); ConsumeViewportLeftMouseClick(*m_busState); } TEST_F(AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture, ConsumeViewportMouseMoveHover) { ConsumeViewportMouseMoveHover(*m_directState); ConsumeViewportMouseMoveHover(*m_busState); } TEST_F(AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture, ConsumeViewportMouseMoveActive) { ConsumeViewportMouseMoveActive(*m_directState); ConsumeViewportMouseMoveActive(*m_busState); } TEST_F(AzManipulatorTestFrameworkWorldSpaceBuilderTestFixture, MoveManipulatorAlongAxis) { MoveManipulatorAlongAxis(*m_directState); MoveManipulatorAlongAxis(*m_busState); } } // namespace UnitTest