diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py index aba506ea20..f4c2a19884 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py @@ -12,13 +12,12 @@ class Tests(): add_terrain_collider = ("Terrain Physics Heightfield Collider component added", "Failed to add a Terrain Physics Heightfield Collider component") box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions") configuration_changed = ("Terrain size changed successfully", "Failed terrain size change") - no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings") #fmt: on def TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges(): """ Summary: - Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree. + Test aspects of the Terrain Physics Heightfield Collider through the BehaviorContext and the Property Tree. Test Steps: Expected Behavior: diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py new file mode 100644 index 0000000000..e68b0932c2 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py @@ -0,0 +1,164 @@ +""" +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 +""" + +#fmt: off +class Tests(): + create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity") + create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity") + create_test_ball = ("Ball created successfully", "Failed to create Ball") + box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions") + shape_changed = ("Shape changed successfully", "Failed Shape change") + entity_added = ("Entity added successfully", "Failed Entity add") + frequency_changed = ("Frequency changed successfully", "Failed Frequency change") + shape_set = ("Shape set to Sphere successfully", "Failed to set Sphere shape") + test_collision = ("Ball collided with terrain", "Ball failed to collide with terrain") + no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings") +#fmt: on + +def Terrain_SupportsPhysics(): + """ + Summary: + Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree. + + Test Steps: + Expected Behavior: + The Editor is stable there are no warnings or errors. + + Test Steps: + 1) Load the base level + 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components + 2a) Create a ball at 600.0, 600.0, 46.0 - This position is not too high over the heightfield so will collide in a reasonable time + 3) Start the Tracer to catch any errors and warnings + 4) Change the Axis Aligned Box Shape dimensions + 5) Set the Vegetation Shape reference to TestEntity1 + 6) Set the FastNoise gradient frequency to 0.01 + 7) Set the Gradient List to TestEntity2 + 8) Set the PhysX Collider to Sphere mode + 9) Disable and Enable the Terrain Gradient List so that it is recognised + 10) Enter game mode and test if the ball hits the heightfield within 3 seconds + 11) Verify there are no errors and warnings in the logs + + + :return: None + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import TestHelper as helper, Report + from editor_python_test_tools.utils import Report, Tracer + import editor_python_test_tools.hydra_editor_utils as hydra + import azlmbr.math as azmath + import azlmbr.legacy.general as general + import azlmbr.bus as bus + import azlmbr.editor as editor + import math + + SET_BOX_X_SIZE = 1024.0 + SET_BOX_Y_SIZE = 1024.0 + SET_BOX_Z_SIZE = 100.0 + + helper.init_idle() + + # 1) Load the level + helper.open_level("", "Base") + helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0) + + #1a) Load the level components + hydra.add_level_component("Terrain World") + hydra.add_level_component("Terrain World Renderer") + + # 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components + entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"] + entity2_components_to_add = ["Vegetation Reference Shape", "Gradient Transform Modifier", "FastNoise Gradient"] + ball_components_to_add = ["Sphere Shape", "PhysX Collider", "PhysX Rigid Body"] + terrain_spawner_entity = hydra.Entity("TestEntity1") + terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add) + Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid()) + height_provider_entity = hydra.Entity("TestEntity2") + height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id) + Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid()) + # 2a) Create a ball at 600.0, 600.0, 46.0 - This position is not too high over the heightfield so will collide in a reasonable time + ball = hydra.Entity("Ball") + ball.create_entity(azmath.Vector3(600.0, 600.0, 46.0), ball_components_to_add) + Report.result(Tests.create_test_ball, ball.id.IsValid()) + # Give everything a chance to finish initializing. + general.idle_wait_frames(1) + + # 3) Start the Tracer to catch any errors and warnings + with Tracer() as section_tracer: + # 4) Change the Axis Aligned Box Shape dimensions + box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE) + terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions) + box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions") + Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions) + + # 5) Set the Vegetaion Shape reference to TestEntity1 + height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id) + entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id") + Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id) + + # 6) Set the FastNoise Gradient frequency to 0.01 + Frequency = 0.01 + height_provider_entity.get_set_test(2, "Configuration|Frequency", Frequency) + FrequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency") + Report.result(Tests.frequency_changed, math.isclose(Frequency, FrequencyVal, abs_tol = 0.00001)) + + # 7) Set the Gradient List to TestEntity2 + propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2]) + propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id) + checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0) + Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id) + + # 8) Set the PhysX Collider to Sphere mode + shape = 0 + hydra.get_set_test(ball, 1, "Shape Configuration|Shape", shape) + setShape = hydra.get_component_property_value(ball.components[1], "Shape Configuration|Shape") + Report.result(Tests.shape_set, shape == setShape) + + # 9) Disable and Enable the Terrain Gradient List so that it is recognised + editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]]) + + general.enter_game_mode() + + general.idle_wait_frames(1) + + # 10) Enter game mode and test if the ball hits the heightfield within 3 seconds + TIMEOUT = 3.0 + + class Collider: + id = general.find_game_entity("Ball") + touched_ground = False + + terrain_id = general.find_game_entity("TestEntity1") + + def on_collision_begin(args): + other_id = args[0] + if other_id.Equal(terrain_id): + Report.info("Touched ground") + Collider.touched_ground = True + + handler = azlmbr.physics.CollisionNotificationBusHandler() + handler.connect(Collider.id) + handler.add_callback("OnCollisionBegin", on_collision_begin) + + helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT) + Report.result(Tests.test_collision, Collider.touched_ground) + + general.exit_game_mode() + + # 11) Verify there are no errors and warnings in the logs + helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0) + for error_info in section_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in section_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + + from editor_python_test_tools.utils import Report + Report.start_test(Terrain_SupportsPhysics) + diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py index 620d84d7db..c5eec74c08 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py @@ -13,13 +13,15 @@ import os import sys from ly_test_tools import LAUNCHERS -from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest +from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSharedTest @pytest.mark.SUITE_main @pytest.mark.parametrize("launcher_platform", ['windows_editor']) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): - #global_extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"] - class test_AxisAlignedBoxShape_ConfigurationWorks(EditorSingleTest): + class test_AxisAlignedBoxShape_ConfigurationWorks(EditorSharedTest): from .EditorScripts import TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges as test_module + + class test_Terrain_SupportsPhysics(EditorSharedTest): + from .EditorScripts import Terrain_SupportsPhysics as test_module diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_SearchFiltering.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_SearchFiltering.py index 33c48c7a77..7366faafdc 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_SearchFiltering.py +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_SearchFiltering.py @@ -62,7 +62,7 @@ def AssetBrowser_SearchFiltering(): from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import TestHelper as helper - def verify_files_appeared(model, allowed_asset_extentions, parent_index=QtCore.QModelIndex()): + def verify_files_appeared(model, allowed_asset_extensions, parent_index=QtCore.QModelIndex()): indexes = [parent_index] while len(indexes) > 0: parent_index = indexes.pop(0) @@ -71,7 +71,7 @@ def AssetBrowser_SearchFiltering(): cur_data = cur_index.data(Qt.DisplayRole) if ( "." in cur_data - and (cur_data.lower().split(".")[-1] not in allowed_asset_extentions) + and (cur_data.lower().split(".")[-1] not in allowed_asset_extensions) and not cur_data[-1] == ")" ): Report.info(f"Incorrect file found: {cur_data}") @@ -94,16 +94,21 @@ def AssetBrowser_SearchFiltering(): Report.info("Asset Browser is already open") editor_window = pyside_utils.get_editor_main_window() app = QtWidgets.QApplication.instance() - - # 3) Type the name of an asset in the search bar and make sure only one asset is filtered in Asset browser + + # 3) Type the name of an asset in the search bar and make sure it is filtered to and selectable asset_browser = editor_window.findChild(QtWidgets.QDockWidget, "Asset Browser") search_bar = asset_browser.findChild(QtWidgets.QLineEdit, "textSearch") search_bar.setText("cedar.fbx") asset_browser_tree = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget") - model_index = pyside_utils.find_child_by_pattern(asset_browser_tree, "cedar.fbx") - pyside_utils.item_view_index_mouse_click(asset_browser_tree, model_index) + asset_browser_table = asset_browser.findChild(QtWidgets.QTreeView, "m_assetBrowserTableViewWidget") + found = await pyside_utils.wait_for_condition(lambda: pyside_utils.find_child_by_pattern(asset_browser_table, "cedar.fbx"), 5.0) + if found: + model_index = pyside_utils.find_child_by_pattern(asset_browser_table, "cedar.fbx") + else: + Report.result(Tests.asset_filtered, found) + pyside_utils.item_view_index_mouse_click(asset_browser_table, model_index) is_filtered = await pyside_utils.wait_for_condition( - lambda: asset_browser_tree.indexBelow(asset_browser_tree.currentIndex()) == QtCore.QModelIndex(), 5.0) + lambda: asset_browser_table.currentIndex() == model_index, 5.0) Report.result(Tests.asset_filtered, is_filtered) # 4) Click the "X" in the search bar. diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_TreeNavigation.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_TreeNavigation.py index b4f0dc7f6c..ecc77778cc 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_TreeNavigation.py +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/AssetBrowser_TreeNavigation.py @@ -84,8 +84,8 @@ def AssetBrowser_TreeNavigation(): # 3) Collapse all files initially main_window = editor_window.findChild(QtWidgets.QMainWindow) - asset_browser = pyside_utils.find_child_by_hierarchy(main_window, ..., "Asset Browser") - tree = pyside_utils.find_child_by_hierarchy(asset_browser, ..., "m_assetBrowserTreeViewWidget") + asset_browser = pyside_utils.find_child_by_pattern(main_window, text="Asset Browser", type=QtWidgets.QDockWidget) + tree = pyside_utils.find_child_by_pattern(asset_browser, "m_assetBrowserTreeViewWidget") scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer") scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar) tree.collapseAll() diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py index afc52f962d..820e4bd2aa 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py @@ -43,7 +43,6 @@ class TestAutomationNoAutoTestMode(EditorTestSuite): class test_InputBindings_Add_Remove_Input_Events(EditorSharedTest): from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module - @pytest.mark.skip(reason="Crashes Editor: ATOM-15493") class test_AssetPicker_UI_UX(EditorSharedTest): from .EditorScripts import AssetPicker_UI_UX as test_module @@ -60,7 +59,6 @@ class TestAutomationAutoTestMode(EditorTestSuite): class test_AssetBrowser_TreeNavigation(EditorSharedTest): from .EditorScripts import AssetBrowser_TreeNavigation as test_module - @pytest.mark.skip(reason="Crashes Editor: ATOM-15493") class test_AssetBrowser_SearchFiltering(EditorSharedTest): from .EditorScripts import AssetBrowser_SearchFiltering as test_module @@ -74,6 +72,5 @@ class TestAutomationAutoTestMode(EditorTestSuite): class test_Menus_FileMenuOptions_Work(EditorSharedTest): from .EditorScripts import Menus_FileMenuOptions as test_module - class test_BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD(EditorSharedTest): from .EditorScripts import BasicEditorWorkflows_ExistingLevel_EntityComponentCRUD as test_module diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py index 398b64bc87..f131a1c8bc 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py @@ -34,12 +34,10 @@ class TestAutomation(TestAutomationBase): from .EditorScripts import AssetBrowser_TreeNavigation as test_module self._run_test(request, workspace, editor, test_module, batch_mode=False) - @pytest.mark.skip(reason="Crashes Editor: ATOM-15493") def test_AssetBrowser_SearchFiltering(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetBrowser_SearchFiltering as test_module self._run_test(request, workspace, editor, test_module, batch_mode=False) - @pytest.mark.skip(reason="Crashes Editor: ATOM-15493") def test_AssetPicker_UI_UX(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetPicker_UI_UX as test_module self._run_test(request, workspace, editor, test_module, autotest_mode=False, batch_mode=False) diff --git a/AutomatedTesting/Levels/Base/Base.prefab b/AutomatedTesting/Levels/Base/Base.prefab index f7e42e7731..7765fe488e 100644 --- a/AutomatedTesting/Levels/Base/Base.prefab +++ b/AutomatedTesting/Levels/Base/Base.prefab @@ -17,7 +17,16 @@ }, "Component_[14126657869720434043]": { "$type": "EditorEntitySortComponent", - "Id": 14126657869720434043 + "Id": 14126657869720434043, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "" + }, + { + "EntityId": "", + "SortIndex": 1 + } + ] }, "Component_[15230859088967841193]": { "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", diff --git a/Code/Editor/2DViewport.cpp b/Code/Editor/2DViewport.cpp index ba433c869f..4d0609907f 100644 --- a/Code/Editor/2DViewport.cpp +++ b/Code/Editor/2DViewport.cpp @@ -234,7 +234,7 @@ void Q2DViewport::UpdateContent(int flags) } ////////////////////////////////////////////////////////////////////////// -void Q2DViewport::OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) +void Q2DViewport::OnRButtonDown([[maybe_unused]] Qt::KeyboardModifiers modifiers, const QPoint& point) { if (GetIEditor()->IsInGameMode()) { @@ -246,9 +246,6 @@ void Q2DViewport::OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& p setFocus(); } - // Check Edit Tool. - MouseCallback(eMouseRDown, point, modifiers); - SetCurrentCursor(STD_CURSOR_MOVE, QString()); // Save the mouse down position @@ -273,17 +270,8 @@ void Q2DViewport::OnRButtonUp([[maybe_unused]] Qt::KeyboardModifiers modifiers, } ////////////////////////////////////////////////////////////////////////// -void Q2DViewport::OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) +void Q2DViewport::OnMButtonDown([[maybe_unused]] Qt::KeyboardModifiers modifiers, const QPoint& point) { - //////////////////////////////////////////////////////////////////////// - // User pressed the middle mouse button - //////////////////////////////////////////////////////////////////////// - // Check Edit Tool. - if (MouseCallback(eMouseMDown, point, modifiers)) - { - return; - } - // Save the mouse down position m_RMouseDownPos = point; @@ -300,14 +288,8 @@ void Q2DViewport::OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& p } ////////////////////////////////////////////////////////////////////////// -void Q2DViewport::OnMButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) +void Q2DViewport::OnMButtonUp([[maybe_unused]] Qt::KeyboardModifiers modifiers, [[maybe_unused]] const QPoint& point) { - // Check Edit Tool. - if (MouseCallback(eMouseMUp, point, modifiers)) - { - return; - } - SetViewMode(NothingMode); ReleaseMouse(); diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index b77c6c4b3a..8ef21425c8 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -3834,7 +3834,7 @@ void CCryEditApp::SetEditorWindowTitle(QString sTitleStr, QString sPreTitleStr, { if (sTitleStr.isEmpty()) { - sTitleStr = QObject::tr("O3DE Editor [Developer Preview]"); + sTitleStr = QObject::tr("O3DE Editor [Stable 21.11]"); } if (!sPreTitleStr.isEmpty()) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 86a91a25bb..8f94afd514 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -2307,10 +2307,10 @@ void* EditorViewportWidget::GetSystemCursorConstraintWindow() const return systemCursorConstrained ? renderOverlayHWND() : nullptr; } -void EditorViewportWidget::BuildDragDropContext(AzQtComponents::ViewportDragContext& context, const QPoint& pt) +void EditorViewportWidget::BuildDragDropContext( + AzQtComponents::ViewportDragContext& context, const AzFramework::ViewportId viewportId, const QPoint& point) { - const auto scaledPoint = WidgetToViewport(pt); - QtViewport::BuildDragDropContext(context, scaledPoint); + QtViewport::BuildDragDropContext(context, viewportId, point); } void EditorViewportWidget::RestoreViewportAfterGameMode() diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index d20f3fe939..7d8daba8c5 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -273,7 +273,8 @@ private: bool CheckRespondToInput() const; - void BuildDragDropContext(AzQtComponents::ViewportDragContext& context, const QPoint& pt) override; + void BuildDragDropContext( + AzQtComponents::ViewportDragContext& context, AzFramework::ViewportId viewportId, const QPoint& point) override; void SetAsActiveViewport(); void PushDisableRendering(); diff --git a/Code/Editor/MainWindow.cpp b/Code/Editor/MainWindow.cpp index 1c5b6c567a..e13a160bd6 100644 --- a/Code/Editor/MainWindow.cpp +++ b/Code/Editor/MainWindow.cpp @@ -519,7 +519,7 @@ MainWindow* MainWindow::instance() void MainWindow::closeEvent(QCloseEvent* event) { - gSettings.Save(); + gSettings.Save(true); AzFramework::SystemCursorState currentCursorState; bool isInGameMode = false; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 4aae4191a0..64bd943d63 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -57,6 +56,7 @@ #include #include #include +#include #include #include @@ -1394,13 +1394,13 @@ void SandboxIntegrationManager::ContextMenu_NewEntity() { AZ::Vector3 worldPosition = AZ::Vector3::CreateZero(); - CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport(); // If we don't have a viewport active to aid in placement, the object // will be created at the origin. - if (view) + if (CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport()) { - const QPoint viewPoint(static_cast(m_contextMenuViewPoint.GetX()), static_cast(m_contextMenuViewPoint.GetY())); - worldPosition = view->GetHitLocation(viewPoint); + worldPosition = AzToolsFramework::FindClosestPickIntersection( + view->GetViewportId(), AzFramework::ScreenPointFromVector2(m_contextMenuViewPoint), AzToolsFramework::EditorPickRayLength, + GetDefaultEntityPlacementDistance()); } CreateNewEntityAtPosition(worldPosition); diff --git a/Code/Editor/Settings.cpp b/Code/Editor/Settings.cpp index d548cffb52..908e1c40f8 100644 --- a/Code/Editor/Settings.cpp +++ b/Code/Editor/Settings.cpp @@ -473,7 +473,7 @@ void SEditorSettings::LoadValue(const char* sSection, const char* sKey, ESystemC } ////////////////////////////////////////////////////////////////////////// -void SEditorSettings::Save() +void SEditorSettings::Save(bool isEditorClosing) { QString strStringPlaceholder; @@ -640,14 +640,16 @@ void SEditorSettings::Save() // --- Settings Registry values // Prefab System UI - AzFramework::ApplicationRequests::Bus::Broadcast( - &AzFramework::ApplicationRequests::SetPrefabSystemEnabled, prefabSystem); + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::SetPrefabSystemEnabled, prefabSystem); AzToolsFramework::Prefab::PrefabLoaderInterface* prefabLoaderInterface = AZ::Interface::Get(); prefabLoaderInterface->SetSaveAllPrefabsPreference(levelSaveSettings.saveAllPrefabsPreference); - SaveSettingsRegistryFile(); + if (!isEditorClosing) + { + SaveSettingsRegistryFile(); + } } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/Settings.h b/Code/Editor/Settings.h index 426d2300d3..f7617dd8c9 100644 --- a/Code/Editor/Settings.h +++ b/Code/Editor/Settings.h @@ -267,7 +267,7 @@ struct SANDBOX_API SEditorSettings AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING SEditorSettings(); ~SEditorSettings() = default; - void Save(); + void Save(bool isEditorClosing = false); void Load(); void LoadCloudSettings(); diff --git a/Code/Editor/Viewport.cpp b/Code/Editor/Viewport.cpp index c8f2e268d9..77be6e260e 100644 --- a/Code/Editor/Viewport.cpp +++ b/Code/Editor/Viewport.cpp @@ -14,14 +14,19 @@ // Qt #include +// AzCore +#include + // AzQtComponents #include #include #include #include +#include // Editor +#include "Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h" #include "ViewManager.h" #include "Include/ITransformManipulator.h" #include "Include/HitContext.h" @@ -32,22 +37,35 @@ #include "GameEngine.h" #include "Settings.h" - #ifdef LoadCursor #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 ////////////////////////////////////////////////////////////////////// -void QtViewport::BuildDragDropContext(AzQtComponents::ViewportDragContext& context, const QPoint& pt) +void QtViewport::BuildDragDropContext( + AzQtComponents::ViewportDragContext& context, const AzFramework::ViewportId viewportId, const QPoint& point) { - context.m_hitLocation = AZ::Vector3::CreateZero(); - context.m_hitLocation = GetHitLocation(pt); + context.m_hitLocation = AzToolsFramework::FindClosestPickIntersection( + viewportId, AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(point), AzToolsFramework::EditorPickRayLength, + GetDefaultEntityPlacementDistance()); } - void QtViewport::dragEnterEvent(QDragEnterEvent* event) { if (!GetIEditor()->GetGameEngine()->IsLevelLoaded()) @@ -66,7 +84,7 @@ void QtViewport::dragEnterEvent(QDragEnterEvent* event) // new bus-based way of doing it (install a listener!) using namespace AzQtComponents; ViewportDragContext context; - BuildDragDropContext(context, event->pos()); + BuildDragDropContext(context, GetViewportId(), event->pos()); DragAndDropEventsBus::Event(DragAndDropContexts::EditorViewport, &DragAndDropEvents::DragEnter, event, context); } } @@ -89,7 +107,7 @@ void QtViewport::dragMoveEvent(QDragMoveEvent* event) // new bus-based way of doing it (install a listener!) using namespace AzQtComponents; ViewportDragContext context; - BuildDragDropContext(context, event->pos()); + BuildDragDropContext(context, GetViewportId(), event->pos()); DragAndDropEventsBus::Event(DragAndDropContexts::EditorViewport, &DragAndDropEvents::DragMove, event, context); } } @@ -112,7 +130,7 @@ void QtViewport::dropEvent(QDropEvent* event) { // new bus-based way of doing it (install a listener!) ViewportDragContext context; - BuildDragDropContext(context, event->pos()); + BuildDragDropContext(context, GetViewportId(), event->pos()); DragAndDropEventsBus::Event(DragAndDropContexts::EditorViewport, &DragAndDropEvents::Drop, event, context); } } @@ -340,13 +358,6 @@ void QtViewport::resizeEvent(QResizeEvent* event) Update(); } -////////////////////////////////////////////////////////////////////////// -void QtViewport::leaveEvent(QEvent* event) -{ - QWidget::leaveEvent(event); - MouseCallback(eMouseLeave, QPoint(), Qt::KeyboardModifiers(), Qt::MouseButtons()); -} - ////////////////////////////////////////////////////////////////////////// void QtViewport::paintEvent([[maybe_unused]] QPaintEvent* event) { @@ -581,63 +592,7 @@ void QtViewport::keyReleaseEvent(QKeyEvent* event) OnKeyUp(nativeKey, 1, event->nativeModifiers()); } -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnLButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - // Save the mouse down position - m_cMouseDownPos = point; - if (MouseCallback(eMouseLDown, point, modifiers)) - { - return; - } -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnLButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - // Check Edit Tool. - MouseCallback(eMouseLUp, point, modifiers); -} -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - MouseCallback(eMouseRDown, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnRButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - MouseCallback(eMouseRUp, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - // Check Edit Tool. - MouseCallback(eMouseMDown, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnMButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - // Move the viewer to the mouse location. - // Check Edit Tool. - MouseCallback(eMouseMUp, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnMButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - MouseCallback(eMouseMDblClick, point, modifiers); -} - - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnMouseMove(Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint& point) -{ - MouseCallback(eMouseMove, point, modifiers, buttons); -} ////////////////////////////////////////////////////////////////////////// void QtViewport::OnSetCursor() @@ -696,44 +651,6 @@ void QtViewport::OnDragSelectRectangle(const QRect& rect, bool bNormalizeRect) GetIEditor()->SetStatusText(szNewStatusText); } -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnLButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - if (GetIEditor()->IsInGameMode()) - { - // Ignore double clicks while in game. - return; - } - - MouseCallback(eMouseLDblClick, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnRButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point) -{ - MouseCallback(eMouseRDblClick, point, modifiers); -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnKeyDown([[maybe_unused]] UINT nChar, [[maybe_unused]] UINT nRepCnt, [[maybe_unused]] UINT nFlags) -{ - if (GetIEditor()->IsInGameMode()) - { - // Ignore key downs while in game. - return; - } -} - -////////////////////////////////////////////////////////////////////////// -void QtViewport::OnKeyUp([[maybe_unused]] UINT nChar, [[maybe_unused]] UINT nRepCnt, [[maybe_unused]] UINT nFlags) -{ - if (GetIEditor()->IsInGameMode()) - { - // Ignore key downs while in game. - return; - } -} - ////////////////////////////////////////////////////////////////////////// void QtViewport::SetCurrentCursor(const QCursor& hCursor, const QString& cursorString) { @@ -1119,29 +1036,6 @@ bool QtViewport::HitTest(const QPoint& point, HitContext& hitInfo) return false; } -AZ::Vector3 QtViewport::GetHitLocation(const QPoint& point) -{ - Vec3 pos = Vec3(ZERO); - HitContext hit; - if (HitTest(point, hit)) - { - pos = hit.raySrc + hit.rayDir * hit.dist; - pos = SnapToGrid(pos); - } - else - { - bool hitTerrain; - pos = ViewToWorld(point, &hitTerrain); - if (hitTerrain) - { - pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y); - } - pos = SnapToGrid(pos); - } - - return AZ::Vector3(pos.x, pos.y, pos.z); -} - ////////////////////////////////////////////////////////////////////////// void QtViewport::SetZoomFactor(float fZoomFactor) { @@ -1315,84 +1209,6 @@ bool QtViewport::GetAdvancedSelectModeFlag() return m_bAdvancedSelectMode; } -////////////////////////////////////////////////////////////////////////// -bool QtViewport::MouseCallback(EMouseEvent event, const QPoint& point, Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons) -{ - AZ_PROFILE_FUNCTION(Editor); - - // Ignore any mouse events in game mode. - if (GetIEditor()->IsInGameMode()) - { - return true; - } - - // We must ignore mouse events when we are in the middle of an assert. - // Reason: If we have an assert called from an engine module under the editor, if we call this function, - // it may call the engine again and cause a deadlock. - // Concrete example: CryPhysics called from Trackview causing an assert, and moving the cursor over the viewport - // would cause the editor to freeze as it calls CryPhysics again for a raycast while it didn't release the lock. - if (gEnv->pSystem->IsAssertDialogVisible()) - { - return true; - } - - ////////////////////////////////////////////////////////////////////////// - // Hit test gizmo objects. - ////////////////////////////////////////////////////////////////////////// - bool bAltClick = (modifiers & Qt::AltModifier); - bool bCtrlClick = (modifiers & Qt::ControlModifier); - bool bShiftClick = (modifiers & Qt::ShiftModifier); - - int flags = (bCtrlClick ? MK_CONTROL : 0) | - (bShiftClick ? MK_SHIFT : 0) | - ((buttons& Qt::LeftButton) ? MK_LBUTTON : 0) | - ((buttons& Qt::MiddleButton) ? MK_MBUTTON : 0) | - ((buttons& Qt::RightButton) ? MK_RBUTTON : 0); - - switch (event) - { - case eMouseMove: - - if (m_nLastUpdateFrame == m_nLastMouseMoveFrame) - { - // If mouse move event generated in the same frame, ignore it. - return false; - } - m_nLastMouseMoveFrame = m_nLastUpdateFrame; - - // Skip the marker position update if anything is selected, since it is only used - // by the info bar which doesn't show the marker when there is an active selection. - // This helps a performance issue when calling ViewToWorld (which calls RayWorldIntersection) - // on every mouse movement becomes very expensive in scenes with large amounts of entities. - CSelectionGroup* selection = GetIEditor()->GetSelection(); - if (!(buttons & Qt::RightButton) /* && m_nLastUpdateFrame != m_nLastMouseMoveFrame*/ && (selection && selection->IsEmpty())) - { - //m_nLastMouseMoveFrame = m_nLastUpdateFrame; - Vec3 pos = ViewToWorld(point); - GetIEditor()->SetMarkerPosition(pos); - } - break; - } - - QPoint tempPoint(point.x(), point.y()); - - ////////////////////////////////////////////////////////////////////////// - // Handle viewport manipulators. - ////////////////////////////////////////////////////////////////////////// - if (!bAltClick) - { - ITransformManipulator* pManipulator = GetIEditor()->GetTransformManipulator(); - if (pManipulator) - { - if (pManipulator->MouseCallback(this, event, tempPoint, flags)) - { - return true; - } - } - } - - return false; -} ////////////////////////////////////////////////////////////////////////// void QtViewport::ProcessRenderLisneters(DisplayContext& rstDisplayContext) { diff --git a/Code/Editor/Viewport.h b/Code/Editor/Viewport.h index 7f8ccc4c4f..bf44b914aa 100644 --- a/Code/Editor/Viewport.h +++ b/Code/Editor/Viewport.h @@ -6,13 +6,12 @@ * */ - // Description : interface for the CViewport class. - #pragma once #if !defined(Q_MOC_RUN) +#include #include #include #include @@ -88,6 +87,9 @@ 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 @@ -201,7 +203,6 @@ public: //! Performs hit testing of 2d point in view to find which object hit. virtual bool HitTest(const QPoint& point, HitContext& hitInfo) = 0; - virtual AZ::Vector3 GetHitLocation(const QPoint& point) = 0; virtual void MakeConstructionPlane(int axis) = 0; @@ -432,7 +433,6 @@ public: //! Performs hit testing of 2d point in view to find which object hit. bool HitTest(const QPoint& point, HitContext& hitInfo) override; - AZ::Vector3 GetHitLocation(const QPoint& point) override; //! Do 2D hit testing of line in world space. // pToCameraDistance is an optional output parameter in which distance from the camera to the line is returned. @@ -522,9 +522,6 @@ protected: void setRenderOverlayVisible(bool); bool isRenderOverlayVisible() const; - // called to process mouse callback inside the viewport. - virtual bool MouseCallback(EMouseEvent event, const QPoint& point, Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons = Qt::NoButton); - void ProcessRenderLisneters(DisplayContext& rstDisplayContext); void mousePressEvent(QMouseEvent* event) override; @@ -535,29 +532,29 @@ protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent* event) override; - void leaveEvent(QEvent* event) override; - void paintEvent(QPaintEvent* event) override; - virtual void OnMouseMove(Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint& point); - virtual void OnMouseWheel(Qt::KeyboardModifiers modifiers, short zDelta, const QPoint& pt); - virtual void OnLButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnLButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnRButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnRButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnMButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnMButtonDown(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnMButtonUp(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnLButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnRButtonDblClk(Qt::KeyboardModifiers modifiers, const QPoint& point); - virtual void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); - virtual void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags); + virtual void OnMouseMove(Qt::KeyboardModifiers, Qt::MouseButtons, const QPoint&) {} + virtual void OnMouseWheel(Qt::KeyboardModifiers, short zDelta, const QPoint&); + virtual void OnLButtonDown(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnLButtonUp(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnRButtonDown(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnRButtonUp(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnMButtonDblClk(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnMButtonDown(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnMButtonUp(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnLButtonDblClk(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnRButtonDblClk(Qt::KeyboardModifiers, const QPoint&) {} + virtual void OnKeyDown([[maybe_unused]] UINT nChar, [[maybe_unused]] UINT nRepCnt, [[maybe_unused]] UINT nFlags) {} + virtual void OnKeyUp([[maybe_unused]] UINT nChar, [[maybe_unused]] UINT nRepCnt, [[maybe_unused]] UINT nFlags) {} #if defined(AZ_PLATFORM_WINDOWS) void OnRawInput(UINT wParam, HRAWINPUT lParam); #endif void OnSetCursor(); - virtual void BuildDragDropContext(AzQtComponents::ViewportDragContext& context, const QPoint& pt); + virtual void BuildDragDropContext( + AzQtComponents::ViewportDragContext& context, AzFramework::ViewportId viewportId, const QPoint& point); + void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dragLeaveEvent(QDragLeaveEvent* event) override; diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index 0c8e5209ca..c45bb21c6d 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -556,16 +556,24 @@ namespace AZ Asset assetData(AssetInternal::GetAssetData(actualId, AZ::Data::AssetLoadBehavior::Default)); if (assetData) { - auto curStatus = assetData->GetStatus(); + auto isReady = assetData->GetStatus() == AssetData::AssetStatus::Ready; bool isError = assetData->IsError(); - connectLock.unlock(); - if (curStatus == AssetData::AssetStatus::Ready) + + if (isReady || isError) { - handler->OnAssetReady(assetData); - } - else if (isError) - { - handler->OnAssetError(assetData); + connectLock.unlock(); + + if (isReady) + { + handler->OnAssetReady(assetData); + } + else if (isError) + { + handler->OnAssetError(assetData); + } + + // Lock the mutex again since some destructors will be modifying the context afterwards + connectLock.lock(); } } } diff --git a/Code/Framework/AzFramework/AzFramework/Entity/EntityDebugDisplayBus.h b/Code/Framework/AzFramework/AzFramework/Entity/EntityDebugDisplayBus.h index 49506364f4..b81a0ab7f0 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/EntityDebugDisplayBus.h +++ b/Code/Framework/AzFramework/AzFramework/Entity/EntityDebugDisplayBus.h @@ -76,9 +76,12 @@ namespace AzFramework virtual void DrawSolidCone(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius, float height, bool drawShaded = true) { (void)pos; (void)dir; (void)radius; (void)height; (void)drawShaded; } virtual void DrawWireCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) { (void)center; (void)axis; (void)radius; (void)height; } virtual void DrawSolidCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height, bool drawShaded = true) { (void)center; (void)axis; (void)radius; (void)height; (void)drawShaded; } + virtual void DrawWireCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) { (void)center; (void)axis; (void)radius; (void)height; } + virtual void DrawSolidCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height, bool drawShaded = true) { (void)center; (void)axis; (void)radius; (void)height; (void)drawShaded; } virtual void DrawWireCapsule(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float heightStraightSection) { (void)center; (void)axis; (void)radius; (void)heightStraightSection; } virtual void DrawWireSphere(const AZ::Vector3& pos, float radius) { (void)pos; (void)radius; } virtual void DrawWireSphere(const AZ::Vector3& pos, const AZ::Vector3 radius) { (void)pos; (void)radius; } + virtual void DrawWireHemisphere(const AZ::Vector3& pos, const AZ::Vector3& axis, float radius) { (void)pos; (void)axis; (void)radius; } virtual void DrawWireDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) { (void)pos; (void)dir; (void)radius; } virtual void DrawBall(const AZ::Vector3& pos, float radius, bool drawShaded = true) { (void)pos; (void)radius; (void)drawShaded; } virtual void DrawDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) { (void)pos; (void)dir; (void)radius; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/ComponentModeCollection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/ComponentModeCollection.cpp index 07e025fc60..f07e87fad2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/ComponentModeCollection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/ComponentModeCollection.cpp @@ -448,7 +448,11 @@ namespace AzToolsFramework ViewportUi::ViewportUiRequestBus::Event( ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, - componentMode.m_componentMode->GetComponentModeName().c_str()); + componentMode.m_componentMode->GetComponentModeName().c_str(), + [] + { + ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode); + }); } RefreshActions(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/EditorBaseComponentMode.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/EditorBaseComponentMode.cpp index f449478306..e780c38822 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/EditorBaseComponentMode.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ComponentMode/EditorBaseComponentMode.cpp @@ -55,8 +55,11 @@ namespace AzToolsFramework GetEntityComponentIdPair(), elementIdsToDisplay); // create the component mode border with the specific name for this component mode ViewportUi::ViewportUiRequestBus::Event( - ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, - GetComponentModeName()); + ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, GetComponentModeName(), + [] + { + ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode); + }); // set the EntityComponentId for this ComponentMode to active in the ComponentModeViewportUi system ComponentModeViewportUiRequestBus::Event( GetComponentType(), &ComponentModeViewportUiRequestBus::Events::SetViewportUiActiveEntityComponentId, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 5b51944a75..744c53ef5a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -98,7 +98,7 @@ namespace AzToolsFramework::Prefab } // Retrieve parent of currently focused prefab. - InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2]; + InstanceOptionalReference parentInstance = GetReferenceFromContainerEntityId(m_instanceFocusHierarchy[hierarchySize - 2]); // Use container entity of parent Instance for focus operations. AZ::EntityId entityId = parentInstance->get().GetContainerEntityId(); @@ -132,7 +132,7 @@ namespace AzToolsFramework::Prefab return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); } - InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index]; + InstanceOptionalReference focusedInstance = GetReferenceFromContainerEntityId(m_instanceFocusHierarchy[index]); return FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId()); } @@ -172,7 +172,8 @@ namespace AzToolsFramework::Prefab // Close all container entities in the old path. CloseInstanceContainers(m_instanceFocusHierarchy); - m_focusedInstance = focusedInstance; + // Do not store the container for the root instance, use an invalid EntityId instead. + m_focusedInstanceContainerEntityId = focusedInstance->get().GetParentInstance().has_value() ? focusedInstance->get().GetContainerEntityId() : AZ::EntityId(); m_focusedTemplateId = focusedInstance->get().GetTemplateId(); // Focus on the descendants of the container entity in the Editor, if the interface is initialized. @@ -206,56 +207,55 @@ namespace AzToolsFramework::Prefab InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance( [[maybe_unused]] AzFramework::EntityContextId entityContextId) const { - return m_focusedInstance; + return GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId); } AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { - if (!m_focusedInstance.has_value()) - { - // PrefabFocusHandler has not been initialized yet. - return AZ::EntityId(); - } - - return m_focusedInstance->get().GetContainerEntityId(); + return m_focusedInstanceContainerEntityId; } bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const { - if (!m_focusedInstance.has_value()) - { - // PrefabFocusHandler has not been initialized yet. - return false; - } - if (!entityId.IsValid()) { return false; } InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); + if (!instance.has_value()) + { + return false; + } - return instance.has_value() && (&instance->get() == &m_focusedInstance->get()); + // If this is owned by the root instance, that corresponds to an invalid m_focusedInstanceContainerEntityId. + if (!instance->get().GetParentInstance().has_value()) + { + return !m_focusedInstanceContainerEntityId.IsValid(); + } + + return (instance->get().GetContainerEntityId() == m_focusedInstanceContainerEntityId); } bool PrefabFocusHandler::IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const { - if (!m_focusedInstance.has_value()) - { - // PrefabFocusHandler has not been initialized yet. - return false; - } - if (!entityId.IsValid()) { return false; } + // If the focus is on the root, m_focusedInstanceContainerEntityId will be the invalid id. + // In those case all entities are in the focus hierarchy and should return true. + if (!m_focusedInstanceContainerEntityId.IsValid()) + { + return true; + } + InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); while (instance.has_value()) { - if (&instance->get() == &m_focusedInstance->get()) + if (instance->get().GetContainerEntityId() == m_focusedInstanceContainerEntityId) { return true; } @@ -290,8 +290,9 @@ namespace AzToolsFramework::Prefab // Determine if the entityId is the container for any of the instances in the vector. auto result = AZStd::find_if( m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(), - [entityId](const InstanceOptionalReference& instance) + [&, entityId](const AZ::EntityId& containerEntityId) { + InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); return (instance->get().GetContainerEntityId() == entityId); } ); @@ -316,8 +317,9 @@ namespace AzToolsFramework::Prefab // Determine if the templateId matches any of the instances in the vector. auto result = AZStd::find_if( m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(), - [templateId](const InstanceOptionalReference& instance) + [&, templateId](const AZ::EntityId& containerEntityId) { + InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); return (instance->get().GetTemplateId() == templateId); } ); @@ -336,10 +338,17 @@ namespace AzToolsFramework::Prefab AZStd::list instanceFocusList; - InstanceOptionalReference currentInstance = m_focusedInstance; + InstanceOptionalReference currentInstance = GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId); while (currentInstance.has_value()) { - m_instanceFocusHierarchy.emplace_back(currentInstance); + if (currentInstance->get().GetParentInstance().has_value()) + { + m_instanceFocusHierarchy.emplace_back(currentInstance->get().GetContainerEntityId()); + } + else + { + m_instanceFocusHierarchy.emplace_back(AZ::EntityId()); + } currentInstance = currentInstance->get().GetParentInstance(); } @@ -357,42 +366,48 @@ namespace AzToolsFramework::Prefab size_t index = 0; size_t maxIndex = m_instanceFocusHierarchy.size() - 1; - for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy) + for (const AZ::EntityId containerEntityId : m_instanceFocusHierarchy) { - AZStd::string prefabName; - - if (index < maxIndex) + InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); + if (instance.has_value()) { - // Get the filename without the extension (stem). - prefabName = instance->get().GetTemplateSourcePath().Stem().Native(); - } - else - { - // Get the full filename. - prefabName = instance->get().GetTemplateSourcePath().Filename().Native(); - } + AZStd::string prefabName; - if (prefabSystemComponentInterface->IsTemplateDirty(instance->get().GetTemplateId())) - { - prefabName += "*"; - } + if (index < maxIndex) + { + // Get the filename without the extension (stem). + prefabName = instance->get().GetTemplateSourcePath().Stem().Native(); + } + else + { + // Get the full filename. + prefabName = instance->get().GetTemplateSourcePath().Filename().Native(); + } - m_instanceFocusPath.Append(prefabName); + if (prefabSystemComponentInterface->IsTemplateDirty(instance->get().GetTemplateId())) + { + prefabName += "*"; + } + + m_instanceFocusPath.Append(prefabName); + } ++index; } } - void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector& instances) const + void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector& instances) const { // If this is called outside the Editor, this interface won't be initialized. if (!m_containerEntityInterface) { return; } - - for (const InstanceOptionalReference& instance : instances) + + for (const AZ::EntityId containerEntityId : instances) { + InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); + if (instance.has_value()) { m_containerEntityInterface->SetContainerOpen(instance->get().GetContainerEntityId(), true); @@ -400,7 +415,7 @@ namespace AzToolsFramework::Prefab } } - void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector& instances) const + void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector& instances) const { // If this is called outside the Editor, this interface won't be initialized. if (!m_containerEntityInterface) @@ -408,8 +423,10 @@ namespace AzToolsFramework::Prefab return; } - for (const InstanceOptionalReference& instance : instances) + for (const AZ::EntityId containerEntityId : instances) { + InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); + if (instance.has_value()) { m_containerEntityInterface->SetContainerOpen(instance->get().GetContainerEntityId(), false); @@ -417,4 +434,22 @@ namespace AzToolsFramework::Prefab } } + InstanceOptionalReference PrefabFocusHandler::GetReferenceFromContainerEntityId(AZ::EntityId containerEntityId) const + { + if (!containerEntityId.IsValid()) + { + PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = + AZ::Interface::Get(); + + if (!prefabEditorEntityOwnershipInterface) + { + return AZStd::nullopt; + } + + return prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); + } + + return m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); + } + } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 75b9666389..2e23059a01 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -73,16 +73,19 @@ namespace AzToolsFramework::Prefab void RefreshInstanceFocusList(); void RefreshInstanceFocusPath(); - void OpenInstanceContainers(const AZStd::vector& instances) const; - void CloseInstanceContainers(const AZStd::vector& instances) const; + void OpenInstanceContainers(const AZStd::vector& instances) const; + void CloseInstanceContainers(const AZStd::vector& instances) const; - //! The instance the editor is currently focusing on. - InstanceOptionalReference m_focusedInstance; + InstanceOptionalReference GetReferenceFromContainerEntityId(AZ::EntityId containerEntityId) const; + + //! The EntityId of the prefab container entity for the instance the editor is currently focusing on. + AZ::EntityId m_focusedInstanceContainerEntityId = AZ::EntityId(); //! The templateId of the focused instance. TemplateId m_focusedTemplateId; - //! The list of instances going from the root (index 0) to the focused instance. - AZStd::vector m_instanceFocusHierarchy; - //! A path containing the names of the containers in the instance focus hierarchy, separated with a /. + //! The list of instances going from the root (index 0) to the focused instance, + //! referenced by their prefab container's EntityId. + AZStd::vector m_instanceFocusHierarchy; + //! A path containing the filenames of the instances in the focus hierarchy, separated with a /. AZ::IO::Path m_instanceFocusPath; ContainerEntityInterface* m_containerEntityInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index 24b2c7466e..384b384a23 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -527,8 +527,8 @@ namespace AzToolsFramework m_errorButton = nullptr; } } - - void PropertyAssetCtrl::UpdateErrorButton(const AZStd::string& errorLog) + + void PropertyAssetCtrl::UpdateErrorButton() { if (m_errorButton) { @@ -543,12 +543,17 @@ namespace AzToolsFramework m_errorButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_errorButton->setFixedSize(QSize(16, 16)); m_errorButton->setMouseTracking(true); - m_errorButton->setIcon(QIcon("Icons/PropertyEditor/error_icon.png")); + m_errorButton->setIcon(QIcon(":/PropertyEditor/Resources/error_icon.png")); m_errorButton->setToolTip("Show Errors"); // Insert the error button after the asset label qobject_cast(layout())->insertWidget(1, m_errorButton); } + } + + void PropertyAssetCtrl::UpdateErrorButtonWithLog(const AZStd::string& errorLog) + { + UpdateErrorButton(); // Connect pressed to opening the error dialog // Must capture this for call to QObject::connect @@ -587,6 +592,21 @@ namespace AzToolsFramework logDialog->show(); }); } + + void PropertyAssetCtrl::UpdateErrorButtonWithMessage(const AZStd::string& message) + { + UpdateErrorButton(); + + connect(m_errorButton, &QPushButton::clicked, this, [this, message]() { + QMessageBox::critical(nullptr, "Error", message.c_str()); + + // Without this, the error button would maintain focus after clicking, which left the red error icon in a blue-highlighted state + if (parentWidget()) + { + parentWidget()->setFocus(); + } + }); + } void PropertyAssetCtrl::ClearAssetInternal() { @@ -960,7 +980,6 @@ namespace AzToolsFramework else { const AZ::Data::AssetId assetID = GetCurrentAssetID(); - m_currentAssetHint = ""; AZ::Outcome jobOutcome = AZ::Failure(); AssetSystemJobRequestBus::BroadcastResult(jobOutcome, &AssetSystemJobRequestBus::Events::GetAssetJobsInfoByAssetID, assetID, false, false); @@ -1018,7 +1037,7 @@ namespace AzToolsFramework // In case of failure, render failure icon case AssetSystem::JobStatus::Failed: { - UpdateErrorButton(errorLog); + UpdateErrorButtonWithLog(errorLog); } break; @@ -1043,6 +1062,10 @@ namespace AzToolsFramework m_currentAssetHint = assetPath; } } + else + { + UpdateErrorButtonWithMessage(AZStd::string::format("Asset is missing.\n\nID: %s\nHint:%s", assetID.ToString().c_str(), GetCurrentAssetHint().c_str())); + } } // Get the asset file name diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx index 0b98278bc5..58ddbb967e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx @@ -168,7 +168,9 @@ namespace AzToolsFramework bool IsCorrectMimeData(const QMimeData* pData, AZ::Data::AssetId* pAssetId = nullptr, AZ::Data::AssetType* pAssetType = nullptr) const; void ClearErrorButton(); - void UpdateErrorButton(const AZStd::string& errorLog); + void UpdateErrorButton(); + void UpdateErrorButtonWithLog(const AZStd::string& errorLog); + void UpdateErrorButtonWithMessage(const AZStd::string& message); virtual const AZStd::string GetFolderSelection() const { return AZStd::string(); } virtual void SetFolderSelection(const AZStd::string& /* folderPath */) {} virtual void ClearAssetInternal(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp index e3b45aca2b..8f8bd3e3f9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.cpp @@ -6,6 +6,7 @@ * */ +#include #include namespace AzToolsFramework @@ -62,4 +63,33 @@ namespace AzToolsFramework return circleBoundWidth; } + + AZ::Vector3 FindClosestPickIntersection( + AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, const float rayLength, 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); + + // attempt a ray intersection with any visible mesh and return the intersection position if successful + if (renderGeometryIntersectionResult) + { + return renderGeometryIntersectionResult.m_worldPosition; + } + else + { + return viewportRay.origin + viewportRay.direction * defaultDistance; + } + } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 84a8daa83e..aa97eb361e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -334,6 +334,12 @@ namespace AzToolsFramework return entityContextId; } + //! 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. + AZ::Vector3 FindClosestPickIntersection( + AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, float rayLength, float defaultDistance); + //! 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 a5ef66e7a3..0a7bed6b18 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp @@ -18,9 +18,6 @@ namespace AzToolsFramework { - // default ray length for picking in the viewport - static const float EditorPickRayLength = 1000.0f; - 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 d58549b329..5c9f47fdd3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h @@ -26,6 +26,9 @@ namespace AzFramework namespace AzToolsFramework { + //! Default ray length for picking in the viewport. + inline constexpr float EditorPickRayLength = 1000.0f; + //! Is the pivot at the center of the object (middle of extents) or at the //! exported authored object root position. inline bool Centered(const EditorTransformComponentSelectionRequests::Pivot pivot) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 5d2b11ca5e..9ac28cfd91 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1014,6 +1015,15 @@ namespace AzToolsFramework ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values); } + // leaves focus mode by focusing on the parent of the current perfab in the entity outliner + static void LeaveFocusMode() + { + if (auto prefabFocusPublicInterface = AZ::Interface::Get()) + { + prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(GetEntityContextId()); + } + } + EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache) : m_entityDataCache(entityDataCache) { @@ -3674,7 +3684,8 @@ namespace AzToolsFramework case ViewportEditorMode::Focus: { ViewportUi::ViewportUiRequestBus::Event( - ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode"); + ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode", + LeaveFocusMode); } break; case ViewportEditorMode::Default: @@ -3703,12 +3714,14 @@ namespace AzToolsFramework if (editorModeState.IsModeActive(ViewportEditorMode::Focus)) { ViewportUi::ViewportUiRequestBus::Event( - ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode"); + ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode", + LeaveFocusMode); } } break; case ViewportEditorMode::Focus: { + ViewportUi::ViewportUiRequestBus::Event( ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RemoveViewportBorder); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index c53aa66d81..a728ce9ac8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -21,6 +21,9 @@ namespace AzToolsFramework::ViewportUi::Internal { const static int HighlightBorderSize = 5; const static char* HighlightBorderColor = "#4A90E2"; + const static int HighlightBorderBackButtonMargin = 5; + const static int HighlightBorderBackButtonIconSize = 20; + const static char* HighlightBorderBackButtonIconFile = "X_axis.svg"; static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup) { @@ -62,6 +65,7 @@ namespace AzToolsFramework::ViewportUi::Internal , m_fullScreenLayout(&m_uiOverlay) , m_uiOverlayLayout() , m_viewportBorderText(&m_uiOverlay) + , m_viewportBorderBackButton(&m_uiOverlay) { } @@ -291,7 +295,8 @@ namespace AzToolsFramework::ViewportUi::Internal return false; } - void ViewportUiDisplay::CreateViewportBorder(const AZStd::string& borderTitle) + void ViewportUiDisplay::CreateViewportBorder( + const AZStd::string& borderTitle, AZStd::optional backButtonCallback) { const AZStd::string styleSheet = AZStd::string::format( "border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, ViewportUiTopBorderSize, @@ -302,6 +307,10 @@ namespace AzToolsFramework::ViewportUi::Internal HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin); m_viewportBorderText.setVisible(true); m_viewportBorderText.setText(borderTitle.c_str()); + + // only display the back button if a callback was provided + m_viewportBorderBackButtonCallback = backButtonCallback; + m_viewportBorderBackButton.setVisible(m_viewportBorderBackButtonCallback.has_value()); } void ViewportUiDisplay::RemoveViewportBorder() @@ -311,6 +320,8 @@ namespace AzToolsFramework::ViewportUi::Internal m_uiOverlayLayout.setContentsMargins( ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin, ViewportUiOverlayMargin); + m_viewportBorderBackButtonCallback.reset(); + m_viewportBorderBackButton.setVisible(false); } void ViewportUiDisplay::PositionViewportUiElementFromWorldSpace(ViewportUiElementId elementId, const AZ::Vector3& pos) @@ -347,6 +358,8 @@ namespace AzToolsFramework::ViewportUi::Internal void ViewportUiDisplay::InitializeUiOverlay() { + AZStd::string styleSheet; + m_uiMainWindow.setObjectName(QString("ViewportUiWindow")); ConfigureWindowForViewportUi(&m_uiMainWindow); m_uiMainWindow.setVisible(false); @@ -361,11 +374,37 @@ namespace AzToolsFramework::ViewportUi::Internal m_fullScreenLayout.addLayout(&m_uiOverlayLayout, 0, 0, 1, 1); // format the label which will appear on top of the highlight border - AZStd::string styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor); + styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor); m_viewportBorderText.setStyleSheet(styleSheet.c_str()); m_viewportBorderText.setFixedHeight(ViewportUiTopBorderSize); m_viewportBorderText.setVisible(false); m_fullScreenLayout.addWidget(&m_viewportBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter); + + // format the back button which will appear in the top right of the highlight border + styleSheet = AZStd::string::format( + "border: 0px; padding-left: %dpx; padding-right: %dpx", HighlightBorderBackButtonMargin, HighlightBorderBackButtonMargin); + m_viewportBorderBackButton.setStyleSheet(styleSheet.c_str()); + m_viewportBorderBackButton.setVisible(false); + QIcon backButtonIcon(QString(AZStd::string::format(":/stylesheet/img/UI20/toolbar/%s", HighlightBorderBackButtonIconFile).c_str())); + m_viewportBorderBackButton.setIcon(backButtonIcon); + m_viewportBorderBackButton.setIconSize(QSize(HighlightBorderBackButtonIconSize, HighlightBorderBackButtonIconSize)); + + // setup the handler for the back button to call the user provided callback (if any) + QObject::connect( + &m_viewportBorderBackButton, &QPushButton::clicked, + [this]() + { + if (m_viewportBorderBackButtonCallback.has_value()) + { + // we need to swap out the existing back button callback because it will be reset in RemoveViewportBorder() + // so preserve the lifetime with this temporary callback until after the call to RemoveViewportBorder() + AZStd::optional backButtonCallback; + m_viewportBorderBackButtonCallback.swap(backButtonCallback); + RemoveViewportBorder(); + (*backButtonCallback)(); + } + }); + m_fullScreenLayout.addWidget(&m_viewportBorderBackButton, 0, 0, Qt::AlignTop | Qt::AlignRight); } void ViewportUiDisplay::PrepareWidgetForViewportUi(QPointer widget) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h index 32b746a1ac..c75310a10f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h @@ -17,6 +17,7 @@ #include #include #include +#include AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") #include @@ -89,7 +90,7 @@ namespace AzToolsFramework::ViewportUi::Internal AZStd::shared_ptr GetViewportUiElement(ViewportUiElementId elementId); bool IsViewportUiElementVisible(ViewportUiElementId elementId); - void CreateViewportBorder(const AZStd::string& borderTitle); + void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional backButtonCallback); void RemoveViewportBorder(); private: @@ -113,7 +114,10 @@ namespace AzToolsFramework::ViewportUi::Internal QWidget m_uiOverlay; //!< The UI Overlay which displays Viewport UI Elements. QGridLayout m_fullScreenLayout; //!< The layout which extends across the full screen. ViewportUiDisplayLayout m_uiOverlayLayout; //!< The layout used for optionally anchoring Viewport UI Elements. - QLabel m_viewportBorderText; //!< The text used for the viewport border. + QLabel m_viewportBorderText; //!< The text used for the viewport highlight border. + QPushButton m_viewportBorderBackButton; //!< The button to return from the viewport highlight border (only displayed if callback provided). + AZStd::optional + m_viewportBorderBackButtonCallback; //!< The optional callback for when the viewport highlight border back button is pressed. QWidget* m_renderOverlay; QPointer m_fullScreenWidget; //!< Reference to the widget attached to m_fullScreenLayout if any. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.cpp index 1f14b12b7d..143da34074 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.cpp @@ -240,9 +240,10 @@ namespace AzToolsFramework::ViewportUi } } - void ViewportUiManager::CreateViewportBorder(const AZStd::string& borderTitle) + void ViewportUiManager::CreateViewportBorder( + const AZStd::string& borderTitle, AZStd::optional backButtonCallback) { - m_viewportUi->CreateViewportBorder(borderTitle); + m_viewportUi->CreateViewportBorder(borderTitle, backButtonCallback); } void ViewportUiManager::RemoveViewportBorder() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.h index ce7e5aafe9..54f1eb5a57 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiManager.h @@ -50,7 +50,8 @@ namespace AzToolsFramework::ViewportUi void RegisterTextFieldCallback(TextFieldId textFieldId, AZ::Event::Handler& handler) override; void RemoveTextField(TextFieldId textFieldId) override; void SetTextFieldVisible(TextFieldId textFieldId, bool visible) override; - void CreateViewportBorder(const AZStd::string& borderTitle) override; + void CreateViewportBorder( + const AZStd::string& borderTitle, AZStd::optional backButtonCallback) override; void RemoveViewportBorder() override; void PressButton(ClusterId clusterId, ButtonId buttonId) override; void PressButton(SwitcherId switcherId, ButtonId buttonId) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiRequestBus.h index 3c6f7094cb..9ac6feb8c8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiRequestBus.h @@ -22,6 +22,9 @@ namespace AzToolsFramework::ViewportUi using SwitcherId = IdType; using TextFieldId = IdType; + //! Callback function for viewport UI back button. + using ViewportUiBackButtonCallback = AZStd::function; + inline const ViewportUiElementId InvalidViewportUiElementId = ViewportUiElementId(0); inline const ButtonId InvalidButtonId = ButtonId(0); inline const ClusterId InvalidClusterId = ClusterId(0); @@ -95,9 +98,9 @@ namespace AzToolsFramework::ViewportUi virtual void RemoveTextField(TextFieldId textFieldId) = 0; //! Sets the visibility of the text field. virtual void SetTextFieldVisible(TextFieldId textFieldId, bool visible) = 0; - //! Create the highlight border for Component Mode. - virtual void CreateViewportBorder(const AZStd::string& borderTitle) = 0; - //! Remove the highlight border for Component Mode. + //! Create the highlight border with optional back button to exit the given editor mode. + virtual void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional backButtonCallback) = 0; + //! Remove the highlight border. virtual void RemoveViewportBorder() = 0; //! Invoke a button press on a cluster. virtual void PressButton(ClusterId clusterId, ButtonId buttonId) = 0; diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp index 86c73e72e5..6bf964f038 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp @@ -106,7 +106,9 @@ namespace UnitTest inline static const char* Passenger2EntityName = "Passenger2"; }; - TEST_F(PrefabFocusTests, PrefabFocus_FocusOnOwningPrefab_RootContainer) + // Test was disabled because the implementation of GetFocusedPrefabInstance now relies on the Prefab EOS, + // which is not used by our test environment. This can be restored once Instance handles are implemented. + TEST_F(PrefabFocusTests, DISABLED_PrefabFocus_FocusOnOwningPrefab_RootContainer) { // Verify FocusOnOwningPrefab works when passing the container entity of the root prefab. { @@ -121,7 +123,9 @@ namespace UnitTest } } - TEST_F(PrefabFocusTests, PrefabFocus_FocusOnOwningPrefab_RootEntity) + // Test was disabled because the implementation of GetFocusedPrefabInstance now relies on the Prefab EOS, + // which is not used by our test environment. This can be restored once Instance handles are implemented. + TEST_F(PrefabFocusTests, DISABLED_PrefabFocus_FocusOnOwningPrefab_RootEntity) { // Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab. { diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 6b19e839d7..205395c935 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index 06e51b5116..9b64c5e276 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -73,13 +73,25 @@ namespace O3DE::ProjectManager emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes); } - void DownloadController::HandleResults(const QString& result) + void DownloadController::HandleResults(const QString& result, const QString& detailedError) { bool succeeded = true; if (!result.isEmpty()) { - QMessageBox::critical(nullptr, tr("Gem download"), result); + if (!detailedError.isEmpty()) + { + QMessageBox gemDownloadError; + gemDownloadError.setIcon(QMessageBox::Critical); + gemDownloadError.setWindowTitle(tr("Gem download")); + gemDownloadError.setText(result); + gemDownloadError.setDetailedText(detailedError); + gemDownloadError.exec(); + } + else + { + QMessageBox::critical(nullptr, tr("Gem download"), result); + } succeeded = false; } diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h index 211d9b48bc..5e637971e9 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.h +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -54,7 +54,7 @@ namespace O3DE::ProjectManager } public slots: void UpdateUIProgress(int bytesDownloaded, int totalBytes); - void HandleResults(const QString& result); + void HandleResults(const QString& result, const QString& detailedError); signals: void StartGemDownload(const QString& gemName); diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp index 560bfe05de..e58c41c89e 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -24,16 +24,16 @@ namespace O3DE::ProjectManager { emit UpdateProgress(bytesDownloaded, totalBytes); }; - AZ::Outcome gemInfoResult = + AZ::Outcome> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true); if (gemInfoResult.IsSuccess()) { - emit Done(""); + emit Done("", ""); } else { - emit Done(tr("Gem download failed")); + emit Done(gemInfoResult.GetError().first.c_str(), gemInfoResult.GetError().second.c_str()); } } diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.h b/Code/Tools/ProjectManager/Source/DownloadWorker.h index 4084080ff7..d33de7bacc 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.h +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.h @@ -32,7 +32,7 @@ namespace O3DE::ProjectManager signals: void UpdateProgress(int bytesDownloaded, int totalBytes); - void Done(QString result = ""); + void Done(QString result = "", QString detailedResult = ""); private: diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp index 9d9110922f..1f41acd3d1 100644 --- a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp @@ -72,12 +72,7 @@ namespace O3DE::ProjectManager bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen) { - if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum()) - { - return true; - } - - return false; + return screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum(); } void EngineScreenCtrl::NotifyCurrentScreen() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index e676ba73d3..d6f4da0051 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -7,25 +7,32 @@ */ #include +#include + #include + #include #include #include #include #include -#include #include #include #include +#include +#include namespace O3DE::ProjectManager { - CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent) - : QWidget(parent) + GemCartWidget::GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent) + : QScrollArea(parent) , m_gemModel(gemModel) , m_downloadController(downloadController) { setObjectName("GemCatalogCart"); + setWidgetResizable(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_layout = new QVBoxLayout(); m_layout->setSpacing(0); @@ -118,17 +125,15 @@ namespace O3DE::ProjectManager } return dependencies; }); - - setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); } - CartOverlayWidget::~CartOverlayWidget() + GemCartWidget::~GemCartWidget() { // disconnect from all download controller signals disconnect(m_downloadController, nullptr, this, nullptr); } - void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices) + void GemCartWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices) { QWidget* widget = new QWidget(); widget->setFixedWidth(s_width); @@ -164,12 +169,12 @@ namespace O3DE::ProjectManager update(); } - void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName) + void GemCartWidget::OnCancelDownloadActivated(const QString& gemName) { m_downloadController->CancelGemDownload(gemName); } - void CartOverlayWidget::CreateDownloadSection() + void GemCartWidget::CreateDownloadSection() { m_downloadSectionWidget = new QWidget(); m_downloadSectionWidget->setFixedWidth(s_width); @@ -223,12 +228,12 @@ namespace O3DE::ProjectManager } // connect to download controller data changed - connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded); - connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved); - connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress); + connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCartWidget::GemDownloadAdded); + connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCartWidget::GemDownloadRemoved); + connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &GemCartWidget::GemDownloadProgress); } - void CartOverlayWidget::GemDownloadAdded(const QString& gemName) + void GemCartWidget::GemDownloadAdded(const QString& gemName) { // Containing widget for the current download item QWidget* newGemDownloadWidget = new QWidget(); @@ -246,7 +251,7 @@ namespace O3DE::ProjectManager nameProgressLayout->addStretch(); QLabel* cancelText = new QLabel(tr("Cancel").arg(gemName), newGemDownloadWidget); cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse); - connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated); + connect(cancelText, &QLabel::linkActivated, this, &GemCartWidget::OnCancelDownloadActivated); nameProgressLayout->addWidget(cancelText); downloadingGemLayout->addLayout(nameProgressLayout); @@ -267,7 +272,7 @@ namespace O3DE::ProjectManager m_downloadingListWidget->show(); } - void CartOverlayWidget::GemDownloadRemoved(const QString& gemName) + void GemCartWidget::GemDownloadRemoved(const QString& gemName) { QWidget* gemToRemove = m_downloadingListWidget->findChild(gemName); if (gemToRemove) @@ -289,7 +294,7 @@ namespace O3DE::ProjectManager } } - void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes) + void GemCartWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes) { QWidget* gemToUpdate = m_downloadingListWidget->findChild(gemName); if (gemToUpdate) @@ -324,7 +329,7 @@ namespace O3DE::ProjectManager } } - QVector CartOverlayWidget::GetTagsFromModelIndices(const QVector& gems) const + QVector GemCartWidget::GetTagsFromModelIndices(const QVector& gems) const { QVector tags; tags.reserve(gems.size()); @@ -349,7 +354,7 @@ namespace O3DE::ProjectManager iconButton->setFocusPolicy(Qt::NoFocus); iconButton->setIcon(QIcon(":/Summary.svg")); iconButton->setFixedSize(s_iconSize, s_iconSize); - connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowOverlay); + connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowGemCart); m_layout->addWidget(iconButton); m_countLabel = new QLabel(); @@ -362,7 +367,7 @@ namespace O3DE::ProjectManager m_dropDownButton->setFocusPolicy(Qt::NoFocus); m_dropDownButton->setIcon(QIcon(":/CarrotArrowDown.svg")); m_dropDownButton->setFixedSize(s_arrowDownIconSize, s_arrowDownIconSize); - connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowOverlay); + connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowGemCart); m_layout->addWidget(m_dropDownButton); // Adjust the label text whenever the model gets updated. @@ -377,28 +382,28 @@ namespace O3DE::ProjectManager m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty()); // Automatically close the overlay window in case there are no gems to be activated or deactivated anymore. - if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty()) + if (m_gemCart && toBeAdded.isEmpty() && toBeRemoved.isEmpty()) { - m_cartOverlay->deleteLater(); - m_cartOverlay = nullptr; + m_gemCart->deleteLater(); + m_gemCart = nullptr; } }); } void CartButton::mousePressEvent([[maybe_unused]] QMouseEvent* event) { - ShowOverlay(); + ShowGemCart(); } void CartButton::hideEvent(QHideEvent*) { - if (m_cartOverlay) + if (m_gemCart) { - m_cartOverlay->hide(); + m_gemCart->hide(); } } - void CartButton::ShowOverlay() + void CartButton::ShowGemCart() { const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true); const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true); @@ -407,37 +412,33 @@ namespace O3DE::ProjectManager return; } - if (m_cartOverlay) + if (m_gemCart) { // Directly delete the former overlay before creating the new one. // Don't use deleteLater() here. This might overwrite the new overlay pointer // depending on the event queue. - delete m_cartOverlay; + delete m_gemCart; } - m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this); - connect(m_cartOverlay, &QWidget::destroyed, this, [=] + m_gemCart = new GemCartWidget(m_gemModel, m_downloadController, this); + connect(m_gemCart, &QWidget::destroyed, this, [=] { // Reset the overlay pointer on destruction to prevent dangling pointers. - m_cartOverlay = nullptr; + m_gemCart = nullptr; + // Tell header gem cart is no longer open + UpdateGemCart(nullptr); }); - m_cartOverlay->show(); + m_gemCart->show(); - const QPoint parentPos = m_dropDownButton->mapToParent(m_dropDownButton->pos()); - const QPoint globalPos = m_dropDownButton->mapToGlobal(m_dropDownButton->pos()); - const QPoint offset(-4, 10); - m_cartOverlay->setGeometry(globalPos.x() - parentPos.x() - m_cartOverlay->width() + width() + offset.x(), - globalPos.y() + offset.y(), - m_cartOverlay->width(), - m_cartOverlay->height()); + emit UpdateGemCart(m_gemCart); } CartButton::~CartButton() { // Make sure the overlay window is automatically closed in case the gem catalog is destroyed. - if (m_cartOverlay) + if (m_gemCart) { - m_cartOverlay->deleteLater(); + m_gemCart->deleteLater(); } } @@ -514,6 +515,17 @@ namespace O3DE::ProjectManager connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded); connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved); + + connect( + m_cartButton, &CartButton::UpdateGemCart, this, + [this](QWidget* gemCart) + { + GemCartShown(gemCart); + if (gemCart) + { + emit UpdateGemCart(gemCart); + } + }); } void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/) @@ -521,7 +533,7 @@ namespace O3DE::ProjectManager m_downloadSpinner->show(); m_downloadLabel->show(); m_downloadSpinnerMovie->start(); - m_cartButton->ShowOverlay(); + m_cartButton->ShowGemCart(); } void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/) @@ -534,8 +546,44 @@ namespace O3DE::ProjectManager } } + void GemCatalogHeaderWidget::GemCartShown(bool state) + { + m_showGemCart = state; + repaint(); + } + void GemCatalogHeaderWidget::ReinitForProject() { m_filterLineEdit->setText({}); } + + void GemCatalogHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event) + { + // Only show triangle when cart is shown + if (!m_showGemCart) + { + return; + } + + const QPoint buttonPos = m_cartButton->pos(); + const QSize buttonSize = m_cartButton->size(); + + // Draw isosceles triangle with top point touching bottom of cartButton + // Bottom aligned with header bottom and top of right panel + const QPoint topPoint(buttonPos.x() + buttonSize.width() / 2, buttonPos.y() + buttonSize.height()); + const QPoint bottomLeftPoint(topPoint.x() - 20, height()); + const QPoint bottomRightPoint(topPoint.x() + 20, height()); + + QPainterPath trianglePath; + trianglePath.moveTo(topPoint); + trianglePath.lineTo(bottomLeftPoint); + trianglePath.lineTo(bottomRightPoint); + trianglePath.lineTo(topPoint); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(Qt::NoPen); + painter.fillPath(trianglePath, QBrush(QColor("#555555"))); + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 5174fde57d..b749e9831d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -14,8 +14,10 @@ #include #include #include -#include #include + +#include +#include #endif QT_FORWARD_DECLARE_CLASS(QPushButton) @@ -28,14 +30,14 @@ QT_FORWARD_DECLARE_CLASS(QMovie) namespace O3DE::ProjectManager { - class CartOverlayWidget - : public QWidget + class GemCartWidget + : public QScrollArea { Q_OBJECT // AUTOMOC public: - CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); - ~CartOverlayWidget(); + GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); + ~GemCartWidget(); public slots: void GemDownloadAdded(const QString& gemName); @@ -68,7 +70,10 @@ namespace O3DE::ProjectManager public: CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); ~CartButton(); - void ShowOverlay(); + void ShowGemCart(); + + signals: + void UpdateGemCart(QWidget* gemCart); private: void mousePressEvent(QMouseEvent* event) override; @@ -78,7 +83,7 @@ namespace O3DE::ProjectManager QHBoxLayout* m_layout = nullptr; QLabel* m_countLabel = nullptr; QPushButton* m_dropDownButton = nullptr; - CartOverlayWidget* m_cartOverlay = nullptr; + GemCartWidget* m_gemCart = nullptr; DownloadController* m_downloadController = nullptr; inline constexpr static int s_iconSize = 24; @@ -99,11 +104,16 @@ namespace O3DE::ProjectManager public slots: void GemDownloadAdded(const QString& gemName); void GemDownloadRemoved(const QString& gemName); + void GemCartShown(bool state = false); signals: void AddGem(); void OpenGemsRepo(); void RefreshGems(); + void UpdateGemCart(QWidget* gemCart); + + protected slots: + void paintEvent(QPaintEvent* event) override; private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; @@ -113,5 +123,6 @@ namespace O3DE::ProjectManager QLabel* m_downloadLabel = nullptr; QMovie* m_downloadSpinnerMovie = nullptr; CartButton* m_cartButton = nullptr; + bool m_showGemCart = false; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index be317f1ecd..c11a9b9de1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -8,6 +8,11 @@ #include #include +#include +#include +#include +#include +#include #include #include #include @@ -28,6 +33,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -51,9 +57,11 @@ namespace O3DE::ProjectManager vLayout->addWidget(m_headerWidget); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); + connect(m_gemModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, [this]{ ShowInspector(); }); connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); + connect(m_headerWidget, &GemCatalogHeaderWidget::UpdateGemCart, this, &GemCatalogScreen::UpdateAndShowGemCart); connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult); QHBoxLayout* hLayout = new QHBoxLayout(); @@ -61,8 +69,11 @@ namespace O3DE::ProjectManager vLayout->addLayout(hLayout); m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this); + + m_rightPanelStack = new QStackedWidget(this); + m_rightPanelStack->setFixedWidth(240); + m_gemInspector = new GemInspector(m_gemModel, this); - m_gemInspector->setFixedWidth(240); connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); }); connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem); @@ -85,7 +96,9 @@ namespace O3DE::ProjectManager hLayout->addWidget(filterWidget); hLayout->addLayout(middleVLayout); - hLayout->addWidget(m_gemInspector); + + hLayout->addWidget(m_rightPanelStack); + m_rightPanelStack->addWidget(m_gemInspector); m_notificationsView = AZStd::make_unique(this, AZ_CRC("GemCatalogNotificationsView")); m_notificationsView->SetOffset(QPoint(10, 70)); @@ -188,7 +201,7 @@ namespace O3DE::ProjectManager } // add all the gem repos into the hash - const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemReposGemInfos(); + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos(); if (allRepoGemInfosResult.IsSuccess()) { const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); @@ -303,6 +316,8 @@ namespace O3DE::ProjectManager QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex); m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); m_gemListView->scrollTo(proxyIndex); + + ShowInspector(); } void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex) @@ -440,7 +455,7 @@ namespace O3DE::ProjectManager m_gemModel->AddGem(gemInfo); } - const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemReposGemInfos(); + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos(); if (allRepoGemInfosResult.IsSuccess()) { const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); @@ -496,6 +511,12 @@ namespace O3DE::ProjectManager } } + void GemCatalogScreen::ShowInspector() + { + m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Inspector); + m_headerWidget->GemCartShown(); + } + GemCatalogScreen::EnableDisableGemsResult GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) { IPythonBindings* pythonBindings = PythonBindingsInterface::Get(); @@ -577,6 +598,18 @@ namespace O3DE::ProjectManager emit ChangeScreenRequest(ProjectManagerScreen::GemRepos); } + void GemCatalogScreen::UpdateAndShowGemCart(QWidget* cartWidget) + { + QWidget* previousCart = m_rightPanelStack->widget(RightPanelWidgetOrder::Cart); + if (previousCart) + { + m_rightPanelStack->removeWidget(previousCart); + } + + m_rightPanelStack->insertWidget(RightPanelWidgetOrder::Cart, cartWidget); + m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Cart); + } + void GemCatalogScreen::OnGemDownloadResult(const QString& gemName, bool succeeded) { if (succeeded) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index c5fbb057ef..2deaa41b9e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -12,18 +12,24 @@ #include #include #include -#include -#include -#include -#include -#include -#include + #include #include #endif +QT_FORWARD_DECLARE_CLASS(QVBoxLayout) +QT_FORWARD_DECLARE_CLASS(QStackedWidget) + namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(GemCatalogHeaderWidget) + QT_FORWARD_DECLARE_CLASS(GemFilterWidget) + QT_FORWARD_DECLARE_CLASS(GemListView) + QT_FORWARD_DECLARE_CLASS(GemInspector) + QT_FORWARD_DECLARE_CLASS(GemModel) + QT_FORWARD_DECLARE_CLASS(GemSortFilterProxyModel) + QT_FORWARD_DECLARE_CLASS(DownloadController) + class GemCatalogScreen : public ScreenWidget { @@ -62,14 +68,22 @@ namespace O3DE::ProjectManager private slots: void HandleOpenGemRepo(); - + void UpdateAndShowGemCart(QWidget* cartWidget); + void ShowInspector(); private: + enum RightPanelWidgetOrder + { + Inspector = 0, + Cart + }; + void FillModel(const QString& projectPath); AZStd::unique_ptr m_notificationsView; GemListView* m_gemListView = nullptr; + QStackedWidget* m_rightPanelStack = nullptr; GemInspector* m_gemInspector = nullptr; GemModel* m_gemModel = nullptr; GemCatalogHeaderWidget* m_headerWidget = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 26844b43e6..8bdd1f0f0f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -53,10 +53,13 @@ namespace O3DE::ProjectManager Update(selectedIndices[0]); } - void SetLabelElidedText(QLabel* label, QString text) + void SetLabelElidedText(QLabel* label, QString text, int labelWidth = 0) { QFontMetrics nameFontMetrics(label->font()); - int labelWidth = label->width(); + if (!labelWidth) + { + labelWidth = label->width(); + } // Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog) if (labelWidth > 100) @@ -84,7 +87,8 @@ namespace O3DE::ProjectManager m_summaryLabel->setText(m_model->GetSummary(modelIndex)); m_summaryLabel->adjustSize(); - m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex)); + // Manually define remaining space to elide text because spacer would like to take all of the space + SetLabelElidedText(m_licenseLinkLabel, m_model->GetLicenseText(modelIndex), width() - m_licenseLabel->width() - 35); m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex)); m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex)); @@ -175,8 +179,8 @@ namespace O3DE::ProjectManager licenseHLayout->setAlignment(Qt::AlignLeft); m_mainLayout->addLayout(licenseHLayout); - QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor); - licenseLabel->setText(tr("License: ")); + m_licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor); + m_licenseLabel->setText(tr("License: ")); m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize); licenseHLayout->addWidget(m_licenseLinkLabel); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index 6a5de17fcd..1713191623 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -64,6 +64,7 @@ namespace O3DE::ProjectManager QLabel* m_nameLabel = nullptr; QLabel* m_creatorLabel = nullptr; QLabel* m_summaryLabel = nullptr; + QLabel* m_licenseLabel = nullptr; LinkLabel* m_licenseLinkLabel = nullptr; LinkLabel* m_directoryLinkLabel = nullptr; LinkLabel* m_documentationLinkLabel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp index 18fb0cc235..436f84019a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp @@ -120,7 +120,7 @@ namespace O3DE::ProjectManager { QString repoUri = GetRepoUri(modelIndex); - const AZ::Outcome, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemRepoGemInfos(repoUri); + const AZ::Outcome, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForRepo(repoUri); if (gemInfosResult.IsSuccess()) { return gemInfosResult.GetValue(); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 91432c2346..f62c30c280 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -92,8 +92,9 @@ namespace O3DE::ProjectManager return; } - bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri); - if (addGemRepoResult) + AZ::Outcome < void, + AZStd::pair> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri); + if (addGemRepoResult.IsSuccess()) { Reinit(); emit OnRefresh(); @@ -101,8 +102,21 @@ namespace O3DE::ProjectManager else { QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri); - QMessageBox::critical(this, tr("Operation failed"), failureMessage); - AZ_Error("Project Manger", false, failureMessage.toUtf8()); + if (!addGemRepoResult.GetError().second.empty()) + { + QMessageBox addRepoError; + addRepoError.setIcon(QMessageBox::Critical); + addRepoError.setWindowTitle(failureMessage); + addRepoError.setText(addGemRepoResult.GetError().first.c_str()); + addRepoError.setDetailedText(addGemRepoResult.GetError().second.c_str()); + addRepoError.exec(); + } + else + { + QMessageBox::critical(this, failureMessage, addGemRepoResult.GetError().first.c_str()); + } + + AZ_Error("Project Manager", false, failureMessage.toUtf8()); } } } diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 98916cccf6..2f4fe901bb 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -203,6 +203,7 @@ namespace O3DE::ProjectManager QMenu* menu = new QMenu(this); menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); }); + menu->addAction(tr("Configure Gems..."), this, [this]() { emit EditProjectGems(m_projectInfo.m_path); }); menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); }); menu->addAction(tr("Open CMake GUI..."), this, [this]() { emit OpenCMakeGUI(m_projectInfo); }); menu->addSeparator(); diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index 5e81dfc2d9..ccb644c458 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -95,6 +95,7 @@ namespace O3DE::ProjectManager signals: void OpenProject(const QString& projectName); void EditProject(const QString& projectName); + void EditProjectGems(const QString& projectName); void CopyProject(const ProjectInfo& projectInfo); void RemoveProject(const QString& projectName); void DeleteProject(const QString& projectName); diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index 88ae3d6319..d4ac43d655 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -33,11 +34,23 @@ namespace O3DE::ProjectManager // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally QFrame* projectSettingsFrame = new QFrame(this); projectSettingsFrame->setObjectName("projectSettings"); - m_verticalLayout = new QVBoxLayout(); - // you cannot remove content margins in qss - m_verticalLayout->setContentsMargins(0, 0, 0, 0); + QVBoxLayout* vLayout = new QVBoxLayout(); + vLayout->setMargin(0); + vLayout->setAlignment(Qt::AlignTop); + projectSettingsFrame->setLayout(vLayout); + + QScrollArea* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + vLayout->addWidget(scrollArea); + + QWidget* scrollWidget = new QWidget(this); + scrollArea->setWidget(scrollWidget); + + m_verticalLayout = new QVBoxLayout(); + m_verticalLayout->setMargin(0); m_verticalLayout->setAlignment(Qt::AlignTop); + scrollWidget->setLayout(m_verticalLayout); m_projectName = new FormLineEditWidget(tr("Project name"), "", this); connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectNameUpdated); diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 7f962bee88..4a82783949 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -183,6 +183,7 @@ namespace O3DE::ProjectManager connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject); connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject); + connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems); connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject); connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject); connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject); @@ -469,6 +470,14 @@ namespace O3DE::ProjectManager emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject); } } + void ProjectsScreen::HandleEditProjectGems(const QString& projectPath) + { + if (!WarnIfInBuildQueue(projectPath)) + { + emit NotifyCurrentProject(projectPath); + emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); + } + } void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo) { if (!WarnIfInBuildQueue(projectInfo.m_path)) diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index 859f8d0eae..c690621fb6 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -46,6 +46,7 @@ namespace O3DE::ProjectManager void HandleAddProjectButton(); void HandleOpenProject(const QString& projectPath); void HandleEditProject(const QString& projectPath); + void HandleEditProjectGems(const QString& projectPath); void HandleCopyProject(const ProjectInfo& projectInfo); void HandleRemoveProject(const QString& projectPath); void HandleDeleteProject(const QString& projectPath); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 70473c86d4..12f97e6d2e 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -210,6 +211,16 @@ namespace RedirectOutput }); SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, []([[maybe_unused]] const char* msg) { + AZStd::string lastPythonError = msg; + constexpr const char* pythonErrorPrefix = "ERROR:root:"; + constexpr size_t lengthOfErrorPrefix = AZStd::char_traits::length(pythonErrorPrefix); + auto errorPrefix = lastPythonError.find(pythonErrorPrefix); + if (errorPrefix != AZStd::string::npos) + { + lastPythonError.erase(errorPrefix, lengthOfErrorPrefix); + } + O3DE::ProjectManager::PythonBindingsInterface::Get()->AddErrorString(lastPythonError); + AZ_TracePrintf("Python", msg); }); @@ -376,6 +387,8 @@ namespace O3DE::ProjectManager pybind11::gil_scoped_release release; pybind11::gil_scoped_acquire acquire; + ClearErrorStrings(); + try { executionCallback(); @@ -1051,7 +1064,7 @@ namespace O3DE::ProjectManager return result && refreshResult; } - bool PythonBindings::AddGemRepo(const QString& repoUri) + AZ::Outcome> PythonBindings::AddGemRepo(const QString& repoUri) { bool registrationResult = false; bool result = ExecuteWithLock( @@ -1065,7 +1078,12 @@ namespace O3DE::ProjectManager registrationResult = !pythonRegistrationResult.cast(); }); - return result && registrationResult; + if (!result || !registrationResult) + { + return AZ::Failure>(GetSimpleDetailedErrorPair()); + } + + return AZ::Success(); } bool PythonBindings::RemoveGemRepo(const QString& repoUri) @@ -1188,7 +1206,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemRepos)); } - AZ::Outcome, AZStd::string> PythonBindings::GetGemRepoGemInfos(const QString& repoUri) + AZ::Outcome, AZStd::string> PythonBindings::GetGemInfosForRepo(const QString& repoUri) { QVector gemInfos; AZ::Outcome result = ExecuteWithLockErrorHandling( @@ -1216,7 +1234,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemInfos)); } - AZ::Outcome, AZStd::string> PythonBindings::GetAllGemReposGemInfos() + AZ::Outcome, AZStd::string> PythonBindings::GetGemInfosForAllRepos() { QVector gemInfos; AZ::Outcome result = ExecuteWithLockErrorHandling( @@ -1243,7 +1261,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemInfos)); } - AZ::Outcome PythonBindings::DownloadGem( + AZ::Outcome> PythonBindings::DownloadGem( const QString& gemName, std::function gemProgressCallback, bool force) { // This process is currently limited to download a single gem at a time. @@ -1272,11 +1290,12 @@ namespace O3DE::ProjectManager if (!result.IsSuccess()) { - return result; + AZStd::pair pythonRunError(result.GetError(), result.GetError()); + return AZ::Failure>(AZStd::move(pythonRunError)); } else if (!downloadSucceeded) { - return AZ::Failure("Failed to download gem."); + return AZ::Failure>(GetSimpleDetailedErrorPair()); } return AZ::Success(); @@ -1302,4 +1321,23 @@ namespace O3DE::ProjectManager return result && updateAvaliableResult; } + + AZStd::pair PythonBindings::GetSimpleDetailedErrorPair() + { + AZStd::string detailedString = m_pythonErrorStrings.size() == 1 + ? "" + : AZStd::accumulate(m_pythonErrorStrings.begin(), m_pythonErrorStrings.end(), AZStd::string("")); + + return AZStd::pair(m_pythonErrorStrings.front(), detailedString); + } + + void PythonBindings::AddErrorString(AZStd::string errorString) + { + m_pythonErrorStrings.push_back(errorString); + } + + void PythonBindings::ClearErrorStrings() + { + m_pythonErrorStrings.clear(); + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index a021cfdacc..48841b6565 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -62,15 +62,19 @@ namespace O3DE::ProjectManager // Gem Repos AZ::Outcome RefreshGemRepo(const QString& repoUri) override; bool RefreshAllGemRepos() override; - bool AddGemRepo(const QString& repoUri) override; + AZ::Outcome> AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; - AZ::Outcome, AZStd::string> GetGemRepoGemInfos(const QString& repoUri) override; - AZ::Outcome, AZStd::string> GetAllGemReposGemInfos() override; - AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force = false) override; + AZ::Outcome, AZStd::string> GetGemInfosForRepo(const QString& repoUri) override; + AZ::Outcome, AZStd::string> GetGemInfosForAllRepos() override; + AZ::Outcome> DownloadGem( + const QString& gemName, std::function gemProgressCallback, bool force = false) override; void CancelDownload() override; bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override; + void AddErrorString(AZStd::string errorString) override; + void ClearErrorStrings() override; + private: AZ_DISABLE_COPY_MOVE(PythonBindings); @@ -83,6 +87,7 @@ namespace O3DE::ProjectManager AZ::Outcome GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false); bool RegisterThisEngine(); bool StopPython(); + AZStd::pair GetSimpleDetailedErrorPair(); bool m_pythonStarted = false; @@ -102,5 +107,6 @@ namespace O3DE::ProjectManager pybind11::handle m_pathlib; bool m_requestCancelDownload = false; + AZStd::vector m_pythonErrorStrings; }; } diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 2024f61ea0..c7c8af2ce1 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -200,9 +200,9 @@ namespace O3DE::ProjectManager /** * Registers this gem repo with the current engine. * @param repoUri the absolute filesystem path or url to the gem repo. - * @return true on success, false on failure. + * @return an outcome with a pair of string error and detailed messages on failure. */ - virtual bool AddGemRepo(const QString& repoUri) = 0; + virtual AZ::Outcome> AddGemRepo(const QString& repoUri) = 0; /** * Unregisters this gem repo with the current engine. @@ -218,26 +218,26 @@ namespace O3DE::ProjectManager virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; /** - * Gathers all gem infos for repos + * Gathers all gem infos from the provided repo * @param repoUri the absolute filesystem path or url to the gem repo. * @return A list of gem infos. */ - virtual AZ::Outcome, AZStd::string> GetGemRepoGemInfos(const QString& repoUri) = 0; + virtual AZ::Outcome, AZStd::string> GetGemInfosForRepo(const QString& repoUri) = 0; /** * Gathers all gem infos for all gems registered from repos. * @return A list of gem infos. */ - virtual AZ::Outcome, AZStd::string> GetAllGemReposGemInfos() = 0; + virtual AZ::Outcome, AZStd::string> GetGemInfosForAllRepos() = 0; /** * Downloads and registers a Gem. * @param gemName the name of the Gem to download. * @param gemProgressCallback a callback function that is called with an int percentage download value. * @param force should we forcibly overwrite the old version of the gem. - * @return an outcome with a string error message on failure. + * @return an outcome with a pair of string error and detailed messages on failure. */ - virtual AZ::Outcome DownloadGem( + virtual AZ::Outcome> DownloadGem( const QString& gemName, std::function gemProgressCallback, bool force = false) = 0; /** @@ -252,6 +252,17 @@ namespace O3DE::ProjectManager * @return true if update is avaliable, false if not. */ virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0; + + /** + * Add an error string to be returned when the current python call is complete. + * @param The error string to be displayed. + */ + virtual void AddErrorString(AZStd::string errorString) = 0; + + /** + * Clears the current list of error strings. + */ + virtual void ClearErrorStrings() = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/Source/ScreenWidget.h b/Code/Tools/ProjectManager/Source/ScreenWidget.h index 148dcdb8c8..a84fb0be80 100644 --- a/Code/Tools/ProjectManager/Source/ScreenWidget.h +++ b/Code/Tools/ProjectManager/Source/ScreenWidget.h @@ -47,9 +47,9 @@ namespace O3DE::ProjectManager return tr("Missing"); } - virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen) + virtual bool ContainsScreen(ProjectManagerScreen screen) { - return false; + return GetScreenEnum() == screen; } virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen) { @@ -58,7 +58,6 @@ namespace O3DE::ProjectManager //! Notify this screen it is the current screen virtual void NotifyCurrentScreen() { - } signals: diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 6cb934560a..36d78ed2ac 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -15,8 +15,8 @@ #include #include #include +#include #include - #include #include @@ -97,6 +97,17 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProject; } + bool UpdateProjectCtrl::ContainsScreen(ProjectManagerScreen screen) + { + // Do not include GemRepos because we don't want to advertise jumping to it from all other screens here + return screen == GetScreenEnum() || screen == ProjectManagerScreen::GemCatalog; + } + + void UpdateProjectCtrl::GoToScreen(ProjectManagerScreen screen) + { + OnChangeScreenRequest(screen); + } + // Called when pressing "Edit Project Settings..." void UpdateProjectCtrl::NotifyCurrentScreen() { @@ -117,6 +128,16 @@ namespace O3DE::ProjectManager m_stack->setCurrentWidget(m_gemRepoScreen); Update(); } + else if (screen == ProjectManagerScreen::GemCatalog) + { + m_stack->setCurrentWidget(m_gemCatalogScreen); + Update(); + } + else if (screen == ProjectManagerScreen::UpdateProjectSettings) + { + m_stack->setCurrentWidget(m_updateSettingsScreen); + Update(); + } else { emit ChangeScreenRequest(screen); diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index ee6fc792f2..070e2c58bf 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -24,7 +24,8 @@ namespace O3DE::ProjectManager QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) QT_FORWARD_DECLARE_CLASS(GemRepoScreen) - class UpdateProjectCtrl : public ScreenWidget + class UpdateProjectCtrl + : public ScreenWidget { Q_OBJECT public: @@ -32,7 +33,8 @@ namespace O3DE::ProjectManager ~UpdateProjectCtrl() = default; ProjectManagerScreen GetScreenEnum() override; - protected: + bool ContainsScreen(ProjectManagerScreen screen) override; + void GoToScreen(ProjectManagerScreen screen) override; void NotifyCurrentScreen() override; protected slots: diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index 3bfc07c5b0..a430102c27 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -35,7 +35,7 @@ namespace O3DE::ProjectManager previewExtrasLayout->setContentsMargins(50, 0, 0, 0); QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") - .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); projectPreviewLabel->setObjectName("projectPreviewLabel"); previewExtrasLayout->addWidget(projectPreviewLabel); diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index a90518006f..81ea166848 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -84,7 +84,7 @@ static constexpr const char TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_F }, "AccountId": "", "Region": "us-west-2", - "Version": "1.0.0" + "Version": "1.1.0" })"; static constexpr const char TEST_INVALID_RESOURCE_MAPPING_CONFIG_FILE[] = diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py index 7926cca970..8c094a895c 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py @@ -24,7 +24,7 @@ _RESOURCE_MAPPING_TYPE_JSON_KEY_NAME: str = "Type" _RESOURCE_MAPPING_NAMEID_JSON_KEY_NAME: str = "Name/ID" _RESOURCE_MAPPING_REGION_JSON_KEY_NAME: str = "Region" _RESOURCE_MAPPING_VERSION_JSON_KEY_NAME: str = "Version" -_RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.0.0" +_RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.1.0" RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME: str = "AccountId" RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: str = "EMPTY" diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl index 6d55648f22..db91c369ef 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.azsl @@ -48,6 +48,9 @@ void MainCS(uint3 group_thread_id : SV_GroupThreadID, uint3 group_id : SV_GroupI LDS_MAX_COC[group_thread_id.x] = 0; } + // Sync LDS + GroupMemoryBarrierWithGroupSync(); + // We use gather to get 2x2 values at once, so thread samples are spaced 2 pixels apart (+1 so the sample position is in between the four pixels) float2 samplePos = float2(dispatch_id.xy) * 2 + float2(1, 1); float2 sampleUV = samplePos * PassSrg::m_inputDimensions.zw; @@ -74,6 +77,9 @@ void MainCS(uint3 group_thread_id : SV_GroupThreadID, uint3 group_id : SV_GroupI InterlockedMin( LDS_MIN_COC[0], LDS_MIN_COC[group_thread_id.x] ); InterlockedMax( LDS_MAX_COC[0], LDS_MAX_COC[group_thread_id.x] ); + // Sync LDS + GroupMemoryBarrierWithGroupSync(); + // Each group write to just one pixel. If we're the last thread in the group, write out if(group_thread_id.x == 0) { diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ModelPreset.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ModelPreset.h index 8bd494dfde..2dd4bed264 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ModelPreset.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ModelPreset.h @@ -30,7 +30,6 @@ namespace AZ AZStd::string m_displayName; AZ::Data::Asset m_modelAsset; - AZ::Data::Asset m_previewImageAsset; }; using ModelPresetPtr = AZStd::shared_ptr; diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomBase.h b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomBase.h index fe731f4ad6..b329a35977 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomBase.h +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomBase.h @@ -155,8 +155,10 @@ namespace AZ enum AuxGeomShapeType { ShapeType_Sphere, + ShapeType_Hemisphere, ShapeType_Cone, ShapeType_Cylinder, + ShapeType_CylinderNoEnds, // Cylinder without disks on either end ShapeType_Disk, ShapeType_Quad, diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp index 29db7d6673..e2998e5009 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp @@ -314,15 +314,40 @@ namespace AZ AddShape(style, shape); } - void AuxGeomDrawQueue::DrawSphere( - const AZ::Vector3& center, + Matrix3x3 CreateMatrix3x3FromDirection(const AZ::Vector3& direction) + { + Vector3 unitDirection(direction.GetNormalized()); + Vector3 unitOrthogonal(direction.GetOrthogonalVector().GetNormalized()); + Vector3 unitCross(unitOrthogonal.Cross(unitDirection)); + return Matrix3x3::CreateFromColumns(unitOrthogonal, unitDirection, unitCross); + } + + void AuxGeomDrawQueue::DrawSphere(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) + { + DrawSphereCommon(center, direction, radius, color, style, depthTest, depthWrite, faceCull, viewProjOverrideIndex, false); + } + + void AuxGeomDrawQueue::DrawSphere(const AZ::Vector3& center, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) + { + DrawSphereCommon(center, AZ::Vector3::CreateAxisZ(), radius, color, style, depthTest, depthWrite, faceCull, viewProjOverrideIndex, false); + } + + void AuxGeomDrawQueue::DrawHemisphere(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) + { + DrawSphereCommon(center, direction, radius, color, style, depthTest, depthWrite, faceCull, viewProjOverrideIndex, true); + } + + void AuxGeomDrawQueue::DrawSphereCommon( + const AZ::Vector3& center, + const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, - int32_t viewProjOverrideIndex) + int32_t viewProjOverrideIndex, + bool isHemisphere) { if (radius <= 0.0f) { @@ -330,12 +355,12 @@ namespace AZ } ShapeBufferEntry shape; - shape.m_shapeType = ShapeType_Sphere; + shape.m_shapeType = isHemisphere ? ShapeType_Hemisphere : ShapeType_Sphere; shape.m_depthRead = ConvertRPIDepthTestFlag(depthTest); shape.m_depthWrite = ConvertRPIDepthWriteFlag(depthWrite); shape.m_faceCullMode = ConvertRPIFaceCullFlag(faceCull); shape.m_color = color; - shape.m_rotationMatrix = Matrix3x3::CreateIdentity(); + shape.m_rotationMatrix = CreateMatrix3x3FromDirection(direction); shape.m_position = center; shape.m_scale = AZ::Vector3(radius, radius, radius); shape.m_pointSize = m_pointSize; @@ -362,13 +387,9 @@ namespace AZ shape.m_faceCullMode = ConvertRPIFaceCullFlag(faceCull); shape.m_color = color; - Vector3 unitDirection(direction.GetNormalized()); - Vector3 unitOrthogonal(direction.GetOrthogonalVector().GetNormalized()); - Vector3 unitCross(unitOrthogonal.Cross(unitDirection)); - // The disk mesh is created with the top of the disk pointing along the positive Y axis. This creates a // rotation so that the top of the disk will point along the given direction vector. - shape.m_rotationMatrix = Matrix3x3::CreateFromColumns(unitOrthogonal, unitDirection, unitCross); + shape.m_rotationMatrix = CreateMatrix3x3FromDirection(direction); shape.m_position = center; shape.m_scale = AZ::Vector3(radius, 1.0f, radius); shape.m_pointSize = m_pointSize; @@ -401,13 +422,7 @@ namespace AZ shape.m_faceCullMode = ConvertRPIFaceCullFlag(faceCull); shape.m_color = color; - Vector3 unitDirection(direction.GetNormalized()); - Vector3 unitOrthogonal(direction.GetOrthogonalVector().GetNormalized()); - Vector3 unitCross(unitOrthogonal.Cross(unitDirection)); - - // The cone mesh is created with the tip of the cone pointing along the positive Y axis. This creates a - // rotation so that the tip of the cone will point along the given direction vector. - shape.m_rotationMatrix = Matrix3x3::CreateFromColumns(unitOrthogonal, unitDirection, unitCross); + shape.m_rotationMatrix = CreateMatrix3x3FromDirection(direction); shape.m_position = center; shape.m_scale = AZ::Vector3(radius, height, radius); shape.m_pointSize = m_pointSize; @@ -416,17 +431,30 @@ namespace AZ AddShape(style, shape); } - void AuxGeomDrawQueue::DrawCylinder( - const AZ::Vector3& center, - const AZ::Vector3& direction, - float radius, - float height, - const AZ::Color& color, - DrawStyle style, - DepthTest depthTest, - DepthWrite depthWrite, - FaceCullMode faceCull, - int32_t viewProjOverrideIndex) + void AuxGeomDrawQueue::DrawCylinder(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, + DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) + { + DrawCylinderCommon(center, direction, radius, height, color, style, depthTest, depthWrite, faceCull, viewProjOverrideIndex, true); + } + + void AuxGeomDrawQueue::DrawCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, + DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) + { + DrawCylinderCommon(center, direction, radius, height, color, style, depthTest, depthWrite, faceCull, viewProjOverrideIndex, false); + } + + void AuxGeomDrawQueue::DrawCylinderCommon( + const AZ::Vector3& center, + const AZ::Vector3& direction, + float radius, + float height, + const AZ::Color& color, + DrawStyle style, + DepthTest depthTest, + DepthWrite depthWrite, + FaceCullMode faceCull, + int32_t viewProjOverrideIndex, + bool drawEnds) { if (radius <= 0.0f || height <= 0.0f) { @@ -434,19 +462,15 @@ namespace AZ } ShapeBufferEntry shape; - shape.m_shapeType = ShapeType_Cylinder; - shape.m_depthRead = ConvertRPIDepthTestFlag(depthTest); + shape.m_shapeType = drawEnds ? ShapeType_Cylinder : ShapeType_CylinderNoEnds; + shape.m_depthRead = ConvertRPIDepthTestFlag(depthTest); shape.m_depthWrite = ConvertRPIDepthWriteFlag(depthWrite); shape.m_faceCullMode = ConvertRPIFaceCullFlag(faceCull); shape.m_color = color; - Vector3 unitDirection(direction.GetNormalized()); - Vector3 unitOrthogonal(direction.GetOrthogonalVector().GetNormalized()); - Vector3 unitCross(unitOrthogonal.Cross(unitDirection)); - // The cylinder mesh is created with the top end cap of the cylinder facing along the positive Y axis. This creates a // rotation so that the top face of the cylinder will face along the given direction vector. - shape.m_rotationMatrix = Matrix3x3::CreateFromColumns(unitOrthogonal, unitDirection, unitCross); + shape.m_rotationMatrix = CreateMatrix3x3FromDirection(direction); shape.m_position = center; shape.m_scale = AZ::Vector3(radius, height, radius); shape.m_pointSize = m_pointSize; diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.h b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.h index 535a992fe0..7fa1bbdca8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.h +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.h @@ -60,9 +60,12 @@ namespace AZ // Fixed shape draws void DrawQuad(float width, float height, const AZ::Matrix3x4& transform, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawSphere(const AZ::Vector3& center, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; + void DrawSphere(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; + void DrawHemisphere(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawDisk(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawCone(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawCylinder(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; + void DrawCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawAabb(const AZ::Aabb& aabb, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawAabb(const AZ::Aabb& aabb, const AZ::Matrix3x4& transform, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; void DrawObb(const AZ::Obb& obb, const AZ::Vector3& position, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex) override; @@ -73,6 +76,9 @@ namespace AZ private: // functions + void DrawCylinderCommon(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex, bool drawEnds); + void DrawSphereCommon(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style, DepthTest depthTest, DepthWrite depthWrite, FaceCullMode faceCull, int32_t viewProjOverrideIndex, bool isHemisphere); + //! Clear the current buffers void ClearCurrentBufferData(); diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp index c2ee397b4c..2e7c0759db 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp @@ -10,6 +10,7 @@ #include "AuxGeomDrawProcessorShared.h" #include +#include #include #include @@ -69,11 +70,13 @@ namespace AZ SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Solid], RHI::PrimitiveTopology::TriangleList, false); SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Shaded], RHI::PrimitiveTopology::TriangleList, true); - CreateSphereBuffersAndViews(); + CreateSphereBuffersAndViews(AuxGeomShapeType::ShapeType_Sphere); + CreateSphereBuffersAndViews(AuxGeomShapeType::ShapeType_Hemisphere); CreateQuadBuffersAndViews(); CreateDiskBuffersAndViews(); CreateConeBuffersAndViews(); - CreateCylinderBuffersAndViews(); + CreateCylinderBuffersAndViews(AuxGeomShapeType::ShapeType_Cylinder); + CreateCylinderBuffersAndViews(AuxGeomShapeType::ShapeType_CylinderNoEnds); CreateBoxBuffersAndViews(); // cache scene pointer for RHI::PipelineState creation. @@ -293,8 +296,11 @@ namespace AZ } } - bool FixedShapeProcessor::CreateSphereBuffersAndViews() + bool FixedShapeProcessor::CreateSphereBuffersAndViews(AuxGeomShapeType sphereShapeType) { + AZ_Assert(sphereShapeType == ShapeType_Sphere || sphereShapeType == ShapeType_Hemisphere, + "Trying to create sphere buffers and views with a non-sphere shape type!"); + const uint32_t numSphereLods = 5; struct LodInfo { @@ -311,13 +317,13 @@ namespace AZ { 9, 9, 0.0000f} }}; - auto& m_shape = m_shapes[ShapeType_Sphere]; + auto& m_shape = m_shapes[sphereShapeType]; m_shape.m_numLods = numSphereLods; for (uint32_t lodIndex = 0; lodIndex < numSphereLods; ++lodIndex) { MeshData meshData; - CreateSphereMeshData(meshData, lodInfo[lodIndex].numRings, lodInfo[lodIndex].numSections); + CreateSphereMeshData(meshData, lodInfo[lodIndex].numRings, lodInfo[lodIndex].numSections, sphereShapeType); ObjectBuffers objectBuffers; @@ -334,12 +340,25 @@ namespace AZ return true; } - void FixedShapeProcessor::CreateSphereMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections) + void FixedShapeProcessor::CreateSphereMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections, AuxGeomShapeType sphereShapeType) { const float radius = 1.0f; + // calculate "inner" vertices + float sectionAngle(DegToRad(360.0f / static_cast(numSections))); + float ringSlice(DegToRad(180.0f / static_cast(numRings))); + + uint32_t numberOfPoles = 2; + + if (sphereShapeType == ShapeType_Hemisphere) + { + numberOfPoles = 1; + numRings = (numRings + 1) / 2; + ringSlice = DegToRad(90.0f / static_cast(numRings)); + } + // calc required number of vertices/indices/triangles to build a sphere for the given parameters - uint32_t numVertices = (numRings - 1) * numSections + 2; + uint32_t numVertices = (numRings - 1) * numSections + numberOfPoles; // setup buffers auto& positions = meshData.m_positions; @@ -354,30 +373,29 @@ namespace AZ using NormalType = AuxGeomNormal; // 1st pole vertex - positions.push_back(PosType(0.0f, 0.0f, radius)); - normals.push_back(NormalType(0.0f, 0.0f, 1.0f)); + positions.push_back(PosType(0.0f, radius, 0.0f)); + normals.push_back(NormalType(0.0f, 1.0f, 0.0f)); - // calculate "inner" vertices - float sectionAngle(DegToRad(360.0f / static_cast(numSections))); - float ringSlice(DegToRad(180.0f / static_cast(numRings))); - - for (uint32_t ring = 1; ring < numRings; ++ring) + for (uint32_t ring = 1; ring < numRings - numberOfPoles + 2; ++ring) { float w(sinf(ring * ringSlice)); for (uint32_t section = 0; section < numSections; ++section) { float x = radius * cosf(section * sectionAngle) * w; - float y = radius * sinf(section * sectionAngle) * w; - float z = radius * cosf(ring * ringSlice); + float y = radius * cosf(ring * ringSlice); + float z = radius * sinf(section * sectionAngle) * w; Vector3 radialVector(x, y, z); positions.push_back(radialVector); normals.push_back(radialVector.GetNormalized()); } } - // 2nd vertex of pole (for end cap) - positions.push_back(PosType(0.0f, 0.0f, -radius)); - normals.push_back(NormalType(0.0f, 0.0f, -1.0f)); + if (sphereShapeType == ShapeType_Sphere) + { + // 2nd vertex of pole (for end cap) + positions.push_back(PosType(0.0f, -radius, 0.0f)); + normals.push_back(NormalType(0.0f, -1.0f, 0.0f)); + } // point indices { @@ -393,7 +411,8 @@ namespace AZ // line indices { - const uint32_t numEdges = (numRings - 2) * numSections * 2 + 2 * numSections * 2; + // NumEdges = NumRingEdges + NumSectionEdges = (numRings * numSections) + (numRings * numSections) + const uint32_t numEdges = numRings * numSections * 2; const uint32_t numLineIndices = numEdges * 2; // build "inner" faces @@ -401,10 +420,9 @@ namespace AZ indices.clear(); indices.reserve(numLineIndices); - for (uint16_t ring = 0; ring < numRings - 2; ++ring) + for (uint16_t ring = 0; ring < numRings - numberOfPoles + 1; ++ring) { uint16_t firstVertOfThisRing = static_cast(1 + ring * numSections); - uint16_t firstVertOfNextRing = static_cast(1 + (ring + 1) * numSections); for (uint16_t section = 0; section < numSections; ++section) { uint32_t nextSection = (section + 1) % numSections; @@ -414,32 +432,33 @@ namespace AZ indices.push_back(static_cast(firstVertOfThisRing + nextSection)); // line around section - indices.push_back(firstVertOfThisRing + section); - indices.push_back(firstVertOfNextRing + section); + int currentVertexIndex = firstVertOfThisRing + section; + // max 0 will implicitly handle the top pole + int previousVertexIndex = AZStd::max(currentVertexIndex - (int)numSections, 0); + indices.push_back(static_cast(currentVertexIndex)); + indices.push_back(static_cast(previousVertexIndex)); } } - // build faces for end caps (to connect "inner" vertices with poles) - uint16_t firstPoleVert = 0; - uint16_t firstVertOfFirstRing = static_cast(1 + (0) * numSections); - for (uint16_t section = 0; section < numSections; ++section) + if (sphereShapeType == ShapeType_Sphere) { - indices.push_back(firstPoleVert); - indices.push_back(firstVertOfFirstRing + section); - } - - uint16_t lastPoleVert = static_cast((numRings - 1) * numSections + 1); - uint16_t firstVertOfLastRing = static_cast(1 + (numRings - 2) * numSections); - for (uint16_t section = 0; section < numSections; ++section) - { - indices.push_back(firstVertOfLastRing + section); - indices.push_back(lastPoleVert); + // build faces for bottom pole (to connect "inner" vertices with poles) + uint16_t lastPoleVert = static_cast((numRings - 1) * numSections + 1); + uint16_t firstVertOfLastRing = static_cast(1 + (numRings - 2) * numSections); + for (uint16_t section = 0; section < numSections; ++section) + { + indices.push_back(firstVertOfLastRing + section); + indices.push_back(lastPoleVert); + } } } // triangle indices { - const uint32_t numTriangles = (numRings - 2) * numSections * 2 + 2 * numSections; + // NumTriangles = NumTrianglesAtPoles + NumQuads * 2 + // = (numSections * 2) + ((numRings - 2) * numSections * 2) + // = (numSections * 2) * (numRings - 2 + 1) + const uint32_t numTriangles = (numRings - 1) * numSections * 2; const uint32_t numTriangleIndices = numTriangles * 3; // build "inner" faces @@ -447,10 +466,10 @@ namespace AZ indices.clear(); indices.reserve(numTriangleIndices); - for (uint32_t ring = 0; ring < numRings - 2; ++ring) + for (uint32_t ring = 0; ring < numRings - numberOfPoles; ++ring) { uint32_t firstVertOfThisRing = 1 + ring * numSections; - uint32_t firstVertOfNextRing = 1 + (ring + 1) * numSections; + uint32_t firstVertOfNextRing = firstVertOfThisRing + numSections; for (uint32_t section = 0; section < numSections; ++section) { @@ -476,14 +495,17 @@ namespace AZ indices.push_back(static_cast(firstPoleVert)); } - uint32_t lastPoleVert = (numRings - 1) * numSections + 1; - uint32_t firstVertOfLastRing = 1 + (numRings - 2) * numSections; - for (uint32_t section = 0; section < numSections; ++section) + if (sphereShapeType == ShapeType_Sphere) { - uint32_t nextSection = (section + 1) % numSections; - indices.push_back(static_cast(firstVertOfLastRing + nextSection)); - indices.push_back(static_cast(firstVertOfLastRing + section)); - indices.push_back(static_cast(lastPoleVert)); + uint32_t lastPoleVert = (numRings - 1) * numSections + 1; + uint32_t firstVertOfLastRing = 1 + (numRings - 2) * numSections; + for (uint32_t section = 0; section < numSections; ++section) + { + uint32_t nextSection = (section + 1) % numSections; + indices.push_back(static_cast(firstVertOfLastRing + nextSection)); + indices.push_back(static_cast(firstVertOfLastRing + section)); + indices.push_back(static_cast(lastPoleVert)); + } } } } @@ -827,8 +849,11 @@ namespace AZ } } - bool FixedShapeProcessor::CreateCylinderBuffersAndViews() + bool FixedShapeProcessor::CreateCylinderBuffersAndViews(AuxGeomShapeType cylinderShapeType) { + AZ_Assert(cylinderShapeType == ShapeType_Cylinder || cylinderShapeType == ShapeType_CylinderNoEnds, + "Trying to create cylinder buffers and views with a non-cylinder shape type!"); + const uint32_t numCylinderLods = 5; struct LodInfo { @@ -836,21 +861,21 @@ namespace AZ float screenPercentage; }; const AZStd::array lodInfo = - {{ + { { { 38, 0.1000f}, { 22, 0.0100f}, { 14, 0.0010f}, { 10, 0.0001f}, { 8, 0.0000f} - }}; + } }; - auto& m_shape = m_shapes[ShapeType_Cylinder]; + auto& m_shape = m_shapes[cylinderShapeType]; m_shape.m_numLods = numCylinderLods; for (uint32_t lodIndex = 0; lodIndex < numCylinderLods; ++lodIndex) { MeshData meshData; - CreateCylinderMeshData(meshData, lodInfo[lodIndex].numSections); + CreateCylinderMeshData(meshData, lodInfo[lodIndex].numSections, cylinderShapeType); ObjectBuffers objectBuffers; @@ -867,13 +892,25 @@ namespace AZ return true; } - void FixedShapeProcessor::CreateCylinderMeshData(MeshData& meshData, uint32_t numSections) + void FixedShapeProcessor::CreateCylinderMeshData(MeshData& meshData, uint32_t numSections, AuxGeomShapeType cylinderShapeType) { const float radius = 1.0f; const float height = 1.0f; + //uint16_t indexOfBottomCenter = 0; + //uint16_t indexOfBottomStart = 1; + //uint16_t indexOfTopCenter = numSections + 1; + //uint16_t indexOfTopStart = numSections + 2; + uint16_t indexOfSidesStart = static_cast(2 * numSections + 2); + + if (cylinderShapeType == ShapeType_CylinderNoEnds) + { + // We won't draw disks at the ends of the cylinder, so no need to offset side indices + indexOfSidesStart = 0; + } + // calc required number of vertices to build a cylinder for the given parameters - uint32_t numVertices = 4 * numSections + 2; + uint32_t numVertices = indexOfSidesStart + 2 * numSections; // setup buffers auto& positions = meshData.m_positions; @@ -888,8 +925,11 @@ namespace AZ float topHeight = height * 0.5f; // Create caps - CreateDiskMeshData(meshData, numSections, Facing::Down, bottomHeight); - CreateDiskMeshData(meshData, numSections, Facing::Up, topHeight); + if (cylinderShapeType == ShapeType_Cylinder) + { + CreateDiskMeshData(meshData, numSections, Facing::Down, bottomHeight); + CreateDiskMeshData(meshData, numSections, Facing::Up, topHeight); + } // create vertices for side (so normal points out correctly) float sectionAngle(DegToRad(360.0f / (float)numSections)); @@ -906,12 +946,6 @@ namespace AZ normals.push_back(normal); } - //uint16_t indexOfBottomCenter = 0; - //uint16_t indexOfBottomStart = 1; - //uint16_t indexOfTopCenter = numSections + 1; - //uint16_t indexOfTopStart = numSections + 2; - uint16_t indexOfSidesStart = static_cast(2 * numSections + 2); - // build point indices { auto& indices = meshData.m_pointIndices; @@ -930,6 +964,24 @@ namespace AZ indices.push_back(indexOfSidesStart + 2 * section); indices.push_back(indexOfSidesStart + 2 * section + 1); } + + // If we're not drawing the disks at the ends of the cylinder, we still want to + // draw a ring around the end to join the tips of lines we created just above + if (cylinderShapeType == ShapeType_CylinderNoEnds) + { + for (uint16_t section = 0; section < numSections; ++section) + { + uint16_t nextSection = (section + 1) % numSections; + + // line around the bottom cap + indices.push_back(section * 2); + indices.push_back(nextSection * 2); + + // line around the top cap + indices.push_back(section * 2 + 1); + indices.push_back(nextSection * 2 + 1); + } + } } // indices for triangles diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.h b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.h index 4007d62f66..958cee3143 100644 --- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.h @@ -138,8 +138,8 @@ namespace AZ Both, }; - bool CreateSphereBuffersAndViews(); - void CreateSphereMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections); + bool CreateSphereBuffersAndViews(AuxGeomShapeType sphereShapeType); + void CreateSphereMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections, AuxGeomShapeType sphereShapeType); bool CreateQuadBuffersAndViews(); void CreateQuadMeshDataSide(MeshData& meshData, bool isUp, bool drawLines); @@ -152,8 +152,8 @@ namespace AZ bool CreateConeBuffersAndViews(); void CreateConeMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections); - bool CreateCylinderBuffersAndViews(); - void CreateCylinderMeshData(MeshData& meshData, uint32_t numSections); + bool CreateCylinderBuffersAndViews(AuxGeomShapeType cylinderShapeType); + void CreateCylinderMeshData(MeshData& meshData, uint32_t numSections, AuxGeomShapeType cylinderShapeType); bool CreateBoxBuffersAndViews(); void CreateBoxMeshData(MeshData& meshData); diff --git a/Gems/Atom/Feature/Common/Code/Source/Utils/EditorModelPreset.cpp b/Gems/Atom/Feature/Common/Code/Source/Utils/EditorModelPreset.cpp index 63c2fc7150..d636fde2fe 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Utils/EditorModelPreset.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Utils/EditorModelPreset.cpp @@ -29,7 +29,6 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &ModelPreset::m_displayName, "Display Name", "Identifier used for display and selection") ->DataElement(AZ::Edit::UIHandlers::Default, &ModelPreset::m_modelAsset, "Model Asset", "Model asset reference") - ->DataElement(AZ::Edit::UIHandlers::Default, &ModelPreset::m_previewImageAsset, "Preview Image Asset", "Preview image asset reference") ; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Utils/ModelPreset.cpp b/Gems/Atom/Feature/Common/Code/Source/Utils/ModelPreset.cpp index c684b3cc89..ce8a1680a9 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Utils/ModelPreset.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Utils/ModelPreset.cpp @@ -24,10 +24,9 @@ namespace AZ if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(3) + ->Version(4) ->Field("displayName", &ModelPreset::m_displayName) ->Field("modelAsset", &ModelPreset::m_modelAsset) - ->Field("previewImageAsset", &ModelPreset::m_previewImageAsset) ; } @@ -41,7 +40,6 @@ namespace AZ ->Constructor() ->Property("displayName", BehaviorValueProperty(&ModelPreset::m_displayName)) ->Property("modelAsset", BehaviorValueProperty(&ModelPreset::m_modelAsset)) - ->Property("previewImageAsset", BehaviorValueProperty(&ModelPreset::m_previewImageAsset)) ; } } diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png index 3e9bc68ea5..198d034892 100644 --- a/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28c3cfd8958813b4b539738bfff589731da0aeec5b3376558f377da2ebe973ff -size 5455 +oid sha256:7028c8db4f935f23aa4396668278a67691d92fe345cc9d417a9f47bd9a4af32b +size 8130 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png.assetinfo b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png.assetinfo new file mode 100644 index 0000000000..264a7f2a25 --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png.assetinfo @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png index 914499d6e7..14c3ec76b0 100644 --- a/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a0935be7347d695ed1716d030b5bae68153a88239b0ff15f67f900ac90be442 -size 5038 +oid sha256:a7d94b9c0a77741736b93d8ed4d2b22bc9ae4cf649f3d8b3f10cbaf595a3ed31 +size 8336 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png.assetinfo b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png.assetinfo new file mode 100644 index 0000000000..4a234ef9f3 --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png.assetinfo @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png index 558b16b96e..aafdcc2681 100644 --- a/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb243cd6d6414b4e95eab919fa94193b57825902b8d09f40ce3f334d829e74e2 -size 6286 +oid sha256:3bbd45e3ef81850da81d1cc01775930a53c424e64e287b00990af3e7e6a682ba +size 9701 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png.assetinfo b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png.assetinfo new file mode 100644 index 0000000000..747ce3e0e2 --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png.assetinfo @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h index bd11965ffa..f758019cfb 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h @@ -12,6 +12,7 @@ #include #include +#include namespace AZ { @@ -21,21 +22,24 @@ namespace AZ { // Declarations... - Outcome MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId); + // Note that these functions default to TraceLevel::Error to preserve legacy behavior of these APIs. It would be nice to make the default match + // RPI.Reflect/Asset/AssetUtils.h which is TraceLevel::Warning, but we are close to a release so it isn't worth the risk at this time. - Outcome MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId); + Outcome MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId, TraceLevel reporting = TraceLevel::Error); + + Outcome MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId, TraceLevel reporting = TraceLevel::Error); template - Outcome> LoadAsset(const AZStd::string& sourcePath, uint32_t productSubId = 0); + Outcome> LoadAsset(const AZStd::string& sourcePath, uint32_t productSubId = 0, TraceLevel reporting = TraceLevel::Error); template - Outcome> LoadAsset(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId = 0); + Outcome> LoadAsset(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId = 0, TraceLevel reporting = TraceLevel::Error); template - Outcome> LoadAsset(const AZ::Data::AssetId& assetId, const char* sourcePathForDebug); + Outcome> LoadAsset(const AZ::Data::AssetId& assetId, const char* sourcePathForDebug, TraceLevel reporting = TraceLevel::Error); template - Outcome> LoadAsset(const AZ::Data::AssetId& assetId); + Outcome> LoadAsset(const AZ::Data::AssetId& assetId, TraceLevel reporting = TraceLevel::Error); //! Attempts to resolve the full path to a product asset given its ID AZStd::string GetProductPathByAssetId(const AZ::Data::AssetId& assetId); @@ -65,12 +69,12 @@ namespace AZ // Definitions... template - Outcome> LoadAsset(const AZStd::string& sourcePath, uint32_t productSubId) + Outcome> LoadAsset(const AZStd::string& sourcePath, uint32_t productSubId, TraceLevel reporting) { - auto assetId = MakeAssetId(sourcePath, productSubId); + auto assetId = MakeAssetId(sourcePath, productSubId, reporting); if (assetId.IsSuccess()) { - return LoadAsset(assetId.GetValue(), sourcePath.c_str()); + return LoadAsset(assetId.GetValue(), sourcePath.c_str(), reporting); } else { @@ -79,20 +83,20 @@ namespace AZ } template - Outcome> LoadAsset(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId) + Outcome> LoadAsset(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId, TraceLevel reporting) { AZStd::string resolvedPath = ResolvePathReference(originatingSourcePath, referencedSourceFilePath); - return LoadAsset(resolvedPath, productSubId); + return LoadAsset(resolvedPath, productSubId, reporting); } template - Outcome> LoadAsset(const AZ::Data::AssetId& assetId) + Outcome> LoadAsset(const AZ::Data::AssetId& assetId, TraceLevel reporting) { - return LoadAsset(assetId, nullptr); + return LoadAsset(assetId, nullptr, reporting); } template - Outcome> LoadAsset(const AZ::Data::AssetId& assetId, [[maybe_unused]] const char* sourcePathForDebug) + Outcome> LoadAsset(const AZ::Data::AssetId& assetId, [[maybe_unused]] const char* sourcePathForDebug, TraceLevel reporting) { if (nullptr == AZ::IO::FileIOBase::GetInstance()->GetAlias("@products@")) { @@ -111,11 +115,11 @@ namespace AZ } else { - AZ_Error("AssetUtils", false, "Could not load %s [Source='%s' Cache='%s' AssetID=%s] ", + AssetUtilsInternal::ReportIssue(reporting, AZStd::string::format("Could not load %s [Source='%s' Cache='%s' AssetID=%s] ", AzTypeInfo::Name(), sourcePathForDebug ? sourcePathForDebug : "", asset.GetHint().empty() ? "" : asset.GetHint().c_str(), - assetId.ToString().c_str()); + assetId.ToString().c_str()).c_str()); return AZ::Failure(); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialUtils.h index d12e848a02..c1183c7aa1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialUtils.h @@ -28,7 +28,18 @@ namespace AZ namespace MaterialUtils { - Outcome> GetImageAssetReference(AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath); + enum class GetImageAssetResult + { + Empty, //! No image was actually requested, the path was empty + Found, //! The requested asset was found + Missing //! The requested asset was not found, and a placeholder asset was used instead + }; + + //! Finds an ImageAsset referenced by a material file (or a placeholder) + //! @param imageAsset the resulting ImageAsset + //! @param materialSourceFilePath the full path to a material source file that is referenfing an image file + //! @param imageFilePath the path to an image source file, which could be relative to the asset root or relative to the material file + GetImageAssetResult GetImageAssetReference(Data::Asset& imageAsset, AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath); //! Resolve an enum to a uint32_t given its name and definition array (in MaterialPropertyDescriptor). //! @param propertyDescriptor it contains the definition of all enum names in an array. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h index 474d0d5b9e..29b127407e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/AuxGeom/AuxGeomDraw.h @@ -147,6 +147,30 @@ namespace AZ //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused virtual void DrawSphere( const AZ::Vector3& center, float radius, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; + //! Draw a sphere. + //! @param center The center of the sphere. + //! @param direction The direction vector. The Pole of the hemisphere will point along this vector. + //! @param radius The radius. + //! @param color The color to draw the sphere. + //! @param style The draw style (point, wireframe, solid, shaded etc). + //! @param depthTest If depth testing should be enabled + //! @param depthWrite If depth writing should be enabled + //! @param faceCull Which (if any) facing triangles should be culled + //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused + virtual void DrawSphere(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; + + //! Draw a hemisphere. + //! @param center The center of the sphere. + //! @param direction The direction vector. The Pole of the hemisphere will point along this vector. + //! @param radius The radius. + //! @param color The color to draw the sphere. + //! @param style The draw style (point, wireframe, solid, shaded etc). + //! @param depthTest If depth testing should be enabled + //! @param depthWrite If depth writing should be enabled + //! @param faceCull Which (if any) facing triangles should be culled + //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused + virtual void DrawHemisphere( const AZ::Vector3& center, const AZ::Vector3& direction, float radius, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; + //! Draw a disk. //! @param center The center of the disk. //! @param direction The direction vector. The disk will be orthogonal this vector. @@ -172,7 +196,7 @@ namespace AZ //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused virtual void DrawCone(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; - //! Draw a cylinder. + //! Draw a cylinder (with flat disks on the end). //! @param center The center of the base circle. //! @param direction The direction vector. The top end cap of the cylinder will face along this vector. //! @param radius The radius. @@ -185,6 +209,19 @@ namespace AZ //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused virtual void DrawCylinder(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; + //! Draw a cylinder without flat disk on the end. + //! @param center The center of the base circle. + //! @param direction The direction vector. The top end cap of the cylinder will face along this vector. + //! @param radius The radius. + //! @param height The height of the cylinder. + //! @param color The color to draw the cylinder. + //! @param style The draw style (point, wireframe, solid, shaded etc). + //! @param depthTest If depth testing should be enabled + //! @param depthWrite If depth writing should be enabled + //! @param faceCull Which (if any) facing triangles should be culled + //! @param viewProjOverrideIndex Which view projection override entry to use, -1 if unused + virtual void DrawCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& direction, float radius, float height, const AZ::Color& color, DrawStyle style = DrawStyle::Shaded, DepthTest depthTest = DepthTest::On, DepthWrite depthWrite = DepthWrite::On, FaceCullMode faceCull = FaceCullMode::Back, int32_t viewProjOverrideIndex = -1) = 0; + //! Draw an axis-aligned bounding box with no transform. //! @param aabb The AABB (typically the bounding box of a set of world space points). //! @param color The color to draw the box. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Image/ImageSystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Image/ImageSystemInterface.h index f567881c50..920b763bc3 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Image/ImageSystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Image/ImageSystemInterface.h @@ -29,6 +29,14 @@ namespace AZ Count }; + namespace DefaultImageAssetPaths + { + static constexpr char DefaultFallback[] = "textures/defaults/defaultfallback.png.streamingimage"; + static constexpr char Processing[] = "textures/defaults/processing.png.streamingimage"; + static constexpr char ProcessingFailed[] = "textures/defaults/processingfailed.png.streamingimage"; + static constexpr char Missing[] = "textures/defaults/missing.png.streamingimage"; + } + class ImageSystemInterface { public: diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index e9cebf29c7..f11b2f94ac 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -63,11 +63,21 @@ namespace AZ { BusDisconnect(); } + + bool MaterialBuilder::ReportMaterialAssetWarningsAsErrors() const + { + bool warningsAsErrors = false; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Get(warningsAsErrors, "/O3DE/Atom/RPI/MaterialBuilder/WarningsAsErrors"); + } + return warningsAsErrors; + } //! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path. //! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found. //! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back - //! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a materialtype file, the job dependency type + //! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a .materialtype file, the job dependency type //! will be set to JobDependencyType::OrderOnce. void AddPossibleDependencies(AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, @@ -277,8 +287,8 @@ namespace AZ return materialTypeAssetOutcome.GetValue(); } - - AZ::Data::Asset CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) + + AZ::Data::Asset MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const { auto material = LoadSourceData(json, materialSourceFilePath); @@ -292,7 +302,7 @@ namespace AZ return {}; } - auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, true); + auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, ReportMaterialAssetWarningsAsErrors()); if (!materialAssetOutcome.IsSuccess()) { return {}; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.h b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.h index afb0789dcf..4fa5f3cf10 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.h @@ -9,6 +9,8 @@ #pragma once #include +#include +#include namespace AZ { @@ -37,6 +39,9 @@ namespace AZ private: + AZ::Data::Asset CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const; + bool ReportMaterialAssetWarningsAsErrors() const; + bool m_isShuttingDown = false; }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp index c940ef808b..e2877aa163 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace AZ { @@ -46,6 +47,12 @@ namespace AZ AZStd::string ResolvePathReference(const AZStd::string& originatingSourceFilePath, const AZStd::string& referencedSourceFilePath) { + // The IsAbsolute part prevents "second join parameter is an absolute path" warnings in StringFunc::Path::Join below + if (referencedSourceFilePath.empty() || AZ::IO::PathView{referencedSourceFilePath}.IsAbsolute()) + { + return referencedSourceFilePath; + } + AZStd::string normalizedReferencedPath = referencedSourceFilePath; AzFramework::StringFunc::Path::Normalize(normalizedReferencedPath); @@ -113,7 +120,7 @@ namespace AZ return results; } - Outcome MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId) + Outcome MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId, TraceLevel reporting) { bool assetFound = false; AZ::Data::AssetInfo sourceInfo; @@ -122,7 +129,7 @@ namespace AZ if (!assetFound) { - AZ_Error("AssetUtils", false, "Could not find asset [%s]", sourcePath.c_str()); + AssetUtilsInternal::ReportIssue(reporting, AZStd::string::format("Could not find asset [%s]", sourcePath.c_str()).c_str()); return AZ::Failure(); } else @@ -131,10 +138,10 @@ namespace AZ } } - Outcome MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId) + Outcome MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId, TraceLevel reporting) { AZStd::string resolvedPath = ResolvePathReference(originatingSourcePath, referencedSourceFilePath); - return MakeAssetId(resolvedPath, productSubId); + return MakeAssetId(resolvedPath, productSubId, reporting); } } // namespace AssetUtils } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index d6308ec655..d886a523cb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -303,7 +303,7 @@ namespace AZ MaterialPropertyId propertyId{ group.first, property.first }; if (!property.second.m_value.IsValid()) { - AZ_Warning("Material source data", false, "Source data for material property value is invalid."); + materialAssetCreator.ReportWarning("Source data for material property value is invalid."); } else { @@ -317,22 +317,20 @@ namespace AZ { case MaterialPropertyDataType::Image: { - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference( - materialSourceFilePath, property.second.m_value.GetValue()); + Data::Asset imageAsset; - if (imageAssetResult.IsSuccess()) + MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( + imageAsset, materialSourceFilePath, property.second.m_value.GetValue()); + + if (result == MaterialUtils::GetImageAssetResult::Missing) { - auto& imageAsset = imageAssetResult.GetValue(); - // Load referenced images when load material - imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); - } - else - { - materialAssetCreator.ReportError( + materialAssetCreator.ReportWarning( "Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.second.m_value.GetValue().data()); } + + imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); + materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); } break; case MaterialPropertyDataType::Enum: diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index d5551e89ae..d8e6c156be 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -451,20 +451,21 @@ namespace AZ { case MaterialPropertyDataType::Image: { - auto imageAssetResult = MaterialUtils::GetImageAssetReference( - materialTypeSourceFilePath, property.m_value.GetValue()); + Data::Asset imageAsset; - if (imageAssetResult) - { - auto imageAsset = imageAssetResult.GetValue(); - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); - } - else + MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( + imageAsset, materialTypeSourceFilePath, property.m_value.GetValue()); + + if (result == MaterialUtils::GetImageAssetResult::Missing) { materialTypeAssetCreator.ReportError( "Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.m_value.GetValue().data()); } + else + { + materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); + } } break; case MaterialPropertyDataType::Enum: diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp index 90ce9e66ce..7fff8d81bc 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp @@ -28,25 +28,36 @@ namespace AZ { namespace MaterialUtils { - Outcome> GetImageAssetReference(AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath) + GetImageAssetResult GetImageAssetReference(Data::Asset& imageAsset, AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath) { + imageAsset = {}; + if (imageFilePath.empty()) { // The image value was present but specified an empty string, meaning the texture asset should be explicitly cleared. - return AZ::Success(Data::Asset()); + return GetImageAssetResult::Empty; } else { - Outcome imageAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, imageFilePath, StreamingImageAsset::GetImageAssetSubId()); + // We use TraceLevel::None because fallback textures are available and we'll return GetImageAssetResult::Missing below in that case. + // Callers of GetImageAssetReference will be responsible for logging warnings or errors as needed. + + Outcome imageAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, imageFilePath, StreamingImageAsset::GetImageAssetSubId(), AssetUtils::TraceLevel::None); + if (!imageAssetId.IsSuccess()) { - return AZ::Failure(); - } - else - { - Data::Asset unloadedImageAssetReference(imageAssetId.GetValue(), azrtti_typeid(), imageFilePath); - return AZ::Success(unloadedImageAssetReference); + // When the AssetId cannot be found, we don't want to outright fail, because the runtime has mechanisms for displaying fallback textures which gives the + // user a better recovery workflow. On the other hand we can't just provide an empty/invalid Asset because that would be interpreted as simply + // no value was present and result in using no texture, and this would amount to a silent failure. + // So we use a randomly generated (well except for the "BADA55E7" bit ;) UUID which the runtime and tools will interpret as a missing asset and represent + // it as such. + static const Uuid InvalidAssetPlaceholderId = "{BADA55E7-1A1D-4940-B655-9D08679BD62F}"; + imageAsset = Data::Asset{InvalidAssetPlaceholderId, azrtti_typeid(), imageFilePath}; + return GetImageAssetResult::Missing; } + + imageAsset = Data::Asset{imageAssetId.GetValue(), azrtti_typeid(), imageFilePath}; + return GetImageAssetResult::Found; } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp index fd04635da3..dd16c565e7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -52,7 +53,7 @@ namespace AZ missingAssetStatus, &AzFramework::AssetSystem::AssetSystemRequests::GetAssetStatusById, asset.GetId().m_guid); // Determine which fallback image to use - const char* relativePath = "textures/defaults/defaultfallback.png.streamingimage"; + const char* relativePath = DefaultImageAssetPaths::DefaultFallback; bool useDebugFallbackImages = true; if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) @@ -66,15 +67,15 @@ namespace AZ { case AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued: case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiling: - relativePath = "textures/defaults/processing.png.streamingimage"; + relativePath = DefaultImageAssetPaths::Processing; break; case AzFramework::AssetSystem::AssetStatus::AssetStatus_Failed: - relativePath = "textures/defaults/processingfailed.png.streamingimage"; + relativePath = DefaultImageAssetPaths::ProcessingFailed; break; case AzFramework::AssetSystem::AssetStatus::AssetStatus_Missing: case AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown: case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiled: - relativePath = "textures/defaults/missing.png.streamingimage"; + relativePath = DefaultImageAssetPaths::Missing; break; } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyValue.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyValue.cpp index d1017139fc..b74305613e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyValue.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyValue.cpp @@ -118,12 +118,16 @@ namespace AZ else if (value.is>()) { result.m_value = Data::Asset( - AZStd::any_cast>(value).GetId(), azrtti_typeid()); + AZStd::any_cast>(value).GetId(), + azrtti_typeid(), + AZStd::any_cast>(value).GetHint()); } else if (value.is>()) { result.m_value = Data::Asset( - AZStd::any_cast>(value).GetId(), azrtti_typeid()); + AZStd::any_cast>(value).GetId(), + azrtti_typeid(), + AZStd::any_cast>(value).GetHint()); } else if (value.is>()) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index fa8eed35de..d4bf3e5eaa 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -625,23 +625,6 @@ namespace UnitTest // We use local functions to easily start a new MaterialAssetCreator for each test case because // the AssetCreator would just skip subsequent operations after the first failure is detected. - auto expectError = [](AZStd::function setOneBadInput, [[maybe_unused]] uint32_t expectedAsserts = 2) - { - MaterialSourceData sourceData; - - sourceData.m_materialType = "@exefolder@/Temp/test.materialtype"; - - AddPropertyGroup(sourceData, "general"); - - setOneBadInput(sourceData); - - AZ_TEST_START_ASSERTTEST; - auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", false); - AZ_TEST_STOP_ASSERTTEST(expectedAsserts); // Usually one for the initial error, and one for when End() is called - - EXPECT_FALSE(materialAssetOutcome.IsSuccess()); - }; - auto expectWarning = [](AZStd::function setOneBadInput, [[maybe_unused]] uint32_t expectedAsserts = 1) { MaterialSourceData sourceData; @@ -692,10 +675,10 @@ namespace UnitTest }); // Missing image reference - expectError([](MaterialSourceData& materialSourceData) + expectWarning([](MaterialSourceData& materialSourceData) { AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage")); - }, 3); // Expect a 3rd error because AssetUtils reports its own assertion failure + }); } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp index 9c3e08ee08..61999d808e 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp @@ -888,4 +888,43 @@ namespace UnitTest EXPECT_EQ(indexFromOldName, indexFromNewName); } + template + void CheckPropertyValueRoundTrip(const T& value) + { + AZ::RPI::MaterialPropertyValue materialPropertyValue{value}; + AZStd::any anyValue{value}; + AZ::RPI::MaterialPropertyValue materialPropertyValueFromAny = MaterialPropertyValue::FromAny(anyValue); + AZ::RPI::MaterialPropertyValue materialPropertyValueFromRoundTrip = MaterialPropertyValue::FromAny(MaterialPropertyValue::ToAny(materialPropertyValue)); + + EXPECT_EQ(materialPropertyValue, materialPropertyValueFromAny); + EXPECT_EQ(materialPropertyValue, materialPropertyValueFromRoundTrip); + + if (materialPropertyValue.Is>()) + { + EXPECT_EQ(materialPropertyValue.GetValue>().GetHint(), materialPropertyValueFromAny.GetValue>().GetHint()); + EXPECT_EQ(materialPropertyValue.GetValue>().GetHint(), materialPropertyValueFromRoundTrip.GetValue>().GetHint()); + } + } + + TEST_F(MaterialTests, TestMaterialPropertyValueAsAny) + { + CheckPropertyValueRoundTrip(true); + CheckPropertyValueRoundTrip(false); + CheckPropertyValueRoundTrip(7); + CheckPropertyValueRoundTrip(8u); + CheckPropertyValueRoundTrip(9.0f); + CheckPropertyValueRoundTrip(AZ::Vector2(1.0f, 2.0f)); + CheckPropertyValueRoundTrip(AZ::Vector3(1.0f, 2.0f, 3.0f)); + CheckPropertyValueRoundTrip(AZ::Vector4(1.0f, 2.0f, 3.0f, 4.0f)); + CheckPropertyValueRoundTrip(AZ::Color(1.0f, 2.0f, 3.0f, 4.0f)); + CheckPropertyValueRoundTrip(Data::Asset{}); + CheckPropertyValueRoundTrip(Data::Asset{}); + CheckPropertyValueRoundTrip(Data::Asset{}); + CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); + CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); + CheckPropertyValueRoundTrip(Data::Asset{Uuid::CreateRandom(), azrtti_typeid(), "TestAssetPath.png"}); + CheckPropertyValueRoundTrip(m_testImageAsset); + CheckPropertyValueRoundTrip(Data::Instance{m_testImage}); + CheckPropertyValueRoundTrip(AZStd::string{"hello"}); + } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt index 460341532d..e7e8208722 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt @@ -42,6 +42,7 @@ ly_add_target( Gem::Atom_RHI.Reflect Gem::Atom_Feature_Common.Static Gem::Atom_Bootstrap.Headers + Gem::ImageProcessingAtom.Headers ) ly_add_target( @@ -60,6 +61,8 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Gem::AtomToolsFramework.Static + RUNTIME_DEPENDENCIES + Gem::ImageProcessingAtom.Editor ) ################################################################################ diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Debug/TraceRecorder.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Debug/TraceRecorder.h index 96a8728a43..fc19aea2d3 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Debug/TraceRecorder.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Debug/TraceRecorder.h @@ -26,6 +26,21 @@ namespace AtomToolsFramework //! Get the combined output of all messages AZStd::string GetDump() const; + //! Return the number of OnAssert calls + size_t GetAssertCount() const; + + //! Return the number of OnException calls + size_t GetExceptionCount() const; + + //! Return the number of OnError calls, and includes OnAssert and OnException if @includeHigher is true + size_t GetErrorCount(bool includeHigher = false) const; + + //! Return the number of OnWarning calls, and includes higher categories if @includeHigher is true + size_t GetWarningCount(bool includeHigher = false) const; + + //! Return the number of OnPrintf calls, and includes higher categories if @includeHigher is true + size_t GetPrintfCount(bool includeHigher = false) const; + private: ////////////////////////////////////////////////////////////////////////// // AZ::Debug::TraceMessageBus::Handler overrides... @@ -38,5 +53,11 @@ namespace AtomToolsFramework size_t m_maxMessageCount = std::numeric_limits::max(); AZStd::list m_messages; + + size_t m_assertCount = 0; + size_t m_exceptionCount = 0; + size_t m_errorCount = 0; + size_t m_warningCount = 0; + size_t m_printfCount = 0; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h index 08f59f9e0b..d27f2403a1 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h @@ -49,6 +49,7 @@ namespace AtomToolsFramework //! @param propertyValue the value being converted before saving bool ConvertToExportFormat( const AZStd::string& exportPath, + [[maybe_unused]] const AZ::Name& propertyId, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, AZ::RPI::MaterialPropertyValue& propertyValue); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/Util.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/Util.h index 1ad6f69961..ab2b8b46c0 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/Util.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/Util.h @@ -8,8 +8,9 @@ #pragma once -#include #include +#include +#include #include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT @@ -18,11 +19,24 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnin #include AZ_POP_DISABLE_WARNING +class QImage; + namespace AtomToolsFramework { + template + T GetSettingOrDefault(AZStd::string_view path, const T& defaultValue) + { + T result; + auto settingsRegistry = AZ::SettingsRegistry::Get(); + return (settingsRegistry && settingsRegistry->Get(result, path)) ? result : defaultValue; + } + + using LoadImageAsyncCallback = AZStd::function; + void LoadImageAsync(const AZStd::string& path, LoadImageAsyncCallback callback); + QFileInfo GetSaveFileInfo(const QString& initialPath); QFileInfo GetOpenFileInfo(const AZStd::vector& assetTypes); QFileInfo GetUniqueFileInfo(const QString& initialPath); QFileInfo GetDuplicationFileInfo(const QString& initialPath); bool LaunchTool(const QString& baseName, const QString& extension, const QStringList& arguments); -} +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h index 9de74752ff..b6ad6e17d1 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -17,7 +18,9 @@ namespace AtomToolsFramework { //! A concrete implementation of the ViewportInteractionRequestBus. //! Primarily concerned with picking (screen to world and world to screen transformations). - class ViewportInteractionImpl : public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler + class ViewportInteractionImpl + : public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler + , private AZ::RPI::ViewportContextIdNotificationBus::Handler { public: explicit ViewportInteractionImpl(AZ::RPI::ViewPtr viewPtr); @@ -37,6 +40,9 @@ namespace AtomToolsFramework AZStd::function m_deviceScalingFactorFn; //! Callback to determine the device scaling factor. private: + // ViewportContextIdNotificationBus overrides ... + void OnViewportDefaultViewChanged(AZ::RPI::ViewPtr view) override; + AZ::RPI::ViewPtr m_viewPtr; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Debug/TraceRecorder.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Debug/TraceRecorder.cpp index 3fd069ff0b..5f8c8b9004 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Debug/TraceRecorder.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Debug/TraceRecorder.cpp @@ -31,6 +31,7 @@ namespace AtomToolsFramework bool TraceRecorder::OnAssert(const char* message) { + ++m_assertCount; if (m_messages.size() < m_maxMessageCount) { m_messages.push_back(AZStd::string::format("Assert: %s", message)); @@ -40,6 +41,7 @@ namespace AtomToolsFramework bool TraceRecorder::OnException(const char* message) { + ++m_exceptionCount; if (m_messages.size() < m_maxMessageCount) { m_messages.push_back(AZStd::string::format("Exception: %s", message)); @@ -49,6 +51,7 @@ namespace AtomToolsFramework bool TraceRecorder::OnError(const char* /*window*/, const char* message) { + ++m_errorCount; if (m_messages.size() < m_maxMessageCount) { m_messages.push_back(AZStd::string::format("Error: %s", message)); @@ -58,6 +61,7 @@ namespace AtomToolsFramework bool TraceRecorder::OnWarning(const char* /*window*/, const char* message) { + ++m_warningCount; if (m_messages.size() < m_maxMessageCount) { m_messages.push_back(AZStd::string::format("Warning: %s", message)); @@ -67,11 +71,58 @@ namespace AtomToolsFramework bool TraceRecorder::OnPrintf(const char* /*window*/, const char* message) { + ++m_printfCount; if (m_messages.size() < m_maxMessageCount) { m_messages.push_back(AZStd::string::format("%s", message)); } return false; +} + + size_t TraceRecorder::GetAssertCount() const + { + return m_assertCount; + } + + size_t TraceRecorder::GetExceptionCount() const + { + return m_exceptionCount; + } + + size_t TraceRecorder::GetErrorCount(bool includeHigher) const + { + if (includeHigher) + { + return m_errorCount + GetAssertCount() + GetExceptionCount(); + } + else + { + return m_errorCount; + } + } + + size_t TraceRecorder::GetWarningCount(bool includeHigher) const + { + if (includeHigher) + { + return m_warningCount + GetErrorCount(true); + } + else + { + return m_warningCount; + } + } + + size_t TraceRecorder::GetPrintfCount(bool includeHigher) const + { + if (includeHigher) + { + return m_printfCount + GetWarningCount(true); + } + else + { + return m_printfCount; + } } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp index 00de2a7c4c..3a392c1413 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp @@ -495,8 +495,6 @@ namespace AtomToolsFramework return AZ::Uuid::CreateNull(); } - traceRecorder.GetDump().clear(); - bool openResult = false; AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Open, requestedPath); if (!openResult) @@ -507,6 +505,12 @@ namespace AtomToolsFramework AtomToolsDocumentSystemRequestBus::Broadcast(&AtomToolsDocumentSystemRequestBus::Events::DestroyDocument, documentId); return AZ::Uuid::CreateNull(); } + else if (traceRecorder.GetWarningCount(true) > 0) + { + QMessageBox::warning( + QApplication::activeWindow(), QString("Document opened with warnings"), + QString("Warnings encountered: \n%1\n\n%2").arg(requestedPath.c_str()).arg(traceRecorder.GetDump().c_str())); + } return documentId; } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp index c49cd3fafb..3ffd8efa6e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp @@ -167,6 +167,7 @@ namespace AtomToolsFramework bool ConvertToExportFormat( const AZStd::string& exportPath, + [[maybe_unused]] const AZ::Name& propertyId, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, AZ::RPI::MaterialPropertyValue& propertyValue) { @@ -175,7 +176,7 @@ namespace AtomToolsFramework const uint32_t index = propertyValue.GetValue(); if (index >= propertyDefinition.m_enumValues.size()) { - AZ_Error("AtomToolsFramework", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str()); + AZ_Error("AtomToolsFramework", false, "Invalid value for material enum property: '%s'.", propertyId.GetCStr()); return false; } @@ -186,18 +187,33 @@ namespace AtomToolsFramework // Image asset references must be converted from asset IDs to a relative source file path if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image) { + AZStd::string imagePath; + AZ::Data::AssetId imageAssetId; + if (propertyValue.Is>()) { const auto& imageAsset = propertyValue.GetValue>(); - const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(imageAsset.GetId()); - propertyValue = GetExteralReferencePath(exportPath, imagePath); - return true; + imageAssetId = imageAsset.GetId(); } if (propertyValue.Is>()) { const auto& image = propertyValue.GetValue>(); - const auto& imagePath = image ? AZ::RPI::AssetUtils::GetSourcePathByAssetId(image->GetAssetId()) : ""; + if (image) + { + imageAssetId = image->GetAssetId(); + } + } + + imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(imageAssetId); + + if (imageAssetId.IsValid() && imagePath.empty()) + { + AZ_Error("AtomToolsFramework", false, "Image asset could not be found for property: '%s'.", propertyId.GetCStr()); + return false; + } + else + { propertyValue = GetExteralReferencePath(exportPath, imagePath); return true; } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp index d3bce34af3..cd50c10e23 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp @@ -6,15 +6,18 @@ * */ +#include +#include #include #include +#include #include #include #include #include +#include #include #include -#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include @@ -24,6 +27,36 @@ AZ_POP_DISABLE_WARNING namespace AtomToolsFramework { + void LoadImageAsync(const AZStd::string& path, LoadImageAsyncCallback callback) + { + AZ::Job* job = AZ::CreateJobFunction( + [path, callback]() + { + ImageProcessingAtom::IImageObjectPtr imageObject; + ImageProcessingAtom::ImageProcessingRequestBus::BroadcastResult( + imageObject, &ImageProcessingAtom::ImageProcessingRequests::LoadImagePreview, path); + + if (imageObject) + { + AZ::u8* imageBuf = nullptr; + AZ::u32 pitch = 0; + AZ::u32 mip = 0; + imageObject->GetImagePointer(mip, imageBuf, pitch); + const AZ::u32 width = imageObject->GetWidth(mip); + const AZ::u32 height = imageObject->GetHeight(mip); + + QImage image(imageBuf, width, height, pitch, QImage::Format_RGBA8888); + + if (callback) + { + callback(image); + } + } + }, + true); + job->Start(); + } + QFileInfo GetSaveFileInfo(const QString& initialPath) { const QFileInfo initialFileInfo(initialPath); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp index 475566ccee..1a015c167c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp @@ -19,10 +19,12 @@ namespace AtomToolsFramework void ViewportInteractionImpl::Connect(const AzFramework::ViewportId viewportId) { AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(viewportId); + AZ::RPI::ViewportContextIdNotificationBus::Handler::BusConnect(viewportId); } void ViewportInteractionImpl::Disconnect() { + AZ::RPI::ViewportContextIdNotificationBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect(); } @@ -58,4 +60,9 @@ namespace AtomToolsFramework { return m_deviceScalingFactorFn(); } + + void ViewportInteractionImpl::OnViewportDefaultViewChanged(AZ::RPI::ViewPtr view) + { + m_viewPtr = AZStd::move(view); + } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp index fa5bc5b6e5..2dfe4f7c29 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp @@ -7,12 +7,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -169,4 +171,39 @@ namespace UnitTest EXPECT_EQ(intersection, AZ::Intersect::SphereIsectTypes::ISECT_RAY_SPHERE_ISECT); } + + TEST_F(ViewportInteractionImplFixture, ViewportInteractionRequestsReturnsNewViewWhenItIsChanged) + { + // Given + const auto primaryViewTransform = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(90.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-45.0f)), + AZ::Vector3(-10.0f, -15.0f, 20.0f)); + + m_view->SetCameraTransform(primaryViewTransform); + + AZ::RPI::ViewPtr secondaryView = AZ::RPI::View::CreateView(AZ::Name("SecondaryView"), AZ::RPI::View::UsageCamera); + + const auto secondaryViewTransform = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-90.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(30.0f)), + AZ::Vector3(-50.0f, -25.0f, 10.0f)); + + secondaryView->SetCameraTransform(secondaryViewTransform); + + // When + AZ::RPI::ViewportContextIdNotificationBus::Event( + TestViewportId, &AZ::RPI::ViewportContextIdNotificationBus::Events::OnViewportDefaultViewChanged, secondaryView); + + // retrieve updated camera transform + AzFramework::CameraState cameraState; + AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::EventResult( + cameraState, TestViewportId, &AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Events::GetCameraState); + + const auto cameraMatrix = AzFramework::CameraTransform(cameraState); + const auto cameraTransform = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateFromMatrix4x4(cameraMatrix), cameraMatrix.GetTranslation()); + + // Then + // camera transform matches that of the secondary view + EXPECT_THAT(cameraTransform, IsClose(secondaryViewTransform)); + } } // namespace UnitTest diff --git a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt index d585e81162..99beb721d6 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt @@ -60,7 +60,6 @@ ly_add_target( Gem::AtomToolsFramework.Editor Gem::Atom_RPI.Public Gem::Atom_Feature_Common.Public - Gem::ImageProcessingAtom.Headers ) ly_add_target( @@ -113,7 +112,6 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::AtomToolsFramework.Editor Gem::EditorPythonBindings.Editor - Gem::ImageProcessingAtom.Editor ) ly_set_gem_variant_to_load(TARGETS MaterialEditor VARIANTS Tools) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Viewport/MaterialViewportRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Viewport/MaterialViewportRequestBus.h index e859d6a433..e37512127c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Viewport/MaterialViewportRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Viewport/MaterialViewportRequestBus.h @@ -62,15 +62,6 @@ namespace MaterialEditor //! Get set of lighting preset names virtual MaterialViewportPresetNameSet GetLightingPresetNames() const = 0; - //! Set lighting preset preview image - //! @param preset used to set preview image - //! @param preview image - virtual void SetLightingPresetPreview(AZ::Render::LightingPresetPtr preset, const QImage& image) = 0; - - //! Get lighting preset preview image - //! @param preset used to find preview image - virtual QImage GetLightingPresetPreview(AZ::Render::LightingPresetPtr preset) const = 0; - //! Get model preset last save path //! @param preset to lookup last save path virtual AZStd::string GetLightingPresetLastSavePath(AZ::Render::LightingPresetPtr preset) const = 0; @@ -108,15 +99,6 @@ namespace MaterialEditor //! Get set of model preset names virtual MaterialViewportPresetNameSet GetModelPresetNames() const = 0; - //! Set model preset preview image - //! @param preset used to set preview image - //! @param preview image - virtual void SetModelPresetPreview(AZ::Render::ModelPresetPtr preset, const QImage& image) = 0; - - //! Get model preset preview image - //! @param preset used to find preview image - virtual QImage GetModelPresetPreview(AZ::Render::ModelPresetPtr preset) const = 0; - //! Get model preset last save path //! @param preset to lookup last save path virtual AZStd::string GetModelPresetLastSavePath(AZ::Render::ModelPresetPtr preset) const = 0; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 8635d1eb3e..2d7768feec 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -594,7 +595,7 @@ namespace MaterialEditor MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); if (propertyValue.IsValid()) { - if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyId.GetFullName(), propertyDefinition, propertyValue)) { AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str()); result = false; @@ -708,6 +709,8 @@ namespace MaterialEditor AZ_Error("MaterialDocument", false, "Material document extension not supported: '%s'.", m_absolutePath.c_str()); return false; } + + const bool elevateWarnings = false; // In order to support automation, general usability, and 'save as' functionality, the user must not have to wait // for their JSON file to be cooked by the asset processor before opening or editing it. @@ -716,7 +719,7 @@ namespace MaterialEditor // Long term, the material document should not be concerned with assets at all. The viewport window should be the // only thing concerned with assets or instances. auto materialAssetResult = - m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, true, true, &m_sourceDependencies); + m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, elevateWarnings, true, &m_sourceDependencies); if (!materialAssetResult) { AZ_Error("MaterialDocument", false, "Material asset could not be created from source data: '%s'.", m_absolutePath.c_str()); @@ -754,7 +757,7 @@ namespace MaterialEditor AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); return false; } - + auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData( parentMaterialAssetIdResult.GetValue(), m_materialSourceData.m_parentMaterial, true, true); if (!parentMaterialAssetResult) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.cpp index a8f41917f9..c8df123d0d 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.cpp @@ -6,61 +6,26 @@ * */ -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace MaterialEditor { - using LoadImageAsyncCallback = AZStd::function; - void LoadImageAsync(const AZStd::string& path, LoadImageAsyncCallback callback) - { - AZ::Job* job = AZ::CreateJobFunction([path, callback]() { - ImageProcessingAtom::IImageObjectPtr imageObject; - ImageProcessingAtom::ImageProcessingRequestBus::BroadcastResult(imageObject, &ImageProcessingAtom::ImageProcessingRequests::LoadImagePreview, path); - - if (imageObject) - { - AZ::u8* imageBuf = nullptr; - AZ::u32 pitch = 0; - AZ::u32 mip = 0; - imageObject->GetImagePointer(mip, imageBuf, pitch); - const AZ::u32 width = imageObject->GetWidth(mip); - const AZ::u32 height = imageObject->GetHeight(mip); - - QImage image(imageBuf, width, height, pitch, QImage::Format_RGBA8888); - - if (callback) - { - callback(image); - } - } - }, true); - job->Start(); - } - MaterialViewportComponent::MaterialViewportComponent() { } @@ -162,12 +127,6 @@ namespace MaterialEditor m_viewportSettings = AZ::UserSettings::CreateFind(AZ::Crc32("MaterialViewportSettings"), AZ::UserSettings::CT_GLOBAL); - m_lightingPresetPreviewImageDefault = QImage(180, 90, QImage::Format::Format_RGBA8888); - m_lightingPresetPreviewImageDefault.fill(Qt::GlobalColor::black); - - m_modelPresetPreviewImageDefault = QImage(90, 90, QImage::Format::Format_RGBA8888); - m_modelPresetPreviewImageDefault.fill(Qt::GlobalColor::black); - MaterialViewportRequestBus::Handler::BusConnect(); AzFramework::AssetCatalogEventBus::Handler::BusConnect(); } @@ -177,12 +136,10 @@ namespace MaterialEditor AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); MaterialViewportRequestBus::Handler::BusDisconnect(); - m_lightingPresetPreviewImages.clear(); m_lightingPresetVector.clear(); m_lightingPresetLastSavePathMap.clear(); m_lightingPresetSelection.reset(); - m_modelPresetPreviewImages.clear(); m_modelPresetVector.clear(); m_modelPresetLastSavePathMap.clear(); m_modelPresetSelection.reset(); @@ -286,14 +243,6 @@ namespace MaterialEditor SelectLightingPreset(presetPtr); } - const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(presetPtr->m_skyboxImageAsset.GetId()); - LoadImageAsync(imagePath, [presetPtr](const QImage& image) { - QImage imageScaled = image.scaled(180, 90, Qt::AspectRatioMode::KeepAspectRatio); - AZ::TickBus::QueueFunction([presetPtr, imageScaled]() { - MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Events::SetLightingPresetPreview, presetPtr, imageScaled); - }); - }); - return presetPtr; } @@ -353,17 +302,6 @@ namespace MaterialEditor return names; } - void MaterialViewportComponent::SetLightingPresetPreview(AZ::Render::LightingPresetPtr preset, const QImage& image) - { - m_lightingPresetPreviewImages[preset] = image; - } - - QImage MaterialViewportComponent::GetLightingPresetPreview(AZ::Render::LightingPresetPtr preset) const - { - auto imageItr = m_lightingPresetPreviewImages.find(preset); - return imageItr != m_lightingPresetPreviewImages.end() ? imageItr->second : m_lightingPresetPreviewImageDefault; - } - AZStd::string MaterialViewportComponent::GetLightingPresetLastSavePath(AZ::Render::LightingPresetPtr preset) const { auto pathItr = m_lightingPresetLastSavePathMap.find(preset); @@ -382,14 +320,6 @@ namespace MaterialEditor SelectModelPreset(presetPtr); } - const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(presetPtr->m_previewImageAsset.GetId()); - LoadImageAsync(imagePath, [presetPtr](const QImage& image) { - QImage imageScaled = image.scaled(90, 90, Qt::AspectRatioMode::KeepAspectRatio); - AZ::TickBus::QueueFunction([presetPtr, imageScaled]() { - MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Events::SetModelPresetPreview, presetPtr, imageScaled); - }); - }); - return presetPtr; } @@ -449,17 +379,6 @@ namespace MaterialEditor return names; } - void MaterialViewportComponent::SetModelPresetPreview(AZ::Render::ModelPresetPtr preset, const QImage& image) - { - m_modelPresetPreviewImages[preset] = image; - } - - QImage MaterialViewportComponent::GetModelPresetPreview(AZ::Render::ModelPresetPtr preset) const - { - auto imageItr = m_modelPresetPreviewImages.find(preset); - return imageItr != m_modelPresetPreviewImages.end() ? imageItr->second : m_modelPresetPreviewImageDefault; - } - AZStd::string MaterialViewportComponent::GetModelPresetLastSavePath(AZ::Render::ModelPresetPtr preset) const { auto pathItr = m_modelPresetLastSavePathMap.find(preset); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.h index 12475bb113..a4f94c3546 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportComponent.h @@ -58,8 +58,6 @@ namespace MaterialEditor void SelectLightingPreset(AZ::Render::LightingPresetPtr preset) override; void SelectLightingPresetByName(const AZStd::string& name) override; MaterialViewportPresetNameSet GetLightingPresetNames() const override; - void SetLightingPresetPreview(AZ::Render::LightingPresetPtr preset, const QImage& image) override; - QImage GetLightingPresetPreview(AZ::Render::LightingPresetPtr preset) const override; AZStd::string GetLightingPresetLastSavePath(AZ::Render::LightingPresetPtr preset) const override; AZ::Render::ModelPresetPtr AddModelPreset(const AZ::Render::ModelPreset& preset) override; @@ -70,8 +68,6 @@ namespace MaterialEditor void SelectModelPreset(AZ::Render::ModelPresetPtr preset) override; void SelectModelPresetByName(const AZStd::string& name) override; MaterialViewportPresetNameSet GetModelPresetNames() const override; - void SetModelPresetPreview(AZ::Render::ModelPresetPtr preset, const QImage& image) override; - QImage GetModelPresetPreview(AZ::Render::ModelPresetPtr preset) const override; AZStd::string GetModelPresetLastSavePath(AZ::Render::ModelPresetPtr preset) const override; void SetShadowCatcherEnabled(bool enable) override; @@ -97,12 +93,6 @@ namespace MaterialEditor AZ::Render::ModelPresetPtrVector m_modelPresetVector; AZ::Render::ModelPresetPtr m_modelPresetSelection; - AZStd::map m_lightingPresetPreviewImages; - AZStd::map m_modelPresetPreviewImages; - - QImage m_lightingPresetPreviewImageDefault; - QImage m_modelPresetPreviewImageDefault; - mutable AZStd::map m_lightingPresetLastSavePathMap; mutable AZStd::map m_modelPresetLastSavePathMap; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/LightingPresetBrowserDialog.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/LightingPresetBrowserDialog.cpp index 72bf7fe003..f8ad038a85 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/LightingPresetBrowserDialog.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/LightingPresetBrowserDialog.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -27,13 +28,16 @@ namespace MaterialEditor MaterialViewportRequestBus::BroadcastResult(presets, &MaterialViewportRequestBus::Events::GetLightingPresets); AZStd::sort(presets.begin(), presets.end(), [](const auto& a, const auto& b) { return a->m_displayName < b->m_displayName; }); + const int itemSize = aznumeric_cast( + AtomToolsFramework::GetSettingOrDefault("/O3DE/Atom/MaterialEditor/PresetBrowserDialog/LightingItemSize", 180)); + QListWidgetItem* selectedItem = nullptr; for (const auto& preset : presets) { - QImage image; - MaterialViewportRequestBus::BroadcastResult(image, &MaterialViewportRequestBus::Events::GetLightingPresetPreview, preset); - - QListWidgetItem* item = CreateListItem(preset->m_displayName.c_str(), image); + AZStd::string path; + MaterialViewportRequestBus::BroadcastResult(path, &MaterialViewportRequestBus::Events::GetLightingPresetLastSavePath, preset); + QListWidgetItem* item = CreateListItem( + preset->m_displayName.c_str(), AZ::RPI::AssetUtils::MakeAssetId(path, 0).GetValue(), QSize(itemSize, itemSize)); m_listItemToPresetMap[item] = preset; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/ModelPresetBrowserDialog.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/ModelPresetBrowserDialog.cpp index c1936cde35..f5a1677462 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/ModelPresetBrowserDialog.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/ModelPresetBrowserDialog.cpp @@ -27,13 +27,13 @@ namespace MaterialEditor MaterialViewportRequestBus::BroadcastResult(presets, &MaterialViewportRequestBus::Events::GetModelPresets); AZStd::sort(presets.begin(), presets.end(), [](const auto& a, const auto& b) { return a->m_displayName < b->m_displayName; }); + const int itemSize = aznumeric_cast( + AtomToolsFramework::GetSettingOrDefault("/O3DE/Atom/MaterialEditor/PresetBrowserDialog/ModelItemSize", 90)); + QListWidgetItem* selectedItem = nullptr; for (const auto& preset : presets) { - QImage image; - MaterialViewportRequestBus::BroadcastResult(image, &MaterialViewportRequestBus::Events::GetModelPresetPreview, preset); - - QListWidgetItem* item = CreateListItem(preset->m_displayName.c_str(), image); + QListWidgetItem* item = CreateListItem(preset->m_displayName.c_str(), preset->m_modelAsset.GetId(), QSize(itemSize, itemSize)); m_listItemToPresetMap[item] = preset; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.cpp index d3aa6350f0..f2bb84dff6 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.cpp @@ -12,11 +12,16 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include +#include namespace MaterialEditor { @@ -41,35 +46,51 @@ namespace MaterialEditor m_ui->m_presetList->setGridSize(QSize(0, 0)); m_ui->m_presetList->setWrapping(true); - QObject::connect(m_ui->m_presetList, &QListWidget::currentItemChanged, [this]() { SelectCurrentPreset(); }); + QObject::connect(m_ui->m_presetList, &QListWidget::currentItemChanged, [this](){ SelectCurrentPreset(); }); } - QListWidgetItem* PresetBrowserDialog::CreateListItem(const QString& title, const QImage& image) + QListWidgetItem* PresetBrowserDialog::CreateListItem(const QString& title, const AZ::Data::AssetId& assetId, const QSize& size) { + const int itemBorder = aznumeric_cast( + AtomToolsFramework::GetSettingOrDefault("/O3DE/Atom/MaterialEditor/PresetBrowserDialog/ItemBorder", 4)); + const int itemSpacing = aznumeric_cast( + AtomToolsFramework::GetSettingOrDefault("/O3DE/Atom/MaterialEditor/PresetBrowserDialog/ItemSpacing", 10)); + const int headerHeight = aznumeric_cast( + AtomToolsFramework::GetSettingOrDefault("/O3DE/Atom/MaterialEditor/PresetBrowserDialog/HeaderHeight", 15)); + const QSize gridSize = m_ui->m_presetList->gridSize(); - m_ui->m_presetList->setGridSize( - QSize(AZStd::max(gridSize.width(), image.width() + 10), AZStd::max(gridSize.height(), image.height() + 10))); + m_ui->m_presetList->setGridSize(QSize( + AZStd::max(gridSize.width(), size.width() + itemSpacing), + AZStd::max(gridSize.height(), size.height() + itemSpacing + headerHeight))); QListWidgetItem* item = new QListWidgetItem(m_ui->m_presetList); item->setData(Qt::UserRole, title); - item->setSizeHint(image.size() + QSize(4, 4)); + item->setSizeHint(size + QSize(itemBorder, itemBorder + headerHeight)); m_ui->m_presetList->addItem(item); - QLabel* previewImage = new QLabel(m_ui->m_presetList); - previewImage->setFixedSize(image.size()); - previewImage->setMargin(0); - previewImage->setPixmap(QPixmap::fromImage(image)); - previewImage->updateGeometry(); + QWidget* itemWidget = new QWidget(m_ui->m_presetList); + itemWidget->setLayout(new QVBoxLayout(itemWidget)); + itemWidget->layout()->setSpacing(0); + itemWidget->layout()->setMargin(0); - AzQtComponents::ElidingLabel* previewLabel = new AzQtComponents::ElidingLabel(previewImage); - previewLabel->setText(title); - previewLabel->setFixedSize(QSize(image.width(), 15)); - previewLabel->setMargin(0); - previewLabel->setStyleSheet("background-color: rgb(35, 35, 35)"); - AzQtComponents::Text::addPrimaryStyle(previewLabel); - AzQtComponents::Text::addLabelStyle(previewLabel); + AzQtComponents::ElidingLabel* header = new AzQtComponents::ElidingLabel(itemWidget); + header->setText(title); + header->setFixedSize(QSize(size.width(), headerHeight)); + header->setMargin(0); + header->setStyleSheet("background-color: rgb(35, 35, 35)"); + AzQtComponents::Text::addPrimaryStyle(header); + AzQtComponents::Text::addLabelStyle(header); + itemWidget->layout()->addWidget(header); - m_ui->m_presetList->setItemWidget(item, previewImage); + AzToolsFramework::Thumbnailer::ThumbnailWidget* thumbnail = new AzToolsFramework::Thumbnailer::ThumbnailWidget(itemWidget); + thumbnail->setFixedSize(size); + thumbnail->SetThumbnailKey( + MAKE_TKEY(AzToolsFramework::AssetBrowser::ProductThumbnailKey, assetId), + AzToolsFramework::Thumbnailer::ThumbnailContext::DefaultContext); + thumbnail->updateGeometry(); + itemWidget->layout()->addWidget(thumbnail); + + m_ui->m_presetList->setItemWidget(item, itemWidget); return item; } @@ -79,15 +100,15 @@ namespace MaterialEditor m_ui->m_searchWidget->setReadOnly(false); m_ui->m_searchWidget->setContextMenuPolicy(Qt::CustomContextMenu); AzQtComponents::LineEdit::applySearchStyle(m_ui->m_searchWidget); - connect(m_ui->m_searchWidget, &QLineEdit::textChanged, this, [this]() { ApplySearchFilter(); }); - connect(m_ui->m_searchWidget, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) { ShowSearchMenu(pos); }); + connect(m_ui->m_searchWidget, &QLineEdit::textChanged, this, [this](){ ApplySearchFilter(); }); + connect(m_ui->m_searchWidget, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos){ ShowSearchMenu(pos); }); } void PresetBrowserDialog::SetupDialogButtons() { connect(m_ui->m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_ui->m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(this, &QDialog::rejected, this, [this]() { SelectInitialPreset(); }); + connect(this, &QDialog::rejected, this, [this](){ SelectInitialPreset(); }); } void PresetBrowserDialog::ApplySearchFilter() diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.h index ee01737352..20e049f6f8 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/PresetBrowserDialogs/PresetBrowserDialog.h @@ -9,12 +9,12 @@ #pragma once #if !defined(Q_MOC_RUN) +#include #include - #include #endif -#include +#include class QImage; class QListWidgetItem; @@ -32,7 +32,7 @@ namespace MaterialEditor protected: void SetupPresetList(); - QListWidgetItem* CreateListItem(const QString& title, const QImage& image); + QListWidgetItem* CreateListItem(const QString& title, const AZ::Data::AssetId& assetId, const QSize& size); void SetupSearchWidget(); void SetupDialogButtons(); diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp index 12e4ccd8ad..39cc2f63b2 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp @@ -1016,6 +1016,55 @@ namespace AZ::AtomBridge } } + void AtomDebugDisplayViewportInterface::DrawWireCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) + { + if (m_auxGeomPtr) + { + const float scale = GetCurrentTransform().RetrieveScale().GetMaxElement(); + const AZ::Vector3 worldCenter = ToWorldSpacePosition(center); + const AZ::Vector3 worldAxis = ToWorldSpaceVector(axis); + m_auxGeomPtr->DrawCylinderNoEnds( + worldCenter, + worldAxis, + scale * radius, + scale * height, + m_rendState.m_color, + AZ::RPI::AuxGeomDraw::DrawStyle::Line, + m_rendState.m_depthTest, + m_rendState.m_depthWrite, + m_rendState.m_faceCullMode, + m_rendState.m_viewProjOverrideIndex + ); + } + } + + void AtomDebugDisplayViewportInterface::DrawSolidCylinderNoEnds( + const AZ::Vector3& center, + const AZ::Vector3& axis, + float radius, + float height, + bool drawShaded) + { + if (m_auxGeomPtr) + { + const float scale = GetCurrentTransform().RetrieveScale().GetMaxElement(); + const AZ::Vector3 worldCenter = ToWorldSpacePosition(center); + const AZ::Vector3 worldAxis = ToWorldSpaceVector(axis); + m_auxGeomPtr->DrawCylinderNoEnds( + worldCenter, + worldAxis, + scale * radius, + scale * height, + m_rendState.m_color, + drawShaded ? AZ::RPI::AuxGeomDraw::DrawStyle::Shaded : AZ::RPI::AuxGeomDraw::DrawStyle::Solid, + m_rendState.m_depthTest, + m_rendState.m_depthWrite, + m_rendState.m_faceCullMode, + m_rendState.m_viewProjOverrideIndex + ); + } + } + void AtomDebugDisplayViewportInterface::DrawWireCapsule( const AZ::Vector3& center, const AZ::Vector3& axis, @@ -1025,83 +1074,24 @@ namespace AZ::AtomBridge if (m_auxGeomPtr && radius > FLT_EPSILON && axis.GetLengthSq() > FLT_EPSILON) { AZ::Vector3 axisNormalized = axis.GetNormalizedEstimate(); - SingleColorStaticSizeLineHelper<(16+1) * 5> lines; // 360/22.5 = 16, 5 possible calls to CreateArbitraryAxisArc - AZ::Vector3 radiusV3 = AZ::Vector3(radius); - float stepAngle = DegToRad(22.5f); - float Deg0 = DegToRad(0.0f); + const float scale = GetCurrentTransform().RetrieveScale().GetMaxElement(); + const AZ::Vector3 worldCenter = ToWorldSpacePosition(center); + const AZ::Vector3 worldAxis = ToWorldSpaceVector(axis); - // Draw cylinder part (or just a circle around the middle) + // Draw cylinder part (if cylinder height is too small, ignore cylinder and just draw both hemispheres) if (heightStraightSection > FLT_EPSILON) { - DrawWireCylinder(center, axis, radius, heightStraightSection); - } - else - { - float Deg360 = DegToRad(360.0f); - CreateArbitraryAxisArc( - lines, - stepAngle, - Deg0, - Deg360, - center, - radiusV3, - axisNormalized - ); + DrawWireCylinderNoEnds(worldCenter, worldAxis, scale * radius, scale * heightStraightSection); } - float Deg90 = DegToRad(90.0f); - float Deg180 = DegToRad(180.0f); - - AZ::Vector3 ortho1Normalized, ortho2Normalized; - CalcBasisVectors(axisNormalized, ortho1Normalized, ortho2Normalized); AZ::Vector3 centerToTopCircleCenter = axisNormalized * heightStraightSection * 0.5f; - AZ::Vector3 topCenter = center + centerToTopCircleCenter; - AZ::Vector3 bottomCenter = center - centerToTopCircleCenter; - // Draw top cap as two criss-crossing 180deg arcs - CreateArbitraryAxisArc( - lines, - stepAngle, - Deg90, - Deg90 + Deg180, - topCenter, - radiusV3, - ortho1Normalized - ); + // Top hemisphere + DrawWireHemisphere(center + centerToTopCircleCenter, worldAxis, scale * radius); - CreateArbitraryAxisArc( - lines, - stepAngle, - Deg180, - Deg180 + Deg180, - topCenter, - radiusV3, - ortho2Normalized - ); - - // Draw bottom cap - CreateArbitraryAxisArc( - lines, - stepAngle, - -Deg90, - -Deg90 + Deg180, - bottomCenter, - radiusV3, - ortho1Normalized - ); - - CreateArbitraryAxisArc( - lines, - stepAngle, - Deg0, - Deg0 + Deg180, - bottomCenter, - radiusV3, - ortho2Normalized - ); - - lines.Draw(m_auxGeomPtr, m_rendState); + // Bottom hemisphere + DrawWireHemisphere(center - centerToTopCircleCenter, -worldAxis, scale * radius); } } @@ -1148,6 +1138,25 @@ namespace AZ::AtomBridge } } + void AtomDebugDisplayViewportInterface::DrawWireHemisphere(const AZ::Vector3& pos, const AZ::Vector3& axis, float radius) + { + if (m_auxGeomPtr) + { + const float scale = GetCurrentTransform().RetrieveScale().GetMaxElement(); + m_auxGeomPtr->DrawHemisphere( + ToWorldSpacePosition(pos), + axis, + scale * radius, + m_rendState.m_color, + AZ::RPI::AuxGeomDraw::DrawStyle::Line, + m_rendState.m_depthTest, + m_rendState.m_depthWrite, + m_rendState.m_faceCullMode, + m_rendState.m_viewProjOverrideIndex + ); + } + } + void AtomDebugDisplayViewportInterface::DrawWireDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) { if (m_auxGeomPtr) @@ -1353,9 +1362,8 @@ namespace AZ::AtomBridge // if 2d draw need to project pos to screen first AzFramework::TextDrawParameters params; AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext(); - const auto dpiScaleFactor = viewportContext->GetDpiScalingFactor(); params.m_drawViewportId = viewportContext->GetId(); // get the viewport ID so default viewport works - params.m_position = AZ::Vector3(x * dpiScaleFactor, y * dpiScaleFactor, 1.0f); + params.m_position = AZ::Vector3(x, y, 1.0f); params.m_color = m_rendState.m_color; params.m_scale = AZ::Vector2(size); params.m_hAlign = center ? AzFramework::TextHorizontalAlignment::Center : AzFramework::TextHorizontalAlignment::Left; //! Horizontal text alignment diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h index 5f902c5884..7f7af9bbcb 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h @@ -168,9 +168,12 @@ namespace AZ::AtomBridge void DrawSolidCone(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius, float height, bool drawShaded) override; void DrawWireCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) override; void DrawSolidCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height, bool drawShaded) override; + void DrawWireCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) override; + void DrawSolidCylinderNoEnds(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height, bool drawShaded) override; void DrawWireCapsule(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float heightStraightSection) override; void DrawWireSphere(const AZ::Vector3& pos, float radius) override; void DrawWireSphere(const AZ::Vector3& pos, const AZ::Vector3 radius) override; + void DrawWireHemisphere(const AZ::Vector3& pos, const AZ::Vector3& axis, float radius) override; void DrawWireDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) override; void DrawBall(const AZ::Vector3& pos, float radius, bool drawShaded) override; void DrawDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index 0fd7b7164d..d3e125426c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -134,7 +134,7 @@ namespace AZ propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); } - if (!AtomToolsFramework::ConvertToExportFormat(path, propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(path, propertyId.GetFullName(), propertyDefinition, propertyValue)) { AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); result = false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.cpp index 91b6d2b02a..8bc9c5ac7d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.cpp @@ -163,9 +163,9 @@ namespace AZ m_modelAsset->GetAabb().GetAsSphere(center, radius); } - const auto distance = radius + NearDist; - const auto cameraRotation = Quaternion::CreateFromAxisAngle(Vector3::CreateAxisZ(), CameraRotationAngle); - const auto cameraPosition = center + cameraRotation.TransformVector(Vector3(0.0f, distance, 0.0f)); + const auto distance = fabsf(radius / sinf(FieldOfView)) + NearDist; + const auto cameraRotation = Quaternion::CreateFromAxisAngle(Vector3::CreateAxisX(), -CameraRotationAngle); + const auto cameraPosition = center + cameraRotation.TransformVector(-Vector3::CreateAxisY() * distance); const auto cameraTransform = Transform::CreateLookAt(cameraPosition, center); m_view->SetCameraTransform(Matrix3x4::CreateFromTransform(cameraTransform)); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.h index 7308aa19bc..dfab7a8630 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewContent.h @@ -51,7 +51,7 @@ namespace AZ static constexpr float NearDist = 0.001f; static constexpr float FarDist = 100.0f; static constexpr float FieldOfView = Constants::HalfPi; - static constexpr float CameraRotationAngle = Constants::QuarterPi / 2.0f; + static constexpr float CameraRotationAngle = Constants::QuarterPi / 3.0f; RPI::ScenePtr m_scene; RPI::ViewPtr m_view; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.cpp index c2988cf53d..d0e288d239 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.cpp @@ -9,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include namespace AZ @@ -20,45 +22,64 @@ namespace AZ { namespace SharedPreviewUtils { - Data::AssetId GetAssetId( - AzToolsFramework::Thumbnailer::SharedThumbnailKey key, - const Data::AssetType& assetType, - const Data::AssetId& defaultAssetId) + AZStd::unordered_set GetSupportedAssetTypes() { + return { RPI::AnyAsset::RTTI_Type(), RPI::MaterialAsset::RTTI_Type(), RPI::ModelAsset::RTTI_Type() }; + } + + bool IsSupportedAssetType(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) + { + return GetSupportedAssetInfo(key).m_assetId.IsValid(); + } + + AZ::Data::AssetInfo GetSupportedAssetInfo(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) + { + const auto& supportedTypeIds = GetSupportedAssetTypes(); + // if it's a source thumbnail key, find first product with a matching asset type auto sourceKey = azrtti_cast(key.data()); if (sourceKey) { bool foundIt = false; - AZStd::vector productsAssetInfo; + AZStd::vector productsAssetInfo; AzToolsFramework::AssetSystemRequestBus::BroadcastResult( foundIt, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID, sourceKey->GetSourceUuid(), productsAssetInfo); - if (!foundIt) - { - return defaultAssetId; - } - auto assetInfoIt = AZStd::find_if( - productsAssetInfo.begin(), productsAssetInfo.end(), - [&assetType](const Data::AssetInfo& assetInfo) - { - return assetInfo.m_assetType == assetType; - }); - if (assetInfoIt == productsAssetInfo.end()) - { - return defaultAssetId; - } - return assetInfoIt->m_assetId; + for (const auto& assetInfo : productsAssetInfo) + { + if (supportedTypeIds.find(assetInfo.m_assetType) != supportedTypeIds.end()) + { + return assetInfo; + } + } + return AZ::Data::AssetInfo(); } // if it's a product thumbnail key just return its assetId + AZ::Data::AssetInfo assetInfo; auto productKey = azrtti_cast(key.data()); - if (productKey && productKey->GetAssetType() == assetType) + if (productKey && supportedTypeIds.find(productKey->GetAssetType()) != supportedTypeIds.end()) { - return productKey->GetAssetId(); + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, productKey->GetAssetId()); } - return defaultAssetId; + return assetInfo; + } + + AZ::Data::AssetId GetSupportedAssetId(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, const AZ::Data::AssetId& defaultAssetId) + { + const AZ::Data::AssetInfo assetInfo = GetSupportedAssetInfo(key); + return assetInfo.m_assetId.IsValid() ? assetInfo.m_assetId : defaultAssetId; + } + + AZ::Data::AssetId GetAssetIdForProductPath(const AZStd::string_view productPath) + { + if (!productPath.empty()) + { + return AZ::RPI::AssetUtils::GetAssetIdForProductPath(productPath.data()); + } + return AZ::Data::AssetId(); } QString WordWrap(const QString& string, int maxLength) @@ -85,32 +106,6 @@ namespace AZ } return result; } - - AZStd::unordered_set GetSupportedAssetTypes() - { - return { RPI::AnyAsset::RTTI_Type(), RPI::MaterialAsset::RTTI_Type(), RPI::ModelAsset::RTTI_Type() }; - } - - bool IsSupportedAssetType(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) - { - for (const AZ::Uuid& typeId : SharedPreviewUtils::GetSupportedAssetTypes()) - { - const AZ::Data::AssetId& assetId = SharedPreviewUtils::GetAssetId(key, typeId); - if (assetId.IsValid()) - { - if (typeId == RPI::AnyAsset::RTTI_Type()) - { - AZ::Data::AssetInfo assetInfo; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, assetId); - return AzFramework::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), "lightingpreset.azasset"); - } - return true; - } - } - - return false; - } } // namespace SharedPreviewUtils } // namespace LyIntegration } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.h index 6c5d83d22a..28c9809d6d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedPreviewUtils.h @@ -8,9 +8,9 @@ #pragma once -#include - #if !defined(Q_MOC_RUN) +#include +#include #include #endif @@ -20,21 +20,25 @@ namespace AZ { namespace SharedPreviewUtils { - //! Get assetId by assetType that belongs to either source or product thumbnail key - Data::AssetId GetAssetId( - AzToolsFramework::Thumbnailer::SharedThumbnailKey key, - const Data::AssetType& assetType, - const Data::AssetId& defaultAssetId = {}); - - //! Word wrap function for previewer QLabel, since by default it does not break long words such as filenames, so manual word - //! wrap needed - QString WordWrap(const QString& string, int maxLength); - //! Get the set of all asset types supported by the shared preview AZStd::unordered_set GetSupportedAssetTypes(); //! Determine if a thumbnail key has an asset supported by the shared preview bool IsSupportedAssetType(AzToolsFramework::Thumbnailer::SharedThumbnailKey key); + + //! Get assetInfo of source or product thumbnail key if asset type is supported by the shared preview + AZ::Data::AssetInfo GetSupportedAssetInfo(AzToolsFramework::Thumbnailer::SharedThumbnailKey key); + + //! Get assetId of source or product thumbnail key if asset type is supported by the shared preview + AZ::Data::AssetId GetSupportedAssetId( + AzToolsFramework::Thumbnailer::SharedThumbnailKey key, const AZ::Data::AssetId& defaultAssetId = {}); + + //! Wraps AZ::RPI::AssetUtils::GetAssetIdForProductPath to handle empty productPath + AZ::Data::AssetId GetAssetIdForProductPath(const AZStd::string_view productPath); + + //! Inserts new line characters into a string whenever the maximum number of characters per line is exceeded + QString WordWrap(const QString& string, int maxLength); + } // namespace SharedPreviewUtils } // namespace LyIntegration } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.cpp index 5d82bac139..d07c4577e8 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.cpp @@ -22,18 +22,13 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// SharedThumbnail::SharedThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) : Thumbnail(key) + , m_assetInfo(SharedPreviewUtils::GetSupportedAssetInfo(key)) { - for (const AZ::Uuid& typeId : SharedPreviewUtils::GetSupportedAssetTypes()) + if (m_assetInfo.m_assetId.IsValid()) { - const AZ::Data::AssetId& assetId = SharedPreviewUtils::GetAssetId(key, typeId); - if (assetId.IsValid()) - { - m_assetId = assetId; - m_typeId = typeId; - AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler::BusConnect(key); - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); - return; - } + AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler::BusConnect(key); + AzFramework::AssetCatalogEventBus::Handler::BusConnect(); + return; } AZ_Error("SharedThumbnail", false, "Failed to find matching assetId for the thumbnailKey."); @@ -43,7 +38,9 @@ namespace AZ void SharedThumbnail::LoadThread() { AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::QueueEvent( - m_typeId, &AzToolsFramework::Thumbnailer::ThumbnailerRendererRequests::RenderThumbnail, m_key, SharedThumbnailSize); + m_assetInfo.m_assetType, &AzToolsFramework::Thumbnailer::ThumbnailerRendererRequests::RenderThumbnail, m_key, + SharedThumbnailSize); + // wait for response from thumbnail renderer m_renderWait.acquire(); } @@ -68,7 +65,7 @@ namespace AZ void SharedThumbnail::OnCatalogAssetChanged([[maybe_unused]] const AZ::Data::AssetId& assetId) { - if (m_assetId == assetId && m_state == State::Ready) + if (m_assetInfo.m_assetId == assetId && m_state == State::Ready) { m_state = State::Unloaded; Load(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.h index dee433e0bb..2d4b17a09e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnail.h @@ -43,8 +43,7 @@ namespace AZ void OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) override; AZStd::binary_semaphore m_renderWait; - Data::AssetId m_assetId; - AZ::Uuid m_typeId; + Data::AssetInfo m_assetInfo; }; //! Cache configuration for shared thumbnails diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.cpp index c43ba5f1cf..4ee75e3436 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,9 +21,9 @@ namespace AZ { SharedThumbnailRenderer::SharedThumbnailRenderer() { - m_defaultModelAsset.Create(DefaultModelAssetId, true); - m_defaultMaterialAsset.Create(DefaultMaterialAssetId, true); - m_defaultLightingPresetAsset.Create(DefaultLightingPresetAssetId, true); + m_defaultModelAsset.Create(SharedPreviewUtils::GetAssetIdForProductPath(DefaultModelPath), true); + m_defaultMaterialAsset.Create(SharedPreviewUtils::GetAssetIdForProductPath(DefaultMaterialPath), true); + m_defaultLightingPresetAsset.Create(SharedPreviewUtils::GetAssetIdForProductPath(DefaultLightingPresetPath), true); for (const AZ::Uuid& typeId : SharedPreviewUtils::GetSupportedAssetTypes()) { @@ -37,17 +38,66 @@ namespace AZ SystemTickBus::Handler::BusDisconnect(); } + SharedThumbnailRenderer::ThumbnailConfig SharedThumbnailRenderer::GetThumbnailConfig( + AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey) + { + ThumbnailConfig thumbnailConfig; + + const auto assetInfo = SharedPreviewUtils::GetSupportedAssetInfo(thumbnailKey); + if (assetInfo.m_assetType == RPI::ModelAsset::RTTI_Type()) + { + static constexpr const char* MaterialAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/ModelAssetType/MaterialAssetPath"; + static constexpr const char* LightingAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/ModelAssetType/LightingAssetPath"; + + thumbnailConfig.m_modelId = assetInfo.m_assetId; + thumbnailConfig.m_materialId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(MaterialAssetPathSetting, DefaultMaterialPath)); + thumbnailConfig.m_lightingId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(LightingAssetPathSetting, DefaultLightingPresetPath)); + } + else if (assetInfo.m_assetType == RPI::MaterialAsset::RTTI_Type()) + { + static constexpr const char* ModelAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/MaterialAssetType/ModelAssetPath"; + static constexpr const char* LightingAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/MaterialAssetType/LightingAssetPath"; + + thumbnailConfig.m_modelId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(ModelAssetPathSetting, DefaultModelPath)); + thumbnailConfig.m_materialId = assetInfo.m_assetId; + thumbnailConfig.m_lightingId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(LightingAssetPathSetting, DefaultLightingPresetPath)); + } + else if (assetInfo.m_assetType == RPI::AnyAsset::RTTI_Type()) + { + static constexpr const char* ModelAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/LightingAssetType/ModelAssetPath"; + static constexpr const char* MaterialAssetPathSetting = + "/O3DE/Atom/CommonFeature/SharedPreview/LightingAssetType/MaterialAssetPath"; + + thumbnailConfig.m_modelId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(ModelAssetPathSetting, DefaultModelPath)); + thumbnailConfig.m_materialId = SharedPreviewUtils::GetAssetIdForProductPath( + AtomToolsFramework::GetSettingOrDefault(MaterialAssetPathSetting, "materials/reflectionprobe/reflectionprobevisualization.azmaterial")); + thumbnailConfig.m_lightingId = assetInfo.m_assetId; + } + + return thumbnailConfig; + } + void SharedThumbnailRenderer::RenderThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, int thumbnailSize) { if (auto previewRenderer = AZ::Interface::Get()) { + const auto& thumbnailConfig = GetThumbnailConfig(thumbnailKey); + previewRenderer->AddCaptureRequest( { thumbnailSize, AZStd::make_shared( previewRenderer->GetScene(), previewRenderer->GetView(), previewRenderer->GetEntityContextId(), - SharedPreviewUtils::GetAssetId(thumbnailKey, RPI::ModelAsset::RTTI_Type(), DefaultModelAssetId), - SharedPreviewUtils::GetAssetId(thumbnailKey, RPI::MaterialAsset::RTTI_Type(), DefaultMaterialAssetId), - SharedPreviewUtils::GetAssetId(thumbnailKey, RPI::AnyAsset::RTTI_Type(), DefaultLightingPresetAssetId), + thumbnailConfig.m_modelId, thumbnailConfig.m_materialId, thumbnailConfig.m_lightingId, Render::MaterialPropertyOverrideMap()), [thumbnailKey]() { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.h index 4db7728109..2166ac49e7 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SharedPreview/SharedThumbnailRenderer.h @@ -33,6 +33,15 @@ namespace AZ ~SharedThumbnailRenderer(); private: + struct ThumbnailConfig + { + Data::AssetId m_modelId; + Data::AssetId m_materialId; + Data::AssetId m_lightingId; + }; + + ThumbnailConfig GetThumbnailConfig(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey); + //! ThumbnailerRendererRequestsBus::Handler interface overrides... void RenderThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, int thumbnailSize) override; bool Installed() const override; @@ -42,15 +51,12 @@ namespace AZ // Default assets to be kept loaded and used for rendering if not overridden static constexpr const char* DefaultLightingPresetPath = "lightingpresets/thumbnail.lightingpreset.azasset"; - const Data::AssetId DefaultLightingPresetAssetId = AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultLightingPresetPath); Data::Asset m_defaultLightingPresetAsset; static constexpr const char* DefaultModelPath = "models/sphere.azmodel"; - const Data::AssetId DefaultModelAssetId = AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultModelPath); Data::Asset m_defaultModelAsset; static constexpr const char* DefaultMaterialPath = ""; - const Data::AssetId DefaultMaterialAssetId; Data::Asset m_defaultMaterialAsset; }; } // namespace LyIntegration diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.cpp index cbdfbfcf0c..bce17c6348 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.cpp @@ -9,6 +9,10 @@ #include +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + namespace GraphCanvas { //////////////////////// @@ -19,7 +23,6 @@ namespace GraphCanvas NodePaletteTreeItem::NodePaletteTreeItem(AZStd::string_view name, EditorId editorId) : GraphCanvas::GraphCanvasTreeItem() - , m_errorIcon(":/GraphCanvasEditorResources/toast_error_icon.png") , m_editorId(editorId) , m_name(QString::fromUtf8(name.data(), static_cast(name.size()))) , m_selected(false) @@ -88,7 +91,7 @@ namespace GraphCanvas case Qt::DecorationRole: if (HasError()) { - return m_errorIcon; + return QIcon(":/GraphCanvasEditorResources/toast_error_icon.png"); } break; default: diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.h b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.h index fcb4d79077..93bd702422 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.h +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/TreeItems/NodePaletteTreeItem.h @@ -9,10 +9,6 @@ #include -AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - #include #include #include @@ -113,7 +109,6 @@ namespace GraphCanvas private: // Error Display - QIcon m_errorIcon; QString m_errorString; AZStd::string m_styleOverride; diff --git a/Gems/LmbrCentral/Assets/Editor/Icons/Components/AxisAlignedBoxShape.svg b/Gems/LmbrCentral/Assets/Editor/Icons/Components/AxisAlignedBoxShape.svg new file mode 100644 index 0000000000..0f3982d713 --- /dev/null +++ b/Gems/LmbrCentral/Assets/Editor/Icons/Components/AxisAlignedBoxShape.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/LmbrCentral/Assets/Editor/Icons/Components/Viewport/AxisAlignedBoxShape.svg b/Gems/LmbrCentral/Assets/Editor/Icons/Components/Viewport/AxisAlignedBoxShape.svg new file mode 100644 index 0000000000..51f0be0572 --- /dev/null +++ b/Gems/LmbrCentral/Assets/Editor/Icons/Components/Viewport/AxisAlignedBoxShape.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/LmbrCentral/Code/Source/Scripting/TagComponent.cpp b/Gems/LmbrCentral/Code/Source/Scripting/TagComponent.cpp index f2701473f3..7eb3c62b5d 100644 --- a/Gems/LmbrCentral/Code/Source/Scripting/TagComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Scripting/TagComponent.cpp @@ -92,7 +92,7 @@ namespace LmbrCentral ; behaviorContext->EBus("TagGlobalRequestBus") - ->Event("RequestTaggedEntities", &TagGlobalRequestBus::Events::RequestTaggedEntities) + ->Event("Get Entity By Tag", &TagGlobalRequestBus::Events::RequestTaggedEntities, "RequestTaggedEntities") ; behaviorContext->EBus("TagComponentNotificationsBus") diff --git a/Gems/LmbrCentral/Code/Source/Shape/EditorAxisAlignedBoxShapeComponent.cpp b/Gems/LmbrCentral/Code/Source/Shape/EditorAxisAlignedBoxShapeComponent.cpp index c833677b1d..f78f2f048d 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/EditorAxisAlignedBoxShapeComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/EditorAxisAlignedBoxShapeComponent.cpp @@ -36,8 +36,8 @@ namespace LmbrCentral "Axis Aligned Box Shape", "The Axis Aligned Box Shape component creates a box around the associated entity") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Shape") - ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Box_Shape.svg") - ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Box_Shape.svg") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/AxisAlignedBoxShape.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/AxisAlignedBoxShape.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/shape/axis-aligned-box-shape/") diff --git a/Gems/PhysX/Assets/Editor/Icons/Components/PhysXHeightfieldCollider.svg b/Gems/PhysX/Assets/Editor/Icons/Components/PhysXHeightfieldCollider.svg new file mode 100644 index 0000000000..f616a26381 --- /dev/null +++ b/Gems/PhysX/Assets/Editor/Icons/Components/PhysXHeightfieldCollider.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/PhysX/Assets/Editor/Icons/Components/Viewport/PhysXHeightfieldCollider.svg b/Gems/PhysX/Assets/Editor/Icons/Components/Viewport/PhysXHeightfieldCollider.svg new file mode 100644 index 0000000000..fbfed18e46 --- /dev/null +++ b/Gems/PhysX/Assets/Editor/Icons/Components/Viewport/PhysXHeightfieldCollider.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp b/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp index 02e684de63..166c44de22 100644 --- a/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp +++ b/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace { @@ -213,7 +214,7 @@ namespace PhysX if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(4, &EditorJointConfig::VersionConverter) + ->Version(5, &EditorJointConfig::VersionConverter) ->Field("Local Position", &EditorJointConfig::m_localPosition) ->Field("Local Rotation", &EditorJointConfig::m_localRotation) ->Field("Parent Entity", &EditorJointConfig::m_leadEntity) @@ -228,6 +229,12 @@ namespace PhysX if (auto* editContext = serializeContext->GetEditContext()) { + editContext->Enum("Joint Display Setup State", "Options for displaying joint setup.") + ->Value("Never", EditorJointConfig::DisplaySetupState::Never) + ->Value("Selected", EditorJointConfig::DisplaySetupState::Selected) + ->Value("Always", EditorJointConfig::DisplaySetupState::Always) + ; + editContext->Class( "PhysX Joint Configuration", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") @@ -244,8 +251,11 @@ namespace PhysX ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorJointConfig::ValidateLeadEntityId) ->DataElement(0, &PhysX::EditorJointConfig::m_selfCollide, "Lead-Follower Collide" , "When active, the lead and follower pair will collide with each other.") - ->DataElement(0, &PhysX::EditorJointConfig::m_displayJointSetup, "Display Setup in Viewport" - , "Display joint setup in the viewport.") + ->DataElement( + AZ::Edit::UIHandlers::ComboBox, &PhysX::EditorJointConfig::m_displayJointSetup, "Display Setup in Viewport" + , "Never = Not shown." + "Select = Show setup display when entity is selected." + "Always = Always show setup display.") ->Attribute(AZ::Edit::Attributes::ReadOnly, &EditorJointConfig::IsInComponentMode) ->DataElement(0, &PhysX::EditorJointConfig::m_selectLeadOnSnap, "Select Lead on Snap" , "Select lead entity on snap to position in component mode.") @@ -306,6 +316,23 @@ namespace PhysX m_followerEntity); } + bool EditorJointConfig::ShowSetupDisplay() const + { + switch(m_displayJointSetup) + { + case DisplaySetupState::Always: + return true; + case DisplaySetupState::Selected: + { + bool showSetup = false; + AzToolsFramework::EditorEntityInfoRequestBus::EventResult( + showSetup, m_followerEntity, &AzToolsFramework::EditorEntityInfoRequests::IsSelected); + return showSetup; + } + } + return false; + } + bool EditorJointConfig::IsInComponentMode() const { return m_inComponentMode; @@ -343,6 +370,31 @@ namespace PhysX } } + // convert m_displayJointSetup from a bool to the enum with the option Never,Selected,Always show joint setup helpers. + if (classElement.GetVersion() <= 4) + { + // get the current bool setting and remove it. + bool oldSetting = false; + const int displayJointSetupIndex = classElement.FindElement(AZ_CRC_CE("Display Debug")); + if (displayJointSetupIndex >= 0) + { + AZ::SerializeContext::DataElementNode& elementNode = classElement.GetSubElement(displayJointSetupIndex); + elementNode.GetData(oldSetting); + classElement.RemoveElement(displayJointSetupIndex); + } + + //if the old setting was on set it to 'Selected'. otherwise 'Never' + if (oldSetting) + { + classElement.AddElementWithData(context, "Display Debug", EditorJointConfig::DisplaySetupState::Selected); + } + else + { + classElement.AddElementWithData(context, "Display Debug", EditorJointConfig::DisplaySetupState::Never); + } + } + + return result; } diff --git a/Gems/PhysX/Code/Editor/EditorJointConfiguration.h b/Gems/PhysX/Code/Editor/EditorJointConfiguration.h index 89ab31e4a7..f3dcd4afed 100644 --- a/Gems/PhysX/Code/Editor/EditorJointConfiguration.h +++ b/Gems/PhysX/Code/Editor/EditorJointConfiguration.h @@ -100,12 +100,21 @@ namespace PhysX AZ_TYPE_INFO(EditorJointConfig, "{8A966D65-CA97-4786-A13C-ACAA519D97EA}"); static void Reflect(AZ::ReflectContext* context); + enum class DisplaySetupState : AZ::u8 + { + Never = 0, + Selected, + Always + }; + void SetLeadEntityId(AZ::EntityId leadEntityId); JointGenericProperties ToGenericProperties() const; JointComponentConfiguration ToGameTimeConfig() const; + bool ShowSetupDisplay() const; + bool m_breakable = false; - bool m_displayJointSetup = false; + DisplaySetupState m_displayJointSetup = DisplaySetupState::Selected; bool m_inComponentMode = false; bool m_selectLeadOnSnap = true; bool m_selfCollide = false; @@ -129,3 +138,8 @@ namespace PhysX }; } // namespace PhysX + +namespace AZ +{ + AZ_TYPE_INFO_SPECIALIZE(PhysX::EditorJointConfig::DisplaySetupState, "{17EBE6BD-289A-4326-8A24-DCE3B7FEC51E}"); +} // namespace AZ diff --git a/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp b/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp index fa71fa0061..d7065a869d 100644 --- a/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp @@ -215,7 +215,7 @@ namespace PhysX { EditorJointComponent::DisplayEntityViewport(viewportInfo, debugDisplay); - if (!m_config.m_displayJointSetup && + if (!m_config.ShowSetupDisplay() && !m_config.m_inComponentMode) { return; diff --git a/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp index 0f09258524..fb0a38fa18 100644 --- a/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp @@ -34,8 +34,8 @@ namespace PhysX "PhysX Heightfield Collider", "Creates geometry in the PhysX simulation based on an attached heightfield component") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "PhysX") - ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg") - ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/PhysXCollider.svg") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/PhysXHeightfieldCollider.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/PhysXHeightfieldCollider.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) ->Attribute( AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/heightfield-collider/") diff --git a/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp b/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp index 1b575074e2..f009c990b4 100644 --- a/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp @@ -211,7 +211,7 @@ namespace PhysX { EditorJointComponent::DisplayEntityViewport(viewportInfo, debugDisplay); - if (!m_config.m_displayJointSetup && + if (!m_config.ShowSetupDisplay() && !m_config.m_inComponentMode) { return; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp index 047a07cd0b..2593104a03 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp @@ -881,6 +881,7 @@ namespace ScriptCanvasEditor void NodePaletteModel::RepopulateModel() { + AZ_PROFILE_FUNCTION(ScriptCanvas); ClearRegistry(); PopulateNodePaletteModel((*this)); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp index 1ad8b58317..e61b015aae 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp @@ -428,6 +428,8 @@ namespace ScriptCanvasEditor , m_closeCurrentGraphAfterSave(false) , m_styleManager(ScriptCanvasEditor::AssetEditorId, "ScriptCanvas/StyleSheet/graphcanvas_style.json") { + AZ_PROFILE_FUNCTION(ScriptCanvas); + VariablePaletteRequestBus::Handler::BusConnect(); GraphCanvas::AssetEditorAutomationRequestBus::Handler::BusConnect(ScriptCanvasEditor::AssetEditorId); diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainHeight.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainHeight.svg index 57835e9c20..78af20cc2d 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainHeight.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainHeight.svg @@ -1,7 +1,5 @@ - - - icon / Environmental / Terrain Height - - - - \ No newline at end of file + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainLayerSpawner.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainLayerSpawner.svg index df73d78276..ad7403d976 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainLayerSpawner.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainLayerSpawner.svg @@ -1,7 +1,5 @@ - - - icon / Environmental / Generate Terrian - - - - \ No newline at end of file + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainMacroMaterial.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainMacroMaterial.svg new file mode 100644 index 0000000000..9a694bfbf7 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainMacroMaterial.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainPhysicsCollider.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainPhysicsCollider.svg new file mode 100644 index 0000000000..56ffb05464 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainPhysicsCollider.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceGradientList.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceGradientList.svg new file mode 100644 index 0000000000..c4e8ce79d2 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceGradientList.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceMaterials.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceMaterials.svg new file mode 100644 index 0000000000..5a25cb3be9 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainSurfaceMaterials.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorld.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorld.svg index c6388d6215..f3c17f66e4 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorld.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorld.svg @@ -1,8 +1,5 @@ - - - icon / Environmental / Terrain Refactor - - - - - \ No newline at end of file + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldDebugger.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldDebugger.svg index bd1512afda..b128d0f316 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldDebugger.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldDebugger.svg @@ -1,8 +1,5 @@ - - - icon / Environmental / Terrain World Debugger - - - - - \ No newline at end of file + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldRenderer.svg b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldRenderer.svg index ab3716ad5d..de97c005fc 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldRenderer.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/TerrainWorldRenderer.svg @@ -1,8 +1,5 @@ - - - icon / Environmental / Terrain World Renderer - - - - - \ No newline at end of file + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainHeight.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainHeight.svg index b87a0b4d7e..87a2d199bd 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainHeight.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainHeight.svg @@ -1,25 +1,22 @@ - - - icon / Environmental / Terrain Height - box - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg index c078d32fe5..a89186819f 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg @@ -1,25 +1,22 @@ - - - icon / Environmental / Generate Terrian - box - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainMacroMaterial.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainMacroMaterial.svg new file mode 100644 index 0000000000..177469e46e --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainMacroMaterial.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg new file mode 100644 index 0000000000..302e585f6e --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg new file mode 100644 index 0000000000..19cb144936 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg new file mode 100644 index 0000000000..5179f61867 --- /dev/null +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorld.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorld.svg index 2aee65f2a8..5466395141 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorld.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorld.svg @@ -1,25 +1,22 @@ - - - icon / Environmental / Terrain Refactor - box - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldDebugger.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldDebugger.svg index 1b729ab73f..ea9935fefb 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldDebugger.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldDebugger.svg @@ -1,25 +1,22 @@ - - - icon / Environmental / Terrain World Debugger - box - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg index 4287508f10..af235047db 100644 --- a/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg +++ b/Gems/Terrain/Assets/Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg @@ -1,25 +1,22 @@ - - - icon / Environmental / Terrain World Renderer - box - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h index d2254161a0..924426c262 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainPhysicsColliderComponent.h @@ -25,8 +25,8 @@ namespace Terrain static constexpr auto s_categoryName = "Terrain"; static constexpr auto s_componentName = "Terrain Physics Heightfield Collider"; static constexpr auto s_componentDescription = "Provides terrain data to a physics collider in the form of a heightfield and surface->material mapping."; - static constexpr auto s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg"; - static constexpr auto s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg"; + static constexpr auto s_icon = "Editor/Icons/Components/TerrainPhysicsCollider.svg"; + static constexpr auto s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg"; static constexpr auto s_helpUrl = ""; }; } diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h index e2c5f1b280..3cf9e7fc47 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainSurfaceGradientListComponent.h @@ -25,8 +25,8 @@ namespace Terrain static constexpr const char* const s_categoryName = "Terrain"; static constexpr const char* const s_componentName = "Terrain Surface Gradient List"; static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system."; - static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg"; - static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg"; static constexpr const char* const s_helpUrl = ""; }; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h index a2fddf5768..67897b7dec 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h @@ -25,8 +25,8 @@ namespace Terrain static constexpr const char* const s_categoryName = "Terrain"; static constexpr const char* const s_componentName = "Terrain Macro Material"; static constexpr const char* const s_componentDescription = "Provides a macro material for a region to the terrain renderer"; - static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerRenderer.svg"; - static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerRenderer.svg"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainMacroMaterial.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainMacroMaterial.svg"; static constexpr const char* const s_helpUrl = ""; }; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.h index f0d03a6082..7fe8c82522 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.h @@ -25,8 +25,8 @@ namespace Terrain static constexpr const char* const s_categoryName = "Terrain"; static constexpr const char* const s_componentName = "Terrain Surface Materials List"; static constexpr const char* const s_componentDescription = "Provides a mapping between surface tags and render materials."; - static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainHeight.svg"; - static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainHeight.svg"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceMaterials.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg"; static constexpr const char* const s_helpUrl = ""; }; } diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index dfec6b1aaa..a3dca1a97e 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -44,7 +44,7 @@ def validate_downloaded_zip_sha256(download_uri_json_data: dict, download_zip_pa try: sha256A = download_uri_json_data['sha256'] except KeyError as e: - logger.warn('SECURITY WARNING: The advertised o3de object you downloaded has no "sha256"!!! Be VERY careful!!!' + logger.warning('SECURITY WARNING: The advertised o3de object you downloaded has no "sha256"!!! Be VERY careful!!!' ' We cannot verify this is the actually the advertised object!!!') return 1 else: @@ -236,13 +236,13 @@ def is_o3de_object_update_available(object_name: str, downloadable_kwarg_key, lo try: repo_copy_updated_string = downloadable_object_data['last_updated'] except KeyError: - logger.warn(f'last_updated field not found for {object_name}.') + logger.warning(f'last_updated field not found for {object_name}.') return False try: local_last_updated_time = datetime.fromisoformat(local_last_updated) except ValueError: - logger.warn(f'last_updated field has incorrect format for local copy of {downloadable_kwarg_key} {object_name}.') + logger.warning(f'last_updated field has incorrect format for local copy of {downloadable_kwarg_key} {object_name}.') # Possible that an earlier version did not have this field so still want to check against cached downloadable version local_last_updated_time = datetime.min diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 297d8e39d2..ceaec55474 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -455,7 +455,7 @@ def get_json_data_file(object_json: pathlib.Path, try: object_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{object_json} failed to load: {e}') + logger.warning(f'{object_json} failed to load: {e}') else: return object_json_data @@ -596,7 +596,7 @@ def get_registered(engine_name: str = None, try: engine_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{engine_json} failed to load: {str(e)}') + logger.warning(f'{engine_json} failed to load: {str(e)}') else: this_engines_name = engine_json_data['engine_name'] if this_engines_name == engine_name: @@ -611,7 +611,7 @@ def get_registered(engine_name: str = None, try: project_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{project_json} failed to load: {str(e)}') + logger.warning(f'{project_json} failed to load: {str(e)}') else: this_projects_name = project_json_data['project_name'] if this_projects_name == project_name: @@ -626,7 +626,7 @@ def get_registered(engine_name: str = None, try: gem_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{gem_json} failed to load: {str(e)}') + logger.warning(f'{gem_json} failed to load: {str(e)}') else: this_gems_name = gem_json_data['gem_name'] if this_gems_name == gem_name: @@ -641,7 +641,7 @@ def get_registered(engine_name: str = None, try: template_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{template_path} failed to load: {str(e)}') + logger.warning(f'{template_path} failed to load: {str(e)}') else: this_templates_name = template_json_data['template_name'] if this_templates_name == template_name: @@ -656,7 +656,7 @@ def get_registered(engine_name: str = None, try: restricted_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{restricted_json} failed to load: {str(e)}') + logger.warning(f'{restricted_json} failed to load: {str(e)}') else: this_restricted_name = restricted_json_data['restricted_name'] if this_restricted_name == restricted_name: @@ -689,7 +689,7 @@ def get_registered(engine_name: str = None, try: repo_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') + logger.warning(f'{cache_file} failed to load: {str(e)}') else: this_repos_name = repo_json_data['repo_name'] if this_repos_name == repo_name: diff --git a/scripts/o3de/o3de/print_registration.py b/scripts/o3de/o3de/print_registration.py index b164243b7c..4b4c9d6515 100644 --- a/scripts/o3de/o3de/print_registration.py +++ b/scripts/o3de/o3de/print_registration.py @@ -326,7 +326,7 @@ def print_repos_data(repos_data: dict) -> int: try: repo_json_data = json.load(s) except json.JSONDecodeError as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') + logger.warning(f'{cache_file} failed to load: {str(e)}') else: print(f'{repo_uri}/repo.json cached as:') print(cache_file) diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 8a2bb788aa..de0934f2e5 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -485,12 +485,12 @@ def register_repo(json_data: dict, json_data['repos'].remove(repo_uri) if remove: - logger.warn(f'Removing repo uri {repo_uri}.') + logger.warning(f'Removing repo uri {repo_uri}.') return 0 repo_sha256 = hashlib.sha256(url.encode()) cache_file = manifest.get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') - result = utils.download_file(parsed_uri, cache_file) + result = utils.download_file(parsed_uri, cache_file, True) if result == 0: json_data.setdefault('repos', []).insert(0, repo_uri) @@ -689,7 +689,7 @@ def remove_invalid_o3de_projects(manifest_path: pathlib.Path = None) -> int: for project in json_data.get('projects', []): if not validation.valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): - logger.warn(f"Project path {project} is invalid.") + logger.warning(f"Project path {project} is invalid.") # Attempt to unregister all invalid projects even if previous projects failed to unregister # but combine the result codes of each command. result = register(project_path=pathlib.Path(project), remove=True) or result @@ -699,7 +699,7 @@ def remove_invalid_o3de_projects(manifest_path: pathlib.Path = None) -> int: def remove_invalid_o3de_objects() -> None: for engine_path in manifest.get_engines(): if not validation.valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): - logger.warn(f"Engine path {engine_path} is invalid.") + logger.warning(f"Engine path {engine_path} is invalid.") register(engine_path=engine_path, remove=True) remove_invalid_o3de_projects() @@ -707,17 +707,17 @@ def remove_invalid_o3de_objects() -> None: for external in manifest.get_external_subdirectories(): external = pathlib.Path(external).resolve() if not external.is_dir(): - logger.warn(f"External subdirectory {external} is invalid.") + logger.warning(f"External subdirectory {external} is invalid.") register(engine_path=engine_path, external_subdir_path=external, remove=True) for template in manifest.get_templates(): if not validation.valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): - logger.warn(f"Template path {template} is invalid.") + logger.warning(f"Template path {template} is invalid.") register(template_path=template, remove=True) for restricted in manifest.get_restricted(): if not validation.valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): - logger.warn(f"Restricted path {restricted} is invalid.") + logger.warning(f"Restricted path {restricted} is invalid.") register(restricted_path=restricted, remove=True) json_data = manifest.load_o3de_manifest() @@ -725,7 +725,7 @@ def remove_invalid_o3de_objects() -> None: if not default_engines_folder.is_dir(): new_default_engines_folder = manifest.get_o3de_folder() / 'Engines' new_default_engines_folder.mkdir(parents=True, exist_ok=True) - logger.warn( + logger.warning( f"Default engines folder {default_engines_folder} is invalid. Set default {new_default_engines_folder}") register(default_engines_folder=new_default_engines_folder.as_posix()) @@ -733,7 +733,7 @@ def remove_invalid_o3de_objects() -> None: if not default_projects_folder.is_dir(): new_default_projects_folder = manifest.get_o3de_folder() / 'Projects' new_default_projects_folder.mkdir(parents=True, exist_ok=True) - logger.warn( + logger.warning( f"Default projects folder {default_projects_folder} is invalid. Set default {new_default_projects_folder}") register(default_projects_folder=new_default_projects_folder.as_posix()) @@ -741,7 +741,7 @@ def remove_invalid_o3de_objects() -> None: if not default_gems_folder.is_dir(): new_default_gems_folder = manifest.get_o3de_folder() / 'Gems' new_default_gems_folder.mkdir(parents=True, exist_ok=True) - logger.warn(f"Default gems folder {default_gems_folder} is invalid." + logger.warning(f"Default gems folder {default_gems_folder} is invalid." f" Set default {new_default_gems_folder}") register(default_gems_folder=new_default_gems_folder.as_posix()) @@ -749,7 +749,7 @@ def remove_invalid_o3de_objects() -> None: if not default_templates_folder.is_dir(): new_default_templates_folder = manifest.get_o3de_folder() / 'Templates' new_default_templates_folder.mkdir(parents=True, exist_ok=True) - logger.warn( + logger.warning( f"Default templates folder {default_templates_folder} is invalid." f" Set default {new_default_templates_folder}") register(default_templates_folder=new_default_templates_folder.as_posix()) @@ -758,7 +758,7 @@ def remove_invalid_o3de_objects() -> None: if not default_restricted_folder.is_dir(): default_restricted_folder = manifest.get_o3de_folder() / 'Restricted' default_restricted_folder.mkdir(parents=True, exist_ok=True) - logger.warn( + logger.warning( f"Default restricted folder {default_restricted_folder} is invalid." f" Set default {default_restricted_folder}") register(default_restricted_folder=default_restricted_folder.as_posix()) @@ -767,7 +767,7 @@ def remove_invalid_o3de_objects() -> None: if not default_third_party_folder.is_dir(): default_third_party_folder = manifest.get_o3de_folder() / '3rdParty' default_third_party_folder.mkdir(parents=True, exist_ok=True) - logger.warn( + logger.warning( f"Default 3rd Party folder {default_third_party_folder} is invalid." f" Set default {default_third_party_folder}") register(default_third_party_folder=default_third_party_folder.as_posix()) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 3ff2a7d982..22c7c54c8f 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -23,6 +23,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, repo_set: set) -> int: file_name = pathlib.Path(file_name).resolve() if not validation.valid_o3de_repo_json(file_name): + logger.error(f'Repository JSON {file_name} could not be loaded or is missing required values') return 1 cache_folder = manifest.get_o3de_cache_folder() @@ -113,7 +114,7 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> set: file_name = pathlib.Path(cache_filename).resolve() if not file_name.is_file(): - logger.error(f'Could not find cached repo json file for {repo_uri}') + logger.error(f'Could not find cached repository json file for {repo_uri}. Try refreshing the repository.') return gem_set with file_name.open('r') as f: @@ -138,7 +139,7 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> set: if cache_gem_json_filepath.is_file(): gem_set.add(cache_gem_json_filepath) else: - logger.warn(f'Could not find cached gem json file {cache_gem_json_filepath} for {o3de_object_uri} in repo {repo_uri}') + logger.warning(f'Could not find cached gem json file {cache_gem_json_filepath} for {o3de_object_uri} in repo {repo_uri}') return gem_set @@ -166,6 +167,7 @@ def refresh_repo(repo_uri: str, download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: + logger.error(f'Repo json {repo_uri} could not download.') return download_file_result if not validation.valid_o3de_repo_json(cache_file): @@ -256,7 +258,7 @@ def search_o3de_object(manifest_json, o3de_object_uris, search_func): try: manifest_json_data = json.load(f) except json.JSONDecodeError as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') + logger.warning(f'{cache_file} failed to load: {str(e)}') else: result_json_data = search_func(manifest_json_data) if result_json_data: diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index 88f84ae75e..6f1dd8b2c5 100644 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -125,7 +125,8 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool """ if download_path.is_file(): if not force_overwrite: - logger.warn(f'File already downloaded to {download_path}.') + logger.error(f'File already downloaded to {download_path} and force_overwrite is not set.') + return 1 else: try: os.unlink(download_path) @@ -134,20 +135,25 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool return 1 if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: - with urllib.request.urlopen(parsed_uri.geturl()) as s: - download_file_size = 0 - try: - download_file_size = s.headers['content-length'] - except KeyError: - pass - def download_progress(downloaded_bytes): - if download_progress_callback: - return download_progress_callback(int(downloaded_bytes), int(download_file_size)) - return False - with download_path.open('wb') as f: - download_cancelled = copyfileobj(s, f, download_progress) - if download_cancelled: - return 1 + try: + with urllib.request.urlopen(parsed_uri.geturl()) as s: + download_file_size = 0 + try: + download_file_size = s.headers['content-length'] + except KeyError: + pass + def download_progress(downloaded_bytes): + if download_progress_callback: + return download_progress_callback(int(downloaded_bytes), int(download_file_size)) + return False + with download_path.open('wb') as f: + download_cancelled = copyfileobj(s, f, download_progress) + if download_cancelled: + logger.info(f'Download of file to {download_path} cancelled.') + return 1 + except urllib.error.HTTPError as e: + logger.error(f'HTTP Error {e.code} opening {parsed_uri.geturl()}') + return 1 else: origin_file = pathlib.Path(parsed_uri.geturl()).resolve() if not origin_file.is_file(): @@ -167,7 +173,7 @@ def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, force_overwri return download_file_result if not zipfile.is_zipfile(download_zip_path): - logger.error(f"File zip {download_zip_path} is invalid.") + logger.error(f"File zip {download_zip_path} is invalid. Try re-downloading the file.") download_zip_path.unlink() return 1