Parcourir la source

Allow for Entity creation and Prefab instantiation in place via viewport interactions. (#17768)

* Menu manager will now store the position of the last context menu triggered, which can be used by actions to retrieve the click position at the time of user interaction for their purposes.

Signed-off-by: Danilo Aimini <[email protected]>

* Create function that retrieves the world position relating to the click/cursor position of the interaction that started an action.

Signed-off-by: Danilo Aimini <[email protected]>

* Alter the actions related to entity creation and prefab instantiation to take the interaction point into account and create/spawn at position if needed.

Signed-off-by: Danilo Aimini <[email protected]>

* Address minor issues found during code review.

Signed-off-by: Danilo Aimini <[email protected]>

---------

Signed-off-by: Danilo Aimini <[email protected]>
Signed-off-by: Steve Pham <[email protected]>
Co-authored-by: Steve Pham <[email protected]>
Danilo Aimini il y a 1 an
Parent
commit
94a6547789

+ 17 - 3
Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp

@@ -363,6 +363,17 @@ AZ::Vector3 SandboxIntegrationManager::GetWorldPositionAtViewportCenter()
     return AZ::Vector3::CreateZero();
 }
 
+AZ::Vector3 SandboxIntegrationManager::GetWorldPositionAtViewportInteraction() const
+{
+    const auto& iEditor = GetIEditor();
+    if (const auto& viewManager = (iEditor != nullptr) ? iEditor->GetViewManager() : nullptr)
+    {
+        return viewManager->GetClickPositionInViewportSpace();
+    }
+
+    return AZ::Vector3::CreateZero();
+}
+
 void SandboxIntegrationManager::ClearRedoStack()
 {
     // We have two separate undo systems that are assumed to be kept in sync,
@@ -515,6 +526,8 @@ void SandboxIntegrationManager::OnActionRegistrationHook()
             actionProperties,
             [this]()
             {
+                AZ::Vector3 worldPosition = GetWorldPositionAtViewportInteraction();
+
                 AzToolsFramework::EntityIdList selectedEntities;
                 AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
                     selectedEntities, &AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
@@ -522,7 +535,7 @@ void SandboxIntegrationManager::OnActionRegistrationHook()
                 // when nothing is selected, entity is created at root level.
                 if (selectedEntities.empty())
                 {
-                    ContextMenu_NewEntity();
+                    CreateNewEntityAtPosition(worldPosition);
                 }
                 // when a single entity is selected, entity is created as its child.
                 else if (selectedEntities.size() == 1)
@@ -533,8 +546,9 @@ void SandboxIntegrationManager::OnActionRegistrationHook()
 
                     if (containerEntityInterface && containerEntityInterface->IsContainerOpen(selectedEntityId) && !selectedEntityIsReadOnly)
                     {
-                        AzToolsFramework::EditorRequestBus::Broadcast(
-                            &AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selectedEntityId);
+                        AZ::Transform entityTransform = AZ::Transform::CreateIdentity();
+                        AZ::TransformBus::EventResult(entityTransform, selectedEntityId, &AZ::TransformBus::Events::GetWorldTM);
+                        CreateNewEntityAtPosition(entityTransform.GetInverse().TransformPoint(worldPosition), selectedEntityId);
                     }
                 }
             }

+ 1 - 0
Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h

@@ -134,6 +134,7 @@ private:
     void GoToSelectedEntitiesInViewports() override;
     bool CanGoToSelectedEntitiesInViewports() override;
     AZ::Vector3 GetWorldPositionAtViewportCenter() override;
+    AZ::Vector3 GetWorldPositionAtViewportInteraction() const override;
     void ClearRedoStack() override;
     //////////////////////////////////////////////////////////////////////////
 

+ 44 - 2
Code/Editor/ViewManager.cpp

@@ -18,6 +18,8 @@
 #include <AzCore/std/smart_ptr/make_shared.h>
 
 // AzToolsFramework
+#include <AzToolsFramework/ActionManager/Menu/MenuManagerInterface.h>
+#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
 #include <AzToolsFramework/Manipulators/ManipulatorManager.h>
 
 // Editor
@@ -208,8 +210,48 @@ void CViewManager::Cycle2DViewport()
 //////////////////////////////////////////////////////////////////////////
 CViewport* CViewManager::GetViewportAtPoint(const QPoint& point) const
 {
-    QWidget* widget = QApplication::widgetAt(point);
-    return qobject_cast<QtViewport*>(widget);
+    const auto viewportIter = AZStd::find_if(
+        m_viewports.begin(),
+        m_viewports.end(),
+        [&point](CViewport* viewport) -> bool
+        {
+            auto* widget = viewport->widget();
+            return widget && widget->rect().contains(widget->mapFromGlobal(point));
+        }
+    );
+
+    return (viewportIter == m_viewports.end()) ? nullptr : *viewportIter;
+}
+
+//////////////////////////////////////////////////////////////////////////
+AZ::Vector3 CViewManager::GetClickPositionInViewportSpace() const
+{
+    // Retrieve click position.
+    QPoint clickPos = QCursor::pos();
+
+    // If a context menu is active, get its position as the click position.
+    if (auto menuManagerInterface = AZ::Interface<AzToolsFramework::MenuManagerInterface>::Get())
+    {
+        auto outcome = menuManagerInterface->GetLastContextMenuPosition();
+        if (outcome.IsSuccess())
+        {
+            clickPos = outcome.GetValue();
+        }
+    }
+
+    // If the click position was on a viewport, retrieve the position in world coordinates.
+    AZ::Vector3 worldPosition = AZ::Vector3::CreateZero();
+    if (CViewport* view = GetViewportAtPoint(clickPos))
+    {
+        QPoint relativeCursor = view->widget()->mapFromGlobal(clickPos);
+        worldPosition = AzToolsFramework::FindClosestPickIntersection(
+            view->GetViewportId(),
+            AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(relativeCursor),
+            AzToolsFramework::EditorPickRayLength,
+            AzToolsFramework::GetDefaultEntityPlacementDistance());
+    }
+
+    return worldPosition;
 }
 
 //////////////////////////////////////////////////////////////////////////

+ 4 - 0
Code/Editor/ViewManager.h

@@ -45,6 +45,10 @@ public:
     //! Find viewport at Screen point.
     CViewport* GetViewportAtPoint(const QPoint& point) const;
 
+    //! Retrieves the position in world space corresponding to the point clicked by the user.
+    //! Will take context menus and cursor position into account as appropriate.
+    AZ::Vector3 GetClickPositionInViewportSpace() const;
+
     void SelectViewport(CViewport* pViewport);
     CViewport* GetSelectedViewport() const { return m_pSelectedView; }
 

+ 4 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h

@@ -871,6 +871,10 @@ namespace AzToolsFramework
         /// Returns the world-space position under the center of the render viewport.
         virtual AZ::Vector3 GetWorldPositionAtViewportCenter() { return AZ::Vector3::CreateZero(); }
 
+        //! Retrieves the position in world space corresponding to the point interacted with by the user.
+        //! Will take context menus and cursor position into account as appropriate.
+        virtual AZ::Vector3 GetWorldPositionAtViewportInteraction() const { return AZ::Vector3::CreateZero(); };
+
         /// Clears current redo stack
         virtual void ClearRedoStack() {}
     };

+ 10 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/ActionManager/Menu/EditorMenu.cpp

@@ -186,6 +186,16 @@ namespace AzToolsFramework
         DisplayAtPosition(QCursor::pos());
     }
 
+    QPoint EditorMenu::GetMenuPosition() const
+    {
+        return m_menu->pos();
+    }
+
+    bool EditorMenu::IsMenuVisible() const
+    {
+        return m_menu->isVisible();
+    }
+
     void EditorMenu::RefreshMenu()
     {
         m_menu->clear();

+ 7 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/ActionManager/Menu/EditorMenu.h

@@ -66,10 +66,16 @@ namespace AzToolsFramework
         QMenu* GetMenu();
         const QMenu* GetMenu() const;
 
-        // Displays the Menu
+        // Displays the menu.
         void DisplayAtPosition(QPoint screenPosition) const;
         void DisplayUnderCursor() const;
 
+        // Returns position of the menu.
+        QPoint GetMenuPosition() const;
+
+        // Returns whether the menu is currently visible.
+        bool IsMenuVisible() const;
+
         // Clears the menu and creates a new one from the EditorMenu information.
         void RefreshMenu();
 

+ 35 - 2
Code/Framework/AzToolsFramework/AzToolsFramework/ActionManager/Menu/MenuManager.cpp

@@ -564,7 +564,7 @@ namespace AzToolsFramework
         return AZ::Success(sortKey.value());
     }
 
-    MenuManagerOperationResult MenuManager::DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition) const
+    MenuManagerOperationResult MenuManager::DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition)
     {
         auto menuIterator = m_menus.find(menuIdentifier);
         if (menuIterator == m_menus.end())
@@ -574,11 +574,13 @@ namespace AzToolsFramework
                 menuIdentifier.c_str()));
         }
 
+        m_lastDisplayedMenuIdentifier = menuIdentifier;
         menuIterator->second.DisplayAtPosition(screenPosition);
+
         return AZ::Success();
     }
 
-    MenuManagerOperationResult MenuManager::DisplayMenuUnderCursor(const AZStd::string& menuIdentifier) const
+    MenuManagerOperationResult MenuManager::DisplayMenuUnderCursor(const AZStd::string& menuIdentifier)
     {
         auto menuIterator = m_menus.find(menuIdentifier);
         if (menuIterator == m_menus.end())
@@ -587,10 +589,28 @@ namespace AzToolsFramework
                 "Menu Manager - Could not display menu \"%s\" - menu has not been registered.", menuIdentifier.c_str()));
         }
 
+        m_lastDisplayedMenuIdentifier = menuIdentifier;
         menuIterator->second.DisplayUnderCursor();
+
         return AZ::Success();
     }
 
+    MenuManagerPositionResult MenuManager::GetLastContextMenuPosition() const
+    {
+        if (m_lastDisplayedMenuIdentifier.empty())
+        {
+            return AZ::Failure("Menu Manager - Could not return last context menu position. No menu was displayed yet.");
+        }
+
+        auto menuIterator = m_menus.find(m_lastDisplayedMenuIdentifier);
+        if (menuIterator == m_menus.end())
+        {
+            return AZ::Failure("Menu Manager - Could not return last context menu position. Menu could not be found.");
+        }
+
+        return menuIterator->second.GetMenuPosition();
+    }
+
     MenuManagerOperationResult MenuManager::QueueRefreshForMenu(const AZStd::string& menuIdentifier)
     {
         if (!m_menus.contains(menuIdentifier))
@@ -693,6 +713,18 @@ namespace AzToolsFramework
         m_menuBarsToRefresh.clear();
     }
 
+    void MenuManager::RefreshLastDisplayedMenu()
+    {
+        if (!m_lastDisplayedMenuIdentifier.empty())
+        {
+            auto menuIterator = m_menus.find(m_lastDisplayedMenuIdentifier);
+            if (menuIterator != m_menus.end() && !menuIterator->second.IsMenuVisible())
+            {
+                m_lastDisplayedMenuIdentifier.clear();
+            }
+        }
+    }
+
     MenuManagerStringResult MenuManager::SerializeMenu(const AZStd::string& menuIdentifier)
     {
         if (!m_menus.contains(menuIdentifier))
@@ -768,6 +800,7 @@ namespace AzToolsFramework
     {
         RefreshMenus();
         RefreshMenuBars();
+        RefreshLastDisplayedMenu();
     }
 
     void MenuManager::OnActionStateChanged(AZStd::string actionIdentifier)

+ 8 - 2
Code/Framework/AzToolsFramework/AzToolsFramework/ActionManager/Menu/MenuManager.h

@@ -66,8 +66,9 @@ namespace AzToolsFramework
         MenuManagerIntegerResult GetSortKeyOfSubMenuInMenu(const AZStd::string& menuIdentifier, const AZStd::string& subMenuIdentifier) const override;
         MenuManagerIntegerResult GetSortKeyOfWidgetInMenu(const AZStd::string& menuIdentifier, const AZStd::string& widgetActionIdentifier) const override;
         MenuManagerIntegerResult GetSortKeyOfMenuInMenuBar(const AZStd::string& menuBarIdentifier, const AZStd::string& menuIdentifier) const override;
-        MenuManagerOperationResult DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition) const override;
-        MenuManagerOperationResult DisplayMenuUnderCursor(const AZStd::string& menuIdentifier) const override;
+        MenuManagerOperationResult DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition) override;
+        MenuManagerOperationResult DisplayMenuUnderCursor(const AZStd::string& menuIdentifier) override;
+        MenuManagerPositionResult GetLastContextMenuPosition() const override;
 
         // MenuManagerInternalInterface overrides ...
         QMenu* GetMenu(const AZStd::string& menuIdentifier) override;
@@ -90,6 +91,11 @@ namespace AzToolsFramework
         // Identifies whether adding a submenu to a menu would generate any circular dependencies.
         bool WouldGenerateCircularDependency(const AZStd::string& menuIdentifier, const AZStd::string& subMenuIdentifier);
 
+        // If the menu that was stored as the last displayed is no longer visible, clear the variable.
+        void RefreshLastDisplayedMenu();
+
+        AZStd::string m_lastDisplayedMenuIdentifier;
+
         AZStd::unordered_map<AZStd::string, EditorMenu> m_menus;
         AZStd::unordered_map<AZStd::string, EditorMenuBar> m_menuBars;
 

+ 8 - 2
Code/Framework/AzToolsFramework/AzToolsFramework/ActionManager/Menu/MenuManagerInterface.h

@@ -21,6 +21,7 @@ namespace AzToolsFramework
     using MenuManagerOperationResult = AZ::Outcome<void, AZStd::string>;
     using MenuManagerIntegerResult = AZ::Outcome<int, AZStd::string>;
     using MenuManagerStringResult = AZ::Outcome<AZStd::string, AZStd::string>;
+    using MenuManagerPositionResult = AZ::Outcome<QPoint, AZStd::string>;
 
     //! Provides additional properties to initialize a Menu upon registration.
     struct MenuProperties
@@ -166,12 +167,17 @@ namespace AzToolsFramework
         //! @param menuIdentifier The identifier for the menu to display.
         //! @param screenPosition The position where the menu should appear.
         //! @return A successful outcome object if the menu could be displayed, or a string with a message detailing the error in case of failure.
-        virtual MenuManagerOperationResult DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition) const = 0;
+        virtual MenuManagerOperationResult DisplayMenuAtScreenPosition(const AZStd::string& menuIdentifier, const QPoint& screenPosition) = 0;
 
         //! Show the menu under the mouse cursor.
         //! @param menuIdentifier The identifier for the menu to display.
         //! @return A successful outcome object if the menu could be displayed, or a string with a message detailing the error in case of failure.
-        virtual MenuManagerOperationResult DisplayMenuUnderCursor(const AZStd::string& menuIdentifier) const = 0;
+        virtual MenuManagerOperationResult DisplayMenuUnderCursor(const AZStd::string& menuIdentifier) = 0;
+
+        //! Returns the position of the last context menu displayed with the DisplayMenu functions.
+        //! Note that the menu must still be active.
+        //! @return A successful outcome object containing the position of the last context menu, or a string with a message detailing the error in case of failure.
+        virtual MenuManagerPositionResult GetLastContextMenuPosition() const = 0;
         
     };
 

+ 31 - 14
Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp

@@ -1371,26 +1371,43 @@ namespace AzToolsFramework
 
         void PrefabIntegrationManager::ContextMenu_InstantiatePrefab()
         {
-            AZStd::string prefabFilePath;
-            bool hasUserSelectedValidSourceFile = PrefabSaveHandler::QueryUserForPrefabFilePath(prefabFilePath);
+            EntityIdList selectedEntities;
+            ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
 
-            if (hasUserSelectedValidSourceFile)
+            // Can't instantiate as child of multiple entities.
+            if (selectedEntities.size() > 1)
             {
-                AZ::EntityId parentId;
-                AZ::Vector3 position = AZ::Vector3::CreateZero();
+                return;
+            }
 
-                EntityIdList selectedEntities;
-                ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
-                // if one entity is selected, instantiate prefab as its child and place it at same position as parent
-                if (selectedEntities.size() == 1)
+            // Can't instantiate under a closed container or a read-only entity.
+            if (selectedEntities.size() == 1)
+            {
+                AZ::EntityId selectedEntityId = selectedEntities.front();
+                bool selectedEntityIsReadOnly = m_readOnlyEntityPublicInterface->IsReadOnly(selectedEntityId);
+                auto containerEntityInterface = AZ::Interface<AzToolsFramework::ContainerEntityInterface>::Get();
+
+                if (!containerEntityInterface || !containerEntityInterface->IsContainerOpen(selectedEntityId) || selectedEntityIsReadOnly)
                 {
-                    parentId = selectedEntities.front();
-                    AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
+                    return;
                 }
-                // otherwise instantiate it at root level and center of viewport
-                else
+            }
+
+            // Retrieve the cursor position before querying for file path so it is retrieved correctly.
+            AZ::Vector3 position = AZ::Vector3::CreateZero();
+            EditorRequestBus::BroadcastResult(position, &EditorRequestBus::Events::GetWorldPositionAtViewportInteraction);
+
+            // Query for the prefab to instantiate.
+            AZStd::string prefabFilePath;
+            if (PrefabSaveHandler::QueryUserForPrefabFilePath(prefabFilePath))
+            {
+                AZ::EntityId parentId = AZ::EntityId();
+
+                // When a single entity is selected, instance is created as its child.
+                if (selectedEntities.size() == 1)
                 {
-                    EditorRequestBus::BroadcastResult(position, &EditorRequestBus::Events::GetWorldPositionAtViewportCenter);
+                    // Set the selected entity as the parent.
+                    parentId = selectedEntities.front();
                 }
 
                 auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabFilePath, parentId, position);