From 6908a3e94555c4e190c9fc0a598e5afefcc32ff4 Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Tue, 30 Nov 2021 15:07:40 +0000 Subject: [PATCH] Update intersect behavior for positioning entities in the viewport (#5906) * update intersect behavior for positioning entities in the viewport and restore SurfaceManipulator Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * tests for surface manipulator from EditorTransformComponentSelection Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * update comments in tests and remove #pragma optimize('', off)@ Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * add new file for FindClosestPickIntersection tests Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * add remaining tests for surface manipulator and snap fixes Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * some small updates and polish before PR Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * small updates following PR feedback Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> --- Code/Editor/EditorViewportWidget.cpp | 26 +- Code/Editor/EditorViewportWidget.h | 5 - .../SandboxIntegration.cpp | 2 +- Code/Editor/Viewport.cpp | 20 +- Code/Editor/Viewport.h | 3 - .../ActionDispatcher.h | 22 ++ .../AzManipulatorTestFramework.h | 3 +- .../DirectManipulatorViewportInteraction.h | 4 +- .../ImmediateModeActionDispatcher.h | 8 +- .../ViewportInteraction.h | 5 +- .../Source/ImmediateModeActionDispatcher.cpp | 48 ++-- .../Source/ViewportInteraction.cpp | 6 +- .../Manipulators/EditorVertexSelection.cpp | 24 +- .../Manipulators/EditorVertexSelection.h | 2 +- .../Manipulators/ManipulatorSnapping.cpp | 24 -- .../Manipulators/ManipulatorSnapping.h | 5 - .../Manipulators/SurfaceManipulator.cpp | 104 ++++--- .../Manipulators/SurfaceManipulator.h | 6 + .../Viewport/ViewportMessages.cpp | 42 ++- .../Viewport/ViewportMessages.h | 41 ++- .../ViewportSelection/EditorSelectionUtil.cpp | 14 + .../ViewportSelection/EditorSelectionUtil.h | 3 + .../EditorTransformComponentSelection.cpp | 96 ++++--- .../EditorTransformComponentSelection.h | 2 +- .../Tests/BoundsTestComponent.cpp | 49 ++++ .../Tests/BoundsTestComponent.h | 18 ++ ...EditorTransformComponentSelectionTests.cpp | 174 +++++++++++- .../Tests/EditorVertexSelectionTests.cpp | 253 +++++++++++++++--- .../Viewport/ViewportInteractionTests.cpp | 76 ++++++ .../Tests/aztoolsframeworktests_files.cmake | 1 + .../Viewport/ViewportInteractionImpl.cpp | 5 +- 31 files changed, 811 insertions(+), 280 deletions(-) create mode 100644 Code/Framework/AzToolsFramework/Tests/Viewport/ViewportInteractionTests.cpp diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 60e4bf6d47..7323da9e95 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -472,7 +472,7 @@ void EditorViewportWidget::Update() { auto start = std::chrono::steady_clock::now(); - m_entityVisibilityQuery.UpdateVisibility(GetCameraState()); + m_entityVisibilityQuery.UpdateVisibility(m_renderViewport->GetCameraState()); if (ed_visibility_logTiming) { @@ -714,7 +714,7 @@ void EditorViewportWidget::RenderAll() m_debugDisplay->DepthTestOff(); m_manipulatorManager->DrawManipulators( - *m_debugDisplay, GetCameraState(), + *m_debugDisplay, m_renderViewport->GetCameraState(), BuildMouseInteractionInternal( AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers, BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos()))))); @@ -878,31 +878,11 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera() } } -AzFramework::CameraState EditorViewportWidget::GetCameraState() -{ - return m_renderViewport->GetCameraState(); -} - -AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& point) -{ - return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true)); -} - -float EditorViewportWidget::TerrainHeight(const AZ::Vector2& position) -{ - return GetIEditor()->GetTerrainElevation(position.GetX(), position.GetY()); -} - void EditorViewportWidget::FindVisibleEntities(AZStd::vector& visibleEntitiesOut) { visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End()); } -AzFramework::ScreenPoint EditorViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition) -{ - return m_renderViewport->ViewportWorldToScreen(worldPosition); -} - QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu() { return this; @@ -2132,7 +2112,7 @@ bool EditorViewportWidget::GetActiveCameraState(AzFramework::CameraState& camera { if (m_pPrimaryViewport == this) { - cameraState = GetCameraState(); + cameraState = m_renderViewport->GetCameraState(); return true; } diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 0101f2ffef..3d52b2416d 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -205,8 +205,6 @@ private: void* GetSystemCursorConstraintWindow() const override; // AzToolsFramework::MainEditorViewportInteractionRequestBus overrides ... - AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) override; - float TerrainHeight(const AZ::Vector2& position) override; bool ShowingWorldSpace() override; QWidget* GetWidgetForViewportContextMenu() override; @@ -293,9 +291,6 @@ private: // This switches the active camera to the next one in the list of (default, all custom cams). void CycleCamera(); - AzFramework::CameraState GetCameraState(); - AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition); - QPoint WidgetToViewport(const QPoint& point) const; QPoint ViewportToWidget(const QPoint& point) const; QSize WidgetToViewport(const QSize& size) const; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 5a1b4dc503..9342c17a37 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -1396,7 +1396,7 @@ void SandboxIntegrationManager::ContextMenu_NewEntity() { worldPosition = AzToolsFramework::FindClosestPickIntersection( view->GetViewportId(), AzFramework::ScreenPointFromVector2(m_contextMenuViewPoint), AzToolsFramework::EditorPickRayLength, - GetDefaultEntityPlacementDistance()); + AzToolsFramework::GetDefaultEntityPlacementDistance()); } CreateNewEntityAtPosition(worldPosition); diff --git a/Code/Editor/Viewport.cpp b/Code/Editor/Viewport.cpp index 613dc0c4e7..437dcead3b 100644 --- a/Code/Editor/Viewport.cpp +++ b/Code/Editor/Viewport.cpp @@ -14,14 +14,11 @@ // Qt #include -// AzCore -#include - // AzQtComponents #include +// AzToolsFramework #include -#include #include #include @@ -41,19 +38,6 @@ #undef LoadCursor #endif -AZ_CVAR( - float, - ed_defaultEntityPlacementDistance, - 10.0f, - nullptr, - AZ::ConsoleFunctorFlags::Null, - "The default distance to place an entity from the camera if no intersection is found"); - -float GetDefaultEntityPlacementDistance() -{ - return ed_defaultEntityPlacementDistance; -} - ////////////////////////////////////////////////////////////////////// // Viewport drag and drop support ////////////////////////////////////////////////////////////////////// @@ -63,7 +47,7 @@ void QtViewport::BuildDragDropContext( { context.m_hitLocation = AzToolsFramework::FindClosestPickIntersection( viewportId, AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(point), AzToolsFramework::EditorPickRayLength, - GetDefaultEntityPlacementDistance()); + AzToolsFramework::GetDefaultEntityPlacementDistance()); } void QtViewport::dragEnterEvent(QDragEnterEvent* event) diff --git a/Code/Editor/Viewport.h b/Code/Editor/Viewport.h index b8c55c7431..a71df6a9cd 100644 --- a/Code/Editor/Viewport.h +++ b/Code/Editor/Viewport.h @@ -87,9 +87,6 @@ enum EStdCursor STD_CURSOR_LAST, }; -//! The default distance an entity is placed from the camera if there is no intersection -SANDBOX_API float GetDefaultEntityPlacementDistance(); - AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING class SANDBOX_API CViewport : public IDisplayViewport diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h index 06db67a05f..99f11c37da 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h @@ -39,6 +39,10 @@ namespace AzManipulatorTestFramework DerivedDispatcherT* MouseLButtonDown(); //! Set the left mouse button up. DerivedDispatcherT* MouseLButtonUp(); + //! Set the middle mouse button down. + DerivedDispatcherT* MouseMButtonDown(); + //! Set the middle mouse button up. + DerivedDispatcherT* MouseMButtonUp(); //! Send a double click event. DerivedDispatcherT* MouseLButtonDoubleClick(); //! Set the keyboard modifier button down. @@ -73,6 +77,8 @@ namespace AzManipulatorTestFramework virtual void CameraStateImpl(const AzFramework::CameraState& cameraState) = 0; virtual void MouseLButtonDownImpl() = 0; virtual void MouseLButtonUpImpl() = 0; + virtual void MouseMButtonDownImpl() = 0; + virtual void MouseMButtonUpImpl() = 0; virtual void MouseLButtonDoubleClickImpl() = 0; virtual void MousePositionImpl(const AzFramework::ScreenPoint& position) = 0; virtual void KeyboardModifierDownImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) = 0; @@ -183,6 +189,22 @@ namespace AzManipulatorTestFramework return static_cast(this); } + template + DerivedDispatcherT* ActionDispatcher::MouseMButtonDown() + { + Log("Mouse middle button down"); + MouseMButtonDownImpl(); + return static_cast(this); + } + + template + DerivedDispatcherT* ActionDispatcher::MouseMButtonUp() + { + Log("Mouse middle button up"); + MouseMButtonUpImpl(); + return static_cast(this); + } + template DerivedDispatcherT* ActionDispatcher::MouseLButtonDoubleClick() { diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h index 5f0179d6ad..66227c692b 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -40,7 +41,7 @@ namespace AzManipulatorTestFramework //! Set the angular step. virtual void SetAngularStep(float step) = 0; //! Get the viewport id. - virtual int GetViewportId() const = 0; + virtual AzFramework::ViewportId GetViewportId() const = 0; //! Updates the visibility state. //! Updates which entities are currently visible given the current camera state. virtual void UpdateVisibility() = 0; diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h index 3421116b96..7cef7b635d 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h @@ -30,7 +30,7 @@ namespace AzManipulatorTestFramework private: AZStd::shared_ptr m_customManager; - std::unique_ptr m_viewportInteraction; - std::unique_ptr m_manipulatorManager; + AZStd::unique_ptr m_viewportInteraction; + AZStd::unique_ptr m_manipulatorManager; }; } // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h index 5d1f606e8c..8a05a15fd9 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h @@ -25,7 +25,7 @@ namespace AzManipulatorTestFramework using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent; public: - explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction); + explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction); ~ImmediateModeActionDispatcher(); //! Clear the current event state. @@ -55,13 +55,15 @@ namespace AzManipulatorTestFramework AZStd::chrono::milliseconds EditorViewportInputTimeNow() override; protected: - // ActionDispatcher ... + // ActionDispatcher overrides ... void SetSnapToGridImpl(bool enabled) override; void SetStickySelectImpl(bool enabled) override; void GridSizeImpl(float size) override; void CameraStateImpl(const AzFramework::CameraState& cameraState) override; void MouseLButtonDownImpl() override; void MouseLButtonUpImpl() override; + void MouseMButtonDownImpl() override; + void MouseMButtonUpImpl() override; void MouseLButtonDoubleClickImpl() override; void MousePositionImpl(const AzFramework::ScreenPoint& position) override; void KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) override; @@ -82,7 +84,7 @@ namespace AzManipulatorTestFramework const MouseInteractionEvent* GetMouseInteractionEvent() const; mutable AZStd::unique_ptr m_event; - ManipulatorViewportInteraction& m_viewportManipulatorInteraction; + ManipulatorViewportInteraction& m_manipulatorViewportInteraction; //! Current time that ticks up after each call to EditorViewportInputTimeNow. AZStd::chrono::milliseconds m_timeNow = AZStd::chrono::milliseconds(0); diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h index 3c41b28851..a353140d4c 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h @@ -33,7 +33,7 @@ namespace AzManipulatorTestFramework void SetAngularSnapping(bool enabled) override; void SetGridSize(float size) override; void SetAngularStep(float step) override; - int GetViewportId() const override; + AzFramework::ViewportId GetViewportId() const override; void UpdateVisibility() override; void SetStickySelect(bool enabled) override; AZ::Vector3 DefaultEditorCameraPosition() const override; @@ -60,9 +60,10 @@ namespace AzManipulatorTestFramework void FindVisibleEntities(AZStd::vector& visibleEntities) override; private: + static constexpr AzFramework::ViewportId m_viewportId = 1234; //!< Arbitrary viewport id for manipulator tests. + AzFramework::EntityVisibilityQuery m_entityVisibilityQuery; AZStd::unique_ptr m_nullDebugDisplayRequests; - const int m_viewportId = 1234; // Arbitrary viewport id for manipulator tests AzFramework::CameraState m_cameraState; bool m_gridSnapping = false; bool m_angularSnapping = false; diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp index 5bfe63bd99..aea93ca342 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp @@ -29,8 +29,8 @@ namespace AzManipulatorTestFramework using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier; using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent; - ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction) - : m_viewportManipulatorInteraction(viewportManipulatorInteraction) + ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction) + : m_manipulatorViewportInteraction(manipulatorViewportInteraction) { AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect(); AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect(); @@ -48,34 +48,34 @@ namespace AzManipulatorTestFramework // mouse down and mouse up event, to match the editor behavior we insert this event // to ensure the tests are simulating the same environment as the editor GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); } void ImmediateModeActionDispatcher::SetSnapToGridImpl(const bool enabled) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSnapping(enabled); + m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSnapping(enabled); } void ImmediateModeActionDispatcher::SetStickySelectImpl(const bool enabled) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetStickySelect(enabled); + m_manipulatorViewportInteraction.GetViewportInteraction().SetStickySelect(enabled); } void ImmediateModeActionDispatcher::GridSizeImpl(const float size) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSize(size); + m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSize(size); } void ImmediateModeActionDispatcher::CameraStateImpl(const AzFramework::CameraState& cameraState) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetCameraState(cameraState); + m_manipulatorViewportInteraction.GetViewportInteraction().SetCameraState(cameraState); } void ImmediateModeActionDispatcher::MouseLButtonDownImpl() { ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); } @@ -83,17 +83,35 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::MouseLButtonUpImpl() { GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); } + void ImmediateModeActionDispatcher::MouseMButtonDownImpl() + { + ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle); + GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down; + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + // the mouse position will be the same as the previous event, thus the delta will be 0 + MouseMoveAfterButton(); + } + + void ImmediateModeActionDispatcher::MouseMButtonUpImpl() + { + GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up; + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle); + // the mouse position will be the same as the previous event, thus the delta will be 0 + MouseMoveAfterButton(); + } + void ImmediateModeActionDispatcher::MouseLButtonDoubleClickImpl() { GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::DoubleClick; ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); @@ -101,10 +119,10 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position) { - const auto cameraState = m_viewportManipulatorInteraction.GetViewportInteraction().GetCameraState(); + const auto cameraState = m_manipulatorViewportInteraction.GetViewportInteraction().GetCameraState(); GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick = BuildMousePick(position, cameraState); GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); } void ImmediateModeActionDispatcher::KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) @@ -144,7 +162,7 @@ namespace AzManipulatorTestFramework { m_event = AZStd::unique_ptr(AZStd::make_unique()); m_event->m_mouseInteraction.m_interactionId.m_viewportId = - m_viewportManipulatorInteraction.GetViewportInteraction().GetViewportId(); + m_manipulatorViewportInteraction.GetViewportInteraction().GetViewportId(); } return m_event.get(); @@ -178,12 +196,12 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::ExpectManipulatorBeingInteractedImpl() { - EXPECT_TRUE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); + EXPECT_TRUE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); } void ImmediateModeActionDispatcher::ExpectManipulatorNotBeingInteractedImpl() { - EXPECT_FALSE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); + EXPECT_FALSE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); } ImmediateModeActionDispatcher* ImmediateModeActionDispatcher::ResetEvent() diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp index 08cae0c738..e56f7d6d72 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp @@ -135,20 +135,20 @@ namespace AzManipulatorTestFramework m_angularStep = step; } - int ViewportInteraction::GetViewportId() const + AzFramework::ViewportId ViewportInteraction::GetViewportId() const { return m_viewportId; } AZ::Vector3 ViewportInteraction::ViewportScreenToWorld([[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { - return AZ::Vector3::CreateZero(); + return AzFramework::ScreenToWorld(screenPosition, m_cameraState); } AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteraction::ViewportScreenToWorldRay( [[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { - return {}; + return AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, screenPosition); } float ViewportInteraction::DeviceScalingFactor() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.cpp index 00f5b1ac47..0c3638412c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.cpp @@ -84,12 +84,12 @@ namespace AzToolsFramework break; case State::Translating: { - if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() && + if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() && mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down && mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() && mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl()) { - SnapVerticesToTerrain(mouseInteraction); + SnapVerticesToSurface(mouseInteraction); return true; } @@ -109,29 +109,23 @@ namespace AzToolsFramework } template - void EditorVertexSelectionBase::SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) + void EditorVertexSelectionBase::SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) { ScopedUndoBatch surfaceSnapUndo("Snap to Surface"); ScopedUndoBatch::MarkEntityDirty(GetEntityId()); const int viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId; - // get unsnapped terrain position (world space) - AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero(); - ; - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain, - mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates); + // get unsnapped surface position (world space) + const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection( + viewportId, mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, EditorPickRayLength, + GetDefaultEntityPlacementDistance()); AZ::Transform worldFromLocal; AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); const AZ::Transform localFromWorld = worldFromLocal.GetInverse(); - // convert to local space - snap if enabled - const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId); - const AZ::Vector3 localFinalSurfacePosition = gridSnapParams.m_gridSnap - ? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocal, viewportId, gridSnapParams.m_gridSize) - : localFromWorld.TransformPoint(worldSurfacePosition); - + // convert to local space + const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition); SetSelectedPosition(localFinalSurfacePosition); OnEntityComponentPropertyChanged(GetEntityComponentIdPair()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.h index 4894a00df6..7e5523f62a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/EditorVertexSelection.h @@ -171,7 +171,7 @@ namespace AzToolsFramework //! Snap the selected vertices to the terrain. //! Note: With a multi-selection the manipulator will be translated to the picked //! terrain position with all vertices moved relative to it. - void SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction); + void SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction); //! The Actions provided by the EditorVertexSelection while it is active. //! e.g. Vertex deletion, duplication etc. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.cpp index e4f6b3b1ea..5bea383630 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.cpp @@ -110,30 +110,6 @@ namespace AzToolsFramework return unsnappedPosition + CalculateSnappedOffset(unsnappedPosition, snapAxes, snapAxesCount, size); } - AZ::Vector3 CalculateSnappedTerrainPosition( - const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, const int viewportId, const float size) - { - const AZ::Transform localFromWorld = worldFromLocal.GetInverse(); - const AZ::Vector3 localSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition); - - // snap in xy plane - AZ::Vector3 localSnappedSurfacePosition = localSurfacePosition + - CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisX(), size) + - CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisY(), size); - - // find terrain height at xy snapped location - float terrainHeight = 0.0f; - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - terrainHeight, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::TerrainHeight, - Vector3ToVector2(worldFromLocal.TransformPoint(localSnappedSurfacePosition))); - - // set snapped z value to terrain height - AZ::Vector3 localTerrainHeight = localFromWorld.TransformPoint(AZ::Vector3(0.0f, 0.0f, terrainHeight)); - localSnappedSurfacePosition.SetZ(localTerrainHeight.GetZ()); - - return localSnappedSurfacePosition; - } - bool GridSnapping(const int viewportId) { bool snapping = false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.h index 660d38ac89..96f49d5276 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSnapping.h @@ -63,11 +63,6 @@ namespace AzToolsFramework AZ::Vector3 CalculateSnappedPosition( const AZ::Vector3& unsnappedPosition, const AZ::Vector3* snapAxes, size_t snapAxesCount, float size); - //! For a given point on the terrain, calculate the closest xy position snapped to the grid - //! (z position is aligned to terrain height, not snapped to z grid) - AZ::Vector3 CalculateSnappedTerrainPosition( - const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, int viewportId, float size); - //! Wrapper for grid snapping and grid size bus calls. GridSnapParameters GridSnapSettings(int viewportId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.cpp index dd565eb0a8..01b8635406 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace AzToolsFramework { @@ -17,28 +18,16 @@ namespace AzToolsFramework const AZ::Transform& worldFromLocal, const AZ::Vector3& worldSurfacePosition, const AZ::Vector3& localStartPosition, - const bool snapping, - const float gridSize, - const int viewportId) + [[maybe_unused]] const bool snapping, + [[maybe_unused]] const float gridSize, + [[maybe_unused]] const int viewportId) { - const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal); - const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse(); - - const AZ::Vector3 localFinalSurfacePosition = snapping - // note: gridSize is not scaled by scaleRecip here as localStartPosition is - // unscaled itself so the position returned by CalculateSnappedTerrainPosition - // must be in the same space (if localStartPosition were also scaled, gridSize - // would need to be multiplied by scaleRecip) - ? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize) - : localFromWorldUniform.TransformPoint(worldSurfacePosition); - - // delta/offset between initial vertex position and terrain pick position - const AZ::Vector3 localSurfaceOffset = localFinalSurfacePosition - localStartPosition; + const AZ::Transform localFromWorld = worldFromLocal.GetInverse(); StartInternal startInternal; - startInternal.m_snapOffset = localSurfaceOffset; - startInternal.m_localPosition = localStartPosition + localSurfaceOffset; - startInternal.m_localHitPosition = localFromWorldUniform.TransformVector(worldSurfacePosition); + startInternal.m_snapOffset = AZ::Vector3::CreateZero(); + startInternal.m_localPosition = localStartPosition; + startInternal.m_localHitPosition = localFromWorld.TransformPoint(worldSurfacePosition); return startInternal; } @@ -46,26 +35,19 @@ namespace AzToolsFramework const StartInternal& startInternal, const AZ::Transform& worldFromLocal, const AZ::Vector3& worldSurfacePosition, - const bool snapping, - const float gridSize, + [[maybe_unused]] const bool snapping, + [[maybe_unused]] const float gridSize, const ViewportInteraction::KeyboardModifiers keyboardModifiers, - const int viewportId) + [[maybe_unused]] const int viewportId) { - const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal); - const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse(); - - const float scaleRecip = ScaleReciprocal(worldFromLocalUniform); - - const AZ::Vector3 localFinalSurfacePosition = snapping - ? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize * scaleRecip) - : localFromWorldUniform.TransformPoint(worldSurfacePosition); + const AZ::Transform localFromWorld = worldFromLocal.GetInverse(); + const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition); Action action; action.m_start.m_localPosition = startInternal.m_localPosition; - action.m_start.m_snapOffset = startInternal.m_snapOffset; + action.m_start.m_snapOffset = AZ::Vector3::CreateZero(); action.m_current.m_localOffset = localFinalSurfacePosition - startInternal.m_localPosition; - // record what modifier keys are held during this action - action.m_modifiers = keyboardModifiers; + action.m_modifiers = keyboardModifiers; // record what modifier keys are held during this action return action; } @@ -78,12 +60,16 @@ namespace AzToolsFramework { SetSpace(worldFromLocal); AttachLeftMouseDownImpl(); + + // only cast rays against objects (entities/meshes etc.) we can actually see + m_rayRequest.m_onlyVisible = true; } void SurfaceManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback) { m_onLeftMouseDownCallback = onMouseDownCallback; } + void SurfaceManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback) { m_onLeftMouseUpCallback = onMouseUpCallback; @@ -95,17 +81,30 @@ namespace AzToolsFramework } void SurfaceManipulator::OnLeftMouseDownImpl( - const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/) + const ViewportInteraction::MouseInteraction& interaction, [[maybe_unused]] float rayIntersectionDistance) { const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace()); - const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId); + const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId; - AZ::Vector3 worldSurfacePosition; - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - worldSurfacePosition, interaction.m_interactionId.m_viewportId, - &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain, - interaction.m_mousePick.m_screenCoordinates); + const auto& entityComponentIdPairs = EntityComponentIdPairs(); + m_rayRequest.m_entityFilter.m_ignoreEntities.clear(); + m_rayRequest.m_entityFilter.m_ignoreEntities.reserve(entityComponentIdPairs.size()); + AZStd::transform( + entityComponentIdPairs.begin(), entityComponentIdPairs.end(), + AZStd::inserter(m_rayRequest.m_entityFilter.m_ignoreEntities, m_rayRequest.m_entityFilter.m_ignoreEntities.begin()), + [](const AZ::EntityComponentIdPair& entityComponentIdPair) + { + return entityComponentIdPair.GetEntityId(); + }); + + // calculate the start and end of the ray + RefreshRayRequest( + m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates), + EditorPickRayLength); + + const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId); + const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance()); m_startInternal = CalculateManipulationDataStart( worldFromLocalUniformScale, worldSurfacePosition, GetLocalPosition(), gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize, @@ -123,17 +122,14 @@ namespace AzToolsFramework { if (m_onLeftMouseUpCallback) { - AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero(); - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - worldSurfacePosition, interaction.m_interactionId.m_viewportId, - &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain, - interaction.m_mousePick.m_screenCoordinates); + const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId; - const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId); + const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId); + const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance()); m_onLeftMouseUpCallback(CalculateManipulationDataAction( m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap, - gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId)); + gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, viewportId)); } } @@ -141,13 +137,15 @@ namespace AzToolsFramework { if (m_onMouseMoveCallback) { - AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero(); - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - worldSurfacePosition, interaction.m_interactionId.m_viewportId, - &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain, - interaction.m_mousePick.m_screenCoordinates); + const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId; - const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId); + // update the start and end of the ray + RefreshRayRequest( + m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates), + EditorPickRayLength); + + const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId); + const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance()); m_onMouseMoveCallback(CalculateManipulationDataAction( m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.h index c9121c49cc..091a71d8da 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/SurfaceManipulator.h @@ -11,6 +11,7 @@ #include "BaseManipulator.h" #include +#include #include namespace AzToolsFramework @@ -58,10 +59,12 @@ namespace AzToolsFramework Start m_start; Current m_current; ViewportInteraction::KeyboardModifiers m_modifiers; + AZ::Vector3 LocalPosition() const { return m_start.m_localPosition + m_current.m_localOffset; } + AZ::Vector3 LocalPositionOffset() const { return m_current.m_localOffset; @@ -106,6 +109,9 @@ namespace AzToolsFramework MouseActionCallback m_onLeftMouseUpCallback = nullptr; MouseActionCallback m_onMouseMoveCallback = nullptr; + //! Cached ray request initialized at mouse down and updated during mouse move. + AzFramework::RenderGeometry::RayRequest m_rayRequest; + static StartInternal CalculateManipulationDataStart( const AZ::Transform& worldFromLocal, const AZ::Vector3& worldSurfacePosition, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp index 8f8bd3e3f9..df96836bde 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp @@ -64,23 +64,12 @@ namespace AzToolsFramework return circleBoundWidth; } - AZ::Vector3 FindClosestPickIntersection( - AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, const float rayLength, const float defaultDistance) + AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, const float defaultDistance) { - using AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus; - AzToolsFramework::ViewportInteraction::ProjectedViewportRay viewportRay{}; - ViewportInteractionRequestBus::EventResult( - viewportRay, viewportId, &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint); - - AzFramework::RenderGeometry::RayRequest ray; - ray.m_startWorldPosition = viewportRay.origin; - ray.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength; - ray.m_onlyVisible = true; - AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult; AzFramework::RenderGeometry::IntersectorBus::EventResult( renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(), - &AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray); + &AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, rayRequest); // attempt a ray intersection with any visible mesh and return the intersection position if successful if (renderGeometryIntersectionResult) @@ -89,7 +78,32 @@ namespace AzToolsFramework } else { - return viewportRay.origin + viewportRay.direction * defaultDistance; + const AZ::Vector3 rayDirection = (rayRequest.m_endWorldPosition - rayRequest.m_startWorldPosition).GetNormalized(); + return rayRequest.m_startWorldPosition + rayDirection * defaultDistance; } } + + void RefreshRayRequest( + AzFramework::RenderGeometry::RayRequest& rayRequest, + const ViewportInteraction::ProjectedViewportRay& viewportRay, + const float rayLength) + { + AZ_Assert(rayLength > 0.0f, "Invalid ray length passed to RefreshRayRequest"); + rayRequest.m_startWorldPosition = viewportRay.origin; + rayRequest.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength; + } + + AZ::Vector3 FindClosestPickIntersection( + const AzFramework::ViewportId viewportId, + const AzFramework::ScreenPoint& screenPoint, + const float rayLength, + const float defaultDistance) + { + AzFramework::RenderGeometry::RayRequest ray; + ray.m_onlyVisible = true; // only consider visible objects + + RefreshRayRequest(ray, ViewportInteraction::ViewportScreenToWorldRay(viewportId, screenPoint), rayLength); + + return FindClosestPickIntersection(ray, defaultDistance); + } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index aa97eb361e..aec2d3fbca 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -15,13 +15,19 @@ #include #include #include +#include #include #include namespace AzFramework { struct ScreenPoint; -} + + namespace RenderGeometry + { + struct RayRequest; + } +} // namespace AzFramework namespace AzToolsFramework { @@ -177,6 +183,26 @@ namespace AzToolsFramework //! Type to inherit to implement ViewportInteractionRequests. using ViewportInteractionRequestBus = AZ::EBus; + //! Utility function to return a viewport ray. + inline ProjectedViewportRay ViewportScreenToWorldRay( + const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint) + { + const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPoint, cameraState); + const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized(); + return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection }; + } + + //! Utility function to return a viewport ray using the ViewportInteractionRequestBus. + inline ProjectedViewportRay ViewportScreenToWorldRay( + const AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint) + { + ProjectedViewportRay viewportRay{}; + ViewportInteractionRequestBus::EventResult( + viewportRay, viewportId, &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint); + + return viewportRay; + } + //! Interface to return only viewport specific settings (e.g. snapping). class ViewportSettingsRequests { @@ -228,10 +254,6 @@ namespace AzToolsFramework class MainEditorViewportInteractionRequests { public: - //! Given a point in screen space, return the terrain position in world space. - virtual AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) = 0; - //! Return the terrain height given a world position in 2d (xy plane). - virtual float TerrainHeight(const AZ::Vector2& position) = 0; //! Is the user holding a modifier key to move the manipulator space from local to world. virtual bool ShowingWorldSpace() = 0; //! Return the widget to use as the parent for the viewport context menu. @@ -337,9 +359,18 @@ namespace AzToolsFramework //! Performs an intersection test against meshes in the scene, if there is a hit (the ray intersects //! a mesh), that position is returned, otherwise a point projected defaultDistance from the //! origin of the ray will be returned. + //! @note The intersection will only consider visible objects. AZ::Vector3 FindClosestPickIntersection( AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, float rayLength, float defaultDistance); + //! Overload of FindClosestPickIntersection taking a RenderGeometry::RayRequest directly. + //! @note rayRequest must contain a valid ray/line segment (start/endWorldPosition must not be at the same position). + AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, float defaultDistance); + + //! Update the in/out parameter rayRequest based on the latest viewport ray. + void RefreshRayRequest( + AzFramework::RenderGeometry::RayRequest& rayRequest, const ViewportInteraction::ProjectedViewportRay& viewportRay, float rayLength); + //! Maps a mouse interaction event to a ClickDetector event. //! @note Function only cares about up or down events, all other events are mapped to Nil (ignored). AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp index 0a7bed6b18..31ea0c3985 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp @@ -9,6 +9,7 @@ #include "EditorSelectionUtil.h" #include +#include #include #include #include @@ -16,8 +17,21 @@ #include #include +AZ_CVAR( + float, + ed_defaultEntityPlacementDistance, + 10.0f, + nullptr, + AZ::ConsoleFunctorFlags::Null, + "The default distance to place an entity from the camera if no intersection is found"); + namespace AzToolsFramework { + float GetDefaultEntityPlacementDistance() + { + return ed_defaultEntityPlacementDistance; + } + AZ::Vector3 CalculateCenterOffset(const AZ::EntityId entityId, const EditorTransformComponentSelectionRequests::Pivot pivot) { if (Centered(pivot)) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h index 5c9f47fdd3..b3a6c6afd4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h @@ -60,6 +60,9 @@ namespace AzToolsFramework //! Wrapper for EBus call to return the DPI scaling for a given viewport. float GetScreenDisplayScaling(int viewportId); + //! The default distance an entity is placed from the camera if there is no intersection. + float GetDefaultEntityPlacementDistance(); + //! A utility to return the center of several points. //! Take several positions and store the min and max of each in //! turn - when all points have been added return the center/midpoint. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index d3540f8c09..d3cfed4665 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -231,8 +231,8 @@ namespace AzToolsFramework { return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() && mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down && - mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() && - !mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt(); + !mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() && + mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl(); } static bool IndividualDitto(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) @@ -243,12 +243,12 @@ namespace AzToolsFramework mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl(); } - static bool SnapTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) + static bool SnapSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) { return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() && mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down && - (mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() || - mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl()); + mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() && + mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl(); } static bool ManipulatorDitto( @@ -923,26 +923,6 @@ namespace AzToolsFramework } } - static AZ::Vector3 PickTerrainPosition(const ViewportInteraction::MouseInteraction& mouseInteraction) - { - AZ_PROFILE_FUNCTION(AzToolsFramework); - - const int viewportId = mouseInteraction.m_interactionId.m_viewportId; - // get unsnapped terrain position (world space) - AZ::Vector3 worldSurfacePosition; - ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult( - worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain, - mouseInteraction.m_mousePick.m_screenCoordinates); - - // convert to local space - snap if enabled - const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId); - const AZ::Vector3 finalSurfacePosition = gridSnapParams.m_gridSnap - ? CalculateSnappedTerrainPosition(worldSurfacePosition, AZ::Transform::CreateIdentity(), viewportId, gridSnapParams.m_gridSize) - : worldSurfacePosition; - - return finalSurfacePosition; - } - // is the passed entity id contained with in the entity id list template static bool IsEntitySelectedInternal(AZ::EntityId entityId, const EntityIdContainer& selectedEntityIds) @@ -1286,7 +1266,7 @@ namespace AzToolsFramework m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation(); m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); - // [ref 1.] + // see comment [ref 1.] above BeginRecordManipulatorCommand(); }); @@ -1321,7 +1301,7 @@ namespace AzToolsFramework m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation(); m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); - // [ref 1.] + // see comment [ref 1.] above BeginRecordManipulatorCommand(); }); @@ -1343,6 +1323,39 @@ namespace AzToolsFramework EndRecordManipulatorCommand(); }); + // surface + translationManipulators->InstallSurfaceManipulatorMouseDownCallback( + [this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action) + { + BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds); + + InitializeTranslationLookup(m_entityIdManipulators); + + m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation(); + m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); + + // see comment [ref 1.] above + BeginRecordManipulatorCommand(); + }); + + translationManipulators->InstallSurfaceManipulatorMouseMoveCallback( + [this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable + { + UpdateTranslationManipulator( + action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, + m_transformChangedInternally, m_spaceCluster.m_spaceLock); + }); + + translationManipulators->InstallSurfaceManipulatorMouseUpCallback( + [this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action) + { + AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast( + &AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged, + manipulatorEntityIds->m_entityIds); + + EndRecordManipulatorCommand(); + }); + // transfer ownership m_entityIdManipulators.m_manipulators = AZStd::move(translationManipulators); } @@ -1395,7 +1408,7 @@ namespace AzToolsFramework m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation(); m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); - // [ref 1.] + // see comment [ref 1.] above BeginRecordManipulatorCommand(); }); @@ -1867,6 +1880,13 @@ namespace AzToolsFramework if (!m_selectedEntityIds.empty()) { + // try snapping to a surface (mesh) if in Translation mode + if (Input::SnapSurface(mouseInteraction)) + { + PerformSnapToSurface(mouseInteraction); + return false; + } + // group copying/alignment to specific entity - 'ditto' position/orientation for group if (Input::GroupDitto(mouseInteraction) && PerformGroupDitto(entityIdUnderCursor)) { @@ -1879,13 +1899,6 @@ namespace AzToolsFramework return false; } - // try snapping to the terrain (if in Translation mode) and entity wasn't picked - if (Input::SnapTerrain(mouseInteraction)) - { - PerformSnapToTerrain(mouseInteraction); - return false; - } - // set manipulator pivot override translation or orientation (update manipulators) if (Input::ManipulatorDitto(clickOutcome, mouseInteraction)) { @@ -1973,25 +1986,28 @@ namespace AzToolsFramework return false; } - void EditorTransformComponentSelection::PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) + void EditorTransformComponentSelection::PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction) { - for (AZ::EntityId entityId : m_selectedEntityIds) + for (const AZ::EntityId& entityId : m_selectedEntityIds) { ScopedUndoBatch::MarkEntityDirty(entityId); } if (m_mode == Mode::Translation) { - const AZ::Vector3 finalSurfacePosition = PickTerrainPosition(mouseInteraction.m_mouseInteraction); + const AZ::Vector3 worldPosition = FindClosestPickIntersection( + mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId, + mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, AzToolsFramework::EditorPickRayLength, + GetDefaultEntityPlacementDistance()); // handle modifier alternatives if (Input::IndividualDitto(mouseInteraction)) { - CopyTranslationToSelectedEntitiesIndividual(finalSurfacePosition); + CopyTranslationToSelectedEntitiesIndividual(worldPosition); } else if (Input::GroupDitto(mouseInteraction)) { - CopyTranslationToSelectedEntitiesGroup(finalSurfacePosition); + CopyTranslationToSelectedEntitiesGroup(worldPosition); } } else if (m_mode == Mode::Rotation) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 10f83db739..afc290380b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -308,7 +308,7 @@ namespace AzToolsFramework bool PerformGroupDitto(AZ::EntityId entityId); bool PerformIndividualDitto(AZ::EntityId entityId); void PerformManipulatorDitto(AZ::EntityId entityId); - void PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction); + void PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction); //! Responsible for keeping the space cluster in sync with the current reference frame. void UpdateSpaceCluster(ReferenceFrame referenceFrame); diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp index fa5a6b344a..0b3c574e16 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp @@ -8,6 +8,8 @@ #include +#include +#include #include namespace UnitTest @@ -62,4 +64,51 @@ namespace UnitTest { return m_localBounds; } + + void RenderGeometryIntersectionTestComponent::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } + } + + void RenderGeometryIntersectionTestComponent::Activate() + { + BoundsTestComponent::Activate(); + + const AZ::EntityId entityId = GetEntityId(); + AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::EntityIdContextQueryBus::EventResult(contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId); + AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId}); + } + + void RenderGeometryIntersectionTestComponent::Deactivate() + { + AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect(); + BoundsTestComponent::Deactivate(); + } + + AzFramework::RenderGeometry::RayResult RenderGeometryIntersectionTestComponent::RenderGeometryIntersect( + const AzFramework::RenderGeometry::RayRequest& ray) + { + AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); + + AzFramework::RenderGeometry::RayResult rayResult; + + float t = 0.0f; + const AZ::Obb obb = GetLocalBounds().GetTransformedObb(worldFromLocal); + const AZ::Vector3 rayDirection = ray.m_endWorldPosition - ray.m_startWorldPosition; + if (AZ::Intersect::IntersectRayObb(ray.m_startWorldPosition, rayDirection, obb, t)) + { + rayResult.m_worldPosition = ray.m_startWorldPosition + rayDirection * t; + rayResult.m_entityAndComponent = AZ::EntityComponentIdPair(GetEntityId(), GetId()); + rayResult.m_distance = t; + rayResult.m_uv = AZ::Vector2::CreateZero(); + rayResult.m_worldNormal = AZ::Vector3::CreateZero(); + } + + return rayResult; + } } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h index 358a9f810a..2eee3c0a66 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -44,4 +45,21 @@ namespace UnitTest AZ::Aabb m_localBounds; //!< Local bounds that can be modified for certain tests (defaults to unit cube). }; + + class RenderGeometryIntersectionTestComponent + : public BoundsTestComponent + , public AzFramework::RenderGeometry::IntersectionRequestBus::Handler + { + public: + AZ_EDITOR_COMPONENT(RenderGeometryIntersectionTestComponent, "{6F46B5BF-60DF-4BDD-9BA7-9658E85B99C2}", BoundsTestComponent); + + static void Reflect(AZ::ReflectContext* context); + + // AZ::Component overrides ... + void Activate() override; + void Deactivate() override; + + // IntersectionRequestBus overrides ... + AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override; + }; } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index f4077e992b..859cc67c41 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -1663,7 +1663,7 @@ namespace UnitTest const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityId1); // ensure final world positions match - EXPECT_TRUE(finalEntityTransform.IsClose(finalTransformWorld, 0.01f)); + EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f)); } TEST_F(EditorTransformComponentSelectionManipulatorTestFixture, TranslatingEntityWithLinearManipulatorNotifiesOnEntityTransformChanged) @@ -2973,4 +2973,176 @@ namespace UnitTest EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue()); EXPECT_THAT(hoveredEntityEntityId, Eq(AZ::EntityId(12345))); } + + class EditorTransformComponentSelectionRenderGeometryIntersectionFixture : public ToolsApplicationFixture + { + public: + void SetUpEditorFixtureImpl() override + { + auto* app = GetApplication(); + // register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus + app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor()); + // register a component implementing RenderGeometry::IntersectionRequestBus + app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor()); + + auto createEntityWithGeometryIntersectionFn = [](const char* entityName) + { + AZ::Entity* entity = nullptr; + AZ::EntityId entityId = CreateDefaultEditorEntity(entityName, &entity); + + entity->Deactivate(); + entity->CreateComponent(); + entity->Activate(); + + return entityId; + }; + + m_entityIdGround = createEntityWithGeometryIntersectionFn("Entity1"); + m_entityIdBox = createEntityWithGeometryIntersectionFn("Entity2"); + + if (auto* ground = AzToolsFramework::GetEntityById(m_entityIdGround)->FindComponent()) + { + ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f)); + } + + AzToolsFramework::SetWorldTransform(m_entityIdGround, AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 10.0f, 5.0f))); + + if (auto* box = AzToolsFramework::GetEntityById(m_entityIdBox)->FindComponent()) + { + box->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f)); + } + + AzToolsFramework::SetWorldTransform( + m_entityIdBox, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(0.0f, 10.0f, 7.0f))); + } + + AZ::EntityId m_entityIdGround; + AZ::EntityId m_entityIdBox; + }; + + using EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture = + IndirectCallManipulatorViewportInteractionFixtureMixin; + + TEST_F( + EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, BoxCanBePlacedOnMeshSurfaceUsingSurfaceManipulator) + { + // camera (go to position format) - 0.00, 20.00, 12.00, -35.00, -180.00 + m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f); + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-35.0f)), + AZ::Vector3(0.0f, 20.0f, 12.0f))); + + // the initial starting position of the entity + const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox); + // where the entity should end up (snapped to the larger ground surface) + const auto finalTransformWorld = + AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(2.5f, 12.5f, 5.5f)); + + // calculate the position in screen space of the initial position of the entity + const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState); + // calculate the position in screen space of the final position of the entity + const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState); + + // select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection) + AzToolsFramework::SelectEntity(m_entityIdBox); + + // press and drag the mouse (starting where the surface manipulator is) + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(initialPositionScreen) + ->MouseLButtonDown() + ->MousePosition(finalPositionScreen) + ->MouseLButtonUp(); + + // read back the position of the entity now + const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox); + + // ensure final world positions match + EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f)); + } + + TEST_F( + EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, + SurfaceManipulatorFollowsMouseAtDefaultEditorDistanceFromCameraWhenNoMeshIntersection) + { + // camera (go to position format) - 0.00, 25.00, 12.00, 0.00, -180.00 + m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f); + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)), AZ::Vector3(0.0f, 25.0f, 12.0f))); + + // the initial starting position of the entity + const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox); + // where the entity should end up (default distance away from the camera/near clip under where the mouse is) + const auto finalTransformWorld = + AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(0.0f, 14.9f, 12.0f)); + + // calculate the position in screen space of the initial position of the entity + const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState); + // calculate the position in screen space of the final position of the entity + const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState); + + // select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection) + AzToolsFramework::SelectEntity(m_entityIdBox); + + // press and drag the mouse (starting where the surface manipulator is) + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(initialPositionScreen) + ->MouseLButtonDown() + ->MousePosition(finalPositionScreen) + ->MouseLButtonUp(); + + // read back the position of the entity now + const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox); + + const auto viewportRay = AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, initialPositionScreen); + const auto distanceAway = (finalEntityTransform.GetTranslation() - viewportRay.origin).GetLength(); + + // ensure final world positions match + EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f)); + // ensure distance away is what we expect + EXPECT_NEAR(distanceAway, AzToolsFramework::GetDefaultEntityPlacementDistance(), 0.001f); + } + + TEST_F( + EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, + MiddleMouseButtonWithShiftAndCtrlHeldOnMeshSurfaceWillSnapSelectedEntityToIntersectionPoint) + { + // camera (go to position format) - 21.00, 8.00, 11.00, -22.00, 150.00 + m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f); + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(150.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-22.0f)), + AZ::Vector3(21.0f, 8.0f, 11.0f))); + + // position the ground entity + AzToolsFramework::SetWorldTransform( + m_entityIdGround, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(40.0f)) * AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)), + AZ::Vector3(14.0f, -6.0f, 5.0f))); + + // select the other entity (a 1x1x1 box) + AzToolsFramework::SelectEntity(m_entityIdBox); + + // expected world position (value taken from editor scenario) + const auto expectedWorldPosition = AZ::Vector3(13.606657f, -2.6753534f, 5.9827675f); + const auto screenPosition = AzFramework::WorldToScreen(expectedWorldPosition, m_cameraState); + + // perform snap action + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(screenPosition) + ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control) + ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift) + ->MouseMButtonDown(); + + // read back the current entity transform after placement + const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox); + EXPECT_THAT(finalEntityTransform.GetTranslation(), IsCloseTolerance(expectedWorldPosition, 0.01f)); + } } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp index 93010aa63d..228dea6b7c 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp @@ -10,22 +10,23 @@ #include #include #include +#include +#include +#include +#include +#include #include #include +#include #include #include #include #include #include #include -#include -#include -#include -#include -#include #include -using namespace AzToolsFramework; +#include namespace UnitTest { @@ -42,20 +43,65 @@ namespace UnitTest void Disconnect(); // FixedVerticesRequestBus/VariableVerticesRequestBus ... - bool GetVertex(size_t index, AZ::Vector3& vertex) const override { return m_vertexContainer.GetVertex(index, vertex); } - bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.UpdateVertex(index, vertex); }; - void AddVertex(const AZ::Vector3& vertex) override { m_vertexContainer.AddVertex(vertex); } - bool InsertVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.InsertVertex(index, vertex); } - bool RemoveVertex(size_t index) override { return m_vertexContainer.RemoveVertex(index); } - void SetVertices(const AZStd::vector& vertices) override { m_vertexContainer.SetVertices(vertices); }; - void ClearVertices() override { m_vertexContainer.Clear(); } - size_t Size() const override { return m_vertexContainer.Size(); } - bool Empty() const override { return m_vertexContainer.Empty(); } + bool GetVertex(size_t index, AZ::Vector3& vertex) const override; + bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override; + void AddVertex(const AZ::Vector3& vertex) override; + bool InsertVertex(size_t index, const AZ::Vector3& vertex) override; + bool RemoveVertex(size_t index) override; + void SetVertices(const AZStd::vector& vertices) override; + void ClearVertices() override; + size_t Size() const override; + bool Empty() const override; private: AZ::VertexContainer m_vertexContainer; }; + bool TestVariableVerticesVertexContainer::GetVertex(size_t index, AZ::Vector3& vertex) const + { + return m_vertexContainer.GetVertex(index, vertex); + } + + bool TestVariableVerticesVertexContainer::UpdateVertex(size_t index, const AZ::Vector3& vertex) + { + return m_vertexContainer.UpdateVertex(index, vertex); + } + + void TestVariableVerticesVertexContainer::AddVertex(const AZ::Vector3& vertex) + { + m_vertexContainer.AddVertex(vertex); + } + + bool TestVariableVerticesVertexContainer::InsertVertex(size_t index, const AZ::Vector3& vertex) + { + return m_vertexContainer.InsertVertex(index, vertex); + } + + bool TestVariableVerticesVertexContainer::RemoveVertex(size_t index) + { + return m_vertexContainer.RemoveVertex(index); + } + + void TestVariableVerticesVertexContainer::SetVertices(const AZStd::vector& vertices) + { + m_vertexContainer.SetVertices(vertices); + } + + void TestVariableVerticesVertexContainer::ClearVertices() + { + m_vertexContainer.Clear(); + } + + size_t TestVariableVerticesVertexContainer::Size() const + { + return m_vertexContainer.Size(); + } + + bool TestVariableVerticesVertexContainer::Empty() const + { + return m_vertexContainer.Empty(); + } + void TestVariableVerticesVertexContainer::Connect(const AZ::EntityId entityId) { AZ::VariableVerticesRequestBus::Handler::BusConnect(entityId); @@ -68,17 +114,18 @@ namespace UnitTest AZ::VariableVerticesRequestBus::Handler::BusDisconnect(); } - class TestEditorVertexSelectionVariable - : public EditorVertexSelectionVariable + class TestEditorVertexSelectionVariable : public AzToolsFramework::EditorVertexSelectionVariable { public: AZ_CLASS_ALLOCATOR(TestEditorVertexSelectionVariable, AZ::SystemAllocator, 0) - void ShowVertexDeletionWarning() override { /*noop*/ } + void ShowVertexDeletionWarning() override + { + // noop + } }; - class EditorVertexSelectionFixture - : public ToolsApplicationFixture + class EditorVertexSelectionFixture : public ToolsApplicationFixture { public: void SetUpEditorFixtureImpl() override @@ -112,26 +159,25 @@ namespace UnitTest void EditorVertexSelectionFixture::RecreateVertexSelection() { + namespace aztf = AzToolsFramework; m_vertexSelection.Create( - AZ::EntityComponentIdPair(m_entityId, TestComponentId), - g_mainManipulatorManagerId, AZStd::make_unique(), - TranslationManipulators::Dimensions::Three, ConfigureTranslationManipulatorAppearance3d); + AZ::EntityComponentIdPair(m_entityId, TestComponentId), aztf::g_mainManipulatorManagerId, + AZStd::make_unique(), aztf::TranslationManipulators::Dimensions::Three, + aztf::ConfigureTranslationManipulatorAppearance3d); } void EditorVertexSelectionFixture::PopulateVertices() { for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex) { - InsertVertexAfter( - AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero()); + AzToolsFramework::InsertVertexAfter(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero()); } } void EditorVertexSelectionFixture::ClearVertices() { for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex) { - SafeRemoveVertex( - AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0); + AzToolsFramework::SafeRemoveVertex(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0); } } @@ -188,7 +234,7 @@ namespace UnitTest /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // When // just provide a placeholder mouse interaction event in this case - m_vertexSelection.SnapVerticesToTerrain(ViewportInteraction::MouseInteractionEvent{}); + m_vertexSelection.SnapVerticesToSurface(AzToolsFramework::ViewportInteraction::MouseInteractionEvent{}); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -197,8 +243,7 @@ namespace UnitTest /////////////////////////////////////////////////////////////////////////////////////////////////////////////// } - using EditorVertexSelectionManipulatorFixture = - IndirectCallManipulatorViewportInteractionFixtureMixin; + using EditorVertexSelectionManipulatorFixture = IndirectCallManipulatorViewportInteractionFixtureMixin; TEST_F(EditorVertexSelectionManipulatorFixture, CannotDeleteAllVertices) { @@ -206,25 +251,23 @@ namespace UnitTest const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId); - const float horizontalPositions[] = {-1.5f, -0.5f, 0.5f, 1.5f}; - for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex) + const float horizontalPositions[] = { -1.5f, -0.5f, 0.5f, 1.5f }; + for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex) { - InsertVertexAfter( - entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f)); + AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f)); } - // rebuild the vertex selection after adding the new verts + // rebuild the vertex selection after adding the new vertices RecreateVertexSelection(); // build a vector of the vertex positions in screen space AZStd::vector vertexScreenPositions; - for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex) + for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex) { - AZ::Vector3 localVertex; + AZ::Vector3 localVertex = AZ::Vector3::CreateZero(); bool found = false; AZ::FixedVerticesRequestBus::EventResult( - found, m_entityId, &AZ::FixedVerticesRequestBus::Handler::GetVertex, - vertIndex, localVertex); + found, m_entityId, &AZ::FixedVerticesRequestBus::Handler::GetVertex, vertIndex, localVertex); if (found) { @@ -267,9 +310,9 @@ namespace UnitTest const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId); // add a single vertex (in front of the camera) - InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f)); + AzToolsFramework::InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f)); - // rebuild the vertex selection after adding the new verts + // rebuild the vertex selection after adding the new vertices RecreateVertexSelection(); AzFramework::ScreenPoint vertexScreenPosition; @@ -300,4 +343,132 @@ namespace UnitTest // deleting the last vertex through a manipulator is disallowed - size should remain the same EXPECT_THAT(vertexCountAfter, Eq(1)); } + + static AZ::EntityId CreateEntityForVertexIntersectionPlacement(EditorVertexSelectionManipulatorFixture& fixture) + { + auto* app = fixture.GetApplication(); + app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor()); + app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor()); + + AZ::Entity* entityGround = nullptr; + AZ::EntityId entityIdGround = CreateDefaultEditorEntity("EntityGround", &entityGround); + + entityGround->Deactivate(); + auto ground = entityGround->CreateComponent(); + entityGround->Activate(); + + ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f)); + + return entityIdGround; + } + + static AZStd::vector SetupVertices( + const AZ::EntityId entityId, EditorVertexSelectionManipulatorFixture& fixture) + { + const auto entityComponentIdPair = AZ::EntityComponentIdPair(entityId, TestComponentId); + const float horizontalPositions[] = { -3.0f, -1.0f, 1.0f, 3.0f }; + for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex) + { + AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 0.0f, 0.0f)); + } + + // rebuild the vertex selection after adding the new vertices + fixture.RecreateVertexSelection(); + + // build a vector of the vertex positions in screen space + AZStd::vector vertexScreenPositions; + for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex) + { + AZ::Vector3 localVertex; + bool found = false; + AZ::FixedVerticesRequestBus::EventResult( + found, entityId, &AZ::FixedVerticesRequestBus::Handler::GetVertex, vertIndex, localVertex); + + if (found) + { + const AZ::Vector3 worldVertex = AzToolsFramework::GetWorldTransform(entityId).TransformPoint(localVertex); + vertexScreenPositions.push_back(AzFramework::WorldToScreen(worldVertex, fixture.m_cameraState)); + } + } + + return vertexScreenPositions; + } + + AzToolsFramework::ViewportInteraction::MouseInteractionEvent BuildMiddleMouseDownEvent( + const AzFramework::ScreenPoint& screenPosition, const AzFramework::ViewportId viewportId) + { + AzToolsFramework::ViewportInteraction::MousePick mousePick; + mousePick.m_screenCoordinates = screenPosition; + + AzToolsFramework::ViewportInteraction::MouseInteraction mouseInteraction; + mouseInteraction.m_interactionId.m_cameraId = AZ::EntityId(); + mouseInteraction.m_interactionId.m_viewportId = viewportId; + mouseInteraction.m_mouseButtons = + AzToolsFramework::ViewportInteraction::MouseButtonsFromButton(AzToolsFramework::ViewportInteraction::MouseButton::Middle); + mouseInteraction.m_mousePick = mousePick; + mouseInteraction.m_keyboardModifiers = AzToolsFramework::ViewportInteraction::KeyboardModifiers( + static_cast(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift) | + static_cast(AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl)); + + return AzToolsFramework::ViewportInteraction::MouseInteractionEvent( + mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Down, /*captured=*/false); + } + + TEST_F(EditorVertexSelectionManipulatorFixture, VertexPlacedWhereIntersectionPointIsFoundWithCustomReferenceSpace) + { + const AZ::EntityId entityIdGround = CreateEntityForVertexIntersectionPlacement(*this); + + // position ground + AzToolsFramework::SetWorldTransform( + entityIdGround, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-20.0f)) * AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(-40.0f)) * + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)), + AZ::Vector3(14.0f, -6.0f, 5.0f))); + + // camera (go to position format) - 12.00, 18.00, 16.00, -38.00, -175.00 + m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f); + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-175.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-38.0f)), + AZ::Vector3(12.0f, 18.0f, 16.0f))); + + // create orientated and scaled transform for vertex selection entity transform + auto vertexSelectionTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(14.0f, 7.0f, 5.0f)); + vertexSelectionTransform.MultiplyByUniformScale(3.0f); + + // set the initial starting position of the vertex selection + AzToolsFramework::SetWorldTransform(m_entityId, vertexSelectionTransform); + + auto vertexScreenPositions = SetupVertices(m_entityId, *this); + + // press and drag the mouse (starting where the surface manipulator is) + // select each vertex (by holding ctrl) + m_actionDispatcher->CameraState(m_cameraState)->MousePosition(vertexScreenPositions[0])->MouseLButtonDown()->MouseLButtonUp(); + + const auto finalPositionWorld = AZ::Vector3(14.3573294f, -8.94695091f, 7.08627319f); + // calculate the position in screen space of the final position of the entity + const auto finalPositionScreen = AzFramework::WorldToScreen(finalPositionWorld, m_cameraState); + + auto middleMouseDownEvent = + BuildMiddleMouseDownEvent(finalPositionScreen, m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId()); + + // explicitly handle mouse event in vertex selection instance + m_vertexSelection.HandleMouse(middleMouseDownEvent); + + // read back the position of the vertex now + AZ::Vector3 localVertex = AZ::Vector3::CreateZero(); + bool found = false; + AZ::FixedVerticesRequestBus::EventResult( + found, m_entityId, &AZ::FixedVerticesRequestBus::Handler::GetVertex, 0, localVertex); + + // transform to world space + const AZ::Vector3 worldVertex = vertexSelectionTransform.TransformPoint(localVertex); + + EXPECT_THAT(found, ::testing::IsTrue()); + // ensure final world positions match + EXPECT_THAT(worldVertex, IsCloseTolerance(finalPositionWorld, 0.01f)); + } } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportInteractionTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportInteractionTests.cpp new file mode 100644 index 0000000000..fdcc8553f2 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportInteractionTests.cpp @@ -0,0 +1,76 @@ +/* + * 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 IndirectCallViewportInteractionIntersectionFixture : public ToolsApplicationFixture + { + public: + void SetUpEditorFixtureImpl() override + { + auto* app = GetApplication(); + // register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus + app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor()); + // register a component implementing RenderGeometry::IntersectionRequestBus + app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor()); + + AZ::Entity* entityGround = nullptr; + m_entityIdGround = CreateDefaultEditorEntity("EntityGround", &entityGround); + + entityGround->Deactivate(); + auto ground = entityGround->CreateComponent(); + entityGround->Activate(); + + ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f)); + } + + AZ::EntityId m_entityIdGround; + }; + + using IndirectCallManipulatorViewportInteractionIntersectionFixture = + IndirectCallManipulatorViewportInteractionFixtureMixin; + + TEST_F(IndirectCallManipulatorViewportInteractionIntersectionFixture, FindClosestPickIntersectionReturnsExpectedSurfacePoint) + { + // camera - 21.00, 8.00, 11.00, -22.00, 150.00 + m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f); + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(150.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-22.0f)), + AZ::Vector3(21.0f, 8.0f, 11.0f))); + + m_actionDispatcher->CameraState(m_cameraState); + + AzToolsFramework::SetWorldTransform( + m_entityIdGround, + AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(40.0f)) * AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)), + AZ::Vector3(14.0f, -6.0f, 5.0f))); + + // expected world position (value taken from editor scenario) + const auto expectedWorldPosition = AZ::Vector3(13.606657f, -2.6753534f, 5.9827675f); + const auto screenPosition = AzFramework::WorldToScreen(expectedWorldPosition, m_cameraState); + + // perform ray intersection against mesh + const auto worldIntersectionPoint = AzToolsFramework::FindClosestPickIntersection( + m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId(), screenPosition, + AzToolsFramework::EditorPickRayLength, AzToolsFramework::GetDefaultEntityPlacementDistance()); + + EXPECT_THAT(worldIntersectionPoint, IsCloseTolerance(expectedWorldPosition, 0.01f)); + } +} // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 9e052de983..8e0297fb1f 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -136,5 +136,6 @@ set(FILES Viewport/ViewportUiDisplayTests.cpp Viewport/ViewportUiManagerTests.cpp Viewport/ViewportUiWidgetManagerTests.cpp + Viewport/ViewportInteractionTests.cpp Visibility/EditorVisibilityTests.cpp ) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp index 1a015c167c..7ae3175e03 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp @@ -50,10 +50,7 @@ namespace AtomToolsFramework AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteractionImpl::ViewportScreenToWorldRay( const AzFramework::ScreenPoint& screenPosition) { - const AzFramework::CameraState cameraState = GetCameraState(); - const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPosition, cameraState); - const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized(); - return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection }; + return AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(GetCameraState(), screenPosition); } float ViewportInteractionImpl::DeviceScalingFactor()