/* * 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 "AzManipulatorTestFrameworkTestFixtures.h" #include #include #include #include #include #include #include #include #include #include #include namespace UnitTest { class GridSnappingFixture : public ToolsApplicationFixture<> { public: GridSnappingFixture() : m_viewportManipulatorInteraction(AZStd::make_unique( AZStd::make_shared())) , m_actionDispatcher( AZStd::make_unique(*m_viewportManipulatorInteraction)) { } protected: void SetUpEditorFixtureImpl() override { m_cameraState = AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize); } public: const float m_boundsRadius = 1.0f; AZStd::unique_ptr m_viewportManipulatorInteraction; AZStd::unique_ptr m_actionDispatcher; AzFramework::CameraState m_cameraState; }; TEST_F(GridSnappingFixture, MouseDownWithSnappingEnabledSnapsToClosestGridSize) { AZStd::shared_ptr linearManipulator(AzManipulatorTestFramework::CreateLinearManipulator( m_viewportManipulatorInteraction->GetManipulatorManager().GetId(), /*position=*/AZ::Vector3(0.0f, 50.0f, 0.0f), /*radius=*/m_boundsRadius)); // the initial starting position of the manipulator (in front of the camera) const auto initialPositionWorld = 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); // callback to update the manipulator's current position linearManipulator->InstallMouseMoveCallback( [linearManipulator = linearManipulator.get()](const AzToolsFramework::LinearManipulator::Action& action) { linearManipulator->SetLocalPosition(action.LocalPosition()); }); m_actionDispatcher->SetSnapToGrid(true) ->GridSize(5.0f) ->CameraState(m_cameraState) ->MousePosition(initialPositionScreen) ->MouseLButtonDown() ->ExpectManipulatorBeingInteracted() ->MousePosition(finalPositionScreen) ->MouseLButtonUp() ->ExpectManipulatorNotBeingInteracted() ->ExpectTrue(linearManipulator->GetLocalPosition().IsClose(finalPositionWorld, 0.01f)); } template void ValidateManipulatorSnappingBehavior( AZStd::shared_ptr manipulator, AzManipulatorTestFramework::ImmediateModeActionDispatcher* actionDispatcher, const AzFramework::CameraState& cameraState) { manipulator->SetLocalOrientation(AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(180.0f, 0.0f, 135.0f))); // the initial starting position of the manipulator (in front of the camera) const auto initialPositionWorld = manipulator->GetLocalPosition() + AZ::Vector3::CreateAxisX(0.15f); // where the manipulator should end up (unmoved) const auto finalPositionWorld = manipulator->GetLocalPosition(); // where we should move the mouse to const auto attemptPositionWorld = manipulator->GetLocalPosition() + AZ::Vector3::CreateAxisX(0.35f); // calculate the position in screen space of the initial position of the manipulator const auto initialPositionScreen = AzFramework::WorldToScreen(initialPositionWorld, cameraState); // calculate the position in screen space of the final position of the manipulator const auto attemptPositionScreen = AzFramework::WorldToScreen(attemptPositionWorld, cameraState); // callback to update the manipulator's current position manipulator->InstallMouseMoveCallback( [manipulator = manipulator.get()](const typename Manipulator::Action& action) { manipulator->SetLocalPosition(action.LocalPosition()); }); actionDispatcher->SetSnapToGrid(true) ->GridSize(1.0f) ->CameraState(cameraState) ->MousePosition(initialPositionScreen) ->MouseLButtonDown() ->ExpectManipulatorBeingInteracted() ->MousePosition(attemptPositionScreen) ->MouseLButtonUp() ->ExpectManipulatorNotBeingInteracted() ->ExpectThat(manipulator->GetLocalPosition(), IsCloseTolerance(finalPositionWorld, 0.01f)); } TEST_F(GridSnappingFixture, MouseDownAndMoveLinearManipulatorDoesNotSnapWithMovementSmallerThanHalfGridSize) { AZStd::shared_ptr linearManipulator(AzManipulatorTestFramework::CreateLinearManipulator( m_viewportManipulatorInteraction->GetManipulatorManager().GetId(), /*position=*/AZ::Vector3(0.0f, 10.0f, 0.0f), /*radius=*/m_boundsRadius)); linearManipulator->SetAxis(AZ::Vector3::CreateAxisY()); ValidateManipulatorSnappingBehavior(linearManipulator, m_actionDispatcher.get(), m_cameraState); } TEST_F(GridSnappingFixture, MouseDownAndMovePlanarManipulatorDoesNotSnapWithMovementSmallerThanHalfGridSize) { AZStd::shared_ptr planarManipulator(AzManipulatorTestFramework::CreatePlanarManipulator( m_viewportManipulatorInteraction->GetManipulatorManager().GetId(), /*position=*/AZ::Vector3(0.0f, 10.0f, 0.0f), /*radius=*/m_boundsRadius)); planarManipulator->SetAxes(AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); ValidateManipulatorSnappingBehavior(planarManipulator, m_actionDispatcher.get(), m_cameraState); } } // namespace UnitTest