diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py index 198fb934d9..077a068a18 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py @@ -12,6 +12,7 @@ import pytest import ly_test_tools.log.log_monitor from AWS.common import constants +from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY # fixture imports from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor @@ -70,6 +71,41 @@ class TestAWSClientAuthWindows(object): halt_on_unexpected=True, ) assert result, 'Anonymous credentials fetched successfully.' + + @pytest.mark.parametrize('level', ['AWS/ClientAuth']) + def test_anonymous_credentials_no_global_accountid(self, + level: str, + launcher: pytest.fixture, + resource_mappings: pytest.fixture, + workspace: pytest.fixture, + asset_processor: pytest.fixture + ): + """ + Test to verify AWS Cognito Identity pool anonymous authorization. + + Setup: Updates resource mapping file using existing CloudFormation stacks. + Tests: Getting credentials when no credentials are configured + Verification: Log monitor looks for success credentials log. + """ + # Remove top-level account ID from resource mappings + resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY]) + + asset_processor.start() + asset_processor.wait_for_idle() + + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME) + log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) + + launcher.args = ['+LoadLevel', level] + launcher.args.extend(['-rhi=null']) + + with launcher.start(launch_ap=False): + result = log_monitor.monitor_log_for_lines( + expected_lines=['(Script) - Success anonymous credentials'], + unexpected_lines=['(Script) - Fail anonymous credentials'], + halt_on_unexpected=True, + ) + assert result, 'Anonymous credentials fetched successfully.' def test_password_signin_credentials(self, launcher: pytest.fixture, diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py index 11832f8846..a0db95c203 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py @@ -150,8 +150,7 @@ def create_basic_atom_level(level_name): entity_position=default_position, components=["HDRi Skybox", "Global Skylight (IBL)"], parent_id=default_level.id) - global_skylight_asset_path = os.path.join( - "LightingPresets", "greenwich_park_02_4k_iblskyboxcm_iblspecular.exr.streamingimage") + global_skylight_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") global_skylight_asset_value = asset.AssetCatalogRequestBus( bus.Broadcast, "GetAssetIdByPath", global_skylight_asset_path, math.Uuid(), False) global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_asset_value) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py index 344a0dbd29..4884dbff56 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py @@ -55,11 +55,13 @@ class AtomComponentProperties: def camera(property: str = 'name') -> str: """ Camera component properties. + - 'Field of view': Sets the value for the camera's FOV (Field of View) in degrees, i.e. 60.0 :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Camera', + 'Field of view': 'Controller|Configuration|Field of view' } return properties[property] @@ -198,11 +200,13 @@ class AtomComponentProperties: def grid(property: str = 'name') -> str: """ Grid component properties. + - 'Secondary Grid Spacing': The spacing value for the secondary grid, i.e. 1.0 :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Grid', + 'Secondary Grid Spacing': 'Controller|Configuration|Secondary Grid Spacing', } return properties[property] @@ -225,11 +229,13 @@ class AtomComponentProperties: def hdri_skybox(property: str = 'name') -> str: """ HDRi Skybox component properties. + - 'Cubemap Texture': Asset.id for the cubemap texture to set. :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'HDRi Skybox', + 'Cubemap Texture': 'Controller|Configuration|Cubemap Texture', } return properties[property] @@ -268,12 +274,14 @@ class AtomComponentProperties: Material component properties. Requires one of Actor OR Mesh component. - 'requires' a list of component names as strings required by this component. Only one of these is required at a time for this component.\n + - 'Material Asset': the material Asset.id of the material. :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Material', 'requires': [AtomComponentProperties.actor(), AtomComponentProperties.mesh()], + 'Material Asset': 'Default Material|Material Asset', } return properties[property] diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_1.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_1.ppm index 0725999dcf..8695f6bb93 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_1.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_1.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:954d7d0df47c840a24e313893800eb3126d0c0d47c3380926776b51833778db7 +oid sha256:aee1fd4d5264e5ef1676b507409ce70af6358cf1ff368d9aeb17f7b2597dfbca size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_2.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_2.ppm index 3a45bd31e3..d45d3f1581 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_2.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_2.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e81c19128f42ba362a2d5f3ccf159dfbc942d67ceeb1ac8c21f295a6fd9d2ce5 +oid sha256:d4787cdafbcc2fe71c1cb3f1da53a249db839a9df539a9e88be43ccd6d8e4d6a size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_3.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_3.ppm index 15d679b784..0661ead69f 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_3.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_3.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e20801213e065b6ea8c95ede81c23faa9b6dc70a2002dc5bced293e1bed989f +oid sha256:5fac5bf41c9b16b6fbd762868e5cf514376af92d6ef7ebb9e819f024f1a3e1a7 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_4.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_4.ppm index 85c083a386..a7fc77f0ec 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_4.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_4.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e250f812e594e5152bf2d6f23caa8b53b78276bfdf344d7a8d355dd96cb995c0 +oid sha256:7a23969670499524725535e8be7428b55b6f3e887cc24e2e903f7ea821a6d1a5 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_5.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_5.ppm index d575de761e..7404ab3ccc 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_5.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AreaLight_5.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95be359041f8291c74b335297a4dfe9902a180510f24a181b15e1a5ba4d3b024 +oid sha256:2f1f4d8865c56ed7f96f339c39e5feb4e0dbc6c6a8b4a7843b4166381b06b00d size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AtomBasicLevelSetup.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AtomBasicLevelSetup.ppm index ef41b6cf77..acfbe57900 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AtomBasicLevelSetup.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/AtomBasicLevelSetup.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07e09d3eb5bf0cee3d9b3752aaad40f3ead1dcc5ddd837a6226fadde55d57274 +oid sha256:5d4ee5641e19eef08dd6b93d2f4054a1aae2165325416ed2cbf0b8243f2c0b06 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_1.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_1.ppm index bbbd127929..3104088689 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_1.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_1.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:118e43e4b915e262726183467cc4b82f244565213fea5b6bfe02be07f0851ab1 +oid sha256:55c8f0d1790bb12660b7557630efca297b2a1b59e6c93167a2563da79e0a8255 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_2.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_2.ppm index 8e716fabcc..287ec406e1 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_2.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_2.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc2ce3256a6552975962c9e113c52c1a22bf3817d417151f6f60640dd568e0fa +oid sha256:082ff368b621e12b083d96562a0889b11a1d683767a74296cbe6d8732830e9e8 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_3.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_3.ppm index 6b6a5a5d6e..9de8785f65 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_3.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_3.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:287d98890b35427688999760f9d066bcbff1a3bc9001534241dc212b32edabd8 +oid sha256:78cc62d89782899747875b41abee57c2efdfacf4c8af6511c88f82d76eaae4ca size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_4.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_4.ppm index eb05228cc2..93acc14dbc 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_4.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_4.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66e91c92c868167c850078cd91714db47e10a96e23cc30191994486bd79c353f +oid sha256:3d6719326f4dacae278d1723090ce1182193b793f250963af8be4b2c298e8841 size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_5.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_5.ppm index 5e12edc46d..9cd58caae5 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_5.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_5.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d950d173f5101820c5e18205401ca08ce5feeff2302ac2920b292750d86a8fa4 +oid sha256:9e492bb394fb18fb117f8a5b61cd2789922f9d6e88fc83189b5b6d59ffb1c3ef size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_6.ppm b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_6.ppm index d442d90287..136eecbaaf 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_6.ppm +++ b/AutomatedTesting/Gem/PythonTests/Atom/golden_images/SpotLight_6.ppm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72eddb7126eae0c839b933886e0fb69d78229f72d49ef13199de28df2b7879db +oid sha256:caca85f7728f660daae36afc81d681ba2de2377c516eb3c637599de5c94012aa size 6220817 diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py index cc891f5929..64bbfb1c4d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py @@ -123,8 +123,7 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): Report.result(Tests.is_visible, global_skylight_entity.is_visible() is True) # 8. Set the Diffuse Image asset on the Global Skylight (IBL) entity. - global_skylight_diffuse_image_property = "Controller|Configuration|Diffuse Image" - diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") + diffuse_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") diffuse_image_asset = Asset.find_asset_by_path(diffuse_image_path, False) global_skylight_component.set_component_property_value( global_skylight_diffuse_image_property, diffuse_image_asset.id) @@ -133,8 +132,7 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): Report.result(Tests.diffuse_image_set, diffuse_image_set == diffuse_image_asset.id) # 9. Set the Specular Image asset on the Global Light (IBL) entity. - global_skylight_specular_image_property = "Controller|Configuration|Specular Image" - specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") + specular_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") specular_image_asset = Asset.find_asset_by_path(specular_image_path, False) global_skylight_component.set_component_property_value( global_skylight_specular_image_property, specular_image_asset.id) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py index 92c555127a..127b3e5b5f 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py @@ -6,30 +6,70 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off -class Tests : - camera_component_added = ("Camera component was added", "Camera component wasn't added") - camera_fov_set = ("Camera component FOV property set", "Camera component FOV property wasn't set") - directional_light_component_added = ("Directional Light component added", "Directional Light component wasn't added") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - global_skylight_component_added = ("Global Skylight (IBL) component added", "Global Skylight (IBL) component wasn't added") - global_skylight_diffuse_image_set = ("Global Skylight Diffuse Image property set", "Global Skylight Diffuse Image property wasn't set") - global_skylight_specular_image_set = ("Global Skylight Specular Image property set", "Global Skylight Specular Image property wasn't set") - ground_plane_material_asset_set = ("Ground Plane Material Asset was set", "Ground Plane Material Asset wasn't set") - ground_plane_material_component_added = ("Ground Plane Material component added", "Ground Plane Material component wasn't added") - ground_plane_mesh_asset_set = ("Ground Plane Mesh Asset property was set", "Ground Plane Mesh Asset property wasn't set") - hdri_skybox_component_added = ("HDRi Skybox component added", "HDRi Skybox component wasn't added") - hdri_skybox_cubemap_texture_set = ("HDRi Skybox Cubemap Texture property set", "HDRi Skybox Cubemap Texture property wasn't set") - mesh_component_added = ("Mesh component added", "Mesh component wasn't added") - no_assert_occurred = ("No asserts detected", "Asserts were detected") - no_error_occurred = ("No errors detected", "Errors were detected") - secondary_grid_spacing = ("Secondary Grid Spacing set", "Secondary Grid Spacing not set") - sphere_material_component_added = ("Sphere Material component added", "Sphere Material component wasn't added") - sphere_material_set = ("Sphere Material Asset was set", "Sphere Material Asset wasn't set") - sphere_mesh_asset_set = ("Sphere Mesh Asset was set", "Sphere Mesh Asset wasn't set") - viewport_set = ("Viewport set to correct size", "Viewport not set to correct size") -# fmt: on +class Tests: + camera_component_added = ( + "Camera component was added", + "Camera component wasn't added") + camera_fov_set = ( + "Camera component FOV property set", + "Camera component FOV property wasn't set") + directional_light_component_added = ( + "Directional Light component added", + "Directional Light component wasn't added") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + global_skylight_component_added = ( + "Global Skylight (IBL) component added", + "Global Skylight (IBL) component wasn't added") + global_skylight_diffuse_image_set = ( + "Global Skylight Diffuse Image property set", + "Global Skylight Diffuse Image property wasn't set") + global_skylight_specular_image_set = ( + "Global Skylight Specular Image property set", + "Global Skylight Specular Image property wasn't set") + ground_plane_material_asset_set = ( + "Ground Plane Material Asset was set", + "Ground Plane Material Asset wasn't set") + ground_plane_material_component_added = ( + "Ground Plane Material component added", + "Ground Plane Material component wasn't added") + ground_plane_mesh_asset_set = ( + "Ground Plane Mesh Asset property was set", + "Ground Plane Mesh Asset property wasn't set") + hdri_skybox_component_added = ( + "HDRi Skybox component added", + "HDRi Skybox component wasn't added") + hdri_skybox_cubemap_texture_set = ( + "HDRi Skybox Cubemap Texture property set", + "HDRi Skybox Cubemap Texture property wasn't set") + mesh_component_added = ( + "Mesh component added", + "Mesh component wasn't added") + no_assert_occurred = ( + "No asserts detected", + "Asserts were detected") + no_error_occurred = ( + "No errors detected", + "Errors were detected") + secondary_grid_spacing = ( + "Secondary Grid Spacing set", + "Secondary Grid Spacing not set") + sphere_material_component_added = ( + "Sphere Material component added", + "Sphere Material component wasn't added") + sphere_material_set = ( + "Sphere Material Asset was set", + "Sphere Material Asset wasn't set") + sphere_mesh_asset_set = ( + "Sphere Mesh Asset was set", + "Sphere Mesh Asset wasn't set") + viewport_set = ( + "Viewport set to correct size", + "Viewport not set to correct size") def AtomGPU_BasicLevelSetup_SetsUpLevel(): @@ -77,19 +117,17 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): import os from math import isclose - import azlmbr.asset as asset - import azlmbr.bus as bus import azlmbr.legacy.general as general import azlmbr.math as math import azlmbr.paths + from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties from Atom.atom_utils.screenshot_utils import ScreenshotHelper - MATERIAL_COMPONENT_NAME = "Material" - MESH_COMPONENT_NAME = "Mesh" SCREENSHOT_NAME = "AtomBasicLevelSetup" SCREEN_WIDTH = 1280 SCREEN_HEIGHT = 720 @@ -98,24 +136,24 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): def initial_viewport_setup(screen_width, screen_height): general.set_viewport_size(screen_width, screen_height) general.update_viewport() - result = isclose( - a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and isclose( - a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1) - - return result + TestHelper.wait_for_condition( + function=lambda: isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) + and isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1), + timeout_in_seconds=4.0 + ) with Tracer() as error_tracer: # Test setup begins. # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Close error windows and display helpers then update the viewport size. - helper.close_error_windows() - helper.close_display_helpers() + TestHelper.close_error_windows() + TestHelper.close_display_helpers() + initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT) general.update_viewport() - Report.critical_result(Tests.viewport_set, initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)) # 2. Create Default Level Entity. default_level_entity_name = "Default Level" @@ -123,168 +161,167 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): math.Vector3(0.0, 0.0, 0.0), default_level_entity_name) # 3. Create Grid Entity as a child entity of the Default Level Entity. - grid_name = "Grid" - grid_entity = EditorEntity.create_editor_entity(grid_name, default_level_entity.id) + grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid(), default_level_entity.id) # 4. Add Grid component to Grid Entity and set Secondary Grid Spacing. - grid_component = grid_entity.add_component(grid_name) - secondary_grid_spacing_property = "Controller|Configuration|Secondary Grid Spacing" + grid_component = grid_entity.add_component(AtomComponentProperties.grid()) secondary_grid_spacing_value = 1.0 - grid_component.set_component_property_value(secondary_grid_spacing_property, secondary_grid_spacing_value) + grid_component.set_component_property_value( + AtomComponentProperties.grid('Secondary Grid Spacing'), secondary_grid_spacing_value) secondary_grid_spacing_set = grid_component.get_component_property_value( - secondary_grid_spacing_property) == secondary_grid_spacing_value + AtomComponentProperties.grid('Secondary Grid Spacing')) == secondary_grid_spacing_value Report.result(Tests.secondary_grid_spacing, secondary_grid_spacing_set) # 5. Create Global Skylight (IBL) Entity as a child entity of the Default Level Entity. - global_skylight_name = "Global Skylight (IBL)" - global_skylight_entity = EditorEntity.create_editor_entity(global_skylight_name, default_level_entity.id) + global_skylight_entity = EditorEntity.create_editor_entity( + AtomComponentProperties.global_skylight(), default_level_entity.id) # 6. Add HDRi Skybox component to the Global Skylight (IBL) Entity. - hdri_skybox_name = "HDRi Skybox" - hdri_skybox_component = global_skylight_entity.add_component(hdri_skybox_name) - Report.result(Tests.hdri_skybox_component_added, global_skylight_entity.has_component(hdri_skybox_name)) + hdri_skybox_component = global_skylight_entity.add_component(AtomComponentProperties.hdri_skybox()) + Report.result(Tests.hdri_skybox_component_added, global_skylight_entity.has_component( + AtomComponentProperties.hdri_skybox())) # 7. Add Global Skylight (IBL) component to the Global Skylight (IBL) Entity. - global_skylight_component = global_skylight_entity.add_component(global_skylight_name) - Report.result(Tests.global_skylight_component_added, global_skylight_entity.has_component(global_skylight_name)) + global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight()) + Report.result(Tests.global_skylight_component_added, global_skylight_entity.has_component( + AtomComponentProperties.global_skylight())) # 8. Set the Cubemap Texture property of the HDRi Skybox component. - global_skylight_image_asset_path = os.path.join( - "LightingPresets", "greenwich_park_02_4k_iblskyboxcm_iblspecular.exr.streamingimage") - global_skylight_image_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", global_skylight_image_asset_path, math.Uuid(), False) - hdri_skybox_cubemap_texture_property = "Controller|Configuration|Cubemap Texture" + global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") + global_skylight_image_asset = Asset.find_asset_by_path(global_skylight_image_asset_path, False) hdri_skybox_component.set_component_property_value( - hdri_skybox_cubemap_texture_property, global_skylight_image_asset) + AtomComponentProperties.hdri_skybox('Cubemap Texture'), global_skylight_image_asset.id) Report.result( Tests.hdri_skybox_cubemap_texture_set, hdri_skybox_component.get_component_property_value( - hdri_skybox_cubemap_texture_property) == global_skylight_image_asset) + AtomComponentProperties.hdri_skybox('Cubemap Texture')) == global_skylight_image_asset.id) # 9. Set the Diffuse Image property of the Global Skylight (IBL) component. # Re-use the same image that was used in the previous test step. - global_skylight_diffuse_image_property = "Controller|Configuration|Diffuse Image" + global_skylight_diffuse_image_asset_path = os.path.join( + "LightingPresets", "default_iblskyboxcm_ibldiffuse.exr.streamingimage") + global_skylight_diffuse_image_asset = Asset.find_asset_by_path(global_skylight_diffuse_image_asset_path, False) global_skylight_component.set_component_property_value( - global_skylight_diffuse_image_property, global_skylight_image_asset) + AtomComponentProperties.global_skylight('Diffuse Image'), global_skylight_diffuse_image_asset.id) Report.result( Tests.global_skylight_diffuse_image_set, global_skylight_component.get_component_property_value( - global_skylight_diffuse_image_property) == global_skylight_image_asset) + AtomComponentProperties.global_skylight('Diffuse Image')) == global_skylight_diffuse_image_asset.id) # 10. Set the Specular Image property of the Global Skylight (IBL) component. # Re-use the same image that was used in the previous test step. - global_skylight_specular_image_property = "Controller|Configuration|Specular Image" + global_skylight_specular_image_asset_path = os.path.join( + "LightingPresets", "default_iblskyboxcm_iblspecular.exr.streamingimage") + global_skylight_specular_image_asset = Asset.find_asset_by_path( + global_skylight_specular_image_asset_path, False) global_skylight_component.set_component_property_value( - global_skylight_specular_image_property, global_skylight_image_asset) + AtomComponentProperties.global_skylight('Specular Image'), global_skylight_specular_image_asset.id) global_skylight_specular_image_set = global_skylight_component.get_component_property_value( - global_skylight_specular_image_property) + AtomComponentProperties.global_skylight('Specular Image')) Report.result( - Tests.global_skylight_specular_image_set, global_skylight_specular_image_set == global_skylight_image_asset) + Tests.global_skylight_specular_image_set, + global_skylight_specular_image_set == global_skylight_specular_image_asset.id) # 11. Create a Ground Plane Entity with a Material component that is a child entity of the Default Level Entity. ground_plane_name = "Ground Plane" ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id) - ground_plane_material_component = ground_plane_entity.add_component(MATERIAL_COMPONENT_NAME) + ground_plane_material_component = ground_plane_entity.add_component(AtomComponentProperties.material()) Report.result( - Tests.ground_plane_material_component_added, ground_plane_entity.has_component(MATERIAL_COMPONENT_NAME)) + Tests.ground_plane_material_component_added, + ground_plane_entity.has_component(AtomComponentProperties.material())) # 12. Set the Material Asset property of the Material component for the Ground Plane Entity. ground_plane_entity.set_local_uniform_scale(32.0) ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") - ground_plane_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) - ground_plane_material_asset_property = "Default Material|Material Asset" + ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False) ground_plane_material_component.set_component_property_value( - ground_plane_material_asset_property, ground_plane_material_asset) + AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id) Report.result( Tests.ground_plane_material_asset_set, ground_plane_material_component.get_component_property_value( - ground_plane_material_asset_property) == ground_plane_material_asset) + AtomComponentProperties.material('Material Asset')) == ground_plane_material_asset.id) # 13. Add the Mesh component to the Ground Plane Entity and set the Mesh component Mesh Asset property. - ground_plane_mesh_component = ground_plane_entity.add_component(MESH_COMPONENT_NAME) - Report.result(Tests.mesh_component_added, ground_plane_entity.has_component(MESH_COMPONENT_NAME)) - ground_plane_mesh_asset_path = os.path.join("Objects", "plane.azmodel") - ground_plane_mesh_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False) - ground_plane_mesh_asset_property = "Controller|Configuration|Mesh Asset" + ground_plane_mesh_component = ground_plane_entity.add_component(AtomComponentProperties.mesh()) + Report.result(Tests.mesh_component_added, ground_plane_entity.has_component(AtomComponentProperties.mesh())) + ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel") + ground_plane_mesh_asset = Asset.find_asset_by_path(ground_plane_mesh_asset_path, False) ground_plane_mesh_component.set_component_property_value( - ground_plane_mesh_asset_property, ground_plane_mesh_asset) + AtomComponentProperties.mesh('Mesh Asset'), ground_plane_mesh_asset.id) Report.result( Tests.ground_plane_mesh_asset_set, ground_plane_mesh_component.get_component_property_value( - ground_plane_mesh_asset_property) == ground_plane_mesh_asset) + AtomComponentProperties.mesh('Mesh Asset')) == ground_plane_mesh_asset.id) # 14. Create a Directional Light Entity as a child entity of the Default Level Entity. - directional_light_name = "Directional Light" directional_light_entity = EditorEntity.create_editor_entity_at( - math.Vector3(0.0, 0.0, 10.0), directional_light_name, default_level_entity.id) + math.Vector3(0.0, 0.0, 10.0), AtomComponentProperties.directional_light(), default_level_entity.id) # 15. Add Directional Light component to Directional Light Entity and set entity rotation. - directional_light_entity.add_component(directional_light_name) + directional_light_entity.add_component(AtomComponentProperties.directional_light()) directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0) directional_light_entity.set_local_rotation(directional_light_entity_rotation) Report.result( - Tests.directional_light_component_added, directional_light_entity.has_component(directional_light_name)) + Tests.directional_light_component_added, directional_light_entity.has_component( + AtomComponentProperties.directional_light())) # 16. Create a Sphere Entity as a child entity of the Default Level Entity then add a Material component. sphere_entity = EditorEntity.create_editor_entity_at( math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id) - sphere_material_component = sphere_entity.add_component(MATERIAL_COMPONENT_NAME) - Report.result(Tests.sphere_material_component_added, sphere_entity.has_component(MATERIAL_COMPONENT_NAME)) + sphere_material_component = sphere_entity.add_component(AtomComponentProperties.material()) + Report.result(Tests.sphere_material_component_added, sphere_entity.has_component( + AtomComponentProperties.material())) # 17. Set the Material Asset property of the Material component for the Sphere Entity. sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") - sphere_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) - sphere_material_asset_property = "Default Material|Material Asset" - sphere_material_component.set_component_property_value(sphere_material_asset_property, sphere_material_asset) + sphere_material_asset = Asset.find_asset_by_path(sphere_material_asset_path, False) + sphere_material_component.set_component_property_value( + AtomComponentProperties.material('Material Asset'), sphere_material_asset.id) Report.result(Tests.sphere_material_set, sphere_material_component.get_component_property_value( - sphere_material_asset_property) == sphere_material_asset) + AtomComponentProperties.material('Material Asset')) == sphere_material_asset.id) # 18. Add Mesh component to Sphere Entity and set the Mesh Asset property for the Mesh component. - sphere_mesh_component = sphere_entity.add_component(MESH_COMPONENT_NAME) + sphere_mesh_component = sphere_entity.add_component(AtomComponentProperties.mesh()) sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel") - sphere_mesh_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False) - sphere_mesh_asset_property = "Controller|Configuration|Mesh Asset" - sphere_mesh_component.set_component_property_value(sphere_mesh_asset_property, sphere_mesh_asset) + sphere_mesh_asset = Asset.find_asset_by_path(sphere_mesh_asset_path, False) + sphere_mesh_component.set_component_property_value( + AtomComponentProperties.mesh('Mesh Asset'), sphere_mesh_asset.id) Report.result(Tests.sphere_mesh_asset_set, sphere_mesh_component.get_component_property_value( - sphere_mesh_asset_property) == sphere_mesh_asset) + AtomComponentProperties.mesh('Mesh Asset')) == sphere_mesh_asset.id) # 19. Create a Camera Entity as a child entity of the Default Level Entity then add a Camera component. - camera_name = "Camera" camera_entity = EditorEntity.create_editor_entity_at( - math.Vector3(5.5, -12.0, 9.0), camera_name, default_level_entity.id) - camera_component = camera_entity.add_component(camera_name) - Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name)) + math.Vector3(5.5, -12.0, 9.0), AtomComponentProperties.camera(), default_level_entity.id) + camera_component = camera_entity.add_component(AtomComponentProperties.camera()) + Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera())) # 20. Set the Camera Entity rotation value and set the Camera component Field of View value. camera_entity_rotation = math.Vector3( DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0) camera_entity.set_local_rotation(camera_entity_rotation) - camera_fov_property = "Controller|Configuration|Field of view" camera_fov_value = 60.0 - camera_component.set_component_property_value(camera_fov_property, camera_fov_value) + camera_component.set_component_property_value(AtomComponentProperties.camera('Field of view'), camera_fov_value) azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) Report.result(Tests.camera_fov_set, camera_component.get_component_property_value( - camera_fov_property) == camera_fov_value) + AtomComponentProperties.camera('Field of view')) == camera_fov_value) # 21. Enter game mode. - helper.enter_game_mode(Tests.enter_game_mode) - helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=4.0) + TestHelper.enter_game_mode(Tests.enter_game_mode) + TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=4.0) # 22. Take screenshot. ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{SCREENSHOT_NAME}.ppm") # 23. Exit game mode. - helper.exit_game_mode(Tests.exit_game_mode) - helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=4.0) + TestHelper.exit_game_mode(Tests.exit_game_mode) + TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=4.0) # 24. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) - Report.result(Tests.no_assert_occurred, not error_tracer.has_asserts) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py index 9515712583..3bf4c46a57 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py @@ -131,8 +131,7 @@ def run(): components=["HDRi Skybox", "Global Skylight (IBL)"], parent_id=default_level.id ) - global_skylight_image_asset_path = os.path.join( - "LightingPresets", "greenwich_park_02_4k_iblskyboxcm_iblspecular.exr.streamingimage") + global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") global_skylight_image_asset = asset.AssetCatalogRequestBus( bus.Broadcast, "GetAssetIdByPath", global_skylight_image_asset_path, math.Uuid(), False) global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_image_asset) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py index 985e32ede5..19aa6e9d3c 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py @@ -57,6 +57,10 @@ def add_level_component(component_name): level_component_list, entity.EntityType().Level) level_component_outcome = editor.EditorLevelComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', [level_component_type_ids_list[0]]) + if not level_component_outcome.IsSuccess(): + print('Failed to add {} level component'.format(component_name)) + return None + level_component = level_component_outcome.GetValue()[0] return level_component diff --git a/Code/Editor/2DViewport.cpp b/Code/Editor/2DViewport.cpp index ed810aea29..ba433c869f 100644 --- a/Code/Editor/2DViewport.cpp +++ b/Code/Editor/2DViewport.cpp @@ -547,13 +547,6 @@ QPoint Q2DViewport::WorldToView(const Vec3& wp) const QPoint p = QPoint(static_cast(sp.x), static_cast(sp.y)); return p; } -////////////////////////////////////////////////////////////////////////// -QPoint Q2DViewport::WorldToViewParticleEditor(const Vec3& wp, [[maybe_unused]] int width, [[maybe_unused]] int height) const //Eric@conffx implement for the children class of IDisplayViewport -{ - Vec3 sp = m_screenTM.TransformPoint(wp); - QPoint p = QPoint(static_cast(sp.x), static_cast(sp.y)); - return p; -} ////////////////////////////////////////////////////////////////////////// Vec3 Q2DViewport::ViewToWorld(const QPoint& vp, [[maybe_unused]] bool* collideWithTerrain, [[maybe_unused]] bool onlyTerrain, [[maybe_unused]] bool bSkipVegetation, [[maybe_unused]] bool bTestRenderMesh, [[maybe_unused]] bool* collideWithObject) const diff --git a/Code/Editor/2DViewport.h b/Code/Editor/2DViewport.h index 007c1a47d3..68823acdb7 100644 --- a/Code/Editor/2DViewport.h +++ b/Code/Editor/2DViewport.h @@ -50,8 +50,6 @@ public: //! Map world space position to viewport position. QPoint WorldToView(const Vec3& wp) const override; - QPoint WorldToViewParticleEditor(const Vec3& wp, int width, int height) const override; //Eric@conffx - //! Map viewport position to world space position. Vec3 ViewToWorld(const QPoint& vp, bool* collideWithTerrain = nullptr, bool onlyTerrain = false, bool bSkipVegetation = false, bool bTestRenderMesh = false, bool* collideWithObject = nullptr) const override; //! Map viewport position to world space ray from camera. diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index 72fd37605a..f145adf72f 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -145,6 +145,15 @@ namespace SandboxEditor } }; + const auto trackingTransform = [viewportId = m_viewportId] + { + bool tracking = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + tracking, viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::IsTrackingTransform); + + return tracking; + }; + m_firstPersonRotateCamera = AZStd::make_shared(SandboxEditor::CameraFreeLookChannelId()); m_firstPersonRotateCamera->m_rotateSpeedFn = [] @@ -152,6 +161,11 @@ namespace SandboxEditor return SandboxEditor::CameraRotateSpeed(); }; + m_firstPersonRotateCamera->m_constrainPitch = [trackingTransform] + { + return !trackingTransform(); + }; + // default behavior is to hide the cursor but this can be disabled (useful for remote desktop) // note: See CaptureCursorLook in the Settings Registry m_firstPersonRotateCamera->SetActivationBeganFn(hideCursor); @@ -255,6 +269,11 @@ namespace SandboxEditor return SandboxEditor::CameraOrbitYawRotationInverted(); }; + m_orbitRotateCamera->m_constrainPitch = [trackingTransform] + { + return !trackingTransform(); + }; + m_orbitTranslateCamera = AZStd::make_shared( translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslateOffsetOrbit); @@ -337,12 +356,12 @@ namespace SandboxEditor AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, worldFromLocal); + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, worldFromLocal); } else { AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame); + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StopTrackingTransform); } } diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 290f0fd17f..86a91a25bb 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -299,13 +299,9 @@ AzToolsFramework::ViewportInteraction::MousePick EditorViewportWidget::BuildMous { AzToolsFramework::ViewportInteraction::MousePick mousePick; mousePick.m_screenCoordinates = AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(point); - if (const auto& ray = m_renderViewport->ViewportScreenToWorldRay(mousePick.m_screenCoordinates); - ray.has_value()) - { - mousePick.m_rayOrigin = ray.value().origin; - mousePick.m_rayDirection = ray.value().direction; - } - + const auto[origin, direction] = m_renderViewport->ViewportScreenToWorldRay(mousePick.m_screenCoordinates); + mousePick.m_rayOrigin = origin; + mousePick.m_rayDirection = direction; return mousePick; } @@ -912,23 +908,6 @@ AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& po return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true)); } -AZ::EntityId EditorViewportWidget::PickEntity(const AzFramework::ScreenPoint& point) -{ - AZ::EntityId entityId; - HitContext hitInfo; - hitInfo.view = this; - if (HitTest(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), hitInfo)) - { - if (hitInfo.object && (hitInfo.object->GetType() == OBJTYPE_AZENTITY)) - { - auto entityObject = static_cast(hitInfo.object); - entityId = entityObject->GetAssociatedEntityId(); - } - } - - return entityId; -} - float EditorViewportWidget::TerrainHeight(const AZ::Vector2& position) { return GetIEditor()->GetTerrainElevation(position.GetX(), position.GetY()); @@ -1653,16 +1632,15 @@ void EditorViewportWidget::RenderSelectedRegion() Vec3 EditorViewportWidget::WorldToView3D(const Vec3& wp, [[maybe_unused]] int nFlags) const { Vec3 out(0, 0, 0); - float x, y, z; + float x, y; - ProjectToScreen(wp.x, wp.y, wp.z, &x, &y, &z); - if (_finite(x) && _finite(y) && _finite(z)) + ProjectToScreen(wp.x, wp.y, wp.z, &x, &y); + if (_finite(x) && _finite(y)) { out.x = (x / 100) * m_rcClient.width(); out.y = (y / 100) * m_rcClient.height(); out.x /= static_cast(QHighDpiScaling::factor(windowHandle()->screen())); out.y /= static_cast(QHighDpiScaling::factor(windowHandle()->screen())); - out.z = z; } return out; } @@ -1672,24 +1650,6 @@ QPoint EditorViewportWidget::WorldToView(const Vec3& wp) const { return AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(m_renderViewport->ViewportWorldToScreen(LYVec3ToAZVec3(wp))); } -////////////////////////////////////////////////////////////////////////// -QPoint EditorViewportWidget::WorldToViewParticleEditor(const Vec3& wp, int width, int height) const -{ - QPoint p; - float x, y, z; - - ProjectToScreen(wp.x, wp.y, wp.z, &x, &y, &z); - if (_finite(x) || _finite(y)) - { - p.rx() = static_cast((x / 100) * width); - p.ry() = static_cast((y / 100) * height); - } - else - { - QPoint(0, 0); - } - return p; -} ////////////////////////////////////////////////////////////////////////// Vec3 EditorViewportWidget::ViewToWorld( @@ -1705,20 +1665,16 @@ Vec3 EditorViewportWidget::ViewToWorld( AZ_UNUSED(collideWithObject); auto ray = m_renderViewport->ViewportScreenToWorldRay(AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(vp)); - if (!ray.has_value()) - { - return Vec3(0, 0, 0); - } const float maxDistance = 10000.f; - Vec3 v = AZVec3ToLYVec3(ray.value().direction) * maxDistance; + Vec3 v = AZVec3ToLYVec3(ray.direction) * maxDistance; if (!_finite(v.x) || !_finite(v.y) || !_finite(v.z)) { return Vec3(0, 0, 0); } - Vec3 colp = AZVec3ToLYVec3(ray.value().origin) + 0.002f * v; + Vec3 colp = AZVec3ToLYVec3(ray.origin) + 0.002f * v; return colp; } @@ -1757,21 +1713,19 @@ bool EditorViewportWidget::RayRenderMeshIntersection(IRenderMesh* pRenderMesh, c return bRes;*/ } -void EditorViewportWidget::UnProjectFromScreen(float sx, float sy, float sz, float* px, float* py, float* pz) const +void EditorViewportWidget::UnProjectFromScreen(float sx, float sy, float* px, float* py, float* pz) const { - AZ::Vector3 wp; - wp = m_renderViewport->ViewportScreenToWorld(AzFramework::ScreenPoint{(int)sx, m_rcClient.bottom() - ((int)sy)}, sz).value_or(wp); + const AZ::Vector3 wp = m_renderViewport->ViewportScreenToWorld(AzFramework::ScreenPoint{(int)sx, m_rcClient.bottom() - ((int)sy)}); *px = wp.GetX(); *py = wp.GetY(); *pz = wp.GetZ(); } -void EditorViewportWidget::ProjectToScreen(float ptx, float pty, float ptz, float* sx, float* sy, float* sz) const +void EditorViewportWidget::ProjectToScreen(float ptx, float pty, float ptz, float* sx, float* sy) const { AzFramework::ScreenPoint screenPosition = m_renderViewport->ViewportWorldToScreen(AZ::Vector3{ptx, pty, ptz}); *sx = static_cast(screenPosition.m_x); *sy = static_cast(screenPosition.m_y); - *sz = 0.f; } ////////////////////////////////////////////////////////////////////////// @@ -1781,32 +1735,22 @@ void EditorViewportWidget::ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& Vec3 pos0, pos1; float wx, wy, wz; - UnProjectFromScreen(static_cast(vp.x()), static_cast(rc.bottom() - vp.y()), 0.0f, &wx, &wy, &wz); - if (!_finite(wx) || !_finite(wy) || !_finite(wz)) - { - return; - } - if (fabs(wx) > 1000000 || fabs(wy) > 1000000 || fabs(wz) > 1000000) - { - return; - } - pos0(wx, wy, wz); - UnProjectFromScreen(static_cast(vp.x()), static_cast(rc.bottom() - vp.y()), 1.0f, &wx, &wy, &wz); - if (!_finite(wx) || !_finite(wy) || !_finite(wz)) - { - return; - } - if (fabs(wx) > 1000000 || fabs(wy) > 1000000 || fabs(wz) > 1000000) - { - return; - } - pos1(wx, wy, wz); + UnProjectFromScreen(static_cast(vp.x()), static_cast(rc.bottom() - vp.y()), &wx, &wy, &wz); - Vec3 v = (pos1 - pos0); - v = v.GetNormalized(); + if (!_finite(wx) || !_finite(wy) || !_finite(wz)) + { + return; + } + + if (fabs(wx) > 1000000 || fabs(wy) > 1000000 || fabs(wz) > 1000000) + { + return; + } + + pos0(wx, wy, wz); raySrc = pos0; - rayDir = v; + rayDir = (pos0 - AZVec3ToLYVec3(m_renderViewport->GetCameraState().m_position)).GetNormalized(); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 01f6068d56..d20f3fe939 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -166,7 +166,6 @@ private: Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, const QPoint& point) override; void SetViewportId(int id) override; QPoint WorldToView(const Vec3& wp) const override; - QPoint WorldToViewParticleEditor(const Vec3& wp, int width, int height) const override; Vec3 WorldToView3D(const Vec3& wp, int nFlags = 0) const override; Vec3 ViewToWorld(const QPoint& vp, bool* collideWithTerrain = nullptr, bool onlyTerrain = false, bool bSkipVegetation = false, bool bTestRenderMesh = false, bool* collideWithObject = nullptr) const override; void ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const override; @@ -208,7 +207,6 @@ private: void* GetSystemCursorConstraintWindow() const override; // AzToolsFramework::MainEditorViewportInteractionRequestBus overrides ... - AZ::EntityId PickEntity(const AzFramework::ScreenPoint& point) override; AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) override; float TerrainHeight(const AZ::Vector2& position) override; bool ShowingWorldSpace() override; @@ -306,8 +304,8 @@ private: const DisplayContext& GetDisplayContext() const { return m_displayContext; } CBaseObject* GetCameraObject() const; - void UnProjectFromScreen(float sx, float sy, float sz, float* px, float* py, float* pz) const; - void ProjectToScreen(float ptx, float pty, float ptz, float* sx, float* sy, float* sz) const; + void UnProjectFromScreen(float sx, float sy, float* px, float* py, float* pz) const; + void ProjectToScreen(float ptx, float pty, float ptz, float* sx, float* sy) const; AZ::RPI::ViewPtr GetCurrentAtomView() const; diff --git a/Code/Editor/Include/IDisplayViewport.h b/Code/Editor/Include/IDisplayViewport.h index c7dff33e50..637d19e39b 100644 --- a/Code/Editor/Include/IDisplayViewport.h +++ b/Code/Editor/Include/IDisplayViewport.h @@ -47,7 +47,6 @@ struct IDisplayViewport virtual const Matrix34& GetViewTM() const = 0; virtual const Matrix34& GetScreenTM() const = 0; virtual QPoint WorldToView(const Vec3& worldPoint) const = 0; - virtual QPoint WorldToViewParticleEditor(const Vec3& worldPoint, int width, int height) const = 0; virtual Vec3 WorldToView3D(const Vec3& worldPoint, int flags = 0) const = 0; virtual Vec3 ViewToWorld(const QPoint& vp, bool* collideWithTerrain = nullptr, bool onlyTerrain = false, bool bSkipVegetation = false, bool bTestRenderMesh = false, bool* collideWithObject = nullptr) const = 0; virtual void ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const = 0; diff --git a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp index 1a44d43370..76e23466f7 100644 --- a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp +++ b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp @@ -38,7 +38,9 @@ namespace UnitTest AzFramework::ViewportControllerListPtr m_controllerList; AZStd::unique_ptr m_entity; - static const AzFramework::ViewportId TestViewportId; + static inline constexpr AzFramework::ViewportId TestViewportId = 2345; + static inline constexpr float HalfInterpolateToTransformDuration = + AtomToolsFramework::ModularViewportCameraControllerRequests::InterpolateToTransformDuration * 0.5f; void SetUp() override { @@ -77,8 +79,6 @@ namespace UnitTest } }; - const AzFramework::ViewportId EditorCameraFixture::TestViewportId = AzFramework::ViewportId(1337); - TEST_F(EditorCameraFixture, ModularViewportCameraControllerReferenceFrameUpdatedWhenViewportEntityisChanged) { // Given @@ -92,8 +92,8 @@ namespace UnitTest &Camera::EditorCameraNotificationBus::Events::OnViewportViewEntityChanged, m_entity->GetId()); // ensure the viewport updates after the viewport view entity change - const float deltaTime = 1.0f / 60.0f; - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() }); + // note: do a large step to ensure smoothing finishes (e.g. not 1.0f/60.0f) + m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(2.0f), AZ::ScriptTimePoint() }); // retrieve updated camera transform const AZ::Transform cameraTransform = m_cameraViewportContextView->GetCameraTransform(); @@ -103,61 +103,40 @@ namespace UnitTest EXPECT_THAT(cameraTransform, IsClose(entityTransform)); } - TEST_F(EditorCameraFixture, ReferenceFrameRemainsIdentityAfterExternalCameraTransformChangeWhenNotSet) + TEST_F(EditorCameraFixture, TrackingTransformIsTrueAfterTransformIsTracked) { - // Given - m_cameraViewportContextView->SetCameraTransform(AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f))); + // Given/When + const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); + AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, referenceFrame); - // When - AZ::Transform referenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); + bool trackingTransform = false; AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( - referenceFrame, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + trackingTransform, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::IsTrackingTransform); // Then - // reference frame is still the identity - EXPECT_THAT(referenceFrame, IsClose(AZ::Transform::CreateIdentity())); + EXPECT_THAT(trackingTransform, ::testing::IsTrue()); } - TEST_F(EditorCameraFixture, ExternalCameraTransformChangeWhenReferenceFrameIsSetUpdatesReferenceFrame) + TEST_F(EditorCameraFixture, TrackingTransformIsFalseAfterTransformIsStoppedBeingTracked) { // Given const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); - - const AZ::Transform nextTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f)); - m_cameraViewportContextView->SetCameraTransform(nextTransform); - - // When - AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); - AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( - currentReferenceFrame, TestViewportId, - &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); - - // Then - EXPECT_THAT(currentReferenceFrame, IsClose(nextTransform)); - } - - TEST_F(EditorCameraFixture, ReferenceFrameReturnedToIdentityAfterClear) - { - // Given - const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( - AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); - AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, referenceFrame); // When AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame); - - AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); - AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( - currentReferenceFrame, TestViewportId, - &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StopTrackingTransform); // Then - EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity())); + bool trackingTransform = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + trackingTransform, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::IsTrackingTransform); + + EXPECT_THAT(trackingTransform, ::testing::IsFalse()); } TEST_F(EditorCameraFixture, InterpolateToTransform) @@ -170,8 +149,10 @@ namespace UnitTest transformToInterpolateTo); // simulate interpolation - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); + m_controllerList->UpdateViewport( + { TestViewportId, AzFramework::FloatSeconds(HalfInterpolateToTransformDuration), AZ::ScriptTimePoint() }); + m_controllerList->UpdateViewport( + { TestViewportId, AzFramework::FloatSeconds(HalfInterpolateToTransformDuration), AZ::ScriptTimePoint() }); const auto finalTransform = m_cameraViewportContextView->GetCameraTransform(); @@ -185,7 +166,7 @@ namespace UnitTest const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f)); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame); + TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, referenceFrame); AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f)); @@ -196,19 +177,86 @@ namespace UnitTest transformToInterpolateTo); // simulate interpolation - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); - m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() }); - - AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f)); - AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( - currentReferenceFrame, TestViewportId, - &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame); + m_controllerList->UpdateViewport( + { TestViewportId, AzFramework::FloatSeconds(HalfInterpolateToTransformDuration), AZ::ScriptTimePoint() }); + m_controllerList->UpdateViewport( + { TestViewportId, AzFramework::FloatSeconds(HalfInterpolateToTransformDuration), AZ::ScriptTimePoint() }); const auto finalTransform = m_cameraViewportContextView->GetCameraTransform(); // Then EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo)); - EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity())); + } + + TEST_F(EditorCameraFixture, BeginningCameraInterpolationReturnsTrue) + { + // Given/When + bool interpolationBegan = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + interpolationBegan, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 10.0f))); + + // Then + EXPECT_THAT(interpolationBegan, ::testing::IsTrue()); + } + + TEST_F(EditorCameraFixture, CameraInterpolationDoesNotBeginDuringAnExistingInterpolation) + { + // Given/When + bool initialInterpolationBegan = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + initialInterpolationBegan, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 10.0f))); + + m_controllerList->UpdateViewport( + { TestViewportId, AzFramework::FloatSeconds(HalfInterpolateToTransformDuration), AZ::ScriptTimePoint() }); + + bool nextInterpolationBegan = true; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + nextInterpolationBegan, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 10.0f))); + + bool interpolating = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + interpolating, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::IsInterpolating); + + // Then + EXPECT_THAT(initialInterpolationBegan, ::testing::IsTrue()); + EXPECT_THAT(nextInterpolationBegan, ::testing::IsFalse()); + EXPECT_THAT(interpolating, ::testing::IsTrue()); + } + + TEST_F(EditorCameraFixture, CameraInterpolationCanBeginAfterAnInterpolationCompletes) + { + // Given/When + bool initialInterpolationBegan = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + initialInterpolationBegan, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 10.0f))); + + m_controllerList->UpdateViewport( + { TestViewportId, + AzFramework::FloatSeconds(AtomToolsFramework::ModularViewportCameraControllerRequests::InterpolateToTransformDuration + 0.5f), + AZ::ScriptTimePoint() }); + + bool interpolating = true; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + interpolating, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::IsInterpolating); + + bool nextInterpolationBegan = false; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + nextInterpolationBegan, TestViewportId, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 10.0f, 10.0f))); + + // Then + EXPECT_THAT(initialInterpolationBegan, ::testing::IsTrue()); + EXPECT_THAT(interpolating, ::testing::IsFalse()); + EXPECT_THAT(nextInterpolationBegan, ::testing::IsTrue()); } } // namespace UnitTest diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp index 275df11784..b796d6506c 100644 --- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp +++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp @@ -74,7 +74,7 @@ namespace UnitTest class ModularViewportCameraControllerFixture : public AllocatorsTestFixture { public: - static const AzFramework::ViewportId TestViewportId; + static inline constexpr AzFramework::ViewportId TestViewportId = 1234; void SetUp() override { @@ -146,6 +146,17 @@ namespace UnitTest controller->SetCameraPropsBuilderCallback( [](AzFramework::CameraProps& cameraProps) { + // note: rotateSmoothness is also used for roll (not related to camera input directly) + cameraProps.m_rotateSmoothnessFn = [] + { + return 5.0f; + }; + + cameraProps.m_translateSmoothnessFn = [] + { + return 5.0f; + }; + cameraProps.m_rotateSmoothingEnabledFn = [] { return false; @@ -209,8 +220,6 @@ namespace UnitTest AZStd::unique_ptr m_editorModularViewportCameraComposer; }; - const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0); - TEST_F(ModularViewportCameraControllerFixture, MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithVaryingDeltaTime) { SandboxEditor::SetCameraCaptureCursorForLook(false); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 82d9f9ede2..4aae4191a0 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -1675,6 +1675,12 @@ void SandboxIntegrationManager::GoToEntitiesInViewports(const AzToolsFramework:: if (auto viewportContext = viewportContextManager->GetViewportContextById(viewIndex)) { const AZ::Transform cameraTransform = viewportContext->GetCameraTransform(); + // do not attempt to interpolate to where we currently are + if (cameraTransform.GetTranslation().IsClose(center)) + { + continue; + } + const AZ::Vector3 forward = (center - cameraTransform.GetTranslation()).GetNormalized(); // move camera 25% further back than required diff --git a/Code/Editor/ViewportManipulatorController.cpp b/Code/Editor/ViewportManipulatorController.cpp index 1766945541..285b338ee9 100644 --- a/Code/Editor/ViewportManipulatorController.cpp +++ b/Code/Editor/ViewportManipulatorController.cpp @@ -8,13 +8,15 @@ #include "ViewportManipulatorController.h" +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include +#include #include @@ -87,8 +89,14 @@ namespace SandboxEditor } using InteractionBus = AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; - using namespace AzToolsFramework::ViewportInteraction; using AzFramework::InputChannel; + using AzToolsFramework::ViewportInteraction::KeyboardModifier; + using AzToolsFramework::ViewportInteraction::MouseButton; + using AzToolsFramework::ViewportInteraction::MouseEvent; + using AzToolsFramework::ViewportInteraction::MouseInteraction; + using AzToolsFramework::ViewportInteraction::MouseInteractionEvent; + using AzToolsFramework::ViewportInteraction::ProjectedViewportRay; + using AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus; bool interactionHandled = false; float wheelDelta = 0.0f; @@ -117,16 +125,13 @@ namespace SandboxEditor aznumeric_cast(position->m_normalizedPosition.GetX() * windowSize.m_width), aznumeric_cast(position->m_normalizedPosition.GetY() * windowSize.m_height)); - m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint; - AZStd::optional ray; + ProjectedViewportRay ray{}; ViewportInteractionRequestBus::EventResult( ray, GetViewportId(), &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint); - if (ray.has_value()) - { - m_mouseInteraction.m_mousePick.m_rayOrigin = ray.value().origin; - m_mouseInteraction.m_mousePick.m_rayDirection = ray.value().direction; - } + m_mouseInteraction.m_mousePick.m_rayOrigin = ray.origin; + m_mouseInteraction.m_mousePick.m_rayDirection = ray.direction; + m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint; } eventType = MouseEvent::Move; @@ -160,8 +165,8 @@ namespace SandboxEditor else if (state == InputChannel::State::Ended) { // If we've actually logged a mouse down event, forward a mouse up event. - // This prevents corner cases like the context menu thinking it should be opened even though no one clicked in this viewport, - // due to RenderViewportWidget ensuring all controllers get InputChannel::State::Ended events. + // This prevents corner cases like the context menu thinking it should be opened even though no one clicked in this + // viewport, due to RenderViewportWidget ensuring all controllers get InputChannel::State::Ended events. if (m_mouseInteraction.m_mouseButtons.m_mouseButtons & mouseButtonValue) { // Erase the button from our state if we're done processing events. @@ -259,4 +264,4 @@ namespace SandboxEditor const double doubleClickThresholdMilliseconds = qApp->doubleClickInterval(); return (m_curTime.GetMilliseconds() - clickIt->second.GetMilliseconds()) < doubleClickThresholdMilliseconds; } -} //namespace SandboxEditor +} // namespace SandboxEditor diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 26c9db64aa..771884ac6a 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -94,27 +94,27 @@ namespace AzFramework float y; float z; - // 2.4 Factor as RzRyRx - if (orientation.GetElement(2, 0) < 1.0f) + // 2.5 Factor as RzRxRy + if (orientation.GetElement(2, 1) < 1.0f) { - if (orientation.GetElement(2, 0) > -1.0f) + if (orientation.GetElement(2, 1) > -1.0f) { - x = AZStd::atan2(orientation.GetElement(2, 1), orientation.GetElement(2, 2)); - y = AZStd::asin(-orientation.GetElement(2, 0)); - z = AZStd::atan2(orientation.GetElement(1, 0), orientation.GetElement(0, 0)); + x = AZStd::asin(orientation.GetElement(2, 1)); + y = AZStd::atan2(-orientation.GetElement(2, 0), orientation.GetElement(2, 2)); + z = AZStd::atan2(-orientation.GetElement(0, 1), orientation.GetElement(1, 1)); } else { - x = 0.0f; - y = AZ::Constants::Pi * 0.5f; - z = -AZStd::atan2(-orientation.GetElement(2, 1), orientation.GetElement(1, 1)); + x = -AZ::Constants::Pi * 0.5f; + y = 0.0f; + z = -AZStd::atan2(orientation.GetElement(0, 2), orientation.GetElement(0, 0)); } } else { - x = 0.0f; - y = -AZ::Constants::Pi * 0.5f; - z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1)); + x = AZ::Constants::Pi * 0.5f; + y = 0.0f; + z = AZStd::atan2(orientation.GetElement(0, 2), orientation.GetElement(0, 0)); } return { x, y, z }; @@ -122,14 +122,36 @@ namespace AzFramework void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform) { - const auto eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(transform)); + UpdateCameraFromTranslationAndRotation( + camera, transform.GetTranslation(), AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(transform))); + } + void UpdateCameraFromTranslationAndRotation(Camera& camera, const AZ::Vector3& translation, const AZ::Vector3& eulerAngles) + { camera.m_pitch = eulerAngles.GetX(); camera.m_yaw = eulerAngles.GetZ(); - camera.m_pivot = transform.GetTranslation(); + camera.m_pivot = translation; camera.m_offset = AZ::Vector3::CreateZero(); } + float SmoothValueTime(const float smoothness, float deltaTime) + { + // note: the math for the lerp smoothing implementation for camera rotation and translation was inspired by this excellent + // article by Scott Lembcke: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php + const float rate = AZStd::exp2(smoothness); + return AZStd::exp2(-rate * deltaTime); + } + + float SmoothValue(const float target, const float current, const float time) + { + return AZ::Lerp(target, current, time); + } + + float SmoothValue(const float target, const float current, const float smoothness, const float deltaTime) + { + return SmoothValue(target, current, SmoothValueTime(smoothness, deltaTime)); + } + bool CameraSystem::HandleEvents(const InputEvent& event) { if (const auto& cursor = AZStd::get_if(&event)) @@ -291,6 +313,11 @@ namespace AzFramework { return false; }; + + m_constrainPitch = []() constexpr + { + return true; + }; } bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) @@ -312,7 +339,10 @@ namespace AzFramework nextCamera.m_yaw -= float(cursorDelta.m_x) * rotateSpeed * Invert(m_invertYawFn()); nextCamera.m_yaw = WrapYawRotation(nextCamera.m_yaw); - nextCamera.m_pitch = ClampPitchRotation(nextCamera.m_pitch); + if (m_constrainPitch()) + { + nextCamera.m_pitch = ClampPitchRotation(nextCamera.m_pitch); + } return nextCamera; } @@ -726,14 +756,14 @@ namespace AzFramework Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const CameraProps& cameraProps, const float deltaTime) { - const auto clamp_rotation = [](const float angle) + const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); }; // keep yaw in 0 - 360 range - float targetYaw = clamp_rotation(targetCamera.m_yaw); - const float currentYaw = clamp_rotation(currentCamera.m_yaw); + float targetYaw = clampRotation(targetCamera.m_yaw); + const float currentYaw = clampRotation(currentCamera.m_yaw); // return the sign of the float input (-1, 0, 1) const auto sign = [](const float value) @@ -742,21 +772,17 @@ namespace AzFramework }; // ensure smooth transition when moving across 0 - 360 boundary - const float yawDelta = targetYaw - currentYaw; - if (AZStd::abs(yawDelta) >= AZ::Constants::Pi) + if (const float yawDelta = targetYaw - currentYaw; AZStd::abs(yawDelta) >= AZ::Constants::Pi) { targetYaw -= AZ::Constants::TwoPi * sign(yawDelta); } Camera camera; - // note: the math for the lerp smoothing implementation for camera rotation and translation was inspired by this excellent - // article by Scott Lembcke: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php if (cameraProps.m_rotateSmoothingEnabledFn()) { - const float lookRate = AZStd::exp2(cameraProps.m_rotateSmoothnessFn()); - const float lookTime = AZStd::exp2(-lookRate * deltaTime); - camera.m_pitch = AZ::Lerp(targetCamera.m_pitch, currentCamera.m_pitch, lookTime); - camera.m_yaw = AZ::Lerp(targetYaw, currentYaw, lookTime); + const float lookTime = SmoothValueTime(cameraProps.m_rotateSmoothnessFn(), deltaTime); + camera.m_pitch = SmoothValue(targetCamera.m_pitch, currentCamera.m_pitch, lookTime); + camera.m_yaw = SmoothValue(targetYaw, currentYaw, lookTime); } else { @@ -766,8 +792,7 @@ namespace AzFramework if (cameraProps.m_translateSmoothingEnabledFn()) { - const float moveRate = AZStd::exp2(cameraProps.m_translateSmoothnessFn()); - const float moveTime = AZStd::exp2(-moveRate * deltaTime); + const float moveTime = SmoothValueTime(cameraProps.m_rotateSmoothnessFn(), deltaTime); camera.m_pivot = targetCamera.m_pivot.Lerp(currentCamera.m_pivot, moveTime); camera.m_offset = targetCamera.m_offset.Lerp(currentCamera.m_offset, moveTime); } diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 2d02bb0b6d..4eea94cbe7 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -85,6 +85,19 @@ namespace AzFramework //! Extracts Euler angles (orientation) and translation from the transform and writes the values to the camera. void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform); + //! Writes the translation value and Euler angles to the camera. + void UpdateCameraFromTranslationAndRotation(Camera& camera, const AZ::Vector3& translation, const AZ::Vector3& eulerAngles); + + //! Returns the time ('t') input value to use with SmoothValue. + //! Useful if it is to be reused for multiple calls to SmoothValue. + float SmoothValueTime(float smoothness, float deltaTime); + + // Smoothly interpolate a value from current to target according to a smoothing parameter. + float SmoothValue(float target, float current, float smoothness, float deltaTime); + + // Overload of SmoothValue that takes time ('t') value directly. + float SmoothValue(float target, float current, float time); + //! Generic motion type. template struct MotionEvent @@ -334,6 +347,7 @@ namespace AzFramework AZStd::function m_rotateSpeedFn; AZStd::function m_invertPitchFn; AZStd::function m_invertYawFn; + AZStd::function m_constrainPitch; private: InputChannelId m_rotateChannelId; //!< Input channel to begin the rotate camera input. diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.cpp index 00cbda7b34..40fb82ca97 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.cpp @@ -8,18 +8,18 @@ #include "CameraState.h" -#include #include #include +#include namespace AzFramework { void SetCameraClippingVolume( - AzFramework::CameraState& cameraState, const float nearPlane, const float farPlane, const float fovRad) + AzFramework::CameraState& cameraState, const float nearPlane, const float farPlane, const float verticalFovRad) { cameraState.m_nearClip = nearPlane; cameraState.m_farClip = farPlane; - cameraState.m_fovOrZoom = fovRad; + cameraState.m_fovOrZoom = verticalFovRad; } void SetCameraTransform(CameraState& cameraState, const AZ::Transform& transform) @@ -35,20 +35,34 @@ namespace AzFramework SetCameraClippingVolume(cameraState, 0.1f, 1000.0f, AZ::DegToRad(60.0f)); } - AzFramework::CameraState CreateDefaultCamera( - const AZ::Transform& transform, const AZ::Vector2& viewportSize) + CameraState CreateCamera( + const AZ::Transform& transform, + const float nearPlane, + const float farPlane, + const float verticalFovRad, + const AZ::Vector2& viewportSize) { AzFramework::CameraState cameraState; - SetDefaultCameraClippingVolume(cameraState); SetCameraTransform(cameraState, transform); + SetCameraClippingVolume(cameraState, nearPlane, farPlane, verticalFovRad); cameraState.m_viewportSize = viewportSize; return cameraState; } - AzFramework::CameraState CreateIdentityDefaultCamera( - const AZ::Vector3& position, const AZ::Vector2& viewportSize) + AzFramework::CameraState CreateDefaultCamera(const AZ::Transform& transform, const AZ::Vector2& viewportSize) + { + AzFramework::CameraState cameraState; + + SetCameraTransform(cameraState, transform); + SetDefaultCameraClippingVolume(cameraState); + cameraState.m_viewportSize = viewportSize; + + return cameraState; + } + + AzFramework::CameraState CreateIdentityDefaultCamera(const AZ::Vector3& position, const AZ::Vector2& viewportSize) { return CreateDefaultCamera(AZ::Transform::CreateTranslation(position), viewportSize); } @@ -89,15 +103,15 @@ namespace AzFramework void CameraState::Reflect(AZ::SerializeContext& serializeContext) { - serializeContext.Class()-> - Field("Position", &CameraState::m_position)-> - Field("Forward", &CameraState::m_forward)-> - Field("Side", &CameraState::m_side)-> - Field("Up", &CameraState::m_up)-> - Field("ViewportSize", &CameraState::m_viewportSize)-> - Field("NearClip", &CameraState::m_nearClip)-> - Field("FarClip", &CameraState::m_farClip)-> - Field("FovZoom", &CameraState::m_fovOrZoom)-> - Field("Ortho", &CameraState::m_orthographic); + serializeContext.Class() + ->Field("Position", &CameraState::m_position) + ->Field("Forward", &CameraState::m_forward) + ->Field("Side", &CameraState::m_side) + ->Field("Up", &CameraState::m_up) + ->Field("ViewportSize", &CameraState::m_viewportSize) + ->Field("NearClip", &CameraState::m_nearClip) + ->Field("FarClip", &CameraState::m_farClip) + ->Field("FovZoom", &CameraState::m_fovOrZoom) + ->Field("Ortho", &CameraState::m_orthographic); } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.h index e174771c3b..cefb144ec1 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraState.h @@ -40,10 +40,14 @@ namespace AzFramework AZ::Vector2 m_viewportSize = AZ::Vector2::CreateZero(); //!< Dimensions of the viewport. float m_nearClip = 0.01f; //!< Near clip plane of the camera. float m_farClip = 100.0f; //!< Far clip plane of the camera. - float m_fovOrZoom = 0.0f; //!< Fov or zoom of camera depending on if it is using orthographic projection or not. + float m_fovOrZoom = 0.0f; //!< Vertical fov or zoom of camera depending on if it is using orthographic projection or not. bool m_orthographic = false; //!< Is the camera using orthographic projection or not. }; + //! Create a camera at the given transform, specifying the near and far clip planes as well as the fov with a specific viewport size. + CameraState CreateCamera( + const AZ::Transform& transform, float nearPlane, float farPlane, float verticalFovRad, const AZ::Vector2& viewportSize); + //! Create a camera at the given transform with a specific viewport size. //! @note The near/far clip planes and fov are sensible default values - please //! use SetCameraClippingVolume to override them. @@ -60,7 +64,7 @@ namespace AzFramework CameraState CreateCameraFromWorldFromViewMatrix(const AZ::Matrix4x4& worldFromView, const AZ::Vector2& viewportSize); //! Override the default near/far clipping planes and fov of the camera. - void SetCameraClippingVolume(CameraState& cameraState, float nearPlane, float farPlane, float fovRad); + void SetCameraClippingVolume(CameraState& cameraState, float nearPlane, float farPlane, float verticalFovRad); //! Override the default near/far clipping planes and fov of the camera by inferring them the specified right handed transform into clip space. void SetCameraClippingVolumeFromPerspectiveFovMatrixRH(CameraState& cameraState, const AZ::Matrix4x4& clipFromView); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.cpp index e26d7cfa9b..f1c21230c7 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.cpp @@ -24,6 +24,10 @@ namespace AzFramework serializeContext->Class()-> Field("X", &ScreenVector::m_x)-> Field("Y", &ScreenVector::m_y); + + serializeContext->Class()-> + Field("Width", &ScreenSize::m_width)-> + Field("Height", &ScreenSize::m_height); } } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h index 1529d760e5..7a5d7fdc62 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h @@ -26,7 +26,7 @@ namespace AzFramework AZ_TYPE_INFO(ScreenPoint, "{8472B6C2-527F-44FC-87F8-C226B1A57A97}"); ScreenPoint() = default; - ScreenPoint(int x, int y) + constexpr ScreenPoint(int x, int y) : m_x(x) , m_y(y) { @@ -45,7 +45,7 @@ namespace AzFramework AZ_TYPE_INFO(ScreenVector, "{1EAA2C62-8FDB-4A28-9FE3-1FA4F1418894}"); ScreenVector() = default; - ScreenVector(int x, int y) + constexpr ScreenVector(int x, int y) : m_x(x) , m_y(y) { @@ -55,6 +55,22 @@ namespace AzFramework int m_y; //!< Y screen delta. }; + //! A wrapper around a screen width and height. + struct ScreenSize + { + AZ_TYPE_INFO(ScreenSize, "{26D28916-6E8E-44B8-83F9-C44BCDA370E2}"); + ScreenSize() = default; + + constexpr ScreenSize(int width, int height) + : m_width(width) + , m_height(height) + { + } + + int m_width; //!< Screen size width. + int m_height; //!< Screen size height. + }; + void ScreenGeometryReflect(AZ::ReflectContext* context); inline const ScreenVector operator-(const ScreenPoint& lhs, const ScreenPoint& rhs) @@ -138,6 +154,16 @@ namespace AzFramework return !operator==(lhs, rhs); } + inline const bool operator==(const ScreenSize& lhs, const ScreenSize& rhs) + { + return lhs.m_width == rhs.m_width && lhs.m_height == rhs.m_height; + } + + inline const bool operator!=(const ScreenSize& lhs, const ScreenSize& rhs) + { + return !operator==(lhs, rhs); + } + inline ScreenVector& operator*=(ScreenVector& lhs, const float rhs) { lhs.m_x = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_x) * rhs)); @@ -152,6 +178,20 @@ namespace AzFramework return result; } + inline ScreenSize& operator*=(ScreenSize& lhs, const float rhs) + { + lhs.m_width = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_width) * rhs)); + lhs.m_height = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_height) * rhs)); + return lhs; + } + + inline const ScreenSize operator*(const ScreenSize& lhs, const float rhs) + { + ScreenSize result{ lhs }; + result *= rhs; + return result; + } + inline float ScreenVectorLength(const ScreenVector& screenVector) { return aznumeric_cast(AZStd::sqrt(screenVector.m_x * screenVector.m_x + screenVector.m_y * screenVector.m_y)); @@ -168,4 +208,28 @@ namespace AzFramework { return AZ::Vector2(aznumeric_cast(screenVector.m_x), aznumeric_cast(screenVector.m_y)); } + + //! Return an AZ::Vector2 from a ScreenSize. + inline AZ::Vector2 Vector2FromScreenSize(const ScreenSize& screenSize) + { + return AZ::Vector2(aznumeric_cast(screenSize.m_width), aznumeric_cast(screenSize.m_height)); + } + + //! Return a ScreenPoint from an AZ::Vector2. + inline ScreenPoint ScreenPointFromVector2(const AZ::Vector2& vector2) + { + return ScreenPoint(aznumeric_cast(AZStd::lround(vector2.GetX())), aznumeric_cast(AZStd::lround(vector2.GetY()))); + } + + //! Return a ScreenVector from an AZ::Vector2. + inline ScreenVector ScreenVectorFromVector2(const AZ::Vector2& vector2) + { + return ScreenVector(aznumeric_cast(AZStd::lround(vector2.GetX())), aznumeric_cast(AZStd::lround(vector2.GetY()))); + } + + //! Return a ScreenSize from an AZ::Vector2. + inline ScreenSize ScreenSizeFromVector2(const AZ::Vector2& vector2) + { + return ScreenSize(aznumeric_cast(AZStd::lround(vector2.GetX())), aznumeric_cast(AZStd::lround(vector2.GetY()))); + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp index afd16a6c23..87eee53fbd 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -112,9 +113,8 @@ namespace AzFramework const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize) { - const auto ndcNormalizedPosition = WorldToScreenNdc(worldPosition, cameraView, cameraProjection); // scale ndc position by screen dimensions to return screen position - return ScreenPointFromNdc(AZ::Vector3ToVector2(ndcNormalizedPosition), viewportSize); + return ScreenPointFromNdc(AZ::Vector3ToVector2(WorldToScreenNdc(worldPosition, cameraView, cameraProjection)), viewportSize); } ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState) @@ -144,9 +144,7 @@ namespace AzFramework const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize) { - const auto normalizedScreenPosition = NdcFromScreenPoint(screenPosition, viewportSize); - - return ScreenNdcToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection); + return ScreenNdcToWorld(NdcFromScreenPoint(screenPosition, viewportSize), inverseCameraView, inverseCameraProjection); } AZ::Vector3 ScreenToWorld(const ScreenPoint& screenPosition, const CameraState& cameraState) diff --git a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp index a89fb0bd84..005623677c 100644 --- a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp +++ b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp @@ -104,9 +104,10 @@ namespace UnitTest AZStd::shared_ptr m_orbitCamera; AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); - //! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based - //! on vertical or horizontal motion) as the rotate speed function is set to be 1/1000. - inline static const int PixelMotionDelta = 1570; + // this is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based + // on vertical or horizontal motion) as the rotate speed function is set to be 1/1000. + inline static const int PixelMotionDelta90Degrees = 1570; + inline static const int PixelMotionDelta135Degrees = 2356; }; TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents) @@ -292,7 +293,7 @@ namespace UnitTest HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); - HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ PixelMotionDelta }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ PixelMotionDelta90Degrees }); const float expectedYaw = AzFramework::WrapYawRotation(-AZ::Constants::HalfPi); @@ -310,7 +311,7 @@ namespace UnitTest HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); - HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta90Degrees }); const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); @@ -331,7 +332,7 @@ namespace UnitTest HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); - HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta90Degrees }); const auto expectedCameraEndingPosition = AZ::Vector3(0.0f, -10.0f, 10.0f); const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); @@ -354,7 +355,7 @@ namespace UnitTest HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); - HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta90Degrees }); const auto expectedCameraEndingPosition = AZ::Vector3(20.0f, -5.0f, 0.0f); const float expectedYaw = AzFramework::WrapYawRotation(AZ::Constants::HalfPi); @@ -366,4 +367,42 @@ namespace UnitTest EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3(5.0f, -10.0f, 0.0f))); EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); } + + TEST_F(CameraInputFixture, CameraPitchCanNotBeMovedPastNinetyDegreesWhenConstrained) + { + const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + // pitch by 135.0 degrees + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ -PixelMotionDelta135Degrees }); + + // clamped to 90.0 degrees + const float expectedPitch = AZ::DegToRad(90.0f); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + } + + TEST_F(CameraInputFixture, CameraPitchCanBeMovedPastNinetyDegreesWhenUnconstrained) + { + m_firstPersonRotateCamera->m_constrainPitch = [] + { + return false; + }; + + const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + // pitch by 135.0 degrees + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ -PixelMotionDelta135Degrees }); + + const float expectedPitch = AZ::DegToRad(135.0f); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + } } // namespace UnitTest diff --git a/Code/Framework/AzFramework/Tests/CameraState.cpp b/Code/Framework/AzFramework/Tests/CameraState.cpp index 70fa8be89a..1f35d5a06c 100644 --- a/Code/Framework/AzFramework/Tests/CameraState.cpp +++ b/Code/Framework/AzFramework/Tests/CameraState.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace UnitTest @@ -51,22 +52,6 @@ namespace UnitTest { }; - // Taken from Atom::MatrixUtils for testing purposes, this can be removed if MakePerspectiveFovMatrixRH makes it into AZ - static AZ::Matrix4x4 MakePerspectiveMatrixRH(float fovY, float aspectRatio, float nearClip, float farClip) - { - float sinFov, cosFov; - AZ::SinCos(0.5f * fovY, sinFov, cosFov); - float yScale = cosFov / sinFov; //cot(fovY/2) - float xScale = yScale / aspectRatio; - - AZ::Matrix4x4 out; - out.SetRow(0, xScale, 0.f, 0.f, 0.f ); - out.SetRow(1, 0.f, yScale, 0.f, 0.f ); - out.SetRow(2, 0.f, 0.f, farClip / (nearClip - farClip), nearClip*farClip / (nearClip - farClip) ); - out.SetRow(3, 0.f, 0.f, -1.f, 0.f ); - return out; - } - TEST_P(Translation, Permutation) { // Given a position @@ -176,7 +161,8 @@ namespace UnitTest { auto [fovY, aspectRatio, nearClip, farClip] = GetParam(); - AZ::Matrix4x4 clipFromView = MakePerspectiveMatrixRH(fovY, aspectRatio, nearClip, farClip); + AZ::Matrix4x4 clipFromView; + MakePerspectiveFovMatrixRH(clipFromView, fovY, aspectRatio, nearClip, farClip); AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(m_cameraState, clipFromView); diff --git a/Code/Framework/AzFramework/Tests/CursorStateTests.cpp b/Code/Framework/AzFramework/Tests/CursorStateTests.cpp index 923cd02a10..fe15580dad 100644 --- a/Code/Framework/AzFramework/Tests/CursorStateTests.cpp +++ b/Code/Framework/AzFramework/Tests/CursorStateTests.cpp @@ -8,12 +8,13 @@ #include #include +#include namespace UnitTest { using AzFramework::CursorState; - using AzFramework::ScreenVector; using AzFramework::ScreenPoint; + using AzFramework::ScreenVector; class CursorStateFixture : public ::testing::Test { diff --git a/Code/Framework/AzFramework/Tests/Utils/Printers.cpp b/Code/Framework/AzFramework/Tests/Utils/Printers.cpp new file mode 100644 index 0000000000..91c9558be7 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Utils/Printers.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "Printers.h" + +#include + +#include +#include + +namespace AzFramework +{ + void PrintTo(const ScreenPoint& screenPoint, std::ostream* os) + { + *os << "(x: " << screenPoint.m_x << ", y: " << screenPoint.m_y << ")"; + } + + void PrintTo(const ScreenVector& screenVector, std::ostream* os) + { + *os << "(x: " << screenVector.m_x << ", y: " << screenVector.m_y << ")"; + } + + void PrintTo(const ScreenSize& screenSize, std::ostream* os) + { + *os << "(width: " << screenSize.m_width << ", height: " << screenSize.m_height << ")"; + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Utils/Printers.h b/Code/Framework/AzFramework/Tests/Utils/Printers.h new file mode 100644 index 0000000000..fc967eabe9 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Utils/Printers.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace AzFramework +{ + struct ScreenPoint; + struct ScreenVector; + struct ScreenSize; + + void PrintTo(const ScreenPoint& screenPoint, std::ostream* os); + void PrintTo(const ScreenVector& screenVector, std::ostream* os); + void PrintTo(const ScreenSize& screenSize, std::ostream* os); +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/framework_shared_tests_files.cmake b/Code/Framework/AzFramework/Tests/framework_shared_tests_files.cmake index 85c00a2e8a..9427738e67 100644 --- a/Code/Framework/AzFramework/Tests/framework_shared_tests_files.cmake +++ b/Code/Framework/AzFramework/Tests/framework_shared_tests_files.cmake @@ -11,5 +11,7 @@ set(FILES Mocks/MockWindowRequests.h Utils/Utils.h Utils/Utils.cpp + Utils/Printers.h + Utils/Printers.cpp FrameworkApplicationFixture.h ) diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h index a8ba63500c..3c41b28851 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h @@ -41,8 +41,8 @@ namespace AzManipulatorTestFramework // ViewportInteractionRequestBus overrides ... AzFramework::CameraState GetCameraState() override; AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override; - AZStd::optional ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) override; - AZStd::optional ViewportScreenToWorldRay( + AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override; + AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay( const AzFramework::ScreenPoint& screenPosition) override; float DeviceScalingFactor() override; diff --git a/Code/Framework/AzManipulatorTestFramework/Source/AzManipulatorTestFrameworkUtils.cpp b/Code/Framework/AzManipulatorTestFramework/Source/AzManipulatorTestFrameworkUtils.cpp index 4f1e108a14..6fbf2cb219 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/AzManipulatorTestFrameworkUtils.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/AzManipulatorTestFrameworkUtils.cpp @@ -95,7 +95,7 @@ namespace AzManipulatorTestFramework AzToolsFramework::ViewportInteraction::MousePick mousePick; mousePick.m_screenCoordinates = screenPoint; - mousePick.m_rayOrigin = cameraState.m_position; + mousePick.m_rayOrigin = nearPlaneWorldPosition; mousePick.m_rayDirection = (nearPlaneWorldPosition - cameraState.m_position).GetNormalized(); return mousePick; diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp index c122a941c4..5bfe63bd99 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp @@ -69,8 +69,6 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::CameraStateImpl(const AzFramework::CameraState& cameraState) { m_viewportManipulatorInteraction.GetViewportInteraction().SetCameraState(cameraState); - GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick.m_rayOrigin = cameraState.m_position; - GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick.m_rayDirection = cameraState.m_forward; } void ImmediateModeActionDispatcher::MouseLButtonDownImpl() diff --git a/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp index 730c106301..9bcb8986d5 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp @@ -20,7 +20,8 @@ namespace AzManipulatorTestFramework { public: IndirectCallManipulatorManager(ViewportInteractionInterface& viewportInteraction); - // ManipulatorManagerInterface ... + + // ManipulatorManagerInterface overrides ... void ConsumeMouseInteractionEvent(const MouseInteractionEvent& event) override; AzToolsFramework::ManipulatorManagerId GetId() const override; bool ManipulatorBeingInteracted() const override; diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp index 269baa703d..08cae0c738 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp @@ -140,13 +140,12 @@ namespace AzManipulatorTestFramework return m_viewportId; } - AZStd::optional ViewportInteraction::ViewportScreenToWorld( - [[maybe_unused]] const AzFramework::ScreenPoint& screenPosition, [[maybe_unused]] float depth) + AZ::Vector3 ViewportInteraction::ViewportScreenToWorld([[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { - return {}; + return AZ::Vector3::CreateZero(); } - AZStd::optional ViewportInteraction::ViewportScreenToWorldRay( + AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteraction::ViewportScreenToWorldRay( [[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { return {}; diff --git a/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp b/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp index 211c8d64ef..376c18072e 100644 --- a/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp @@ -140,8 +140,8 @@ namespace UnitTest // given a left mouse down ray in world space // consume the mouse move event state.m_actionDispatcher->CameraState(m_cameraState) - ->MouseLButtonDown() ->MousePosition(AzManipulatorTestFramework::GetCameraStateViewportCenter(m_cameraState)) + ->MouseLButtonDown() ->ExpectTrue(state.m_linearManipulator->PerformingAction()) ->ExpectManipulatorBeingInteracted() ->MouseLButtonUp() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/PythonLoader.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/PythonLoader.h new file mode 100644 index 0000000000..29125667d6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/PythonLoader.h @@ -0,0 +1,25 @@ +/* + * 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 + * + */ + +#pragma once + +namespace AzToolsFramework::EmbeddedPython +{ + // When using embedded Python, some platforms need to explicitly load the python library. + // For any modules that depend on 3rdParty::Python package, the AZ::Module should inherit this class. + class PythonLoader + { + public: + PythonLoader(); + ~PythonLoader(); + + private: + void* m_embeddedLibPythonHandle{ nullptr }; + }; + +} // namespace AzToolsFramework::EmbeddedPython diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index 056872edab..d3b3b09583 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -1196,14 +1196,25 @@ namespace AzToolsFramework AZ::EntityId ToolsApplication::GetCurrentLevelEntityId() { - AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); - AZ::SliceComponent* rootSliceComponent = nullptr; - AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSliceComponent, editorEntityContextId, - &AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice); - if (rootSliceComponent && rootSliceComponent->GetMetadataEntity()) + if (IsPrefabSystemEnabled()) { - return rootSliceComponent->GetMetadataEntity()->GetId(); + if (auto prefabPublicInterface = AZ::Interface::Get()) + { + return prefabPublicInterface->GetLevelInstanceContainerEntityId(); + } + } + else + { + AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + AZ::SliceComponent* rootSliceComponent = nullptr; + AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult( + rootSliceComponent, editorEntityContextId, &AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice); + if (rootSliceComponent && rootSliceComponent->GetMetadataEntity()) + { + return rootSliceComponent->GetMetadataEntity()->GetId(); + } } return AZ::EntityId(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h index 4a7039423c..1e29f1dbce 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h @@ -158,7 +158,10 @@ namespace UnitTest { // Create & Start a new ToolsApplication if there's no existing one m_app = CreateTestApplication(); - m_app->Start(AzFramework::Application::Descriptor()); + AZ::ComponentApplication::StartupParameters startupParameters; + startupParameters.m_loadAssetCatalog = false; + + m_app->Start(AzFramework::Application::Descriptor(), startupParameters); } // without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 8ec772f0da..84a8daa83e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -162,12 +162,11 @@ namespace AzToolsFramework //! Multiply by DeviceScalingFactor to get the position in viewport pixel space. virtual AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) = 0; //! Transforms a point from Qt widget screen space to world space based on the given clip space depth. - //! Depth specifies a relative camera depth to project in the range of [0.f, 1.f]. //! Returns the world space position if successful. - virtual AZStd::optional ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) = 0; + virtual AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) = 0; //! Casts a point in screen space to a ray in world space originating from the viewport camera frustum's near plane. //! Returns a ray containing the ray's origin and a direction normal, if successful. - virtual AZStd::optional ViewportScreenToWorldRay(const AzFramework::ScreenPoint& screenPosition) = 0; + virtual ProjectedViewportRay ViewportScreenToWorldRay(const AzFramework::ScreenPoint& screenPosition) = 0; //! Gets the DPI scaling factor that translates Qt widget space into viewport pixel space. virtual float DeviceScalingFactor() = 0; @@ -229,9 +228,6 @@ namespace AzToolsFramework class MainEditorViewportInteractionRequests { public: - //! Given a point in screen space, return the picked entity (if any). - //! Picked EntityId will be returned, InvalidEntityId will be returned on failure. - virtual AZ::EntityId PickEntity(const AzFramework::ScreenPoint& point) = 0; //! Given a point in screen space, return the terrain position in world space. virtual AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) = 0; //! Return the terrain height given a world position in 2d (xy plane). @@ -266,7 +262,6 @@ namespace AzToolsFramework { public: static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; //! Returns the current state of the keyboard modifier keys. virtual KeyboardModifiers QueryKeyboardModifiers() = 0; @@ -290,7 +285,6 @@ namespace AzToolsFramework { public: static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; //! Returns the current time in seconds. //! This interface can be overridden for the purposes of testing to simplify viewport input requests. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index 0f94b95e5f..ee30b9e29d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -148,7 +148,6 @@ namespace AzToolsFramework return false; } - EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache) : m_entityDataCache(entityDataCache) { @@ -190,7 +189,10 @@ namespace AzToolsFramework if (helpersVisible) { // some components choose to hide their icons (e.g. meshes) - if (!m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex)) + // we also do not want to test against icons that may not be showing as they're inside a 'closed' entity container + // (these icons only become visible when it is opened for editing) + if (!m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex) && + m_entityDataCache->IsVisibleEntityIndividuallySelectableInViewport(entityCacheIndex)) { const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex); @@ -235,7 +237,7 @@ namespace AzToolsFramework viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::SetOverrideCursor, ViewportInteraction::CursorStyleOverride::Forbidden); } - + if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() && mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down || mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index c16a458be7..5d2b11ca5e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -409,7 +409,7 @@ namespace AzToolsFramework const AzFramework::CameraState cameraState = GetCameraState(viewportId); for (size_t entityCacheIndex = 0; entityCacheIndex < entityDataCache.VisibleEntityDataCount(); ++entityCacheIndex) { - if (!entityDataCache.IsVisibleEntitySelectableInViewport(entityCacheIndex)) + if (!entityDataCache.IsVisibleEntityIndividuallySelectableInViewport(entityCacheIndex)) { continue; } @@ -983,7 +983,7 @@ namespace AzToolsFramework { if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId)) { - if (entityDataCache.IsVisibleEntitySelectableInViewport(*entityIndex)) + if (entityDataCache.IsVisibleEntityIndividuallySelectableInViewport(*entityIndex)) { return *entityIndex; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.cpp index 328cfc3ae5..58c25b944c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.cpp @@ -293,12 +293,10 @@ namespace AzToolsFramework return m_impl->m_visibleEntityDatas[index].m_iconHidden; } - bool EditorVisibleEntityDataCache::IsVisibleEntitySelectableInViewport(size_t index) const + bool EditorVisibleEntityDataCache::IsVisibleEntityIndividuallySelectableInViewport(const size_t index) const { - return m_impl->m_visibleEntityDatas[index].m_visible - && !m_impl->m_visibleEntityDatas[index].m_locked - && m_impl->m_visibleEntityDatas[index].m_inFocus - && !m_impl->m_visibleEntityDatas[index].m_descendantOfClosedContainer; + return m_impl->m_visibleEntityDatas[index].m_visible && !m_impl->m_visibleEntityDatas[index].m_locked && + m_impl->m_visibleEntityDatas[index].m_inFocus && !m_impl->m_visibleEntityDatas[index].m_descendantOfClosedContainer; } AZStd::optional EditorVisibleEntityDataCache::GetVisibleEntityIndexFromId(const AZ::EntityId entityId) const diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h index 16fa1b6d14..4262d334df 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h @@ -55,7 +55,10 @@ namespace AzToolsFramework bool IsVisibleEntityVisible(size_t index) const; bool IsVisibleEntitySelected(size_t index) const; bool IsVisibleEntityIconHidden(size_t index) const; - bool IsVisibleEntitySelectableInViewport(size_t index) const; + //! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity). + //! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container + //! to select the container itself, not the individual entity. + bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const; AZStd::optional GetVisibleEntityIndexFromId(AZ::EntityId entityId) const; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index b57eedcd81..d61d6486a9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -47,6 +47,7 @@ set(FILES API/EntityCompositionRequestBus.h API/EntityCompositionNotificationBus.h API/EditorViewportIconDisplayInterface.h + API/PythonLoader.h API/ViewPaneOptions.h API/ViewportEditorModeTrackerInterface.h Application/Ticker.h diff --git a/Code/Framework/AzToolsFramework/Platform/Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp b/Code/Framework/AzToolsFramework/Platform/Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp new file mode 100644 index 0000000000..42fef21db6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Platform/Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp @@ -0,0 +1,20 @@ +/* +* Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: Apache-2.0 OR MIT +* +*/ + +#include + +namespace AzToolsFramework::EmbeddedPython +{ + PythonLoader::PythonLoader() + { + } + + PythonLoader::~PythonLoader() + { + } +} diff --git a/Code/Framework/AzToolsFramework/Platform/Linux/AzToolsFramework/API/PythonLoader_Linux.cpp b/Code/Framework/AzToolsFramework/Platform/Linux/AzToolsFramework/API/PythonLoader_Linux.cpp new file mode 100644 index 0000000000..76fa36a048 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Platform/Linux/AzToolsFramework/API/PythonLoader_Linux.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AzToolsFramework::EmbeddedPython +{ + PythonLoader::PythonLoader() + { + constexpr char libPythonName[] = "libpython3.7m.so.1.0"; + if (m_embeddedLibPythonHandle = dlopen(libPythonName, RTLD_NOW | RTLD_GLOBAL); + m_embeddedLibPythonHandle == nullptr) + { + char* err = dlerror(); + AZ_Error("PythonLoader", false, "Failed to load %s with error: %s\n", libPythonName, err ? err : "Unknown Error"); + } + } + + PythonLoader::~PythonLoader() + { + if (m_embeddedLibPythonHandle) + { + dlclose(m_embeddedLibPythonHandle); + } + } + +} // namespace AzToolsFramework::EmbeddedPython diff --git a/Code/Framework/AzToolsFramework/Platform/Linux/platform_linux_files.cmake b/Code/Framework/AzToolsFramework/Platform/Linux/platform_linux_files.cmake index c2c5a11c4c..3b04a903a4 100644 --- a/Code/Framework/AzToolsFramework/Platform/Linux/platform_linux_files.cmake +++ b/Code/Framework/AzToolsFramework/Platform/Linux/platform_linux_files.cmake @@ -7,4 +7,5 @@ # set(FILES + AzToolsFramework/API/PythonLoader_Linux.cpp ) diff --git a/Code/Framework/AzToolsFramework/Platform/Mac/platform_mac_files.cmake b/Code/Framework/AzToolsFramework/Platform/Mac/platform_mac_files.cmake index c2c5a11c4c..6342747a38 100644 --- a/Code/Framework/AzToolsFramework/Platform/Mac/platform_mac_files.cmake +++ b/Code/Framework/AzToolsFramework/Platform/Mac/platform_mac_files.cmake @@ -7,4 +7,5 @@ # set(FILES + ../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp ) diff --git a/Code/Framework/AzToolsFramework/Platform/Windows/platform_windows_files.cmake b/Code/Framework/AzToolsFramework/Platform/Windows/platform_windows_files.cmake index c2c5a11c4c..6342747a38 100644 --- a/Code/Framework/AzToolsFramework/Platform/Windows/platform_windows_files.cmake +++ b/Code/Framework/AzToolsFramework/Platform/Windows/platform_windows_files.cmake @@ -7,4 +7,5 @@ # set(FILES + ../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp ) diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp index 1aa7b39593..fa5a6b344a 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp @@ -40,6 +40,9 @@ namespace UnitTest { AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId()); AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(GetEntityId()); + + // default local bounds to unit cube + m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f)); } void BoundsTestComponent::Deactivate() @@ -57,7 +60,6 @@ namespace UnitTest AZ::Aabb BoundsTestComponent::GetLocalBounds() { - return AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f)); + return m_localBounds; } - } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h index 036f9c8798..358a9f810a 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h @@ -41,5 +41,7 @@ namespace UnitTest // BoundsRequestBus overrides ... AZ::Aabb GetWorldBounds() override; AZ::Aabb GetLocalBounds() override; + + AZ::Aabb m_localBounds; //!< Local bounds that can be modified for certain tests (defaults to unit cube). }; } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index 81909ac511..4f4c3eb456 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -38,7 +38,7 @@ #include #include -#include +#include namespace AZ { @@ -493,12 +493,8 @@ namespace UnitTest /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Then - AzToolsFramework::EntityIdList selectedEntities; - AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( - selectedEntities, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetSelectedEntities); - - AzToolsFramework::EntityIdList expectedSelectedEntities = { entity4, entity5, entity6 }; - + const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities(); + const AzToolsFramework::EntityIdList expectedSelectedEntities = { entity4, entity5, entity6 }; EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities)); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// } @@ -527,12 +523,8 @@ namespace UnitTest /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Then - AzToolsFramework::EntityIdList selectedEntities; - AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( - selectedEntities, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetSelectedEntities); - - AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId1, entity2, entity3, entity4 }; - + const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities(); + const AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId1, entity2, entity3, entity4 }; EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities)); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// } @@ -946,6 +938,42 @@ namespace UnitTest EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1)); } + TEST_F( + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, BoundsBetweenCameraAndNearClipPlaneDoesNotIntersectMouseRay) + { + // move camera to 10 units along the y-axis + AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(10.0f))); + + // send a very narrow bounds for entity1 + AZ::Entity* entity1 = AzToolsFramework::GetEntityById(m_entityId1); + auto* boundTestComponent = entity1->FindComponent(); + boundTestComponent->m_localBounds = + AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f, -0.0025f, -0.5f), AZ::Vector3(0.5f, 0.0025f, 0.5f)); + + // move entity1 in front of the camera between it and the near clip plane + AZ::TransformBus::Event( + m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(10.05f))); + // move entity2 behind entity1 + AZ::TransformBus::Event( + m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(15.0f))); + + const auto entity2ScreenPosition = AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(m_entityId2), m_cameraState); + + // click the entity in the viewport + m_actionDispatcher->SetStickySelect(true) + ->CameraState(m_cameraState) + ->MousePosition(entity2ScreenPosition) + ->CameraState(m_cameraState) + ->MouseLButtonDown() + ->MouseLButtonUp(); + + // ensure entity1 is not selected as it is before the near clip plane + using ::testing::UnorderedElementsAreArray; + const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities(); + const AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId2 }; + EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities)); + } + class EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam : public EditorTransformComponentSelectionViewportPickingManipulatorTestFixture , public ::testing::WithParamInterface diff --git a/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp index d08c9646a2..93010aa63d 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorVertexSelectionTests.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace AzToolsFramework; diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp index e8fce6653f..58c28f8568 100644 --- a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace UnitTest { @@ -35,6 +36,7 @@ namespace UnitTest const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState); return AzFramework::WorldToScreen(worldResult, cameraState); } + //////////////////////////////////////////////////////////////////////////////////////////////////////// // ScreenPoint tests TEST(ViewportScreen, WorldToScreenAndScreenToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin) @@ -102,8 +104,8 @@ namespace UnitTest } //////////////////////////////////////////////////////////////////////////////////////////////////////// - // NDC tests - TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin) + // Ndc tests + TEST(ViewportScreen, WorldToScreenNdcAndScreenNdcToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin) { using NdcPoint = AZ::Vector2; @@ -136,7 +138,7 @@ namespace UnitTest } } - TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueOrientatedCamera) + TEST(ViewportScreen, WorldToScreenNdcAndScreenNdcToWorldReturnsTheSameValueOrientatedCamera) { using NdcPoint = AZ::Vector2; @@ -153,7 +155,7 @@ namespace UnitTest // note: nearClip is 0.1 - the world space value returned will be aligned to the near clip // plane of the camera so use that to confirm the mapping to/from is correct - TEST(ViewportScreen, ScreenNDCToWorldReturnsPositionOnNearClipPlaneInWorldSpace) + TEST(ViewportScreen, ScreenNdcToWorldReturnsPositionOnNearClipPlaneInWorldSpace) { using NdcPoint = AZ::Vector2; diff --git a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp index f9b5d6aef7..b5323dd419 100644 --- a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp +++ b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp @@ -23,6 +23,8 @@ #include ////////////////////////////////////////////////////////////////////////// +#include + namespace AssetBuilderSDK { const char* const ErrorWindow = "Error"; //Use this window name to log error messages. @@ -1599,4 +1601,70 @@ namespace AssetBuilderSDK { return m_errorsOccurred; } + + AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut, int hashMsDelay) + { + constexpr AZ::u64 HashBufferSize = 1024 * 64; + char buffer[HashBufferSize]; + + if(readStream.IsOpen() && readStream.CanRead()) + { + AZ::IO::SizeType bytesRead; + + auto* state = XXH64_createState(); + + if(state == nullptr) + { + AZ_Assert(false, "Failed to create hash state"); + return 0; + } + + if (XXH64_reset(state, 0) == XXH_ERROR) + { + AZ_Assert(false, "Failed to reset hash state"); + return 0; + } + + do + { + // In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked, + // the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size + // was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read + // will be out of date in the edge cases where another process is actively writing to this file while this hash is running. + // The stream's length ends up more accurate in this case, preventing this assert and shut down. + // One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level, + // the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change. + AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast(AZ_ARRAY_SIZE(buffer))); + bytesRead = readStream.Read(remainingToRead, buffer); + + if(bytesReadOut) + { + *bytesReadOut += bytesRead; + } + + XXH64_update(state, buffer, bytesRead); + + // Used by unit tests to force the race condition mentioned above, to verify the crash fix. + if(hashMsDelay > 0) + { + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay)); + } + + } while (bytesRead > 0); + + auto hash = XXH64_digest(state); + + XXH64_freeState(state); + + return hash; + } + return 0; + } + + AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut, int hashMsDelay) + { + constexpr bool ErrorOnReadFailure = true; + AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure); + return GetHashFromIOStream(readStream, bytesReadOut, hashMsDelay); + } } diff --git a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h index c85c45dd3e..6fb099e6d3 100644 --- a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h +++ b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h @@ -910,6 +910,19 @@ namespace AssetBuilderSDK //! There can be multiple builders running at once, so we need to filter out ones coming from other builders AZStd::thread_id m_jobThreadId; }; + + //! Get hash for a whole file + //! @filePath the path for the file + //! @bytesReadOut output the read file size in bytes + //! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading. + AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0); + + //! Get hash for a generic IO stream + //! @readStream the input readable stream + //! @bytesReadOut output the read size in bytes + //! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading. + AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0); + } // namespace AssetBuilderSDK namespace AZ diff --git a/Code/Tools/AssetProcessor/AssetBuilderSDK/CMakeLists.txt b/Code/Tools/AssetProcessor/AssetBuilderSDK/CMakeLists.txt index 635cd55f6c..bfbcc35663 100644 --- a/Code/Tools/AssetProcessor/AssetBuilderSDK/CMakeLists.txt +++ b/Code/Tools/AssetProcessor/AssetBuilderSDK/CMakeLists.txt @@ -32,6 +32,7 @@ ly_add_target( PUBLIC AZ::AzFramework AZ::AzToolsFramework + 3rdParty::xxhash ) ly_add_source_properties( SOURCES AssetBuilderSDK/AssetBuilderSDK.cpp diff --git a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp index 2d3e671999..b7f7affd6d 100644 --- a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp +++ b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp @@ -32,7 +32,8 @@ struct FolderRootWatch::PlatformImplementation { if (m_iNotifyHandle < 0) { - m_iNotifyHandle = inotify_init(); + // The CLOEXEC flag prevents the inotify watchers from copying on fork/exec + m_iNotifyHandle = inotify_init1(IN_CLOEXEC); } return (m_iNotifyHandle >= 0); } diff --git a/Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp b/Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp index cacd6c4cc9..340f5343bd 100644 --- a/Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/assetUtils.cpp @@ -1161,7 +1161,7 @@ namespace AssetUtilities { #ifndef AZ_TESTS_ENABLED // Only used for unit tests, speed is critical for GetFileHash. - AZ_UNUSED(hashMsDelay); + hashMsDelay = 0; #endif bool useFileHashing = ShouldUseFileHashing(); @@ -1170,10 +1170,10 @@ namespace AssetUtilities return 0; } + AZ::u64 hash = 0; if(!force) { auto* fileStateInterface = AZ::Interface::Get(); - AZ::u64 hash = 0; if (fileStateInterface && fileStateInterface->GetHash(filePath, &hash)) { @@ -1181,64 +1181,8 @@ namespace AssetUtilities } } - char buffer[FileHashBufferSize]; - - constexpr bool ErrorOnReadFailure = true; - AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure); - - if(readStream.IsOpen() && readStream.CanRead()) - { - AZ::IO::SizeType bytesRead; - - auto* state = XXH64_createState(); - - if(state == nullptr) - { - AZ_Assert(false, "Failed to create hash state"); - return 0; - } - - if (XXH64_reset(state, 0) == XXH_ERROR) - { - AZ_Assert(false, "Failed to reset hash state"); - return 0; - } - - do - { - // In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked, - // the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size - // was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read - // will be out of date in the edge cases where another process is actively writing to this file while this hash is running. - // The stream's length ends up more accurate in this case, preventing this assert and shut down. - // One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level, - // the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change. - AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast(AZ_ARRAY_SIZE(buffer))); - bytesRead = readStream.Read(remainingToRead, buffer); - - if(bytesReadOut) - { - *bytesReadOut += bytesRead; - } - - XXH64_update(state, buffer, bytesRead); -#ifdef AZ_TESTS_ENABLED - // Used by unit tests to force the race condition mentioned above, to verify the crash fix. - if(hashMsDelay > 0) - { - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay)); - } -#endif - - } while (bytesRead > 0); - - auto hash = XXH64_digest(state); - - XXH64_freeState(state); - - return hash; - } - return 0; + hash = AssetBuilderSDK::GetFileHash(filePath, bytesReadOut, hashMsDelay); + return hash; } AZ::u64 AdjustTimestamp(QDateTime timestamp) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 0dacbbb906..be317f1ecd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -188,7 +188,7 @@ namespace O3DE::ProjectManager } // add all the gem repos into the hash - const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemReposGemInfos(); if (allRepoGemInfosResult.IsSuccess()) { const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); @@ -363,7 +363,10 @@ namespace O3DE::ProjectManager { const QString selectedGemPath = m_gemModel->GetPath(modelIndex); - // Remove gem from gems to be added + const bool wasAdded = GemModel::WasPreviouslyAdded(modelIndex); + const bool wasAddedDependency = GemModel::WasPreviouslyAddedDependency(modelIndex); + + // Remove gem from gems to be added to update any dependencies GemModel::SetIsAdded(*m_gemModel, modelIndex, false); // Unregister the gem @@ -391,6 +394,8 @@ namespace O3DE::ProjectManager // Select remote gem QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName); + GemModel::SetWasPreviouslyAdded(*m_gemModel, remoteGemIndex, wasAdded); + GemModel::SetWasPreviouslyAddedDependency(*m_gemModel, remoteGemIndex, wasAddedDependency); QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex); m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); } @@ -435,7 +440,7 @@ namespace O3DE::ProjectManager m_gemModel->AddGem(gemInfo); } - const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemReposGemInfos(); if (allRepoGemInfosResult.IsSuccess()) { const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); @@ -523,7 +528,9 @@ namespace O3DE::ProjectManager const QString& gemPath = GemModel::GetPath(modelIndex); // make sure any remote gems we added were downloaded successfully - if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote && GemModel::GetDownloadStatus(modelIndex) != GemInfo::Downloaded) + const GemInfo::DownloadStatus status = GemModel::GetDownloadStatus(modelIndex); + if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote && + !(status == GemInfo::Downloaded || status == GemInfo::DownloadSuccessful)) { QMessageBox::critical( nullptr, "Cannot add gem that isn't downloaded", diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h index f1d1c2a8a2..c22511faad 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager QString m_additionalInfo = ""; QString m_directoryLink = ""; QString m_repoUri = ""; - QStringList m_includedGemPaths = {}; + QStringList m_includedGemUris = {}; QDateTime m_lastUpdated; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp index f816e86733..24a3e58ea2 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -60,8 +61,10 @@ namespace O3DE::ProjectManager // Repo name and url link m_nameLabel->setText(m_model->GetName(modelIndex)); - m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex)); - m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex)); + + const QString repoUri = m_model->GetRepoUri(modelIndex); + m_repoLinkLabel->setText(repoUri); + m_repoLinkLabel->SetUrl(repoUri); // Repo summary m_summaryLabel->setText(m_model->GetSummary(modelIndex)); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp index 6189b6d8bf..18fb0cc235 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp @@ -41,7 +41,7 @@ namespace O3DE::ProjectManager item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated); item->setData(gemRepoInfo.m_path, RolePath); item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo); - item->setData(gemRepoInfo.m_includedGemPaths, RoleIncludedGems); + item->setData(gemRepoInfo.m_includedGemUris, RoleIncludedGems); appendRow(item); @@ -98,7 +98,7 @@ namespace O3DE::ProjectManager return modelIndex.data(RolePath).toString(); } - QStringList GemRepoModel::GetIncludedGemPaths(const QModelIndex& modelIndex) + QStringList GemRepoModel::GetIncludedGemUris(const QModelIndex& modelIndex) { return modelIndex.data(RoleIncludedGems).toStringList(); } @@ -118,23 +118,19 @@ namespace O3DE::ProjectManager QVector GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex) { - QVector allGemInfos; - QStringList repoGemPaths = GetIncludedGemPaths(modelIndex); + QString repoUri = GetRepoUri(modelIndex); - for (const QString& gemPath : repoGemPaths) + const AZ::Outcome, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemRepoGemInfos(repoUri); + if (gemInfosResult.IsSuccess()) { - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(gemPath); - if (gemInfoResult.IsSuccess()) - { - allGemInfos.append(gemInfoResult.GetValue()); - } - else - { - QMessageBox::critical(nullptr, tr("Gem Not Found"), tr("Cannot find info for gem %1.").arg(gemPath)); - } + return gemInfosResult.GetValue(); + } + else + { + QMessageBox::critical(nullptr, tr("Gems not found"), tr("Cannot find info for gems from repo %1").arg(GetName(modelIndex))); } - return allGemInfos; + return QVector(); } bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex) diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h index 66fe972a95..68991a0509 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h @@ -39,7 +39,7 @@ namespace O3DE::ProjectManager static QDateTime GetLastUpdated(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex); - static QStringList GetIncludedGemPaths(const QModelIndex& modelIndex); + static QStringList GetIncludedGemUris(const QModelIndex& modelIndex); static QVector GetIncludedGemTags(const QModelIndex& modelIndex); static QVector GetIncludedGemInfos(const QModelIndex& modelIndex); diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp index 7981e9d758..ecb125ae4e 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp @@ -9,12 +9,14 @@ #include #include #include +#include + +#include #include #include #include - namespace O3DE::ProjectManager { ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent) @@ -27,6 +29,15 @@ namespace O3DE::ProjectManager m_worker = new ProjectBuilderWorker(m_projectInfo); m_worker->moveToThread(&m_workerThread); + auto settingsRegistry = AZ::SettingsRegistry::Get(); + if (settingsRegistry) + { + // Remove key here in case Project Manager crashing while building that causes HandleResults to not be called + QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); + settingsRegistry->Remove(settingsKey.toStdString().c_str()); + SaveProjectManagerSettings(); + } + connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater); connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject); connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults); @@ -80,6 +91,8 @@ namespace O3DE::ProjectManager void ProjectBuilderController::HandleResults(const QString& result) { + QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); + if (!result.isEmpty()) { if (result.contains(tr("log"))) @@ -109,12 +122,26 @@ namespace O3DE::ProjectManager emit NotifyBuildProject(m_projectInfo); } + auto settingsRegistry = AZ::SettingsRegistry::Get(); + if (settingsRegistry) + { + settingsRegistry->Remove(settingsKey.toStdString().c_str()); + SaveProjectManagerSettings(); + } + emit Done(false); return; } else { m_projectInfo.m_buildFailed = false; + + auto settingsRegistry = AZ::SettingsRegistry::Get(); + if (settingsRegistry) + { + settingsRegistry->Set(settingsKey.toStdString().c_str(), true); + SaveProjectManagerSettings(); + } } emit Done(true); diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp new file mode 100644 index 0000000000..3049a6d70c --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "ProjectManagerSettings.h" + +#include +#include +#include + +namespace O3DE::ProjectManager +{ + void SaveProjectManagerSettings() + { + auto settingsRegistry = AZ::SettingsRegistry::Get(); + AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; + dumperSettings.m_prettifyOutput = true; + dumperSettings.m_jsonPointerPrefix = ProjectManagerKeyPrefix; + + AZStd::string stringBuffer; + AZ::IO::ByteContainerStream stringStream(&stringBuffer); + if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream( + *settingsRegistry, ProjectManagerKeyPrefix, stringStream, dumperSettings)) + { + AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream"); + return; + } + + AZ::IO::FixedMaxPath o3deUserPath = AZ::Utils::GetO3deManifestDirectory(); + o3deUserPath /= AZ::SettingsRegistryInterface::RegistryFolder; + o3deUserPath /= "ProjectManager.setreg"; + + bool saved = false; + constexpr auto configurationMode = + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; + + AZ::IO::SystemFile outputFile; + if (outputFile.Open(o3deUserPath.c_str(), configurationMode)) + { + saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); + } + + AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str()); + } + + QString GetProjectBuiltSuccessfullyKey(const QString& projectName) + { + return QString("%1/Projects/%2/BuiltSuccessfully").arg(ProjectManagerKeyPrefix).arg(projectName); + } +} diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h new file mode 100644 index 0000000000..3454909062 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h @@ -0,0 +1,21 @@ +/* + * 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 + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager"; + + void SaveProjectManagerSettings(); + QString GetProjectBuiltSuccessfullyKey(const QString& projectName); +} diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index f86a689e59..7f962bee88 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include @@ -269,17 +271,36 @@ namespace O3DE::ProjectManager // Add any missing project buttons and restore buttons to default state for (const ProjectInfo& project : projectsVector) { + ProjectButton* currentButton = nullptr; if (!m_projectButtons.contains(QDir::toNativeSeparators(project.m_path))) { - m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), CreateProjectButton(project)); + currentButton = CreateProjectButton(project); + m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), currentButton); } else { auto projectButtonIter = m_projectButtons.find(QDir::toNativeSeparators(project.m_path)); if (projectButtonIter != m_projectButtons.end()) { - projectButtonIter.value()->RestoreDefaultState(); - m_projectsFlowLayout->addWidget(projectButtonIter.value()); + currentButton = projectButtonIter.value(); + currentButton->RestoreDefaultState(); + m_projectsFlowLayout->addWidget(currentButton); + } + } + + // Check whether project manager has successfully built the project + if (currentButton) + { + auto settingsRegistry = AZ::SettingsRegistry::Get(); + bool projectBuiltSuccessfully = false; + if (settingsRegistry) + { + QString settingsKey = GetProjectBuiltSuccessfullyKey(project.m_projectName); + settingsRegistry->Get(projectBuiltSuccessfully, settingsKey.toStdString().c_str()); + } + if (!projectBuiltSuccessfully) + { + currentButton->ShowBuildRequired(); } } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 5f80705e97..9023d04331 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -1154,11 +1154,11 @@ namespace O3DE::ProjectManager gemRepoInfo.m_isEnabled = false; } - if (data.contains("gem_paths")) + if (data.contains("gems")) { - for (auto gemPath : data["gem_paths"]) + for (auto gemPath : data["gems"]) { - gemRepoInfo.m_includedGemPaths.push_back(Py_To_String(gemPath)); + gemRepoInfo.m_includedGemUris.push_back(Py_To_String(gemPath)); } } } @@ -1207,7 +1207,35 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemRepos)); } - AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + AZ::Outcome, AZStd::string> PythonBindings::GetGemRepoGemInfos(const QString& repoUri) + { + QVector gemInfos; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto pyUri = QString_To_Py_String(repoUri); + auto gemPaths = m_repo.attr("get_gem_json_paths_from_cached_repo")(pyUri); + + if (pybind11::isinstance(gemPaths)) + { + for (auto path : gemPaths) + { + GemInfo gemInfo = GemInfoFromPath(path, pybind11::none()); + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded; + gemInfos.push_back(gemInfo); + } + } + }); + + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError()); + } + + return AZ::Success(AZStd::move(gemInfos)); + } + + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemReposGemInfos() { QVector gemInfos; AZ::Outcome result = ExecuteWithLockErrorHandling( diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index fd36d6d13e..9ac040a428 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -65,7 +65,8 @@ namespace O3DE::ProjectManager AZ::Outcome> AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; - AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() 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; void CancelDownload() override; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 45fb8928f9..9dc1078d6c 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -217,11 +217,18 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; + /** + * Gathers all gem infos for repos + * @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; + /** * Gathers all gem infos for all gems registered from repos. * @return A list of gem infos. */ - virtual AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() = 0; + virtual AZ::Outcome, AZStd::string> GetAllGemReposGemInfos() = 0; /** * Downloads and registers a Gem. diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index fb16484961..6cb934560a 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -15,6 +15,9 @@ #include #include #include +#include + +#include #include #include @@ -280,6 +283,21 @@ namespace O3DE::ProjectManager } } + if (newProjectSettings.m_projectName != m_projectInfo.m_projectName) + { + // update reg key + QString oldSettingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); + QString newSettingsKey = GetProjectBuiltSuccessfullyKey(newProjectSettings.m_projectName); + + auto settingsRegistry = AZ::SettingsRegistry::Get(); + bool projectBuiltSuccessfully = false; + if (settingsRegistry && settingsRegistry->Get(projectBuiltSuccessfully, oldSettingsKey.toStdString().c_str())) + { + settingsRegistry->Set(newSettingsKey.toStdString().c_str(), projectBuiltSuccessfully); + SaveProjectManagerSettings(); + } + } + if (!newProjectSettings.m_newPreviewImagePath.isEmpty()) { if (!ProjectUtils::ReplaceProjectFile( diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index fcfae2f336..43c11edacc 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -58,6 +58,8 @@ set(FILES Source/CreateProjectCtrl.cpp Source/UpdateProjectCtrl.h Source/UpdateProjectCtrl.cpp + Source/ProjectManagerSettings.h + Source/ProjectManagerSettings.cpp Source/ProjectsScreen.h Source/ProjectsScreen.cpp Source/ProjectSettingsScreen.h diff --git a/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.h b/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.h new file mode 100644 index 0000000000..ad7faf4671 --- /dev/null +++ b/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.h @@ -0,0 +1,44 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace AWSClientAuth +{ + //! Cognito Caching Credentials Provider implementation that is derived from AWS Native SDK. + //! For use with authenticated credentials. + class AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider + : public Aws::Auth::CognitoCachingCredentialsProvider + { + public: + AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider( + const std::shared_ptr& identityRepository, + const std::shared_ptr& cognitoIdentityClient = nullptr); + + protected: + Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome GetCredentialsFromCognito() const override; + }; + + //! Cognito Caching Credentials Provider implementation that is eventually derived from AWS Native SDK. + //! For use with anonymous credentials. + class AWSClientAuthCachingAnonymousCredsProvider : public AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider + { + public: + AWSClientAuthCachingAnonymousCredsProvider( + const std::shared_ptr& identityRepository, + const std::shared_ptr& cognitoIdentityClient = nullptr); + + protected: + Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome GetCredentialsFromCognito() const override; + }; + +} // namespace AWSClientAuth diff --git a/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSCognitoAuthorizationController.h b/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSCognitoAuthorizationController.h index 042be8fe89..1378feff19 100644 --- a/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSCognitoAuthorizationController.h +++ b/Gems/AWSClientAuth/Code/Include/Private/Authorization/AWSCognitoAuthorizationController.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -51,8 +52,8 @@ namespace AWSClientAuth std::shared_ptr m_persistentCognitoIdentityProvider; std::shared_ptr m_persistentAnonymousCognitoIdentityProvider; - std::shared_ptr m_cognitoCachingCredentialsProvider; - std::shared_ptr m_cognitoCachingAnonymousCredentialsProvider; + std::shared_ptr m_cognitoCachingCredentialsProvider; + std::shared_ptr m_cognitoCachingAnonymousCredentialsProvider; AZStd::string m_cognitoIdentityPoolId; AZStd::string m_formattedCognitoUserPoolId; diff --git a/Gems/AWSClientAuth/Code/Source/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.cpp b/Gems/AWSClientAuth/Code/Source/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.cpp new file mode 100644 index 0000000000..2492afa441 --- /dev/null +++ b/Gems/AWSClientAuth/Code/Source/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace AWSClientAuth +{ + static const char* AUTH_LOG_TAG = "AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider"; + static const char* ANON_LOG_TAG = "AWSClientAuthCachingAnonymousCredsProvider"; + + // Modification of https://github.com/aws/aws-sdk-cpp/blob/main/aws-cpp-sdk-identity-management/source/auth/CognitoCachingCredentialsProvider.cpp#L92 + // to work around account ID requirement. Account id is not required for call to succeed and is not set unless provided. + // see: https://github.com/aws/aws-sdk-cpp/issues/1448 + Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome FetchCredsFromCognito( + const Aws::CognitoIdentity::CognitoIdentityClient& cognitoIdentityClient, + Aws::Auth::PersistentCognitoIdentityProvider& identityRepository, + const char* logTag, + bool includeLogins) + { + auto logins = identityRepository.GetLogins(); + Aws::Map cognitoLogins; + for (auto& login : logins) + { + cognitoLogins[login.first] = login.second.accessToken; + } + + if (!identityRepository.HasIdentityId()) + { + auto accountId = identityRepository.GetAccountId(); + auto identityPoolId = identityRepository.GetIdentityPoolId(); + + Aws::CognitoIdentity::Model::GetIdRequest getIdRequest; + getIdRequest.SetIdentityPoolId(identityPoolId); + + if (!accountId.empty()) // new check + { + getIdRequest.SetAccountId(accountId); + AWS_LOGSTREAM_INFO(logTag, "Identity not found, requesting an id for accountId " + << accountId << " identity pool id " + << identityPoolId << " with logins."); + } + else + { + AWS_LOGSTREAM_INFO( + logTag, "Identity not found, requesting an id for identity pool id %s" << identityPoolId << " with logins."); + } + if (includeLogins) + { + getIdRequest.SetLogins(cognitoLogins); + } + + auto getIdOutcome = cognitoIdentityClient.GetId(getIdRequest); + if (getIdOutcome.IsSuccess()) + { + auto identityId = getIdOutcome.GetResult().GetIdentityId(); + AWS_LOGSTREAM_INFO(logTag, "Successfully retrieved identity: " << identityId); + identityRepository.PersistIdentityId(identityId); + } + else + { + AWS_LOGSTREAM_ERROR( + logTag, + "Failed to retrieve identity. Error: " << getIdOutcome.GetError().GetExceptionName() << " " + << getIdOutcome.GetError().GetMessage()); + return Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome(getIdOutcome.GetError()); + } + } + + Aws::CognitoIdentity::Model::GetCredentialsForIdentityRequest getCredentialsForIdentityRequest; + getCredentialsForIdentityRequest.SetIdentityId(identityRepository.GetIdentityId()); + if (includeLogins) + { + getCredentialsForIdentityRequest.SetLogins(cognitoLogins); + } + + return cognitoIdentityClient.GetCredentialsForIdentity(getCredentialsForIdentityRequest); + } + + AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider::AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider( + const std::shared_ptr& identityRepository, + const std::shared_ptr& cognitoIdentityClient) + : CognitoCachingCredentialsProvider(identityRepository, cognitoIdentityClient) + { + } + + Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome + AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider::GetCredentialsFromCognito() const + { + return FetchCredsFromCognito(*m_cognitoIdentityClient, *m_identityRepository, AUTH_LOG_TAG, true); + } + + AWSClientAuthCachingAnonymousCredsProvider::AWSClientAuthCachingAnonymousCredsProvider( + const std::shared_ptr& identityRepository, + const std::shared_ptr& cognitoIdentityClient) + : AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider(identityRepository, cognitoIdentityClient) + { + } + + Aws::CognitoIdentity::Model::GetCredentialsForIdentityOutcome AWSClientAuthCachingAnonymousCredsProvider:: + GetCredentialsFromCognito() const + { + return FetchCredsFromCognito(*m_cognitoIdentityClient, *m_identityRepository, ANON_LOG_TAG, false); + } + + +} // namespace AWSClientAuth diff --git a/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp b/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp index 3af28582d6..f53149c90f 100644 --- a/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -38,10 +39,12 @@ namespace AWSClientAuth auto identityClient = AZ::Interface::Get()->GetCognitoIdentityClient(); m_cognitoCachingCredentialsProvider = - std::make_shared(m_persistentCognitoIdentityProvider, identityClient); + std::make_shared( + m_persistentCognitoIdentityProvider, identityClient); m_cognitoCachingAnonymousCredentialsProvider = - std::make_shared(m_persistentAnonymousCognitoIdentityProvider, identityClient); + std::make_shared( + m_persistentAnonymousCognitoIdentityProvider, identityClient); } AWSCognitoAuthorizationController::~AWSCognitoAuthorizationController() @@ -65,9 +68,13 @@ namespace AWSClientAuth AWSCore::AWSResourceMappingRequestBus::BroadcastResult( m_cognitoIdentityPoolId, &AWSCore::AWSResourceMappingRequests::GetResourceNameId, CognitoIdentityPoolIdResourceMappingKey); - if (m_awsAccountId.empty() || m_cognitoIdentityPoolId.empty()) + if (m_awsAccountId.empty()) + { + AZ_TracePrintf("AWSCognitoAuthorizationController", "AWS account id not not configured. Proceeding without it."); + } + + if (m_cognitoIdentityPoolId.empty()) { - AZ_Warning("AWSCognitoAuthorizationController", !m_awsAccountId.empty(), "Missing AWS account id not configured."); AZ_Warning("AWSCognitoAuthorizationController", !m_cognitoIdentityPoolId.empty(), "Missing Cognito Identity pool id in resource mappings."); return false; } diff --git a/Gems/AWSClientAuth/Code/Tests/Authorization/AWSCognitoAuthorizationControllerTest.cpp b/Gems/AWSClientAuth/Code/Tests/Authorization/AWSCognitoAuthorizationControllerTest.cpp index 9f31891512..ca1d58aea4 100644 --- a/Gems/AWSClientAuth/Code/Tests/Authorization/AWSCognitoAuthorizationControllerTest.cpp +++ b/Gems/AWSClientAuth/Code/Tests/Authorization/AWSCognitoAuthorizationControllerTest.cpp @@ -62,6 +62,14 @@ TEST_F(AWSCognitoAuthorizationControllerTest, Initialize_Success) ASSERT_TRUE(m_mockController->m_cognitoIdentityPoolId == AWSClientAuthUnitTest::TEST_RESOURCE_NAME_ID); } +TEST_F(AWSCognitoAuthorizationControllerTest, Initialize_Success_GetAWSAccountEmpty) +{ + EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetResourceNameId(testing::_)).Times(2); + EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetDefaultAccountId()).Times(1).WillOnce(testing::Return("")); + EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetDefaultRegion()).Times(1); + ASSERT_TRUE(m_mockController->Initialize()); +} + TEST_F(AWSCognitoAuthorizationControllerTest, RequestAWSCredentials_WithLogins_Success) { AWSClientAuth::AuthenticationTokens tokens( @@ -121,7 +129,7 @@ TEST_F(AWSCognitoAuthorizationControllerTest, MultipleCalls_UsesCacheCredentials m_mockController->RequestAWSCredentialsAsync(); } -TEST_F(AWSCognitoAuthorizationControllerTest, RequestAWSCredentials_Fail_GetIdError) +TEST_F(AWSCognitoAuthorizationControllerTest, RequestAWSCredentials_Fail_GetIdError) // fail { AWSClientAuth::AuthenticationTokens cognitoTokens( AWSClientAuthUnitTest::TEST_TOKEN, AWSClientAuthUnitTest::TEST_TOKEN, AWSClientAuthUnitTest::TEST_TOKEN, @@ -321,7 +329,7 @@ TEST_F(AWSCognitoAuthorizationControllerTest, GetCredentialsProvider_NoPersisted EXPECT_TRUE(actualCredentialsProvider == m_mockController->m_cognitoCachingAnonymousCredentialsProvider); } -TEST_F(AWSCognitoAuthorizationControllerTest, GetCredentialsProvider_NoPersistedLogins_NoAnonymousCredentials_ResultNullPtr) +TEST_F(AWSCognitoAuthorizationControllerTest, GetCredentialsProvider_NoPersistedLogins_NoAnonymousCredentials_ResultNullPtr) // fails { Aws::Client::AWSError error; error.SetExceptionName(AWSClientAuthUnitTest::TEST_EXCEPTION); @@ -431,11 +439,3 @@ TEST_F(AWSCognitoAuthorizationControllerTest, Initialize_Fail_GetResourceNameEmp EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetDefaultAccountId()).Times(1); ASSERT_FALSE(m_mockController->Initialize()); } - -TEST_F(AWSCognitoAuthorizationControllerTest, Initialize_Fail_GetAWSAccountEmpty) -{ - EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetResourceNameId(testing::_)).Times(1); - EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetDefaultAccountId()).Times(1).WillOnce(testing::Return("")); - EXPECT_CALL(m_awsResourceMappingRequestBusMock, GetDefaultRegion()).Times(0); - ASSERT_FALSE(m_mockController->Initialize()); -} diff --git a/Gems/AWSClientAuth/Code/awsclientauth_files.cmake b/Gems/AWSClientAuth/Code/awsclientauth_files.cmake index bd4c971377..3b07b710e7 100644 --- a/Gems/AWSClientAuth/Code/awsclientauth_files.cmake +++ b/Gems/AWSClientAuth/Code/awsclientauth_files.cmake @@ -24,6 +24,7 @@ set(FILES Include/Private/Authorization/AWSCognitoAuthorizationController.h Include/Private/Authorization/AWSClientAuthPersistentCognitoIdentityProvider.h Include/Private/Authorization/AWSCognitoAuthorizationNotificationBusBehaviorHandler.h + Include/Private/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.h Include/Private/UserManagement/AWSCognitoUserManagementController.h Include/Private/UserManagement/UserManagementNotificationBusBehaviorHandler.h @@ -45,6 +46,7 @@ set(FILES Source/Authorization/ClientAuthAWSCredentials.cpp Source/Authorization/AWSCognitoAuthorizationController.cpp Source/Authorization/AWSClientAuthPersistentCognitoIdentityProvider.cpp + Source/Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.cpp Source/UserManagement/AWSCognitoUserManagementController.cpp ) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Albedo.preset similarity index 60% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Albedo.preset index fe2e7e0cb4..dc1f38d2da 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Albedo.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Albedo.preset @@ -7,16 +7,6 @@ "UUID": "{08A95286-ADB2-41E4-96EB-DB48F4726D6A}", "Name": "Albedo", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_basecolor", - "_diff", - "_color", - "_col", - "_albedo", - "_alb", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1", "DiscardAlpha": true, "IsPowerOf2": true, @@ -29,16 +19,6 @@ "UUID": "{08A95286-ADB2-41E4-96EB-DB48F4726D6A}", "Name": "Albedo", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_col", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, "DiscardAlpha": true, @@ -51,16 +31,6 @@ "UUID": "{08A95286-ADB2-41E4-96EB-DB48F4726D6A}", "Name": "Albedo", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_col", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, "DiscardAlpha": true, @@ -73,16 +43,6 @@ "UUID": "{08A95286-ADB2-41E4-96EB-DB48F4726D6A}", "Name": "Albedo", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_col", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1", "DiscardAlpha": true, "IsPowerOf2": true, @@ -94,16 +54,6 @@ "UUID": "{08A95286-ADB2-41E4-96EB-DB48F4726D6A}", "Name": "Albedo", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_col", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1", "DiscardAlpha": true, "IsPowerOf2": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithCoverage.preset similarity index 60% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithCoverage.preset index fda5a9cc52..439a057410 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithCoverage.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithCoverage.preset @@ -7,15 +7,6 @@ "UUID": "{57ED16B1-407B-4E29-BCFC-D3BAE60F2C85}", "Name": "AlbedoWithCoverage", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1a", "IsPowerOf2": true, "MipMapSetting": { @@ -27,15 +18,6 @@ "UUID": "{57ED16B1-407B-4E29-BCFC-D3BAE60F2C85}", "Name": "AlbedoWithCoverage", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { @@ -46,15 +28,6 @@ "UUID": "{57ED16B1-407B-4E29-BCFC-D3BAE60F2C85}", "Name": "AlbedoWithCoverage", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "IsPowerOf2": true, "MipMapSetting": { @@ -65,15 +38,6 @@ "UUID": "{57ED16B1-407B-4E29-BCFC-D3BAE60F2C85}", "Name": "AlbedoWithCoverage", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1a", "IsPowerOf2": true, "MipMapSetting": { @@ -84,15 +48,6 @@ "UUID": "{57ED16B1-407B-4E29-BCFC-D3BAE60F2C85}", "Name": "AlbedoWithCoverage", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC1a", "IsPowerOf2": true, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithGenericAlpha.preset similarity index 55% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithGenericAlpha.preset index 19d2b2bbe6..c315ede21c 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AlbedoWithGenericAlpha.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AlbedoWithGenericAlpha.preset @@ -7,17 +7,7 @@ "UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}", "Name": "AlbedoWithGenericAlpha", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], - "PixelFormat": "BC3", - "IsPowerOf2": true, + "PixelFormat": "ASTC_4x4", "MipMapSetting": { "MipGenType": "Box" } @@ -27,18 +17,8 @@ "UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}", "Name": "AlbedoWithGenericAlpha", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, - "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" } @@ -47,18 +27,8 @@ "UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}", "Name": "AlbedoWithGenericAlpha", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, - "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" } @@ -67,17 +37,7 @@ "UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}", "Name": "AlbedoWithGenericAlpha", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC3", - "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" } @@ -86,17 +46,7 @@ "UUID": "{5D9ECB52-4CD9-4CB8-80E3-10CAE5EFB8A2}", "Name": "AlbedoWithGenericAlpha", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_diff", - "_color", - "_albedo", - "_alb", - "_basecolor", - "_bc", - "_diffuse" - ], "PixelFormat": "BC3", - "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AmbientOcclusion.preset similarity index 64% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AmbientOcclusion.preset index d5acef34d6..573de18b88 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/AmbientOcclusion.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/AmbientOcclusion.preset @@ -8,12 +8,6 @@ "Name": "AmbientOcclusion", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ao", - "_ambocc", - "_amb", - "_ambientocclusion" - ], "PixelFormat": "BC4" }, "PlatformsPresets": { @@ -22,12 +16,6 @@ "Name": "AmbientOcclusion", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ao", - "_ambocc", - "_amb", - "_ambientocclusion" - ], "MaxTextureSize": 2048, "PixelFormat": "ASTC_4x4" }, @@ -36,12 +24,6 @@ "Name": "AmbientOcclusion", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ao", - "_ambocc", - "_amb", - "_ambientocclusion" - ], "MaxTextureSize": 2048, "PixelFormat": "ASTC_4x4" }, @@ -50,12 +32,6 @@ "Name": "AmbientOcclusion", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ao", - "_ambocc", - "_amb", - "_ambientocclusion" - ], "PixelFormat": "BC4" }, "provo": { @@ -63,12 +39,6 @@ "Name": "AmbientOcclusion", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ao", - "_ambocc", - "_amb", - "_ambientocclusion" - ], "PixelFormat": "BC4" } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ConvolvedCubemap.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ConvolvedCubemap.preset similarity index 88% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/ConvolvedCubemap.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ConvolvedCubemap.preset index abdf6501be..1ef15ada45 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/ConvolvedCubemap.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ConvolvedCubemap.preset @@ -8,10 +8,6 @@ "Name": "ConvolvedCubemap", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ccm", - "_convolvedcubemap" - ], "SuppressEngineReduce": true, "PixelFormat": "R9G9B9E5", "DiscardAlpha": true, @@ -35,10 +31,6 @@ "Name": "ConvolvedCubemap", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ccm", - "_convolvedcubemap" - ], "SuppressEngineReduce": true, "PixelFormat": "R9G9B9E5", "DiscardAlpha": true, @@ -61,10 +53,6 @@ "Name": "ConvolvedCubemap", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ccm", - "_convolvedcubemap" - ], "SuppressEngineReduce": true, "PixelFormat": "R9G9B9E5", "DiscardAlpha": true, @@ -87,10 +75,6 @@ "Name": "ConvolvedCubemap", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ccm", - "_convolvedcubemap" - ], "SuppressEngineReduce": true, "PixelFormat": "R9G9B9E5", "DiscardAlpha": true, @@ -113,10 +97,6 @@ "Name": "ConvolvedCubemap", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ccm", - "_convolvedcubemap" - ], "SuppressEngineReduce": true, "PixelFormat": "R9G9B9E5", "DiscardAlpha": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Decal_AlbedoWithOpacity.preset similarity index 86% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Decal_AlbedoWithOpacity.preset index e2e009afc5..873d434380 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Decal_AlbedoWithOpacity.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Decal_AlbedoWithOpacity.preset @@ -6,9 +6,6 @@ "DefaultPreset": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", "Name": "Decal_AlbedoWithOpacity", - "FileMasks": [ - "_decal" - ], "PixelFormat": "BC7t", "IsPowerOf2": true, "MipMapSetting": { @@ -21,9 +18,6 @@ "android": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", "Name": "Decal_AlbedoWithOpacity", - "FileMasks": [ - "_decal" - ], "PixelFormat": "ASTC_4x4", "MaxTextureSize": 2048, "IsPowerOf2": true, @@ -36,9 +30,6 @@ "ios": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", "Name": "Decal_AlbedoWithOpacity", - "FileMasks": [ - "_decal" - ], "PixelFormat": "ASTC_4x4", "MaxTextureSize": 2048, "IsPowerOf2": true, @@ -51,9 +42,6 @@ "mac": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", "Name": "Decal_AlbedoWithOpacity", - "FileMasks": [ - "_decal" - ], "PixelFormat": "BC3", "IsPowerOf2": true, "MipMapSetting": { @@ -65,9 +53,6 @@ "provo": { "UUID": "{E06B5087-2640-49B6-B9BA-D40048162B90}", "Name": "Decal_AlbedoWithOpacity", - "FileMasks": [ - "_decal" - ], "PixelFormat": "BC7t", "IsPowerOf2": true, "MipMapSetting": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Displacement.preset similarity index 59% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Displacement.preset index 28ac84d646..04f35dbd6f 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Displacement.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Displacement.preset @@ -8,18 +8,6 @@ "Name": "Displacement", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_displ", - "_disp", - "_dsp", - "_d", - "_dm", - "_displacement", - "_height", - "_hm", - "_ht", - "_h" - ], "PixelFormat": "BC4", "DiscardAlpha": true, "IsPowerOf2": true, @@ -33,18 +21,6 @@ "Name": "Displacement", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_displ", - "_disp", - "_dsp", - "_d", - "_dm", - "_displacement", - "_height", - "_hm", - "_ht", - "_h" - ], "PixelFormat": "ASTC_4x4", "MaxTextureSize": 2048, "DiscardAlpha": true, @@ -59,18 +35,6 @@ "Name": "Displacement", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_displ", - "_disp", - "_dsp", - "_d", - "_dm", - "_displacement", - "_height", - "_hm", - "_ht", - "_h" - ], "PixelFormat": "ASTC_4x4", "MaxTextureSize": 2048, "DiscardAlpha": true, @@ -84,18 +48,6 @@ "Name": "Displacement", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_displ", - "_disp", - "_dsp", - "_d", - "_dm", - "_displacement", - "_height", - "_hm", - "_ht", - "_h" - ], "PixelFormat": "BC4", "DiscardAlpha": true, "IsPowerOf2": true, @@ -108,18 +60,6 @@ "Name": "Displacement", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_displ", - "_disp", - "_dsp", - "_d", - "_dm", - "_displacement", - "_height", - "_hm", - "_ht", - "_h" - ], "PixelFormat": "BC4", "DiscardAlpha": true, "IsPowerOf2": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Emissive.preset similarity index 61% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Emissive.preset index f5e3a79357..798b8d1657 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Emissive.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Emissive.preset @@ -7,13 +7,6 @@ "UUID": "{07041D83-E0C3-4726-8735-CA0FE550C9A0}", "Name": "Emissive", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_emissive", - "_e", - "_glow", - "_em", - "_emit" - ], "PixelFormat": "BC7", "DiscardAlpha": true }, @@ -22,13 +15,6 @@ "UUID": "{07041D83-E0C3-4726-8735-CA0FE550C9A0}", "Name": "Emissive", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_emissive", - "_e", - "_glow", - "_em", - "_emit" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, "DiscardAlpha": true @@ -37,13 +23,6 @@ "UUID": "{07041D83-E0C3-4726-8735-CA0FE550C9A0}", "Name": "Emissive", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_emissive", - "_e", - "_glow", - "_em", - "_emit" - ], "PixelFormat": "ASTC_6x6", "MaxTextureSize": 2048, "DiscardAlpha": true @@ -52,13 +31,6 @@ "UUID": "{07041D83-E0C3-4726-8735-CA0FE550C9A0}", "Name": "Emissive", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_emissive", - "_e", - "_glow", - "_em", - "_emit" - ], "PixelFormat": "BC7", "DiscardAlpha": true }, @@ -66,13 +38,6 @@ "UUID": "{07041D83-E0C3-4726-8735-CA0FE550C9A0}", "Name": "Emissive", "RGB_Weight": "CIEXYZ", - "FileMasks": [ - "_emissive", - "_e", - "_glow", - "_em", - "_emit" - ], "PixelFormat": "BC7", "DiscardAlpha": true } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Gradient.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Gradient.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Gradient.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Gradient.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Greyscale.preset similarity index 77% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Greyscale.preset index c71ada1269..19439bad2d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Greyscale.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Greyscale.preset @@ -8,11 +8,8 @@ "Name": "Greyscale", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_mask" - ], "PixelFormat": "BC4", - "IsPowerOf2": true, + "Swizzle": "rrr1", "MipMapSetting": { "MipGenType": "Box" } @@ -23,11 +20,8 @@ "Name": "Greyscale", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_mask" - ], "PixelFormat": "ASTC_4x4", - "IsPowerOf2": true, + "Swizzle": "rrr1", "MipMapSetting": { "MipGenType": "Box" } @@ -37,11 +31,8 @@ "Name": "Greyscale", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_mask" - ], "PixelFormat": "ASTC_4x4", - "IsPowerOf2": true, + "Swizzle": "rrr1", "MipMapSetting": { "MipGenType": "Box" } @@ -51,11 +42,8 @@ "Name": "Greyscale", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_mask" - ], "PixelFormat": "BC4", - "IsPowerOf2": true, + "Swizzle": "rrr1", "MipMapSetting": { "MipGenType": "Box" } @@ -65,11 +53,8 @@ "Name": "Greyscale", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_mask" - ], "PixelFormat": "BC4", - "IsPowerOf2": true, + "Swizzle": "rrr1", "MipMapSetting": { "MipGenType": "Box" } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLDiffuse.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLDiffuse.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLDiffuse.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLDiffuse.preset index 8bd6b348d1..845f8e13e4 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLDiffuse.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLDiffuse.preset @@ -7,9 +7,6 @@ "UUID": "{E3706342-BF21-4D9C-AE28-9670EB3EF3C5}", "Name": "IBLDiffuse", "Description": "The input cubemap generates an IBL diffuse output cubemap.", - "FileMasks": [ - "_ibldiffusecm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -31,9 +28,6 @@ "android": { "UUID": "{E3706342-BF21-4D9C-AE28-9670EB3EF3C5}", "Name": "IBLDiffuse", - "FileMasks": [ - "_ibldiffusecm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -54,9 +48,6 @@ "ios": { "UUID": "{E3706342-BF21-4D9C-AE28-9670EB3EF3C5}", "Name": "IBLDiffuse", - "FileMasks": [ - "_ibldiffusecm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -77,9 +68,6 @@ "mac": { "UUID": "{E3706342-BF21-4D9C-AE28-9670EB3EF3C5}", "Name": "IBLDiffuse", - "FileMasks": [ - "_ibldiffusecm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -100,9 +88,6 @@ "provo": { "UUID": "{E3706342-BF21-4D9C-AE28-9670EB3EF3C5}", "Name": "IBLDiffuse", - "FileMasks": [ - "_ibldiffusecm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLGlobal.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLGlobal.preset similarity index 83% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLGlobal.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLGlobal.preset index 717157f2aa..51daf44d6e 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLGlobal.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLGlobal.preset @@ -8,11 +8,6 @@ "Name": "IBLGlobal", "Description": "The input cubemap generates IBL specular and diffuse cubemaps.", "GenerateIBLOnly": true, - "FileMasks": [ - "_iblglobalcm", - "_cubemap", - "_cm" - ], "CubemapSettings": { "GenerateIBLSpecular": true, "IBLSpecularPreset": "IBLSpecular", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSkybox.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSkybox.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSkybox.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSkybox.preset index eee9af4cea..1a596468bd 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSkybox.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSkybox.preset @@ -7,9 +7,6 @@ "UUID": "{E6441EAC-9843-484B-8EFC-C03B2935B48D}", "Name": "IBLSkybox", "Description": "The input cubemap generates a skybox, IBL specular, and IBL diffuse output cubemaps.", - "FileMasks": [ - "_iblskyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -29,9 +26,6 @@ "android": { "UUID": "{E6441EAC-9843-484B-8EFC-C03B2935B48D}", "Name": "IBLSkybox", - "FileMasks": [ - "_iblskyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -50,9 +44,6 @@ "ios": { "UUID": "{E6441EAC-9843-484B-8EFC-C03B2935B48D}", "Name": "IBLSkybox", - "FileMasks": [ - "_iblskyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -71,9 +62,6 @@ "mac": { "UUID": "{E6441EAC-9843-484B-8EFC-C03B2935B48D}", "Name": "IBLSkybox", - "FileMasks": [ - "_iblskyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -92,9 +80,6 @@ "provo": { "UUID": "{E6441EAC-9843-484B-8EFC-C03B2935B48D}", "Name": "IBLSkybox", - "FileMasks": [ - "_iblskyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecular.preset similarity index 87% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecular.preset index 4f935c73ff..691f554540 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecular.preset @@ -7,10 +7,6 @@ "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", "Description": "The input cubemap generates an IBL specular output cubemap.", - "FileMasks": [ - "_iblspecularcm", - "_iblspecularcm256" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -34,10 +30,6 @@ "android": { "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", - "FileMasks": [ - "_iblspecularcm", - "_iblspecularcm256" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -60,10 +52,6 @@ "ios": { "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", - "FileMasks": [ - "_iblspecularcm", - "_iblspecularcm256" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -86,10 +74,6 @@ "mac": { "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", - "FileMasks": [ - "_iblspecularcm", - "_iblspecularcm256" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -112,10 +96,6 @@ "provo": { "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", - "FileMasks": [ - "_iblspecularcm", - "_iblspecularcm256" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularHigh.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularHigh.preset index ff4e143326..b436386864 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularHigh.preset @@ -7,9 +7,6 @@ "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", "Name": "IBLSpecularHigh", "Description": "The input cubemap generates an IBL specular output cubemap.", - "FileMasks": [ - "_iblspecularcm512" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -33,9 +30,6 @@ "android": { "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", "Name": "IBLSpecularHigh", - "FileMasks": [ - "_iblspecularcm512" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -58,9 +52,6 @@ "ios": { "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", "Name": "IBLSpecularHigh", - "FileMasks": [ - "_iblspecularcm512" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -83,9 +74,6 @@ "mac": { "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", "Name": "IBLSpecularHigh", - "FileMasks": [ - "_iblspecularcm512" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -108,9 +96,6 @@ "provo": { "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", "Name": "IBLSpecularHigh", - "FileMasks": [ - "_iblspecularcm512" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularLow.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularLow.preset index ee9ddd6ac7..7810028efa 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularLow.preset @@ -7,9 +7,6 @@ "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", "Name": "IBLSpecularLow", "Description": "The input cubemap generates an IBL specular output cubemap.", - "FileMasks": [ - "_iblspecularcm128" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -33,9 +30,6 @@ "android": { "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", "Name": "IBLSpecularLow", - "FileMasks": [ - "_iblspecularcm128" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -58,9 +52,6 @@ "ios": { "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", "Name": "IBLSpecularLow", - "FileMasks": [ - "_iblspecularcm128" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -83,9 +74,6 @@ "mac": { "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", "Name": "IBLSpecularLow", - "FileMasks": [ - "_iblspecularcm128" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -108,9 +96,6 @@ "provo": { "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", "Name": "IBLSpecularLow", - "FileMasks": [ - "_iblspecularcm128" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryHigh.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryHigh.preset index 08d9416935..a18885dad9 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryHigh.preset @@ -7,9 +7,6 @@ "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", "Name": "IBLSpecularVeryHigh", "Description": "The input cubemap generates an IBL specular output cubemap.", - "FileMasks": [ - "_iblspecularcm1024" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -33,9 +30,6 @@ "android": { "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", "Name": "IBLSpecularVeryHigh", - "FileMasks": [ - "_iblspecularcm1024" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -58,9 +52,6 @@ "ios": { "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", "Name": "IBLSpecularVeryHigh", - "FileMasks": [ - "_iblspecularcm1024" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -83,9 +74,6 @@ "mac": { "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", "Name": "IBLSpecularVeryHigh", - "FileMasks": [ - "_iblspecularcm1024" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -108,9 +96,6 @@ "provo": { "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", "Name": "IBLSpecularVeryHigh", - "FileMasks": [ - "_iblspecularcm1024" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryLow.preset similarity index 90% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryLow.preset index c5c0788848..fb910b563a 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/IBLSpecularVeryLow.preset @@ -7,9 +7,6 @@ "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", "Name": "IBLSpecularVeryLow", "Description": "The input cubemap generates an IBL specular output cubemap.", - "FileMasks": [ - "_iblspecularcm64" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -33,9 +30,6 @@ "android": { "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", "Name": "IBLSpecularVeryLow", - "FileMasks": [ - "_iblspecularcm64" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -58,9 +52,6 @@ "ios": { "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", "Name": "IBLSpecularVeryLow", - "FileMasks": [ - "_iblspecularcm64" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -83,9 +74,6 @@ "mac": { "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", "Name": "IBLSpecularVeryLow", - "FileMasks": [ - "_iblspecularcm64" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -108,9 +96,6 @@ "provo": { "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", "Name": "IBLSpecularVeryLow", - "FileMasks": [ - "_iblspecularcm64" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ImageBuilder.settings b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ImageBuilder.settings new file mode 100644 index 0000000000..b26ae03260 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ImageBuilder.settings @@ -0,0 +1,150 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "BuilderSettingManager", + "ClassData": { + "BuildSettings": { + "android": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": true + }, + "ios": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": true + }, + "mac": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": true + }, + "pc": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": true + }, + "linux": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": true + }, + "provo": { + "GlossScale": 16.0, + "GlossBias": 0.0, + "Streaming": false, + "Enable": false + } + }, + "PresetsByFileMask": { + // albedo + "_basecolor": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_diff": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_diffuse": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_color": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_col": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_albedo": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_alb": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + "_bc": [ "Albedo", "AlbedoWithGenericAlpha", "AlbedoWithCoverage" ], + // normals + "_ddn": [ "Normals" ], + "_normal": [ "Normals" ], + "_normalmap": [ "Normals" ], + "_normals": [ "Normals" ], + "_norm": [ "Normals" ], + "_nor": [ "Normals" ], + "_nrm": [ "Normals" ], + "_nm": [ "Normals" ], + "_n": [ "Normals" ], + "_ddna": [ "NormalsWithSmoothness" ], + "_normala": [ "NormalsWithSmoothness" ], + "_nrma": [ "NormalsWithSmoothness" ], + "_nma": [ "NormalsWithSmoothness" ], + "_na": [ "NormalsWithSmoothness" ], + // refelctance + "_spec": [ "Reflectance" ], + "_specular": [ "Reflectance" ], + "_metallic": [ "Reflectance" ], + "_refl": [ "Reflectance" ], + "_ref": [ "Reflectance" ], + "_rf": [ "Reflectance" ], + "_gloss": [ "Reflectance" ], + "_g": [ "Reflectance" ], + "_f0": [ "Reflectance" ], + "_specf0": [ "Reflectance" ], + "_metal": [ "Reflectance" ], + "_mtl": [ "Reflectance" ], + "_m": [ "Reflectance" ], + "_mt": [ "Reflectance" ], + "_metalness": [ "Reflectance" ], + "_rough": [ "Reflectance" ], + "_roughness": [ "Reflectance" ], + // opacity + "_sss": [ "Opacity" ], + "_trans": [ "Opacity" ], + "_opac": [ "Opacity" ], + "_opacity": [ "Opacity" ], + "_o": [ "Opacity" ], + "_op": [ "Opacity" ], + "_mask": [ "Opacity", "Greyscale" ], + "_msk": [ "Opacity" ], + "_blend": [ "Opacity" ], + // AO + "_ao": [ "AmbientOcclusion" ], + "_ambocc": [ "AmbientOcclusion" ], + "_amb": [ "AmbientOcclusion" ], + "_ambientocclusion": [ "AmbientOcclusion" ], + // emissive + "_emissive": [ "Emissive" ], + "_e": [ "Emissive" ], + "_glow": [ "Emissive" ], + "_em": [ "Emissive" ], + "_emit": [ "Emissive" ], + // displacement + "_displ": [ "Emissive" ], + "_disp": [ "Emissive" ], + "_dsp": [ "Emissive" ], + "_d": [ "Emissive" ], + "_dm": [ "Emissive" ], + "_displacement": [ "Emissive" ], + "_height": [ "Emissive" ], + "_hm": [ "Emissive" ], + "_ht": [ "Emissive" ], + "_h": [ "Emissive" ], + // cubemap + "_ibldiffusecm": [ "IBLDiffuse" ], + "_iblskyboxcm": [ "IBLSkybox" ], + "_iblspecularcm": [ "IBLSpecular" ], + "_iblspecularcm128": [ "IBLSpecularLow" ], + "_iblspecularcm256": [ "IBLSpecular" ], + "_iblspecularcm512": [ "IBLSpecularHigh" ], + "_iblspecularcm1024": [ "IBLSpecularVeryHigh" ], + "_skyboxcm": [ "Skybox" ], + "_ccm": [ "ConvolvedCubemap" ], + "_convolvedcubemap": [ "ConvolvedCubemap" ], + "_iblglobalcm": [ "IBLGlobal" ], + "_cubemap": [ "IBLGlobal" ], + "_cm": [ "IBLGlobal" ], + // lut + "_lut": [ "LUT_RG8" ], + "_lutr32f": [ "LUT_R32F" ], + "_lutrgba16": [ "LUT_RGBA16" ], + "_lutrg32f": [ "LUT_RG32F" ], + "_lutrgba32f": [ "LUT_RGBA32F" ], + // layer mask + "_layers": [ "LayerMask" ], + "_rgbmask": [ "LayerMask" ], + // decal + "_decal": [ "Decal_AlbedoWithOpacity" ], + // ui + "_ui": [ "UserInterface_Lossless" ] + }, + "DefaultPreset": "Albedo", + "DefaultPresetAlpha": "AlbedoWithGenericAlpha" + } +} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_R32F.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_R32F.preset similarity index 97% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_R32F.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_R32F.preset index 1bb23c6e96..693f268304 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_R32F.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_R32F.preset @@ -6,7 +6,6 @@ "DefaultPreset": { "UUID": "{10D4D7D8-23E2-4FC5-BE6A-DA9949D2C603}", "Name": "LUT_R32F", - "FileMasks": ["_lutr32f"], "SourceColor": "Linear", "DestColor": "Linear", "PixelFormat": "R32F" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG16.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG16.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG16.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG16.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG32F.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG32F.preset similarity index 97% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG32F.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG32F.preset index 2cf0c6ca0a..7277a4b111 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG32F.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG32F.preset @@ -6,7 +6,6 @@ "DefaultPreset": { "UUID": "{52470B8B-0798-4E03-B0D3-039D5141CFEC}", "Name": "LUT_RG32F", - "FileMasks": ["_lutrg32f"], "SourceColor": "Linear", "DestColor": "Linear", "PixelFormat": "R32G32F" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG8.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG8.preset similarity index 79% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG8.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG8.preset index 9838d532b2..051cc2bedc 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RG8.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RG8.preset @@ -8,9 +8,6 @@ "Name": "LUT_RG8", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lut" - ], "PixelFormat": "R8G8" }, "PlatformsPresets": { @@ -19,9 +16,6 @@ "Name": "LUT_RG8", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lut" - ], "PixelFormat": "R8G8" }, "ios": { @@ -29,9 +23,6 @@ "Name": "LUT_RG8", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lut" - ], "PixelFormat": "R8G8" }, "mac": { @@ -39,9 +30,6 @@ "Name": "LUT_RG8", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lut" - ], "PixelFormat": "R8G8" }, "provo": { @@ -49,9 +37,6 @@ "Name": "LUT_RG8", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lut" - ], "PixelFormat": "R8G8" } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16.preset similarity index 78% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16.preset index f36d566d7e..ed940e5f25 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16.preset @@ -8,9 +8,6 @@ "Name": "LUT_RGBA16", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16" - ], "PixelFormat": "R16G16B16A16" }, "PlatformsPresets": { @@ -19,9 +16,6 @@ "Name": "LUT_RGBA16", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16" - ], "PixelFormat": "R16G16B16A16" }, "ios": { @@ -29,9 +23,6 @@ "Name": "LUT_RGBA16", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16" - ], "PixelFormat": "R16G16B16A16" }, "osx_gl": { @@ -39,9 +30,6 @@ "Name": "LUT_RGBA16", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16" - ], "PixelFormat": "R16G16B16A16" }, "provo": { @@ -49,9 +37,6 @@ "Name": "LUT_RGBA16", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16" - ], "PixelFormat": "R16G16B16A16" } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16F.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16F.preset similarity index 78% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16F.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16F.preset index 367c5101b3..f5d109b4a1 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA16F.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA16F.preset @@ -8,9 +8,6 @@ "Name": "LUT_RGBA16F", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16f" - ], "PixelFormat": "R16G16B16A16F" }, "PlatformsPresets": { @@ -19,9 +16,6 @@ "Name": "LUT_RGBA16F", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16f" - ], "PixelFormat": "R16G16B16A16F" }, "ios": { @@ -29,9 +23,6 @@ "Name": "LUT_RGBA16F", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16f" - ], "PixelFormat": "R16G16B16A16F" }, "osx_gl": { @@ -39,9 +30,6 @@ "Name": "LUT_RGBA16F", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16f" - ], "PixelFormat": "R16G16B16A16F" }, "provo": { @@ -49,9 +37,6 @@ "Name": "LUT_RGBA16F", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_lutrgba16f" - ], "PixelFormat": "R16G16B16A16F" } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA32F.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA32F.preset similarity index 97% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA32F.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA32F.preset index 3a456825bf..b85cb66c9d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA32F.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA32F.preset @@ -6,7 +6,6 @@ "DefaultPreset": { "UUID": "{AC4C49D4-2C70-425A-8DBF-E7FB2C61CF8D}", "Name": "LUT_RGBA32F", - "FileMasks": ["_lutrgba32f"], "SourceColor": "Linear", "DestColor": "Linear", "PixelFormat": "R32G32B32A32F" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA8.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA8.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LUT_RGBA8.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LUT_RGBA8.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/LayerMask.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LayerMask.preset similarity index 72% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/LayerMask.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LayerMask.preset index 5ce06aaea2..d33db40547 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/LayerMask.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/LayerMask.preset @@ -8,10 +8,6 @@ "Name": "LayerMask", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_layers", - "_rgbmask" - ], "PixelFormat": "R8G8B8X8" }, "PlatformsPresets": { @@ -20,10 +16,6 @@ "Name": "LayerMask", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_layers", - "_rgbmask" - ], "PixelFormat": "R8G8B8X8" }, "ios": { @@ -31,10 +23,6 @@ "Name": "LayerMask", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_layers", - "_rgbmask" - ], "PixelFormat": "R8G8B8X8" }, "mac": { @@ -42,10 +30,6 @@ "Name": "LayerMask", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_layers", - "_rgbmask" - ], "PixelFormat": "R8G8B8X8" }, "provo": { @@ -53,10 +37,6 @@ "Name": "LayerMask", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_layers", - "_rgbmask" - ], "PixelFormat": "R8G8B8X8" } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Normals.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Normals.preset similarity index 62% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Normals.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Normals.preset index eee0b88686..3f7a9ff111 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Normals.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Normals.preset @@ -8,17 +8,6 @@ "Name": "Normals", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddn", - "_normal", - "_normalmap", - "_normals", - "_norm", - "_nor", - "_nrm", - "_nm", - "_n" - ], "PixelFormat": "BC5s", "DiscardAlpha": true, "IsPowerOf2": true, @@ -33,17 +22,6 @@ "Name": "Normals", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddn", - "_normal", - "_normalmap", - "_normals", - "_norm", - "_nor", - "_nrm", - "_nm", - "_n" - ], "PixelFormat": "ASTC_4x4", "DiscardAlpha": true, "MaxTextureSize": 1024, @@ -58,17 +36,6 @@ "Name": "Normals", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddn", - "_normal", - "_normalmap", - "_normals", - "_norm", - "_nor", - "_nrm", - "_nm", - "_n" - ], "PixelFormat": "ASTC_4x4", "DiscardAlpha": true, "MaxTextureSize": 1024, @@ -83,17 +50,6 @@ "Name": "Normals", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddn", - "_normal", - "_normalmap", - "_normals", - "_norm", - "_nor", - "_nrm", - "_nm", - "_n" - ], "PixelFormat": "BC5s", "DiscardAlpha": true, "IsPowerOf2": true, @@ -107,17 +63,6 @@ "Name": "Normals", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddn", - "_normal", - "_normalmap", - "_normals", - "_norm", - "_nor", - "_nrm", - "_nm", - "_n" - ], "PixelFormat": "BC5s", "DiscardAlpha": true, "IsPowerOf2": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/NormalsWithSmoothness.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/NormalsWithSmoothness.preset similarity index 74% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/NormalsWithSmoothness.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/NormalsWithSmoothness.preset index 2c66cf9190..fd0abf3467 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/NormalsWithSmoothness.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/NormalsWithSmoothness.preset @@ -8,13 +8,6 @@ "Name": "NormalsWithSmoothness", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddna", - "_normala", - "_nrma", - "_nma", - "_na" - ], "PixelFormat": "BC5s", "PixelFormatAlpha": "BC4", "IsPowerOf2": true, @@ -30,13 +23,6 @@ "Name": "NormalsWithSmoothness", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddna", - "_normala", - "_nrma", - "_nma", - "_na" - ], "PixelFormat": "ASTC_4x4", "PixelFormatAlpha": "ASTC_4x4", "MaxTextureSize": 2048, @@ -52,13 +38,6 @@ "Name": "NormalsWithSmoothness", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddna", - "_normala", - "_nrma", - "_nma", - "_na" - ], "PixelFormat": "ASTC_4x4", "PixelFormatAlpha": "ASTC_4x4", "MaxTextureSize": 2048, @@ -74,13 +53,6 @@ "Name": "NormalsWithSmoothness", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddna", - "_normala", - "_nrma", - "_nma", - "_na" - ], "PixelFormat": "BC5s", "PixelFormatAlpha": "BC4", "IsPowerOf2": true, @@ -95,13 +67,6 @@ "Name": "NormalsWithSmoothness", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_ddna", - "_normala", - "_nrma", - "_nma", - "_na" - ], "PixelFormat": "BC5s", "PixelFormatAlpha": "BC4", "IsPowerOf2": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Opacity.preset similarity index 56% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Opacity.preset index 53998583cc..e896b74522 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Opacity.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Opacity.preset @@ -8,19 +8,8 @@ "Name": "Opacity", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_sss", - "_trans", - "_opac", - "_opacity", - "_o", - "_opac", - "_op", - "_mask", - "_msk", - "_blend" - ], "PixelFormat": "BC4", + "Swizzle": "rrr1", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" @@ -32,19 +21,8 @@ "Name": "Opacity", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_sss", - "_trans", - "_opac", - "_opacity", - "_o", - "_opac", - "_op", - "_mask", - "_msk", - "_blend" - ], "PixelFormat": "ASTC_4x4", + "Swizzle": "rrr1", "MaxTextureSize": 2048, "IsPowerOf2": true, "MipMapSetting": { @@ -56,19 +34,8 @@ "Name": "Opacity", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_sss", - "_trans", - "_opac", - "_opacity", - "_o", - "_opac", - "_op", - "_mask", - "_msk", - "_blend" - ], "PixelFormat": "ASTC_4x4", + "Swizzle": "rrr1", "MaxTextureSize": 2048, "IsPowerOf2": true, "MipMapSetting": { @@ -80,19 +47,8 @@ "Name": "Opacity", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_sss", - "_trans", - "_opac", - "_opacity", - "_o", - "_opac", - "_op", - "_mask", - "_msk", - "_blend" - ], "PixelFormat": "BC4", + "Swizzle": "rrr1", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" @@ -103,19 +59,8 @@ "Name": "Opacity", "SourceColor": "Linear", "DestColor": "Linear", - "FileMasks": [ - "_sss", - "_trans", - "_opac", - "_opacity", - "_o", - "_opac", - "_op", - "_mask", - "_msk", - "_blend" - ], "PixelFormat": "BC4", + "Swizzle": "rrr1", "IsPowerOf2": true, "MipMapSetting": { "MipGenType": "Box" diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_HDRLinear.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_HDRLinear.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_HDRLinear.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_HDRLinear.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_HDRLinearUncompressed.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_HDRLinearUncompressed.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_HDRLinearUncompressed.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_HDRLinearUncompressed.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_Linear.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_Linear.preset similarity index 100% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/ReferenceImage_Linear.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/ReferenceImage_Linear.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Reflectance.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Reflectance.preset new file mode 100644 index 0000000000..e6fd73b1be --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Reflectance.preset @@ -0,0 +1,66 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "MultiplatformPresetSettings", + "ClassData": { + "DefaultPreset": { + "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", + "Name": "Reflectance", + "SourceColor": "Linear", + "DestColor": "Linear", + "PixelFormat": "BC1", + "IsPowerOf2": true, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "PlatformsPresets": { + "android": { + "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", + "Name": "Reflectance", + "SourceColor": "Linear", + "DestColor": "Linear", + "PixelFormat": "ASTC_6x6", + "MaxTextureSize": 2048, + "IsPowerOf2": true, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "ios": { + "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", + "Name": "Reflectance", + "SourceColor": "Linear", + "DestColor": "Linear", + "PixelFormat": "ASTC_6x6", + "MaxTextureSize": 2048, + "IsPowerOf2": true, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "mac": { + "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", + "Name": "Reflectance", + "SourceColor": "Linear", + "DestColor": "Linear", + "PixelFormat": "BC1", + "IsPowerOf2": true, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "provo": { + "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", + "Name": "Reflectance", + "SourceColor": "Linear", + "DestColor": "Linear", + "PixelFormat": "BC1", + "IsPowerOf2": true, + "MipMapSetting": { + "MipGenType": "Box" + } + } + } + } +} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Skybox.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Skybox.preset similarity index 86% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/Skybox.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Skybox.preset index 4f71855ecf..b502872f92 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Skybox.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/Skybox.preset @@ -6,9 +6,6 @@ "DefaultPreset": { "UUID": "{F359CD3B-37E6-4627-B4F6-2DFC2C0E3C1C}", "Name": "Skybox", - "FileMasks": [ - "_skyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -24,9 +21,6 @@ "android": { "UUID": "{F359CD3B-37E6-4627-B4F6-2DFC2C0E3C1C}", "Name": "Skybox", - "FileMasks": [ - "_skyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -41,9 +35,6 @@ "ios": { "UUID": "{F359CD3B-37E6-4627-B4F6-2DFC2C0E3C1C}", "Name": "Skybox", - "FileMasks": [ - "_skyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -58,9 +49,6 @@ "mac": { "UUID": "{F359CD3B-37E6-4627-B4F6-2DFC2C0E3C1C}", "Name": "Skybox", - "FileMasks": [ - "_skyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, @@ -75,9 +63,6 @@ "provo": { "UUID": "{F359CD3B-37E6-4627-B4F6-2DFC2C0E3C1C}", "Name": "Skybox", - "FileMasks": [ - "_skyboxcm" - ], "SourceColor": "Linear", "DestColor": "Linear", "SuppressEngineReduce": true, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Compressed.preset similarity index 95% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Compressed.preset index 13334de700..7e2c42fa6b 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Compressed.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Compressed.preset @@ -9,8 +9,7 @@ "SuppressEngineReduce": true, "PixelFormat": "R8G8B8A8", "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ "_ui" ] + "DestColor": "Linear" }, "PlatformsPresets": { "android": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Lossless.preset b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Lossless.preset similarity index 95% rename from Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Lossless.preset rename to Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Lossless.preset index 39066b242b..bec6a604ef 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/UserInterface_Lossless.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/UserInterface_Lossless.preset @@ -9,8 +9,7 @@ "SuppressEngineReduce": true, "PixelFormat": "R8G8B8A8", "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ "_ui" ] + "DestColor": "Linear" }, "PlatformsPresets": { "android": { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp index a4ba846f1b..2b24fc1bc4 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.cpp @@ -8,6 +8,7 @@ #include "BuilderSettingManager.h" +#include #include #include #include @@ -17,8 +18,9 @@ #include #include #include -#include #include +#include +#include #include #include @@ -41,13 +43,18 @@ namespace ImageProcessingAtom { - const char* BuilderSettingManager::s_defaultConfigRelativeFolder = "Gems/Atom/Asset/ImageProcessingAtom/Config/"; + const char* BuilderSettingManager::s_defaultConfigRelativeFolder = "Gems/Atom/Asset/ImageProcessingAtom/Assets/Config/"; const char* BuilderSettingManager::s_projectConfigRelativeFolder = "Config/AtomImageBuilder/"; const char* BuilderSettingManager::s_builderSettingFileName = "ImageBuilder.settings"; - const char* BuilderSettingManager::s_presetFileExtension = ".preset"; + const char* BuilderSettingManager::s_presetFileExtension = "preset"; const char FileMaskDelimiter = '_'; + namespace + { + static constexpr const char* const LogWindow = "Image Processing"; + } + #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3) \ namespace ImageProcess##PrivateName \ @@ -69,13 +76,15 @@ namespace ImageProcessingAtom if (serialize) { serialize->Class() - ->Version(1) - ->Field("AnalysisFingerprint", &BuilderSettingManager::m_analysisFingerprint) + ->Version(2) ->Field("BuildSettings", &BuilderSettingManager::m_builderSettings) - ->Field("DefaultPresetsByFileMask", &BuilderSettingManager::m_defaultPresetByFileMask) + ->Field("PresetsByFileMask", &BuilderSettingManager::m_presetFilterMap) ->Field("DefaultPreset", &BuilderSettingManager::m_defaultPreset) ->Field("DefaultPresetAlpha", &BuilderSettingManager::m_defaultPresetAlpha) - ->Field("DefaultPresetNonePOT", &BuilderSettingManager::m_defaultPresetNonePOT); + ->Field("DefaultPresetNonePOT", &BuilderSettingManager::m_defaultPresetNonePOT) + // deprecated properties + ->Field("DefaultPresetsByFileMask", &BuilderSettingManager::m_defaultPresetByFileMask) + ->Field("AnalysisFingerprint", &BuilderSettingManager::m_analysisFingerprint); } } @@ -122,7 +131,7 @@ namespace ImageProcessingAtom s_globalInstance.Reset(); } - const PresetSettings* BuilderSettingManager::GetPreset(const PresetName& presetName, const PlatformName& platform, AZStd::string_view* settingsFilePathOut) + const PresetSettings* BuilderSettingManager::GetPreset(const PresetName& presetName, const PlatformName& platform, AZStd::string_view* settingsFilePathOut) const { AZStd::lock_guard lock(m_presetMapLock); auto itr = m_presets.find(presetName); @@ -137,16 +146,17 @@ namespace ImageProcessingAtom return nullptr; } - const BuilderSettings* BuilderSettingManager::GetBuilderSetting(const PlatformName& platform) + const BuilderSettings* BuilderSettingManager::GetBuilderSetting(const PlatformName& platform) const { - if (m_builderSettings.find(platform) != m_builderSettings.end()) + auto itr = m_builderSettings.find(platform); + if (itr != m_builderSettings.end()) { - return &m_builderSettings[platform]; + return &itr->second; } return nullptr; } - const PlatformNameList BuilderSettingManager::GetPlatformList() + const PlatformNameList BuilderSettingManager::GetPlatformList() const { PlatformNameList platforms; @@ -161,7 +171,7 @@ namespace ImageProcessingAtom return platforms; } - const AZStd::map >& BuilderSettingManager::GetPresetFilterMap() + const AZStd::map >& BuilderSettingManager::GetPresetFilterMap() const { AZStd::lock_guard lock(m_presetMapLock); return m_presetFilterMap; @@ -188,7 +198,6 @@ namespace ImageProcessingAtom m_presetFilterMap.clear(); m_builderSettings.clear(); m_presets.clear(); - m_defaultPresetByFileMask.clear(); } StringOutcome BuilderSettingManager::LoadConfig() @@ -198,44 +207,53 @@ namespace ImageProcessingAtom auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); if (fileIoBase == nullptr) { - return AZ::Failure(AZStd::string("File IO instance needs to be initialized to resolve ImageProcessing builder file aliases")); + return AZ::Failure( + AZStd::string("File IO instance needs to be initialized to resolve ImageProcessing builder file aliases")); } - // Construct the default setting path - - AZ::IO::FixedMaxPath defaultConfigFolder; if (auto engineRoot = fileIoBase->ResolvePath("@engroot@"); engineRoot.has_value()) { - defaultConfigFolder = *engineRoot; - defaultConfigFolder /= s_defaultConfigRelativeFolder; + m_defaultConfigFolder = *engineRoot; + m_defaultConfigFolder /= s_defaultConfigRelativeFolder; } - AZ::IO::FixedMaxPath projectConfigFolder; if (auto sourceGameRoot = fileIoBase->ResolvePath("@projectroot@"); sourceGameRoot.has_value()) { - projectConfigFolder = *sourceGameRoot; - projectConfigFolder /= s_projectConfigRelativeFolder; + m_projectConfigFolder = *sourceGameRoot; + m_projectConfigFolder /= s_projectConfigRelativeFolder; } AZStd::lock_guard lock(m_presetMapLock); ClearSettings(); - outcome = LoadSettings((projectConfigFolder / s_builderSettingFileName).Native()); - - if (!outcome.IsSuccess()) - { - outcome = LoadSettings((defaultConfigFolder / s_builderSettingFileName).Native()); - } + outcome = LoadSettings(); if (outcome.IsSuccess()) { // Load presets in default folder first, then load from project folder. // The same presets which loaded last will overwrite previous loaded one. - LoadPresets(defaultConfigFolder.Native()); - LoadPresets(projectConfigFolder.Native()); + LoadPresets(m_defaultConfigFolder.Native()); + LoadPresets(m_projectConfigFolder.Native()); + } - // Regenerate file mask mapping after all presets loaded - RegenerateMappings(); + // Collect extra file masks from preset files + CollectFileMasksFromPresets(); + + + if (QCoreApplication::instance()) + { + m_fileWatcher.reset(new QFileSystemWatcher); + // track preset files + // Note, the QT signal would only works for AP but not AssetBuilder + // We use file time stamp to track preset file change in builder's CreateJob + for (auto& preset : m_presets) + { + m_fileWatcher.data()->addPath(QString(preset.second.m_presetFilePath.c_str())); + } + m_fileWatcher.data()->addPath(QString(m_defaultConfigFolder.c_str())); + m_fileWatcher.data()->addPath(QString(m_projectConfigFolder.c_str())); + QObject::connect(m_fileWatcher.data(), &QFileSystemWatcher::fileChanged, this, &BuilderSettingManager::OnFileChanged); + QObject::connect(m_fileWatcher.data(), &QFileSystemWatcher::directoryChanged, this, &BuilderSettingManager::OnFolderChanged); } return outcome; @@ -243,36 +261,84 @@ namespace ImageProcessingAtom void BuilderSettingManager::LoadPresets(AZStd::string_view presetFolder) { - AZStd::lock_guard lock(m_presetMapLock); - QDirIterator it(presetFolder.data(), QStringList() << "*.preset", QDir::Files, QDirIterator::NoIteratorFlags); while (it.hasNext()) { QString filePath = it.next(); - QFileInfo fileInfo = it.fileInfo(); + LoadPreset(filePath.toUtf8().data()); + } + } - MultiplatformPresetSettings preset; - auto result = AZ::JsonSerializationUtils::LoadObjectFromFile(preset, filePath.toUtf8().data()); - if (!result.IsSuccess()) + bool BuilderSettingManager::LoadPreset(const AZStd::string& filePath) + { + QFileInfo fileInfo (filePath.c_str()); + + if (!fileInfo.exists()) + { + return false; + } + + MultiplatformPresetSettings preset; + auto result = AZ::JsonSerializationUtils::LoadObjectFromFile(preset, filePath); + if (!result.IsSuccess()) + { + AZ_Warning(LogWindow, false, "Failed to load preset file %s. Error: %s", + filePath.c_str(), result.GetError().c_str()); + return false; + } + + PresetName presetName(fileInfo.baseName().toUtf8().data()); + + AZ_Warning(LogWindow, presetName == preset.GetPresetName(), "Preset file name '%s' is not" + " same as preset name '%s'. Using preset file name as preset name", + filePath.c_str(), preset.GetPresetName().GetCStr()); + + preset.SetPresetName(presetName); + + m_presets[presetName] = PresetEntry{preset, filePath.c_str(), fileInfo.lastModified()}; + return true; + } + + void BuilderSettingManager::ReloadPreset(const PresetName& presetName) + { + // Find the preset file from project or default config folder + AZStd::string presetFileName = AZStd::string::format("%s.%s", presetName.GetCStr(), s_presetFileExtension); + AZ::IO::FixedMaxPath filePath = m_projectConfigFolder/presetFileName; + QFileInfo fileInfo (filePath.c_str()); + if (!fileInfo.exists()) + { + filePath = (m_defaultConfigFolder/presetFileName).c_str(); + fileInfo = QFileInfo(filePath.c_str()); + } + + AZStd::lock_guard lock(m_presetMapLock); + + //Skip the loading if the file wasn't chagned + if (fileInfo.exists()) + { + if (m_presets.find(presetName) != m_presets.end()) { - AZ_Warning("Image Processing", false, "Failed to load preset file %s. Error: %s", - filePath.toUtf8().data(), result.GetError().c_str()); + if (m_presets[presetName].m_lastModifiedTime == fileInfo.lastModified() + && m_presets[presetName].m_presetFilePath == filePath.c_str()) + { + return; + } } + } - PresetName presetName(fileInfo.baseName().toUtf8().data()); + // remove preset + m_presets.erase(presetName); - AZ_Warning("Image Processing", presetName == preset.GetPresetName(), "Preset file name '%s' is not" - " same as preset name '%s'. Using preset file name as preset name", - filePath.toUtf8().data(), preset.GetPresetName().GetCStr()); - - preset.SetPresetName(presetName); - - m_presets[presetName] = PresetEntry{preset, filePath.toUtf8().data()}; + if (fileInfo.exists()) + { + LoadPreset(filePath.c_str()); } } StringOutcome BuilderSettingManager::LoadConfigFromFolder(AZStd::string_view configFolder) { + AZStd::lock_guard lock(m_presetMapLock); + // Load builder settings AZStd::string settingFilePath = AZStd::string::format("%.*s%s", aznumeric_cast(configFolder.size()), configFolder.data(), s_builderSettingFileName); @@ -282,12 +348,108 @@ namespace ImageProcessingAtom if (result.IsSuccess()) { LoadPresets(configFolder); - RegenerateMappings(); } return result; } + void BuilderSettingManager::ReportDeprecatedSettings() + { + // reported deprecated attributes in image builder settings + if (!m_analysisFingerprint.empty()) + { + AZ_Warning(LogWindow, false, "'AnalysisFingerprint' is deprecated and it should be removed from file [%s]", s_builderSettingFileName); + } + if (!m_defaultPresetByFileMask.empty()) + { + AZ_Warning(LogWindow, false, "'DefaultPresetsByFileMask' is deprecated and it should be removed from file [%s]. Use PresetsByFileMask instead", s_builderSettingFileName); + } + } + + StringOutcome BuilderSettingManager::LoadSettings() + { + // If the project image build setting file exist, it will merge image builder settings from project folder to the settings from default config folder. + bool needMerge = false; + AZStd::string projectSettingFile{ (m_projectConfigFolder / s_builderSettingFileName).Native() }; + + if (AZ::IO::SystemFile::Exists(projectSettingFile.c_str())) + { + needMerge = true; + } + + AZ::Outcome outcome; + AZStd::string defaultSettingFile{ (m_defaultConfigFolder / s_builderSettingFileName).Native() }; + if (needMerge) + { + auto outcome1 = AZ::JsonSerializationUtils::ReadJsonFile(defaultSettingFile); + auto outcome2 = AZ::JsonSerializationUtils::ReadJsonFile(projectSettingFile); + + // return error if it failed to load default settings + if (!outcome1.IsSuccess()) + { + return STRING_OUTCOME_ERROR(outcome1.GetError()); + } + + // if project config was loaded successfully, apply merge patch + rapidjson::Document& originDoc = outcome1.GetValue(); + if (outcome2.IsSuccess()) + { + const rapidjson::Document& patchDoc = outcome2.GetValue(); + AZ::JsonSerializationResult::ResultCode result = + AZ::JsonSerialization::ApplyPatch(originDoc, originDoc.GetAllocator(), patchDoc, AZ::JsonMergeApproach::JsonMergePatch); + + if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Completed) + { + AZStd::vector outBuffer; + AZ::IO::ByteContainerStream> outStream{ &outBuffer }; + AZ::JsonSerializationUtils::WriteJsonStream(originDoc, outStream); + + outStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); + + outcome = AZ::JsonSerializationUtils::LoadObjectFromStream(*this, outStream); + if (!outcome.IsSuccess()) + { + return STRING_OUTCOME_ERROR(outcome.GetError()); + } + + ReportDeprecatedSettings(); + + + // Generate config file fingerprint + outStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); + AZ::u64 hash = AssetBuilderSDK::GetHashFromIOStream(outStream); + m_analysisFingerprint = AZStd::string::format("%llX", hash); + } + else + { + needMerge = false; + AZ_Warning(LogWindow, false, "Failed to fully merge data into image builder settings. Skipping project build setting file [%s]", projectSettingFile.c_str()); + } + } + else + { + AZ_Warning(LogWindow, false, "Failed to load project setting file [%s]. Skipping", projectSettingFile.c_str()); + } + } + + if (!needMerge) + { + outcome = AZ::JsonSerializationUtils::LoadObjectFromFile(*this, defaultSettingFile); + if (!outcome.IsSuccess()) + { + return STRING_OUTCOME_ERROR(outcome.GetError()); + } + + ReportDeprecatedSettings(); + + // Generate config file fingerprint + AZ::u64 hash = AssetBuilderSDK::GetFileHash(defaultSettingFile.c_str()); + m_analysisFingerprint = AZStd::string::format("%llX", hash); + } + + return STRING_OUTCOME_SUCCESS; + } + StringOutcome BuilderSettingManager::LoadSettings(AZStd::string_view filepath) { AZStd::lock_guard lock(m_presetMapLock); @@ -336,13 +498,13 @@ namespace ImageProcessingAtom return m_analysisFingerprint; } - void BuilderSettingManager::RegenerateMappings() + void BuilderSettingManager::CollectFileMasksFromPresets() { AZStd::lock_guard lock(m_presetMapLock); AZStd::string noFilter = AZStd::string(); - - m_presetFilterMap.clear(); + + AZStd::string extraString; for (const auto& presetIter : m_presets) { @@ -357,22 +519,31 @@ namespace ImageProcessingAtom { if (filemask.empty() || filemask[0] != FileMaskDelimiter) { - AZ_Warning("Image Processing", false, "File mask '%s' is invalid. It must start with '%c'.", filemask.c_str(), FileMaskDelimiter); + AZ_Warning(LogWindow, false, "File mask '%s' is invalid. It must start with '%c'.", filemask.c_str(), FileMaskDelimiter); continue; } else if (filemask.size() < 2) { - AZ_Warning("Image Processing", false, "File mask '%s' is invalid. The '%c' must be followed by at least one other character.", filemask.c_str()); + AZ_Warning(LogWindow, false, "File mask '%s' is invalid. The '%c' must be followed by at least one other character.", filemask.c_str()); continue; } else if (filemask.find(FileMaskDelimiter, 1) != AZStd::string::npos) { - AZ_Warning("Image Processing", false, "File mask '%s' is invalid. It must contain only a single '%c' character.", filemask.c_str(), FileMaskDelimiter); + AZ_Warning(LogWindow, false, "File mask '%s' is invalid. It must contain only a single '%c' character.", filemask.c_str(), FileMaskDelimiter); continue; } + + extraString += (filemask + preset.m_name.GetCStr()); + m_presetFilterMap[filemask].insert(preset.m_name); } } + + if (!extraString.empty()) + { + AZ::u64 hash = AZStd::hash{}(extraString); + m_analysisFingerprint += AZStd::string::format("%llX", hash); + } } void BuilderSettingManager::MetafilePathFromImagePath(AZStd::string_view imagePath, AZStd::string& metafilePath) @@ -419,38 +590,15 @@ namespace ImageProcessingAtom return m_presets.find(presetName) != m_presets.end(); } - PresetName BuilderSettingManager::GetSuggestedPreset(AZStd::string_view imageFilePath, IImageObjectPtr imageFromFile) + PresetName BuilderSettingManager::GetSuggestedPreset(AZStd::string_view imageFilePath) const { PresetName emptyPreset; - //load the image to get its size for later use - IImageObjectPtr image = imageFromFile; - //if the input image is empty we will try to load it from the path - if (imageFromFile == nullptr) - { - image = IImageObjectPtr(LoadImageFromFile(imageFilePath)); - } - - if (image == nullptr) - { - return emptyPreset; - } - //get file mask of this image file AZStd::string fileMask = GetFileMask(imageFilePath); PresetName outPreset = emptyPreset; - //check default presets for some file masks - if (m_defaultPresetByFileMask.find(fileMask) != m_defaultPresetByFileMask.end()) - { - outPreset = m_defaultPresetByFileMask[fileMask]; - if (!IsValidPreset(outPreset)) - { - outPreset = emptyPreset; - } - } - //use the preset filter map to find if (outPreset.IsEmpty() && !fileMask.empty()) { @@ -461,54 +609,21 @@ namespace ImageProcessingAtom } } - const PresetSettings* presetInfo = nullptr; - - if (!outPreset.IsEmpty()) - { - presetInfo = GetPreset(outPreset); - - //special case for cubemap - if (presetInfo && presetInfo->m_cubemapSetting) - { - // If it's not a latitude-longitude map or it doesn't match any cubemap layouts then reset its preset - if (!IsValidLatLongMap(image) && CubemapLayout::GetCubemapLayoutInfo(image) == nullptr) - { - outPreset = emptyPreset; - } - } - } - if (outPreset == emptyPreset) { - if (image->GetAlphaContent() == EAlphaContent::eAlphaContent_Absent) - { - outPreset = m_defaultPreset; - } - else - { - outPreset = m_defaultPresetAlpha; - } + outPreset = m_defaultPreset; } - //get the pixel format for selected preset - presetInfo = GetPreset(outPreset); + return outPreset; + } - if (presetInfo) - { - //valid whether image size work with pixel format - if (CPixelFormats::GetInstance().IsImageSizeValid(presetInfo->m_pixelFormat, - image->GetWidth(0), image->GetHeight(0), false)) - { - return outPreset; - } - else - { - AZ_Warning("Image Processing", false, "Image dimensions are not compatible with preset '%s'. The default preset will be used.", presetInfo->m_name.GetCStr()); - } - } - - //uncompressed one which could be used for almost everything - return m_defaultPresetNonePOT; + AZStd::vector BuilderSettingManager::GetPossiblePresetPaths(const PresetName& presetName) const + { + AZStd::vector paths; + AZStd::string presetFile = AZStd::string::format("%s.preset", presetName.GetCStr()); + paths.push_back((m_defaultConfigFolder / presetFile).c_str()); + paths.push_back((m_projectConfigFolder / presetFile).c_str()); + return paths; } bool BuilderSettingManager::DoesSupportPlatform(AZStd::string_view platformId) @@ -526,18 +641,50 @@ namespace ImageProcessingAtom AZStd::string filePath; if (!AzFramework::StringFunc::Path::Join(outputFolder.data(), fileName.c_str(), filePath)) { - AZ_Warning("Image Processing", false, "Failed to construct path with folder '%.*s' and file: '%s' to save preset", + AZ_Warning(LogWindow, false, "Failed to construct path with folder '%.*s' and file: '%s' to save preset", aznumeric_cast(outputFolder.size()), outputFolder.data(), filePath.c_str()); continue; } auto result = AZ::JsonSerializationUtils::SaveObjectToFile(&presetEntry.m_multiPreset, filePath); if (!result.IsSuccess()) { - AZ_Warning("Image Processing", false, "Failed to save preset '%s' to file '%s'. Error: %s", + AZ_Warning(LogWindow, false, "Failed to save preset '%s' to file '%s'. Error: %s", presetEntry.m_multiPreset.GetDefaultPreset().m_name.GetCStr(), filePath.c_str(), result.GetError().c_str()); } } } + void BuilderSettingManager::OnFileChanged(const QString &path) + { + // handles preset file change + // Note: this signal only works with AP but not AssetBuilder + AZ_TracePrintf(LogWindow, "File changed %s\n", path.toUtf8().data()); + QFileInfo info(path); + // skip if the file is not a preset file + // Note: for .settings file change it's handled when restart AP. + if (info.suffix() != s_presetFileExtension) + { + return; + } + + ReloadPreset(PresetName(info.baseName().toUtf8().data())); + } + + void BuilderSettingManager::OnFolderChanged([[maybe_unused]] const QString &path) + { + // handles new file added or removed + // Note: this signal only works with AP but not AssetBuilder + AZ_TracePrintf(LogWindow, "folder changed %s\n", path.toUtf8().data()); + + AZStd::lock_guard lock(m_presetMapLock); + m_presets.clear(); + LoadPresets(m_defaultConfigFolder.Native()); + LoadPresets(m_projectConfigFolder.Native()); + + for (auto& preset : m_presets) + { + m_fileWatcher.data()->addPath(QString(preset.second.m_presetFilePath.c_str())); + } + } } // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h index 3bbb71ea43..e4d45f4186 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/BuilderSettingManager.h @@ -10,10 +10,15 @@ #include #include -#include #include +#include +#include #include +#include +#include +#include + class QSettings; class QString; @@ -36,6 +41,7 @@ namespace ImageProcessingAtom * Each preset setting may have different values on different platform, but they are using same uuid. */ class BuilderSettingManager + : public QObject // required for using QFileSystemWatcher { friend class ImageProcessingTest; @@ -49,17 +55,17 @@ namespace ImageProcessingAtom static void DestroyInstance(); static void Reflect(AZ::ReflectContext* context); - const PresetSettings* GetPreset(const PresetName& presetName, const PlatformName& platform = "", AZStd::string_view* settingsFilePathOut = nullptr); + const PresetSettings* GetPreset(const PresetName& presetName, const PlatformName& platform = "", AZStd::string_view* settingsFilePathOut = nullptr) const; - const BuilderSettings* GetBuilderSetting(const PlatformName& platform); + const BuilderSettings* GetBuilderSetting(const PlatformName& platform) const; //! Return A list of platform supported - const PlatformNameList GetPlatformList(); + const PlatformNameList GetPlatformList() const; //! Return A map of preset settings based on their filemasks. //! @key filemask string, empty string means no filemask //! @value set of preset setting names supporting the specified filemask - const AZStd::map>& GetPresetFilterMap(); + const AZStd::map>& GetPresetFilterMap() const; //! Find preset name based on the preset id. const PresetName GetPresetNameFromId(const AZ::Uuid& presetId); @@ -68,7 +74,11 @@ namespace ImageProcessingAtom StringOutcome LoadConfig(); //! Load configurations files from a folder which includes builder settings and presets - StringOutcome LoadConfigFromFolder(AZStd::string_view configFolder); + //! Note: this is only used for unit test. Use LoadConfig() for editor or game launcher + StringOutcome LoadConfigFromFolder(AZStd::string_view configFolder); + + //! Reload preset from config folders + void ReloadPreset(const PresetName& presetName); const AZStd::string& GetAnalysisFingerprint() const; @@ -81,7 +91,12 @@ namespace ImageProcessingAtom //! @param imageFilePath: Filepath string of the image file. The function may load the image from the path for better detection //! @param image: an optional image object which can be used for preset selection if there is no match based file mask. //! @return suggested preset name. - PresetName GetSuggestedPreset(AZStd::string_view imageFilePath, IImageObjectPtr image = nullptr); + PresetName GetSuggestedPreset(AZStd::string_view imageFilePath) const; + + //! Get the possible preset config's full file paths + //! This function is only used for setting up image's source dependency if a preset file is missing + //! Otherwise, the preset's file path can be retrieved in GetPreset() function + AZStd::vector GetPossiblePresetPaths(const PresetName& presetName) const; bool IsValidPreset(PresetName presetName) const; @@ -105,25 +120,41 @@ namespace ImageProcessingAtom private: // functions AZ_DISABLE_COPY_MOVE(BuilderSettingManager); + // Write image builder setting to the file specified by filepath StringOutcome WriteSettings(AZStd::string_view filepath); + // Load image builder settings from the file specified by filepath StringOutcome LoadSettings(AZStd::string_view filepath); + // Load merge image builder settings (project and default) + StringOutcome LoadSettings(); + + // report warnings for the deprecated properties in image builder setting data + void ReportDeprecatedSettings(); + // Clear Builder Settings and any cached maps/lists void ClearSettings(); - // Regenerate Builder Settings and any cached maps/lists - void RegenerateMappings(); + // collect file masks + void CollectFileMasksFromPresets(); // Functions to save/load preset from a folder void SavePresets(AZStd::string_view outputFolder); void LoadPresets(AZStd::string_view presetFolder); + // Load a preset to m_presets and return true if success + bool LoadPreset(const AZStd::string& filePath); + + // handle preset files changes + void OnFileChanged(const QString &path); + void OnFolderChanged(const QString &path); + private: // variables struct PresetEntry { MultiplatformPresetSettings m_multiPreset; AZStd::string m_presetFilePath; // Can be used for debug output + QDateTime m_lastModifiedTime; }; // Builder settings for each platform @@ -131,13 +162,13 @@ namespace ImageProcessingAtom AZStd::unordered_map m_presets; - // Cached list of presets mapped by their file masks. + // a list of presets mapped by their file masks. // @Key file mask, use empty string to indicate all presets without filtering // @Value set of preset names that matches the file mask AZStd::map > m_presetFilterMap; - // A mutex to protect when modifying any map in this manager - AZStd::recursive_mutex m_presetMapLock; + // A mutex to protect when modifying any map in this manager + mutable AZStd::recursive_mutex m_presetMapLock; // Default presets for certain file masks AZStd::map m_defaultPresetByFileMask; @@ -153,5 +184,14 @@ namespace ImageProcessingAtom // Image builder's version AZStd::string m_analysisFingerprint; + + // default config folder + AZ::IO::FixedMaxPath m_defaultConfigFolder; + + // project config folder + AZ::IO::FixedMaxPath m_projectConfigFolder; + + // File system watcher to detect preset file changes + QScopedPointer m_fileWatcher; }; } // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/EditorCommon.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/EditorCommon.cpp index 067faf3dab..ca999fcd10 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/EditorCommon.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/EditorCommon.cpp @@ -171,7 +171,7 @@ namespace ImageProcessingAtomEditor if (!preset) { AZ_Warning("Texture Editor", false, "Cannot find preset %s! Will assign a suggested one for the texture.", presetName.GetCStr()); - presetName = BuilderSettingManager::Instance()->GetSuggestedPreset(m_fullPath, m_img); + presetName = BuilderSettingManager::Instance()->GetSuggestedPreset(m_fullPath); for (auto& settingIter : m_settingsMap) { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp index 90d45ae65a..57596f4a71 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp @@ -221,6 +221,58 @@ namespace ImageProcessingAtom m_isShuttingDown = true; } + PresetName GetImagePreset(const AZStd::string& filepath) + { + // first let preset from asset info + TextureSettings textureSettings; + StringOutcome output = TextureSettings::LoadTextureSetting(filepath, textureSettings); + + if (!textureSettings.m_preset.IsEmpty()) + { + return textureSettings.m_preset; + } + + return BuilderSettingManager::Instance()->GetSuggestedPreset(filepath); + } + + void HandlePresetDependency(PresetName presetName, AZStd::vector& sourceDependencyList) + { + // Reload preset if it was changed + ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetName); + + AZStd::string_view filePath; + auto presetSettings = BuilderSettingManager::Instance()->GetPreset(presetName, /*default platform*/"", &filePath); + + AssetBuilderSDK::SourceFileDependency sourceFileDependency; + sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute; + + // Need to watch any possibe preset paths + AZStd::vector possiblePresetPaths = BuilderSettingManager::Instance()->GetPossiblePresetPaths(presetName); + for (const auto& path:possiblePresetPaths) + { + sourceFileDependency.m_sourceFileDependencyPath = path; + sourceDependencyList.push_back(sourceFileDependency); + } + + if (presetSettings) + { + // handle special case here + // Cubemap setting may reference some other presets + if (presetSettings->m_cubemapSetting) + { + if (presetSettings->m_cubemapSetting->m_generateIBLDiffuse && !presetSettings->m_cubemapSetting->m_iblDiffusePreset.IsEmpty()) + { + HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblDiffusePreset, sourceDependencyList); + } + + if (presetSettings->m_cubemapSetting->m_generateIBLSpecular && !presetSettings->m_cubemapSetting->m_iblSpecularPreset.IsEmpty()) + { + HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblSpecularPreset, sourceDependencyList); + } + } + } + } + // this happens early on in the file scanning pass // this function should consistently always create the same jobs, and should do no checking whether the job is up to date or not - just be consistent. void ImageBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) @@ -242,13 +294,26 @@ namespace ImageProcessingAtom if (ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformInfo.m_identifier)) { AssetBuilderSDK::JobDescriptor descriptor; - descriptor.m_jobKey = ext + " Atom Compile"; + descriptor.m_jobKey = "Image Compile: " + ext; descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str()); descriptor.m_critical = false; + descriptor.m_additionalFingerprintInfo = ""; response.m_createJobOutputs.push_back(descriptor); } } + // add source dependency for .assetinfo file + AssetBuilderSDK::SourceFileDependency sourceFileDependency; + sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute; + sourceFileDependency.m_sourceFileDependencyPath = request.m_sourceFile; + AZ::StringFunc::Path::ReplaceExtension(sourceFileDependency.m_sourceFileDependencyPath, TextureSettings::ExtensionName); + response.m_sourceFileDependencyList.push_back(sourceFileDependency); + + // add source dependencies for .preset files + // Get the preset for this file + auto presetName = GetImagePreset(request.m_sourceFile); + HandlePresetDependency(presetName, response.m_sourceFileDependencyList); + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; return; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp index 5b23009cff..471472486d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -229,12 +230,24 @@ namespace ImageProcessingAtom AZStd::unique_ptr& cubemapSettings = m_input->m_presetSetting.m_cubemapSetting; if (cubemapSettings->m_generateIBLSpecular && !cubemapSettings->m_iblSpecularPreset.IsEmpty()) { - CreateIBLCubemap(cubemapSettings->m_iblSpecularPreset, SpecularCubemapSuffix, m_iblSpecularCubemapImage); + bool success = CreateIBLCubemap(cubemapSettings->m_iblSpecularPreset, SpecularCubemapSuffix, m_iblSpecularCubemapImage); + if (!success) + { + m_isSucceed = false; + m_isFinished = true; + break; + } } if (cubemapSettings->m_generateIBLDiffuse && !cubemapSettings->m_iblDiffusePreset.IsEmpty()) { - CreateIBLCubemap(cubemapSettings->m_iblDiffusePreset, DiffuseCubemapSuffix, m_iblDiffuseCubemapImage); + bool success = CreateIBLCubemap(cubemapSettings->m_iblDiffusePreset, DiffuseCubemapSuffix, m_iblDiffuseCubemapImage); + if (!success) + { + m_isSucceed = false; + m_isFinished = true; + break; + } } } @@ -251,7 +264,12 @@ namespace ImageProcessingAtom { if (m_input->m_presetSetting.m_cubemapSetting->m_requiresConvolve) { - FillCubemapMipmaps(); + bool success = FillCubemapMipmaps(); + if (!success) + { + m_isSucceed = false; + m_isFinished = true; + } } } else @@ -268,9 +286,7 @@ namespace ImageProcessingAtom // get gloss from normal for all mipmaps and save to alpha channel if (m_input->m_presetSetting.m_glossFromNormals) { - bool hasAlpha = (m_alphaContent == EAlphaContent::eAlphaContent_OnlyBlack - || m_alphaContent == EAlphaContent::eAlphaContent_OnlyBlackAndWhite - || m_alphaContent == EAlphaContent::eAlphaContent_Greyscale); + bool hasAlpha = Utils::NeedAlphaChannel(m_alphaContent); m_image->Get()->GlossFromNormals(hasAlpha); // set alpha content so it won't be ignored later. @@ -347,7 +363,11 @@ namespace ImageProcessingAtom } else { - AZ_TracePrintf("Image Processing", "Image converted with preset [%s] [%s] and saved to [%s] (%d bytes) taking %f seconds\n", + + [[maybe_unused]] const PixelFormatInfo* formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(m_image->Get()->GetPixelFormat()); + AZ_TracePrintf("Image Processing", "Image [%dx%d] [%s] converted with preset [%s] [%s] and saved to [%s] (%d bytes) taking %f seconds\n", + m_image->Get()->GetWidth(0), m_image->Get()->GetHeight(0), + formatInfo->szName, m_input->m_presetSetting.m_name.GetCStr(), m_input->m_filePath.c_str(), m_input->m_outputFolder.c_str(), sizeTotal, m_processTime); @@ -421,6 +441,17 @@ namespace ImageProcessingAtom outHeight >>= 1; outReduce++; } + + // resize to min texture size if it's smaller + if (outWidth < presetSettings->m_minTextureSize) + { + outWidth = presetSettings->m_minTextureSize; + } + + if (outHeight < presetSettings->m_minTextureSize) + { + outHeight = presetSettings->m_minTextureSize; + } } bool ImageConvertProcess::ConvertToLinear() @@ -647,7 +678,7 @@ namespace ImageProcessingAtom } else if (!CPixelFormats::GetInstance().IsImageSizeValid(dstFmt, dwWidth, dwHeight, false)) { - AZ_Warning("Image Processing", false, "Image size will be scaled for pixel format %s", CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->szName); + AZ_TracePrintf("Image processing", "Image size will be scaled for pixel format %s\n", CPixelFormats::GetInstance().GetPixelFormatInfo(dstFmt)->szName); } #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) @@ -758,7 +789,7 @@ namespace ImageProcessingAtom // in very rare user case, an old texture setting file may not have a preset. We fix it over here too. if (textureSettings.m_preset.IsEmpty()) { - textureSettings.m_preset = BuilderSettingManager::Instance()->GetSuggestedPreset(imageFilePath, srcImage); + textureSettings.m_preset = BuilderSettingManager::Instance()->GetSuggestedPreset(imageFilePath); } // Get preset @@ -795,7 +826,7 @@ namespace ImageProcessingAtom return process; } - void ImageConvertProcess::CreateIBLCubemap(PresetName preset, const char* fileNameSuffix, IImageObjectPtr& cubemapImage) + bool ImageConvertProcess::CreateIBLCubemap(PresetName preset, const char* fileNameSuffix, IImageObjectPtr& cubemapImage) { const AZStd::string& platformId = m_input->m_platform; AZStd::string_view filePath; @@ -803,7 +834,7 @@ namespace ImageProcessingAtom if (presetSettings == nullptr) { AZ_Error("Image Processing", false, "Couldn't find preset for IBL cubemap generation"); - return; + return false; } // generate export file name @@ -838,14 +869,14 @@ namespace ImageProcessingAtom if (!imageConvertProcess) { AZ_Error("Image Processing", false, "Failed to create image convert process for the IBL cubemap"); - return; + return false; } imageConvertProcess->ProcessAll(); if (!imageConvertProcess->IsSucceed()) { AZ_Error("Image Processing", false, "Image convert process for the IBL cubemap failed"); - return; + return false; } // append the output products to the job's product list @@ -853,6 +884,7 @@ namespace ImageProcessingAtom // store the output cubemap so it can be accessed by unit tests cubemapImage = imageConvertProcess->m_image->Get(); + return true; } bool ConvertImageFile(const AZStd::string& imageFilePath, const AZStd::string& exportDir, diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h index fe57b1a89f..0d06b15c39 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h @@ -160,7 +160,7 @@ namespace ImageProcessingAtom bool FillCubemapMipmaps(); //IBL cubemap generation, this creates a separate ImageConvertProcess - void CreateIBLCubemap(PresetName preset, const char* fileNameSuffix, IImageObjectPtr& cubemapImage); + bool CreateIBLCubemap(PresetName preset, const char* fileNameSuffix, IImageObjectPtr& cubemapImage); //convert color space to linear with pixel format rgba32f bool ConvertToLinear(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageObjectImpl.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageObjectImpl.cpp index 465d992ef6..758df462ec 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageObjectImpl.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageObjectImpl.cpp @@ -183,10 +183,9 @@ namespace ImageProcessingAtom return EAlphaContent::eAlphaContent_Absent; } - //if it's compressed format, return indeterminate. if user really want to know the content, they may convert the format to ARGB8 first if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_pixelFormat)) { - AZ_Assert(false, "the function only works right with uncompressed formats. convert to uncompressed format if you get accurate result"); + AZ_TracePrintf("Image processing", "GetAlphaContent() was called for compressed format\n"); return EAlphaContent::eAlphaContent_Indeterminate; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp index 29ba8c3351..20e1b18e77 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp @@ -385,6 +385,13 @@ namespace ImageProcessingAtom } return true; } + + bool NeedAlphaChannel(EAlphaContent alphaContent) + { + return (alphaContent == EAlphaContent::eAlphaContent_OnlyBlack + || alphaContent == EAlphaContent::eAlphaContent_OnlyBlackAndWhite + || alphaContent == EAlphaContent::eAlphaContent_Greyscale); + } } } // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.h index d5905eddbc..59503a2366 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.h @@ -26,5 +26,7 @@ namespace ImageProcessingAtom IImageObjectPtr LoadImageFromImageAsset(const AZ::Data::Asset& asset); bool SaveImageToDdsFile(IImageObjectPtr image, AZStd::string_view filePath); + + bool NeedAlphaChannel(EAlphaContent alphaContent); } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp index d0029ffc86..91b0952a06 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp @@ -203,7 +203,7 @@ namespace UnitTest m_gemFolder = AZ::Test::GetEngineRootPath() + "/Gems/Atom/Asset/ImageProcessingAtom/"; m_outputFolder = m_gemFolder + AZStd::string("Code/Tests/TestAssets/temp/"); - m_defaultSettingFolder = m_gemFolder + AZStd::string("Config/"); + m_defaultSettingFolder = m_gemFolder + AZStd::string("Assets/Config/"); m_testFileFolder = m_gemFolder + AZStd::string("Code/Tests/TestAssets/"); InitialImageFilenames(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/ImageBuilder.settings b/Gems/Atom/Asset/ImageProcessingAtom/Config/ImageBuilder.settings deleted file mode 100644 index 466ac4b71d..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/ImageBuilder.settings +++ /dev/null @@ -1,67 +0,0 @@ -{ - "Type": "JsonSerialization", - "Version": 1, - "ClassName": "BuilderSettingManager", - "ClassData": { - "AnalysisFingerprint": "2", - "BuildSettings": { - "android": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": true - }, - "ios": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": true - }, - "mac": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": true - }, - "pc": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": true - }, - "linux": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": true - }, - "provo": { - "GlossScale": 16.0, - "GlossBias": 0.0, - "Streaming": false, - "Enable": false - } - }, - "DefaultPresetsByFileMask": { - "_basecolor": "Albedo", - "_diff": "Albedo", - "_diffuse": "Albedo", - "_ddn": "Normals", - "_normal": "Normals", - "_ddna": "NormalsWithSmoothness", - "_glossness": "Reflectance", - "_spec": "Reflectance", - "_specular": "Reflectance", - "_metallic": "Reflectance", - "_refl": "Reflectance", - "_roughness": "Reflectance", - "_ibldiffusecm": "IBLDiffuse", - "_iblskyboxcm": "IBLSkybox", - "_iblspecularcm": "IBLSpecular", - "_skyboxcm": "Skybox" - }, - "DefaultPreset": "Albedo", - "DefaultPresetAlpha": "AlbedoWithGenericAlpha", - "DefaultPresetNonePOT": "ReferenceImage" - } -} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset deleted file mode 100644 index 2ac88dca85..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/Reflectance.preset +++ /dev/null @@ -1,157 +0,0 @@ -{ - "Type": "JsonSerialization", - "Version": 1, - "ClassName": "MultiplatformPresetSettings", - "ClassData": { - "DefaultPreset": { - "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", - "Name": "Reflectance", - "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ - "_spec", - "_refl", - "_ref", - "_rf", - "_gloss", - "_g", - "_f0", - "_specf0", - "_specular", - "_metal", - "_mtl", - "_m", - "_mt", - "_metalness", - "_metallic", - "_roughness", - "_rough" - ], - "PixelFormat": "BC1", - "IsPowerOf2": true, - "MipMapSetting": { - "MipGenType": "Box" - } - }, - "PlatformsPresets": { - "android": { - "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", - "Name": "Reflectance", - "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ - "_spec", - "_refl", - "_ref", - "_rf", - "_gloss", - "_g", - "_f0", - "_specf0", - "_metal", - "_mtl", - "_m", - "_mt", - "_metalness", - "_metallic", - "_roughness", - "_rough" - ], - "PixelFormat": "ASTC_6x6", - "MaxTextureSize": 2048, - "IsPowerOf2": true, - "MipMapSetting": { - "MipGenType": "Box" - } - }, - "ios": { - "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", - "Name": "Reflectance", - "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ - "_spec", - "_refl", - "_ref", - "_rf", - "_gloss", - "_g", - "_f0", - "_specf0", - "_metal", - "_mtl", - "_m", - "_mt", - "_metalness", - "_metallic", - "_roughness", - "_rough" - ], - "PixelFormat": "ASTC_6x6", - "MaxTextureSize": 2048, - "IsPowerOf2": true, - "MipMapSetting": { - "MipGenType": "Box" - } - }, - "mac": { - "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", - "Name": "Reflectance", - "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ - "_spec", - "_refl", - "_ref", - "_rf", - "_gloss", - "_g", - "_f0", - "_specf0", - "_metal", - "_mtl", - "_m", - "_mt", - "_metalness", - "_metallic", - "_roughness", - "_rough" - ], - "PixelFormat": "BC1", - "IsPowerOf2": true, - "MipMapSetting": { - "MipGenType": "Box" - } - }, - "provo": { - "UUID": "{7A3CC95E-0A0C-4CA1-8357-5712B028B77D}", - "Name": "Reflectance", - "SourceColor": "Linear", - "DestColor": "Linear", - "FileMasks": [ - "_spec", - "_refl", - "_ref", - "_rf", - "_gloss", - "_g", - "_f0", - "_specf0", - "_metal", - "_mtl", - "_m", - "_mt", - "_metalness", - "_metallic", - "_roughness", - "_rough" - ], - "PixelFormat": "BC1", - "IsPowerOf2": true, - "MipMapSetting": { - "MipGenType": "Box" - } - } - } - } -} diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index ce5d3cc363..8a1ff95f29 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -343,7 +343,6 @@ namespace AZ m_shadowBufferNeedsUpdate = true; m_shadowProperties.GetData(index).m_cameraConfigurations[nullptr] = {}; - m_shadowProperties.GetData(index).m_cameraTransforms[nullptr] = Transform::CreateIdentity(); const LightHandle handle(index); m_shadowingLightHandle = handle; // only the recent light has shadows. @@ -495,20 +494,10 @@ namespace AZ void DirectionalLightFeatureProcessor::SetCameraTransform( LightHandle handle, - const Transform& cameraTransform, - const RPI::RenderPipelineId& renderPipelineId) + const Transform&, + const RPI::RenderPipelineId&) { ShadowProperty& property = m_shadowProperties.GetData(handle.GetIndex()); - - if (RPI::RenderPipeline* renderPipeline = GetParentScene()->GetRenderPipeline(renderPipelineId).get()) - { - const RPI::View* cameraView = renderPipeline->GetDefaultView().get(); - property.m_cameraTransforms[cameraView] = cameraTransform; - } - else - { - property.m_cameraTransforms[nullptr] = cameraTransform; - } property.m_shadowmapViewNeedsUpdate = true; } @@ -934,17 +923,6 @@ namespace AZ return property.m_cameraConfigurations.at(nullptr); } - const Transform& DirectionalLightFeatureProcessor::GetCameraTransform(LightHandle handle, const RPI::View* cameraView) const - { - const ShadowProperty& property = m_shadowProperties.GetData(handle.GetIndex()); - const auto findIt = property.m_cameraTransforms.find(cameraView); - if (findIt != property.m_cameraTransforms.end()) - { - return findIt->second; - } - return property.m_cameraTransforms.at(nullptr); - } - void DirectionalLightFeatureProcessor::UpdateFrustums( LightHandle handle) { @@ -1365,10 +1343,11 @@ namespace AZ // If we used an AABB whose Y-direction range is from a segment, // the depth value on the shadowmap saturated to 0 or 1, // and we could not draw shadow correctly. + const Transform cameraTransform = cameraView->GetCameraTransform(); const Vector3 entireFrustumCenterLight = - lightTransform.GetInverseFast() * (GetCameraTransform(handle, cameraView).TransformPoint(property.m_entireFrustumCenterLocal)); + lightTransform.GetInverseFast() * (cameraTransform.TransformPoint(property.m_entireFrustumCenterLocal)); const float entireCenterY = entireFrustumCenterLight.GetElement(1); - const Vector3 cameraLocationWorld = GetCameraTransform(handle, cameraView).GetTranslation(); + const Vector3 cameraLocationWorld = cameraTransform.GetTranslation(); const Vector3 cameraLocationLight = lightTransformInverse * cameraLocationWorld; // Extend light view frustum by camera depth far in order to avoid shadow lacking behind camera. const float cameraBehindMinY = cameraLocationLight.GetElement(1) - GetCameraConfiguration(handle, cameraView).GetDepthFar(); @@ -1428,8 +1407,8 @@ namespace AZ GetCameraConfiguration(handle, cameraView).GetDepthCenter(depthNear, depthFar), depthFar); - const Vector3 localCenter{ 0.f, depthCenter, 0.f }; - return GetCameraTransform(handle, cameraView).TransformPoint(localCenter); + const Vector3 localCenter{ 0.f, depthCenter, 0.f }; + return cameraView->GetCameraTransform().TransformPoint(localCenter); } float DirectionalLightFeatureProcessor::GetRadius( @@ -1483,7 +1462,7 @@ namespace AZ const ShadowProperty& property = m_shadowProperties.GetData(handle.GetIndex()); const Vector3& boundaryCenter = GetWorldCenterPosition(handle, cameraView, depthNear, depthFar); const CascadeShadowCameraConfiguration& cameraConfiguration = GetCameraConfiguration(handle, cameraView); - const Transform& cameraTransform = GetCameraTransform(handle, cameraView); + const Transform cameraTransform = cameraView->GetCameraTransform(); const Vector3& cameraFwd = cameraTransform.GetBasis(1); const Vector3& cameraUp = cameraTransform.GetBasis(2); const Vector3 cameraToBoundaryCenter = boundaryCenter - cameraTransform.GetTranslation(); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 77f2db38d6..c206a5097f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -134,9 +134,6 @@ namespace AZ // Default far depth of each cascade. AZStd::array m_defaultFarDepths; - // Transforms of camera who offers view frustum for each camera view. - AZStd::unordered_map m_cameraTransforms; - // Configuration offers shape of the camera view frustum for each camera view. AZStd::unordered_map m_cameraConfigurations; @@ -259,11 +256,6 @@ namespace AZ //! it returns one of the fallback render pipeline ID. const CascadeShadowCameraConfiguration& GetCameraConfiguration(LightHandle handle, const RPI::View* cameraView) const; - //! This returns the camera transform. - //! If it has not been registered for the given camera view. - //! it returns one of the fallback render pipeline ID. - const Transform& GetCameraTransform(LightHandle handle, const RPI::View* cameraView) const; - //! This update view frustum of camera. void UpdateFrustums(LightHandle handle); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h index cdaf59ff75..eb3592944d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h @@ -92,6 +92,8 @@ namespace AZ const AZ::Matrix4x4& GetViewToWorldMatrix() const; const AZ::Matrix4x4& GetViewToClipMatrix() const; const AZ::Matrix4x4& GetWorldToClipMatrix() const; + const AZ::Matrix4x4& GetClipToWorldMatrix() const; + //! Get the camera's world transform, converted from the viewToWorld matrix's native y-up to z-up AZ::Transform GetCameraTransform() const; @@ -130,17 +132,22 @@ namespace AZ //! Returns the masked occlusion culling interface MaskedOcclusionCulling* GetMaskedOcclusionCulling(); + //! This is called by RenderPipeline when this view is added to the pipeline. + void OnAddToRenderPipeline(); + private: View() = delete; View(const AZ::Name& name, UsageFlags usage); - //! Sorts the finalized draw lists in this view void SortFinalizedDrawLists(); //! Sorts a drawList using the sort function from a pass with the corresponding drawListTag void SortDrawList(RHI::DrawList& drawList, RHI::DrawListTag tag); + //! Attempt to create a shader resource group. + void TryCreateShaderResourceGroup(); + AZ::Name m_name; UsageFlags m_usageFlags; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp index 8d4303f187..062eaf02bd 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp @@ -178,6 +178,10 @@ namespace AZ pipelineViews.m_views.resize(1); } ViewPtr previousView = pipelineViews.m_views[0]; + if (view) + { + view->OnAddToRenderPipeline(); + } pipelineViews.m_views[0] = view; if (previousView) @@ -238,6 +242,7 @@ namespace AZ pipelineViews.m_type = PipelineViewType::Transient; } view->SetPassesByDrawList(&pipelineViews.m_passesByDrawList); + view->OnAddToRenderPipeline(); pipelineViews.m_views.push_back(view); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp index 8c1e38ba58..4ad8dae41c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp @@ -47,18 +47,14 @@ namespace AZ { AZ_Assert(!name.IsEmpty(), "invalid name"); - // Set default matrixes. + // Set default matrices SetWorldToViewMatrix(AZ::Matrix4x4::CreateIdentity()); AZ::Matrix4x4 viewToClipMatrix; AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::Constants::HalfPi, 1, 0.1f, 1000.f, true); SetViewToClipMatrix(viewToClipMatrix); - Data::Asset viewSrgShaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs(); + TryCreateShaderResourceGroup(); - if (viewSrgShaderAsset.IsReady()) - { - m_shaderResourceGroup = ShaderResourceGroup::Create(viewSrgShaderAsset, RPISystemInterface::Get()->GetViewSrgLayout()->GetName()); - } #if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED m_maskedOcclusionCulling = MaskedOcclusionCulling::Create(); m_maskedOcclusionCulling->SetResolution(MaskedSoftwareOcclusionCullingWidth, MaskedSoftwareOcclusionCullingHeight); @@ -125,6 +121,7 @@ namespace AZ m_worldToViewMatrix = worldToView; m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix; + m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull(); m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix); m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix); @@ -162,6 +159,7 @@ namespace AZ m_worldToViewMatrix = m_viewToWorldMatrix.GetInverseFast(); m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix; + m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull(); // Only signal an update when there is a change, otherwise this might block // user input from changing the value. @@ -177,6 +175,7 @@ namespace AZ m_viewToClipMatrix = viewToClip; m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix; + m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull(); // Update z depth constant simultaneously // zNear -> n, zFar -> f @@ -227,6 +226,11 @@ namespace AZ return m_worldToClipMatrix; } + const AZ::Matrix4x4& View::GetClipToWorldMatrix() const + { + return m_clipToWorldMatrix; + } + bool View::HasDrawListTag(RHI::DrawListTag drawListTag) { return drawListTag.IsValid() && m_drawListMask[drawListTag.GetIndex()]; @@ -361,16 +365,19 @@ namespace AZ { if (m_clipSpaceOffset.IsZero()) { - Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix; - m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix); - m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix); - m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull()); + if (m_shaderResourceGroup) + { + Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix; + m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix); + m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix); + m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull()); + } } else { - // Offset the current and previous frame clip matricies + // Offset the current and previous frame clip matrices Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix; offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); @@ -379,27 +386,33 @@ namespace AZ offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); - // Build other matricies dependent on the view to clip matricies + // Build other matrices dependent on the view to clip matrices Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix; Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix; Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull(); Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix; - - m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix); - m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix); - m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull()); + + if (m_shaderResourceGroup) + { + m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix); + m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix); + m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull()); + } } - m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position); - m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix); - m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull()); - m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ); - m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants); + if (m_shaderResourceGroup) + { + m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position); + m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix); + m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull()); + m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ); + m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants); - m_shaderResourceGroup->Compile(); + m_shaderResourceGroup->Compile(); + } m_viewToClipPrevMatrix = m_viewToClipMatrix; m_worldToViewPrevMatrix = m_worldToViewMatrix; @@ -418,5 +431,30 @@ namespace AZ { return m_maskedOcclusionCulling; } + + void View::TryCreateShaderResourceGroup() + { + if (!m_shaderResourceGroup) + { + if (auto rpiSystemInterface = RPISystemInterface::Get()) + { + if (Data::Asset viewSrgShaderAsset = rpiSystemInterface->GetCommonShaderAssetForSrgs(); + viewSrgShaderAsset.IsReady()) + { + m_shaderResourceGroup = + ShaderResourceGroup::Create(viewSrgShaderAsset, rpiSystemInterface->GetViewSrgLayout()->GetName()); + } + } + } + } + + void View::OnAddToRenderPipeline() + { + TryCreateShaderResourceGroup(); + if (!m_shaderResourceGroup) + { + AZ_Warning("RPI::View", false, "Shader Resource Group failed to initialize"); + } + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.cpp b/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.cpp index 5343c295d8..8c685656ba 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.cpp +++ b/Gems/Atom/RPI/Code/Tests/Common/RPITestFixture.cpp @@ -69,6 +69,19 @@ namespace UnitTest assetPath /= "Cache"; AZ::IO::FileIOBase::GetInstance()->SetAlias("@products@", assetPath.c_str()); + // Remark, AZ::Utils::GetProjectPath() is not used when defining "user" folder, + // instead We use AZ::Test::GetEngineRootPath();. + // Reason: + // When running unit tests, using AZ::Utils::GetProjectPath() will resolve to something like: + // "/data/workspace/o3de/build/linux/External/Atom-9a4d112b/RPI/Code/Cache" + // The ShaderMetricSystem.cpp writes to the @user@ folder and the following runtime error occurs: + // "You may not alter data inside the asset cache. Please check the call stack and consider writing into the source asset folder instead." + // "Attempted write location: /data/workspace/o3de/build/linux/External/Atom-9a4d112b/RPI/Code/Cache/user/shadermetrics.json" + // To avoid the error We use AZ::Test::GetEngineRootPath(); + AZ::IO::Path userPath = AZ::Test::GetEngineRootPath(); + userPath /= "user"; + AZ::IO::FileIOBase::GetInstance()->SetAlias("@user@", userPath.c_str()); + m_jsonRegistrationContext = AZStd::make_unique(); m_jsonSystemComponent = AZStd::make_unique(); m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get()); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt index 40c8d7956e..460341532d 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/CMakeLists.txt @@ -80,6 +80,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest AZ::AzTestShared + AZ::AzFrameworkTestShared Gem::AtomToolsFramework.Static Gem::Atom_Utils.TestUtils.Static ) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 1200cb3d79..45c5394fd8 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -112,15 +112,16 @@ namespace AtomToolsFramework void UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) override; // ModularViewportCameraControllerRequestBus overrides ... - void InterpolateToTransform(const AZ::Transform& worldFromLocal) override; - AZ::Transform GetReferenceFrame() const override; - void SetReferenceFrame(const AZ::Transform& worldFromLocal) override; - void ClearReferenceFrame() override; + bool InterpolateToTransform(const AZ::Transform& worldFromLocal) override; + bool IsInterpolating() const override; + void StartTrackingTransform(const AZ::Transform& worldFromLocal) override; + void StopTrackingTransform() override; + bool IsTrackingTransform() const override; private: - //! Update the reference frame after a change has been made to the camera - //! view without updating the internal camera via user input. - void RefreshReferenceFrame(); + //! Combine the current camera transform with any potential roll from the tracked + //! transform (this is usually zero). + AZ::Transform CombinedCameraTransform() const; //! The current mode the camera controller is in. enum class CameraMode @@ -141,21 +142,21 @@ namespace AtomToolsFramework AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance). AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to. AzFramework::Camera m_previousCamera; //!< The state of the camera from the previous frame. - AZStd::optional m_storedCamera; //!< A potentially stored camera for when a custom reference frame is set. + AZStd::optional m_storedCamera; //!< A potentially stored camera for when a transform is being tracked. AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs. AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness. CameraControllerPriorityFn m_priorityFn; //!< Controls at what priority the camera controller should respond to events. CameraAnimation m_cameraAnimation; //!< Camera animation state (used during CameraMode::Animation). CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in. - //! An additional reference frame the camera can operate in (identity has no effect). - AZ::Transform m_referenceFrameOverride = AZ::Transform::CreateIdentity(); - //! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). - bool m_updatingTransformInternally = false; + float m_roll = 0.0f; //!< The current amount of roll to be applied to the camera. + float m_targetRoll = 0.0f; //!< The target amount of roll to be applied to the camera (current will move towards this). //! Listen for camera view changes outside of the camera controller. AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler; //! The current instance of the modular camera viewport context. AZStd::unique_ptr m_modularCameraViewportContext; + //! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally). + bool m_updatingTransformInternally = false; }; //! Placeholder implementation for ModularCameraViewportContext (useful for verifying the interface). diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h index ab397692e4..4422751d6b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h @@ -23,23 +23,31 @@ namespace AtomToolsFramework class ModularViewportCameraControllerRequests : public AZ::EBusTraits { public: + static inline constexpr float InterpolateToTransformDuration = 1.0f; + using BusIdType = AzFramework::ViewportId; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; //! Begin a smooth transition of the camera to the requested transform. //! @param worldFromLocal The transform of where the camera should end up. - virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal) = 0; + //! @return Returns true if the call began an interpolation and false otherwise. Calls to InterpolateToTransform + //! will have no effect if an interpolation is currently in progress. + virtual bool InterpolateToTransform(const AZ::Transform& worldFromLocal) = 0; - //! Return the current reference frame. - //! @note If a reference frame has not been set or a frame has been cleared, this is just the identity. - virtual AZ::Transform GetReferenceFrame() const = 0; + //! Returns if the camera is currently interpolating to a new transform. + virtual bool IsInterpolating() const = 0; - //! Set a new reference frame other than the identity for the camera controller. - virtual void SetReferenceFrame(const AZ::Transform& worldFromLocal) = 0; + //! Start tracking a transform. + //! Store the current camera transform and move to the next camera transform. + virtual void StartTrackingTransform(const AZ::Transform& worldFromLocal) = 0; - //! Clear the current reference frame to restore the identity. - virtual void ClearReferenceFrame() = 0; + //! Stop tracking the set transform. + //! The previously stored camera transform is restored. + virtual void StopTrackingTransform() = 0; + + //! Return if the tracking transform is set. + virtual bool IsTrackingTransform() const = 0; protected: ~ModularViewportCameraControllerRequests() = default; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index 8233658ded..5216f511cb 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace AtomToolsFramework { @@ -30,8 +31,8 @@ namespace AtomToolsFramework //! @see AZ::RPI::ViewportContext for Atom's API for setting up class RenderViewportWidget : public QWidget - , public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler , public AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler + , public AzToolsFramework::ViewportInteraction::ViewportInteractionRequests , public AzFramework::WindowRequestBus::Handler , protected AzFramework::InputChannelEventListener , protected AZ::TickBus::Handler @@ -90,11 +91,11 @@ namespace AtomToolsFramework //! Input processing is enabled by default. void SetInputProcessingEnabled(bool enabled); - // AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler overrides ... + // ViewportInteractionRequests overrides ... AzFramework::CameraState GetCameraState() override; AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override; - AZStd::optional ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) override; - AZStd::optional ViewportScreenToWorldRay( + AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override; + AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay( const AzFramework::ScreenPoint& screenPosition) override; float DeviceScalingFactor() override; @@ -149,5 +150,7 @@ namespace AtomToolsFramework AZ::ScriptTimePoint m_time; // Maps our internal Qt events into AzFramework InputChannels for our ViewportControllerList. AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr; + // Implementation of ViewportInteractionRequests (handles viewport picking operations). + AZStd::unique_ptr m_viewportInteractionImpl; }; } //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 new file mode 100644 index 0000000000..9de74752ff --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h @@ -0,0 +1,42 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +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 + { + public: + explicit ViewportInteractionImpl(AZ::RPI::ViewPtr viewPtr); + + void Connect(AzFramework::ViewportId viewportId); + void Disconnect(); + + // ViewportInteractionRequestBus overrides ... + AzFramework::CameraState GetCameraState() override; + AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override; + AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override; + AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay( + const AzFramework::ScreenPoint& screenPosition) override; + float DeviceScalingFactor() override; + + AZStd::function m_screenSizeFn; //! Callback to determine the screen size. + AZStd::function m_deviceScalingFactorFn; //! Callback to determine the device scaling factor. + + private: + AZ::RPI::ViewPtr m_viewPtr; + }; +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index ba3bfe4718..5c5eb834c6 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -79,12 +79,18 @@ namespace AtomToolsFramework m_styleManager.reset(new AzQtComponents::StyleManager(this)); m_styleManager->initialize(this, engineRootPath); - connect(&m_timer, &QTimer::timeout, this, [&]() + m_timer.setInterval(1); + connect(&m_timer, &QTimer::timeout, this, [this]() { this->PumpSystemEventLoopUntilEmpty(); this->Tick(); }); + connect(this, &QGuiApplication::applicationStateChanged, this, [this]() + { + // Limit the update interval when not in focus to reduce power consumption and interference with other applications + this->m_timer.setInterval((applicationState() & Qt::ApplicationActive) ? 1 : 32); + }); } AtomToolsApplication ::~AtomToolsApplication() diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/AtomToolsFrameworkModule.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/AtomToolsFrameworkModule.h index 759b2558bf..ae60220314 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/AtomToolsFrameworkModule.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/AtomToolsFrameworkModule.h @@ -9,11 +9,13 @@ #pragma once #include +#include namespace AtomToolsFramework { class AtomToolsFrameworkModule : public AZ::Module + , public AzToolsFramework::EmbeddedPython::PythonLoader { public: AZ_RTTI(AtomToolsFrameworkModule, "{B58B7CA8-98C9-4DC8-8607-E094989BBBE2}", AZ::Module); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index a92bfdbcdb..179ccd9fd8 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -175,16 +176,12 @@ namespace AtomToolsFramework // ignore these updates if the camera is being updated internally if (!m_updatingTransformInternally) { - if (m_storedCamera.has_value()) - { - // if an external change occurs ensure we update the stored reference frame if one is set - RefreshReferenceFrame(); - return; - } - m_previousCamera = m_targetCamera; - UpdateCameraFromTransform(m_targetCamera, m_modularCameraViewportContext->GetCameraTransform()); - m_camera = m_targetCamera; + + const AZ::Transform transform = m_modularCameraViewportContext->GetCameraTransform(); + const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(transform)); + UpdateCameraFromTranslationAndRotation(m_targetCamera, transform.GetTranslation(), eulerAngles); + m_targetRoll = eulerAngles.GetY(); } }; @@ -227,7 +224,9 @@ namespace AtomToolsFramework { m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count()); m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, m_cameraProps, event.m_deltaTime.count()); - m_modularCameraViewportContext->SetCameraTransform(m_referenceFrameOverride * m_camera.Transform()); + m_roll = AzFramework::SmoothValue(m_targetRoll, m_roll, m_cameraProps.m_rotateSmoothnessFn(), event.m_deltaTime.count()); + + m_modularCameraViewportContext->SetCameraTransform(CombinedCameraTransform()); } else if (m_cameraMode == CameraMode::Animation) { @@ -236,7 +235,10 @@ namespace AtomToolsFramework return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; - m_cameraAnimation.m_time = AZ::GetClamp(m_cameraAnimation.m_time + event.m_deltaTime.count(), 0.0f, 1.0f); + m_cameraAnimation.m_time = AZ::GetClamp( + m_cameraAnimation.m_time + + (event.m_deltaTime.count() / ModularViewportCameraControllerRequests::InterpolateToTransformDuration), + 0.0f, 1.0f); const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation; @@ -250,6 +252,7 @@ namespace AtomToolsFramework m_camera.m_yaw = eulerAngles.GetZ(); m_camera.m_pivot = current.GetTranslation(); m_camera.m_offset = AZ::Vector3::CreateZero(); + m_targetRoll = eulerAngles.GetY(); m_targetCamera = m_camera; m_modularCameraViewportContext->SetCameraTransform(current); @@ -257,55 +260,59 @@ namespace AtomToolsFramework if (animationTime >= 1.0f) { m_cameraMode = CameraMode::Control; - RefreshReferenceFrame(); } } m_updatingTransformInternally = false; } - void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal) + bool ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal) { - m_cameraMode = CameraMode::Animation; - m_cameraAnimation = CameraAnimation{ m_referenceFrameOverride * m_camera.Transform(), worldFromLocal, 0.0f }; + if (!IsInterpolating()) + { + m_cameraMode = CameraMode::Animation; + m_cameraAnimation = CameraAnimation{ CombinedCameraTransform(), worldFromLocal, 0.0f }; + + return true; + } + + return false; } - AZ::Transform ModularViewportCameraControllerInstance::GetReferenceFrame() const + bool ModularViewportCameraControllerInstance::IsInterpolating() const { - return m_referenceFrameOverride; + return m_cameraMode == CameraMode::Animation; } - void ModularViewportCameraControllerInstance::SetReferenceFrame(const AZ::Transform& worldFromLocal) + void ModularViewportCameraControllerInstance::StartTrackingTransform(const AZ::Transform& worldFromLocal) { if (!m_storedCamera.has_value()) { m_storedCamera = m_previousCamera; } - m_referenceFrameOverride = worldFromLocal; - m_targetCamera.m_pitch = 0.0f; - m_targetCamera.m_yaw = 0.0f; + const auto angles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromQuaternion(worldFromLocal.GetRotation())); + m_targetCamera.m_pitch = angles.GetX(); + m_targetCamera.m_yaw = angles.GetZ(); m_targetCamera.m_offset = AZ::Vector3::CreateZero(); - m_targetCamera.m_pivot = AZ::Vector3::CreateZero(); - m_camera = m_targetCamera; + m_targetCamera.m_pivot = worldFromLocal.GetTranslation(); + m_targetRoll = angles.GetY(); } - void ModularViewportCameraControllerInstance::ClearReferenceFrame() + void ModularViewportCameraControllerInstance::StopTrackingTransform() { - m_referenceFrameOverride = AZ::Transform::CreateIdentity(); - if (m_storedCamera.has_value()) { m_targetCamera = m_storedCamera.value(); - m_camera = m_targetCamera; + m_targetRoll = 0.0f; } m_storedCamera.reset(); } - void ModularViewportCameraControllerInstance::RefreshReferenceFrame() + bool ModularViewportCameraControllerInstance::IsTrackingTransform() const { - m_referenceFrameOverride = m_modularCameraViewportContext->GetCameraTransform() * m_camera.Transform().GetInverse(); + return m_storedCamera.has_value(); } AZ::Transform PlaceholderModularCameraViewportContextImpl::GetCameraTransform() const @@ -324,4 +331,9 @@ namespace AtomToolsFramework { handler.Connect(m_viewMatrixChangedEvent); } + + AZ::Transform ModularViewportCameraControllerInstance::CombinedCameraTransform() const + { + return m_camera.Transform() * AZ::Transform::CreateFromMatrix3x3(AZ::Matrix3x3::CreateRotationY(m_targetRoll)); + } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 9672abfd99..aac76afd84 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -75,7 +75,11 @@ namespace AtomToolsFramework m_defaultCamera = AZ::RPI::View::CreateView(cameraName, AZ::RPI::View::UsageFlags::UsageCamera); AZ::Interface::Get()->PushView(m_viewportContext->GetName(), m_defaultCamera); - AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(GetId()); + m_viewportInteractionImpl = AZStd::make_unique(m_defaultCamera); + m_viewportInteractionImpl->m_deviceScalingFactorFn = [this] { return aznumeric_cast(devicePixelRatioF()); }; + m_viewportInteractionImpl->m_screenSizeFn = [this] { return AzFramework::ScreenSize(width(), height()); }; + m_viewportInteractionImpl->Connect(id); + AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusConnect(GetId()); AzFramework::InputChannelEventListener::Connect(); AZ::TickBus::Handler::BusConnect(); @@ -107,7 +111,7 @@ namespace AtomToolsFramework AZ::TickBus::Handler::BusDisconnect(); AzFramework::InputChannelEventListener::Disconnect(); AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusDisconnect(); - AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect(); + m_viewportInteractionImpl->Disconnect(); } void RenderViewportWidget::LockRenderTargetSize(uint32_t width, uint32_t height) @@ -278,77 +282,23 @@ namespace AtomToolsFramework AzFramework::CameraState RenderViewportWidget::GetCameraState() { - AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView(); - if (currentView == nullptr) - { - return {}; - } - - // Build camera state from Atom camera transforms - AzFramework::CameraState cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix( - currentView->GetViewToWorldMatrix(), - AZ::Vector2{aznumeric_cast(width()), aznumeric_cast(height())} - ); - AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix()); - - // Convert from Z-up - AZStd::swap(cameraState.m_forward, cameraState.m_up); - cameraState.m_forward = -cameraState.m_forward; - - return cameraState; + return m_viewportInteractionImpl->GetCameraState(); } AzFramework::ScreenPoint RenderViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition) { - if (AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView(); - currentView == nullptr) - { - return AzFramework::ScreenPoint(0, 0); - } - - return AzFramework::WorldToScreen(worldPosition, GetCameraState()); + return m_viewportInteractionImpl->ViewportWorldToScreen(worldPosition); } - AZStd::optional RenderViewportWidget::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) + AZ::Vector3 RenderViewportWidget::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) { - const auto& cameraProjection = m_viewportContext->GetCameraProjectionMatrix(); - const auto& cameraView = m_viewportContext->GetCameraViewMatrix(); - - const AZ::Vector4 normalizedScreenPosition { - screenPosition.m_x * 2.f / width() - 1.0f, - (height() - screenPosition.m_y) * 2.f / height() - 1.0f, - 1.f - depth, // [GFX TODO] [ATOM-1501] Currently we always assume reverse depth - 1.f - }; - - AZ::Matrix4x4 worldFromScreen = cameraProjection * cameraView; - worldFromScreen.InvertFull(); - - const AZ::Vector4 projectedPosition = worldFromScreen * normalizedScreenPosition; - if (projectedPosition.GetW() == 0.0f) - { - return {}; - } - - return projectedPosition.GetAsVector3() / projectedPosition.GetW(); + return m_viewportInteractionImpl->ViewportScreenToWorld(screenPosition); } - AZStd::optional RenderViewportWidget::ViewportScreenToWorldRay( + AzToolsFramework::ViewportInteraction::ProjectedViewportRay RenderViewportWidget::ViewportScreenToWorldRay( const AzFramework::ScreenPoint& screenPosition) { - auto pos0 = ViewportScreenToWorld(screenPosition, 0.f); - auto pos1 = ViewportScreenToWorld(screenPosition, 1.f); - if (!pos0.has_value() || !pos1.has_value()) - { - return {}; - } - - pos0 = m_viewportContext->GetDefaultView()->GetViewToWorldMatrix().GetTranslation(); - AZ::Vector3 rayOrigin = pos0.value(); - AZ::Vector3 rayDirection = pos1.value() - pos0.value(); - rayDirection.Normalize(); - - return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{rayOrigin, rayDirection}; + return m_viewportInteractionImpl->ViewportScreenToWorldRay(screenPosition); } float RenderViewportWidget::DeviceScalingFactor() diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp new file mode 100644 index 0000000000..475566ccee --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ViewportInteractionImpl.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AtomToolsFramework +{ + ViewportInteractionImpl::ViewportInteractionImpl(AZ::RPI::ViewPtr viewPtr) + : m_viewPtr(AZStd::move(viewPtr)) + { + } + + void ViewportInteractionImpl::Connect(const AzFramework::ViewportId viewportId) + { + AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(viewportId); + } + + void ViewportInteractionImpl::Disconnect() + { + AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect(); + } + + AzFramework::CameraState ViewportInteractionImpl::GetCameraState() + { + // build camera state from atom camera transforms + AzFramework::CameraState cameraState = + AzFramework::CreateDefaultCamera(m_viewPtr->GetCameraTransform(), AzFramework::Vector2FromScreenSize(m_screenSizeFn())); + AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, m_viewPtr->GetViewToClipMatrix()); + return cameraState; + } + + AzFramework::ScreenPoint ViewportInteractionImpl::ViewportWorldToScreen(const AZ::Vector3& worldPosition) + { + return AzFramework::WorldToScreen(worldPosition, GetCameraState()); + } + + AZ::Vector3 ViewportInteractionImpl::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) + { + return AzFramework::ScreenToWorld(screenPosition, GetCameraState()); + } + + AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteractionImpl::ViewportScreenToWorldRay( + const AzFramework::ScreenPoint& screenPosition) + { + const AzFramework::CameraState cameraState = GetCameraState(); + const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPosition, cameraState); + const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized(); + return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection }; + } + + float ViewportInteractionImpl::DeviceScalingFactor() + { + return m_deviceScalingFactorFn(); + } +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp index df16cfbc43..3124372bd2 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/AtomToolsFrameworkTest.cpp @@ -12,16 +12,25 @@ namespace UnitTest { + class AtomToolsFrameworkTestEnvironment : public AZ::Test::ITestEnvironment + { + protected: + void SetupEnvironment() override + { + AZ::AllocatorInstance::Create(); + } + + void TeardownEnvironment() override + { + AZ::AllocatorInstance::Destroy(); + } + }; + class AtomToolsFrameworkTest : public ::testing::Test { protected: void SetUp() override { - if (!AZ::AllocatorInstance::IsReady()) - { - AZ::AllocatorInstance::Create(AZ::SystemAllocator::Descriptor()); - } - m_assetSystemStub.Activate(); RegisterSourceAsset("objects/upgrades/materials/supercondor.material"); @@ -38,11 +47,6 @@ namespace UnitTest void TearDown() override { m_assetSystemStub.Deactivate(); - - if (AZ::AllocatorInstance::IsReady()) - { - AZ::AllocatorInstance::Destroy(); - } } void RegisterSourceAsset(const AZStd::string& path) @@ -73,5 +77,5 @@ namespace UnitTest ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 0), "materials/condor.material"); } - AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); + AZ_UNIT_TEST_HOOK(new AtomToolsFrameworkTestEnvironment); } // namespace UnitTest diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp new file mode 100644 index 0000000000..fa5bc5b6e5 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Tests/ViewportInteractionImplTests.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class ViewportInteractionImplFixture : public ::testing::Test + { + public: + static inline constexpr AzFramework::ViewportId TestViewportId = 1234; + static inline constexpr AzFramework::ScreenSize ScreenDimensions = AzFramework::ScreenSize(1280, 720); + + static AzFramework::ScreenPoint ScreenCenter() + { + const auto halfScreenDimensions = ScreenDimensions * 0.5f; + return AzFramework::ScreenPoint(halfScreenDimensions.m_width, halfScreenDimensions.m_height); + } + + void SetUp() override + { + AZ::NameDictionary::Create(); + + m_view = AZ::RPI::View::CreateView(AZ::Name("TestView"), AZ::RPI::View::UsageCamera); + + const auto aspectRatio = aznumeric_cast(ScreenDimensions.m_width) / aznumeric_cast(ScreenDimensions.m_height); + + AZ::Matrix4x4 viewToClipMatrix; + AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::DegToRad(60.0f), aspectRatio, 0.1f, 1000.f, true); + m_view->SetViewToClipMatrix(viewToClipMatrix); + + m_viewportInteractionImpl = AZStd::make_unique(m_view); + + m_viewportInteractionImpl->m_deviceScalingFactorFn = [] + { + return 1.0f; + }; + m_viewportInteractionImpl->m_screenSizeFn = [] + { + return ScreenDimensions; + }; + + m_viewportInteractionImpl->Connect(TestViewportId); + } + + void TearDown() override + { + m_viewportInteractionImpl->Disconnect(); + m_viewportInteractionImpl.reset(); + + m_view.reset(); + + AZ::NameDictionary::Destroy(); + } + + AZ::RPI::ViewPtr m_view; + AZStd::unique_ptr m_viewportInteractionImpl; + }; + + // transform a point from screen space to world space, and then from world space back to screen space + AzFramework::ScreenPoint ScreenToWorldToScreen( + const AzFramework::ScreenPoint& screenPoint, + AzToolsFramework::ViewportInteraction::ViewportInteractionRequests& viewportInteractionRequests) + { + const auto worldResult = viewportInteractionRequests.ViewportScreenToWorld(screenPoint); + return viewportInteractionRequests.ViewportWorldToScreen(worldResult); + } + + TEST_F(ViewportInteractionImplFixture, ViewportInteractionRequestsMapsFromScreenToWorldAndBack) + { + using AzFramework::ScreenPoint; + + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(10.0f, 0.0f, 5.0f))); + + { + const auto expectedScreenPoint = ScreenPoint{ 600, 450 }; + const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl); + EXPECT_EQ(resultScreenPoint, expectedScreenPoint); + } + + { + auto expectedScreenPoint = ScreenCenter(); + const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl); + EXPECT_EQ(resultScreenPoint, expectedScreenPoint); + } + + { + const auto expectedScreenPoint = ScreenPoint{ 0, 0 }; + const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl); + EXPECT_EQ(resultScreenPoint, expectedScreenPoint); + } + + { + const auto expectedScreenPoint = ScreenPoint{ ScreenDimensions.m_width, ScreenDimensions.m_height }; + const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl); + EXPECT_EQ(resultScreenPoint, expectedScreenPoint); + } + } + + TEST_F(ViewportInteractionImplFixture, ScreenToWorldReturnsPositionOnNearClipPlaneInWorldSpace) + { + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-90.0f)), AZ::Vector3(20.0f, 0.0f, 0.0f))); + + const auto worldResult = m_viewportInteractionImpl->ViewportScreenToWorld(ScreenCenter()); + EXPECT_THAT(worldResult, IsClose(AZ::Vector3(20.1f, 0.0f, 0.0f))); + } + + // note: values produced by reproducing in the editor viewport + TEST_F(ViewportInteractionImplFixture, WorldToScreenGivesExpectedScreenCoordinates) + { + using AzFramework::ScreenPoint; + + { + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(160.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-18.0f)), + AZ::Vector3(-21.0f, 2.5f, 6.0f))); + + const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-21.0f, -1.5f, 5.0f)); + EXPECT_EQ(screenResult, ScreenPoint(420, 326)); + } + + { + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(175.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-90.0f)), + AZ::Vector3(-10.0f, -11.0f, 2.5f))); + + const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-10.0f, -10.5f, 0.5f)); + EXPECT_EQ(screenResult, ScreenPoint(654, 515)); + } + + { + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(70.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(65.0f)), + AZ::Vector3(-22.5f, -10.0f, 1.5f))); + + const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-23.0f, -9.5f, 3.0f)); + EXPECT_EQ(screenResult, ScreenPoint(754, 340)); + } + } + + TEST_F(ViewportInteractionImplFixture, ScreenToWorldRayGivesGivesExpectedOriginAndDirection) + { + using AzFramework::ScreenPoint; + + m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(34.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-24.0f)), + AZ::Vector3(-9.3f, -9.8f, 4.0f))); + + const auto ray = m_viewportInteractionImpl->ViewportScreenToWorldRay(ScreenPoint(832, 226)); + + float unused; + auto intersection = AZ::Intersect::IntersectRaySphere(ray.origin, ray.direction, AZ::Vector3(-14.0f, 5.7f, 0.75f), 0.5f, unused); + + EXPECT_EQ(intersection, AZ::Intersect::SphereIsectTypes::ISECT_RAY_SPHERE_ISECT); + } +} // namespace UnitTest diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake index a2446cebcc..3ddcc05245 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake @@ -28,6 +28,7 @@ set(FILES Include/AtomToolsFramework/Util/MaterialPropertyUtil.h Include/AtomToolsFramework/Util/Util.h Include/AtomToolsFramework/Viewport/RenderViewportWidget.h + Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h Include/AtomToolsFramework/Window/AtomToolsMainWindow.h @@ -55,6 +56,7 @@ set(FILES Source/Util/Util.cpp Source/Viewport/RenderViewportWidget.cpp Source/Viewport/ModularViewportCameraController.cpp + Source/Viewport/ViewportInteractionImpl.cpp Source/Window/AtomToolsMainWindow.cpp Source/Window/AtomToolsMainWindowSystemComponent.cpp Source/Window/AtomToolsMainWindowSystemComponent.h diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake index bd9ad9b3d8..a071d29f47 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_tests_files.cmake @@ -8,4 +8,5 @@ set(FILES Tests/AtomToolsFrameworkTest.cpp + Tests/ViewportInteractionImplTests.cpp ) \ No newline at end of file diff --git a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp index dc091db9e5..eb1fd9890c 100644 --- a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp +++ b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp @@ -7,14 +7,14 @@ */ #include "ViewportCameraSelectorWindow.h" #include "ViewportCameraSelectorWindow_Internals.h" -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include namespace Qt { @@ -64,12 +64,14 @@ namespace Camera CameraListModel::CameraListModel(QWidget* myParent) : QAbstractListModel(myParent) { + m_lastActiveCamera = AZ::EntityId(); m_cameraItems.push_back(AZ::EntityId()); CameraNotificationBus::Handler::BusConnect(); } CameraListModel::~CameraListModel() { + m_firstEntry = true; // set the view entity id back to Invalid, thus enabling the editor camera EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewFromEntityPerspective, AZ::EntityId()); @@ -98,11 +100,13 @@ namespace Camera { // If the camera entity is not an editor camera entity, don't add it to the list. // This occurs when we're in simulation mode. + + //We reset the m_firstEntry value so we can update m_lastActiveCamera when we remove from the cameras list + m_firstEntry = true; + bool isEditorEntity = false; AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( - isEditorEntity, - &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, - cameraId); + isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, cameraId); if (!isEditorEntity) { return; @@ -111,11 +115,25 @@ namespace Camera beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_cameraItems.push_back(cameraId); endInsertRows(); + + if (m_lastActiveCamera.IsValid() && m_lastActiveCamera == cameraId) + { + Camera::CameraRequestBus::Event(cameraId, &Camera::CameraRequestBus::Events::MakeActiveView); + } } void CameraListModel::OnCameraRemoved(const AZ::EntityId& cameraId) { - auto cameraIt = AZStd::find_if(m_cameraItems.begin(), m_cameraItems.end(), + //Check it is the first time we remove a camera from the list before any other addition + //So we don't end up with the wrong camera ID. + if (m_firstEntry) + { + CameraSystemRequestBus::BroadcastResult(m_lastActiveCamera, &CameraSystemRequestBus::Events::GetActiveCamera); + m_firstEntry = false; + } + + auto cameraIt = AZStd::find_if( + m_cameraItems.begin(), m_cameraItems.end(), [&cameraId](const CameraListItem& entry) { return entry.m_cameraId == cameraId; @@ -162,7 +180,12 @@ namespace Camera // use the stylesheet for elements in a set where one item must be selected at all times setProperty("class", "SingleRequiredSelection"); - connect(m_cameraList, &CameraListModel::rowsInserted, this, [sortedProxyModel](const QModelIndex&, int, int) { sortedProxyModel->sortColumn(); }); + connect( + m_cameraList, &CameraListModel::rowsInserted, this, + [sortedProxyModel](const QModelIndex&, int, int) + { + sortedProxyModel->sortColumn(); + }); // highlight the current selected camera entity AZ::EntityId currentSelection; @@ -188,7 +211,8 @@ namespace Camera QScopedValueRollback rb(m_ignoreViewportViewEntityChanged, true); AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value(); - EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewAndMovementLockFromEntityPerspective, entityId, lockCameraMovement); + EditorCameraRequests::Bus::Broadcast( + &EditorCameraRequests::SetViewAndMovementLockFromEntityPerspective, entityId, lockCameraMovement); } } @@ -220,7 +244,9 @@ namespace Camera } // swallow mouse move events so we can disable sloppy selection - void ViewportCameraSelectorWindow::mouseMoveEvent(QMouseEvent*) {} + void ViewportCameraSelectorWindow::mouseMoveEvent(QMouseEvent*) + { + } // double click selects the entity void ViewportCameraSelectorWindow::mouseDoubleClickEvent([[maybe_unused]] QMouseEvent* event) @@ -228,11 +254,13 @@ namespace Camera AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value(); if (entityId.IsValid()) { - AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList { entityId }); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList{ entityId }); } else { - AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList {}); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList{}); } } @@ -290,7 +318,10 @@ namespace Camera : QWidget(parent) { setLayout(new QVBoxLayout(this)); - auto label = new QLabel("Select the camera you wish to view and navigate through. Closing this window will return you to the default editor camera.", this); + auto label = new QLabel( + "Select the camera you wish to view and navigate through. Closing this window will return you to the default editor " + "camera.", + this); label->setWordWrap(true); layout()->addWidget(label); layout()->addWidget(new ViewportCameraSelectorWindow(this)); @@ -309,6 +340,8 @@ namespace Camera viewOptions.isPreview = true; viewOptions.showInMenu = true; viewOptions.preferedDockingArea = Qt::DockWidgetArea::LeftDockWidgetArea; - AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Events::RegisterViewPane, s_viewportCameraSelectorName, "Viewport", viewOptions, &Internal::CreateNewSelectionWindow); + AzToolsFramework::EditorRequestBus::Broadcast( + &AzToolsFramework::EditorRequestBus::Events::RegisterViewPane, s_viewportCameraSelectorName, "Viewport", viewOptions, + &Internal::CreateNewSelectionWindow); } } // namespace Camera diff --git a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow_Internals.h b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow_Internals.h index b07ae7789f..21f316b83d 100644 --- a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow_Internals.h +++ b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow_Internals.h @@ -58,6 +58,11 @@ namespace Camera private: AZStd::vector m_cameraItems; AZ::EntityId m_sequenceCameraEntityId; + AZ::EntityId m_lastActiveCamera; + + //Value to check that is the first time that we remove a camera before adding a new one. + //So we can update m_lastActiveCamera properly + bool m_firstEntry = true; }; struct ViewportCameraSelectorWindow diff --git a/Gems/EditorPythonBindings/Code/Source/EditorPythonBindingsModule.cpp b/Gems/EditorPythonBindings/Code/Source/EditorPythonBindingsModule.cpp index cef8931722..f168cc5ab2 100644 --- a/Gems/EditorPythonBindings/Code/Source/EditorPythonBindingsModule.cpp +++ b/Gems/EditorPythonBindings/Code/Source/EditorPythonBindingsModule.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include #include @@ -18,6 +20,7 @@ namespace EditorPythonBindings { class EditorPythonBindingsModule : public AZ::Module + , public AzToolsFramework::EmbeddedPython::PythonLoader { public: AZ_RTTI(EditorPythonBindingsModule, "{851B9E35-4FD5-49B1-8207-E40D4BBA36CC}", AZ::Module); diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp index 4ed2825cc2..46f4c04880 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp @@ -344,6 +344,55 @@ namespace PhysX bool isAcceleration = true; return physx::PxD6JointDrive(stiffness, damping, forceLimit, isAcceleration); } + + AZStd::vector ComputeHierarchyDepths(const AZStd::vector& parentIndices) + { + const size_t numNodes = parentIndices.size(); + AZStd::vector nodeDepths(numNodes); + for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) + { + nodeDepths[nodeIndex] = { -1, nodeIndex }; + } + + for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) + { + if (nodeDepths[nodeIndex].m_depth != -1) + { + continue; + } + int depth = -1; // initial depth value for this node + int ancestorDepth = 0; // the depth of the first ancestor we find when iteratively visiting parents + bool ancestorFound = false; // whether we have found either an ancestor which already has a depth value, or the root + size_t currentIndex = nodeIndex; + while (!ancestorFound) + { + depth++; + if (depth > numNodes) + { + AZ_Error("PhysX Ragdoll", false, "Loop detected in hierarchy depth computation."); + return nodeDepths; + } + const size_t parentIndex = parentIndices[currentIndex]; + + if (parentIndex >= numNodes || nodeDepths[currentIndex].m_depth != -1) + { + ancestorFound = true; + ancestorDepth = (nodeDepths[currentIndex].m_depth != -1) ? nodeDepths[currentIndex].m_depth : 0; + } + + currentIndex = parentIndex; + } + + currentIndex = nodeIndex; + for (int i = depth; i >= 0; i--) + { + nodeDepths[currentIndex] = { ancestorDepth + i, currentIndex }; + currentIndex = parentIndices[currentIndex]; + } + } + + return nodeDepths; + } } // namespace Characters } // namespace Utils } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.h b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.h index 0f51a5d9b9..245dc01abd 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.h +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.h @@ -49,6 +49,18 @@ namespace PhysX //! @param forceLimit The upper limit on the force the joint can apply to reach its target. //! @return The created joint drive. physx::PxD6JointDrive CreateD6JointDrive(float strength, float dampingRatio, float forceLimit); + + //! Contains information about a node in a hierarchy and how deep it is in the hierarchy relative to the root. + struct DepthData + { + int m_depth = -1; //!< Depth of the joint in the hierarchy. The root has depth 0, its children depth 1, and so on. + size_t m_index = 0; // ComputeHierarchyDepths(const AZStd::vector& parentIndices); } // namespace Characters } // namespace Utils } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp index 57972fea3e..7615a175d1 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp @@ -7,20 +7,20 @@ */ #include -#include #include -#include #include +#include +#include #include +#include #include #include -#include +#include #include namespace PhysX { - bool RagdollComponent::VersionConverter(AZ::SerializeContext& context, - AZ::SerializeContext::DataElementNode& classElement) + bool RagdollComponent::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { // The element "PhysXRagdoll" was changed from a shared pointer to a unique pointer, but a version converter was // not added at the time. This means there may be serialized data with either the shared or unique pointer, but @@ -76,13 +76,13 @@ namespace PhysX ->Field("EnableJointProjection", &RagdollComponent::m_enableJointProjection) ->Field("ProjectionLinearTol", &RagdollComponent::m_jointProjectionLinearTolerance) ->Field("ProjectionAngularTol", &RagdollComponent::m_jointProjectionAngularToleranceDegrees) - ; + ->Field("EnableMassRatioClamping", &RagdollComponent::m_enableMassRatioClamping) + ->Field("MaxMassRatio", &RagdollComponent::m_maxMassRatio); AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { - editContext->Class( - "PhysX Ragdoll", "Creates a PhysX ragdoll simulation for an animation actor.") + editContext->Class("PhysX Ragdoll", "Creates a PhysX ragdoll simulation for an animation actor.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "PhysX") ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXRagdoll.svg") @@ -90,34 +90,49 @@ namespace PhysX ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/ragdoll/") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_positionIterations, "Position Iteration Count", + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_positionIterations, "Position Iteration Count", "The frequency at which ragdoll collider positions are resolved. Higher values can increase fidelity but decrease " "performance. Very high values might introduce instability.") ->Attribute(AZ::Edit::Attributes::Min, 1) ->Attribute(AZ::Edit::Attributes::Max, 255) - ->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_velocityIterations, "Velocity Iteration Count", + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_velocityIterations, "Velocity Iteration Count", "The frequency at which ragdoll collider velocities are resolved. Higher values can increase fidelity but decrease " "performance. Very high values might introduce instability.") ->Attribute(AZ::Edit::Attributes::Min, 1) ->Attribute(AZ::Edit::Attributes::Max, 255) - ->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_enableJointProjection, - "Enable Joint Projection", "When active, preserves joint constraints in volatile simulations. " + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_enableJointProjection, "Enable Joint Projection", + "When active, preserves joint constraints in volatile simulations. " "Might not be physically correct in all simulations.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) - ->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionLinearTolerance, + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionLinearTolerance, "Joint Projection Linear Tolerance", "Maximum linear joint error. Projection is applied to linear joint errors above this value.") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Step, 1e-3f) ->Attribute(AZ::Edit::Attributes::Visibility, &RagdollComponent::IsJointProjectionVisible) - ->DataElement(AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionAngularToleranceDegrees, + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_jointProjectionAngularToleranceDegrees, "Joint Projection Angular Tolerance", "Maximum angular joint error. Projection is applied to angular joint errors above this value.") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Step, 0.1f) ->Attribute(AZ::Edit::Attributes::Suffix, " degrees") ->Attribute(AZ::Edit::Attributes::Visibility, &RagdollComponent::IsJointProjectionVisible) - ; + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_enableMassRatioClamping, "Enable Mass Ratio Clamping", + "When active, ragdoll node mass values may be overridden to avoid unstable mass ratios.") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->DataElement( + AZ::Edit::UIHandlers::Default, &RagdollComponent::m_maxMassRatio, "Maximum Mass Ratio", + "The mass of the child body of a joint may be clamped to avoid its ratio with the parent " + "body mass exceeding this threshold.") + ->Attribute(AZ::Edit::Attributes::Min, 1.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->Attribute(AZ::Edit::Attributes::Visibility, &RagdollComponent::IsMaxMassRatioVisible); } } @@ -126,11 +141,16 @@ namespace PhysX } } - bool RagdollComponent::IsJointProjectionVisible() + bool RagdollComponent::IsJointProjectionVisible() const { return m_enableJointProjection; } + bool RagdollComponent::IsMaxMassRatioVisible() const + { + return m_enableMassRatioClamping; + } + // AZ::Component void RagdollComponent::Init() { @@ -272,7 +292,6 @@ namespace PhysX return ragdoll->IsSimulated(); } return false; - } AZ::Aabb RagdollComponent::GetAabb() const @@ -318,20 +337,19 @@ namespace PhysX if (numNodes == 0) { - AZ_Error("PhysX Ragdoll Component", false, - "Ragdoll configuration has 0 nodes, ragdoll will not be created for entity \"%s\".", + AZ_Error( + "PhysX Ragdoll Component", false, "Ragdoll configuration has 0 nodes, ragdoll will not be created for entity \"%s\".", GetEntity()->GetName().c_str()); return; } - ragdollConfiguration.m_parentIndices.resize(numNodes); for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) { AZStd::string parentName; AZStd::string nodeName = ragdollConfiguration.m_nodes[nodeIndex].m_debugName; - AzFramework::CharacterPhysicsDataRequestBus::EventResult(parentName, GetEntityId(), - &AzFramework::CharacterPhysicsDataRequests::GetParentNodeName, nodeName); + AzFramework::CharacterPhysicsDataRequestBus::EventResult( + parentName, GetEntityId(), &AzFramework::CharacterPhysicsDataRequests::GetParentNodeName, nodeName); AZ::Outcome parentIndex = Utils::Characters::GetNodeIndex(ragdollConfiguration, parentName); ragdollConfiguration.m_parentIndices[nodeIndex] = parentIndex ? parentIndex.GetValue() : SIZE_MAX; @@ -339,8 +357,8 @@ namespace PhysX } Physics::RagdollState bindPose; - AzFramework::CharacterPhysicsDataRequestBus::EventResult(bindPose, GetEntityId(), - &AzFramework::CharacterPhysicsDataRequests::GetBindPose, ragdollConfiguration); + AzFramework::CharacterPhysicsDataRequestBus::EventResult( + bindPose, GetEntityId(), &AzFramework::CharacterPhysicsDataRequests::GetBindPose, ragdollConfiguration); AZ::Transform entityTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(entityTransform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); @@ -354,13 +372,12 @@ namespace PhysX m_ragdollHandle = sceneInterface->AddSimulatedBody(m_attachedSceneHandle, &ragdollConfiguration); } auto* ragdoll = GetPhysXRagdoll(); - if (ragdoll == nullptr || - m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle) + if (ragdoll == nullptr || m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle) { AZ_Error("PhysX Ragdoll Component", false, "Failed to create ragdoll."); return; } - + for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) { if (physx::PxRigidDynamic* pxRigidBody = ragdoll->GetPxRigidDynamic(nodeIndex)) @@ -389,17 +406,63 @@ namespace PhysX } } + // If mass ratio clamping is enabled, iterate out from the root and clamp mass values + if (m_enableMassRatioClamping) + { + const float maxMassRatio = AZStd::GetMax(1.0f + AZ::Constants::FloatEpsilon, m_maxMassRatio); + + // figure out the depth of each node in the tree, so that nodes can be visited from the root outwards + AZStd::vector nodeDepths = + Utils::Characters::ComputeHierarchyDepths(ragdollConfiguration.m_parentIndices); + + AZStd::sort( + nodeDepths.begin(), nodeDepths.end(), + [](const Utils::Characters::DepthData& d1, const Utils::Characters::DepthData& d2) + { + return d1.m_depth < d2.m_depth; + }); + + bool massesClamped = false; + for (const auto& nodeDepth : nodeDepths) + { + const size_t nodeIndex = nodeDepth.m_index; + const size_t parentIndex = ragdollConfiguration.m_parentIndices[nodeIndex]; + if (parentIndex < numNodes) + { + AzPhysics::RigidBody& nodeRigidBody = ragdoll->GetNode(nodeIndex)->GetRigidBody(); + const float originalMass = nodeRigidBody.GetMass(); + const float parentMass = ragdoll->GetNode(parentIndex)->GetRigidBody().GetMass(); + const float minMass = parentMass / maxMassRatio; + const float maxMass = parentMass; + if (originalMass < minMass || originalMass > maxMass) + { + const float clampedMass = AZStd::clamp(originalMass, minMass, maxMass); + nodeRigidBody.SetMass(clampedMass); + massesClamped = true; + if (!AZ::IsClose(originalMass, 0.0f)) + { + // scale the inertia proportionally to how the mass was modified + auto pxRigidBody = static_cast(nodeRigidBody.GetNativePointer()); + pxRigidBody->setMassSpaceInertiaTensor(clampedMass / originalMass * pxRigidBody->getMassSpaceInertiaTensor()); + } + } + } + } + + AZ_WarningOnce("PhysX Ragdoll", !massesClamped, + "Mass values for ragdoll on entity \"%s\" were modified based on max mass ratio setting to avoid instability.", + GetEntity()->GetName().c_str()); + } + AzFramework::RagdollPhysicsRequestBus::Handler::BusConnect(GetEntityId()); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId()); - AzFramework::RagdollPhysicsNotificationBus::Event(GetEntityId(), - &AzFramework::RagdollPhysicsNotifications::OnRagdollActivated); + AzFramework::RagdollPhysicsNotificationBus::Event(GetEntityId(), &AzFramework::RagdollPhysicsNotifications::OnRagdollActivated); } void RagdollComponent::DestroyRagdoll() { - if (m_ragdollHandle != AzPhysics::InvalidSimulatedBodyHandle && - m_attachedSceneHandle != AzPhysics::InvalidSceneHandle) + if (m_ragdollHandle != AzPhysics::InvalidSimulatedBodyHandle && m_attachedSceneHandle != AzPhysics::InvalidSceneHandle) { AzFramework::RagdollPhysicsRequestBus::Handler::BusDisconnect(); AzFramework::RagdollPhysicsNotificationBus::Event( @@ -421,8 +484,7 @@ namespace PhysX const Ragdoll* RagdollComponent::GetPhysXRagdollConst() const { - if (m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle || - m_attachedSceneHandle == AzPhysics::InvalidSceneHandle) + if (m_ragdollHandle == AzPhysics::InvalidSimulatedBodyHandle || m_attachedSceneHandle == AzPhysics::InvalidSceneHandle) { return nullptr; } diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.h b/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.h index 63dd1354dd..3ef59bf623 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.h +++ b/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.h @@ -103,7 +103,8 @@ namespace PhysX Ragdoll* GetPhysXRagdoll(); const Ragdoll* GetPhysXRagdollConst() const; - bool IsJointProjectionVisible(); + bool IsJointProjectionVisible() const; + bool IsMaxMassRatioVisible() const; AzPhysics::SimulatedBodyHandle m_ragdollHandle = AzPhysics::InvalidSimulatedBodyHandle; AzPhysics::SceneHandle m_attachedSceneHandle = AzPhysics::InvalidSceneHandle; @@ -119,5 +120,9 @@ namespace PhysX float m_jointProjectionLinearTolerance = 1e-3f; /// Angular joint error (in degrees) above which projection will be applied. float m_jointProjectionAngularToleranceDegrees = 1.0f; + /// Allows ragdoll node mass values to be overridden to avoid unstable mass ratios. + bool m_enableMassRatioClamping = false; + /// If mass ratio clamping is enabled, masses will be clamped to within this ratio. + float m_maxMassRatio = 2.0f; }; } // namespace PhysX diff --git a/Gems/PhysX/Code/Tests/RagdollTests.cpp b/Gems/PhysX/Code/Tests/RagdollTests.cpp index 30e9400790..ff4637fd6e 100644 --- a/Gems/PhysX/Code/Tests/RagdollTests.cpp +++ b/Gems/PhysX/Code/Tests/RagdollTests.cpp @@ -367,4 +367,19 @@ namespace PhysX float minZ = ragdoll->GetAabb().GetMin().GetZ(); EXPECT_NEAR(minZ, 0.0f, 0.05f); } + + TEST(ComputeHierarchyDepthsTest, DepthValuesCorrect) + { + AZStd::vector parentIndices = + { 3, 5, AZStd::numeric_limits::max(), 1, 2, 9, 7, 4, 0, 6, 11, 12, 5, 14, 15, 16, 5, 18, 19, 4, 21, 22, 4 }; + + const AZStd::vector nodeDepths = Utils::Characters::ComputeHierarchyDepths(parentIndices); + + std::vector expectedDepths = { 8, 6, 0, 7, 1, 5, 3, 2, 9, 4, 8, 7, 6, 9, 8, 7, 6, 4, 3, 2, 4, 3, 2 }; + + for (size_t i = 0; i < parentIndices.size(); i++) + { + EXPECT_EQ(nodeDepths[i].m_depth, expectedDepths[i]); + } + } } // namespace PhysX diff --git a/Gems/PythonAssetBuilder/Code/Source/PythonAssetBuilderModule.cpp b/Gems/PythonAssetBuilder/Code/Source/PythonAssetBuilderModule.cpp index d7143a159a..1d330c90df 100644 --- a/Gems/PythonAssetBuilder/Code/Source/PythonAssetBuilderModule.cpp +++ b/Gems/PythonAssetBuilder/Code/Source/PythonAssetBuilderModule.cpp @@ -9,12 +9,15 @@ #include #include +#include + #include namespace PythonAssetBuilder { class PythonAssetBuilderModule : public AZ::Module + , public AzToolsFramework::EmbeddedPython::PythonLoader { public: AZ_RTTI(PythonAssetBuilderModule, "{35C9457E-54C2-474C-AEBE-5A70CC1D435D}", AZ::Module); diff --git a/Gems/QtForPython/Code/CMakeLists.txt b/Gems/QtForPython/Code/CMakeLists.txt index 48ce5c02d3..da763978a6 100644 --- a/Gems/QtForPython/Code/CMakeLists.txt +++ b/Gems/QtForPython/Code/CMakeLists.txt @@ -21,7 +21,8 @@ ly_add_target( NAME QtForPython.Editor.Static STATIC NAMESPACE Gem FILES_CMAKE - qtforpython_editor_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + qtforpython_editor_files.cmake + ${CMAKE_CURRENT_SOURCE_DIR}/Source/Platform/${PAL_PLATFORM_NAME}/qtforpython_editor_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake PLATFORM_INCLUDE_FILES ${common_source_dir}/${PAL_TRAIT_COMPILER_ID}/qtforpython_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES @@ -45,6 +46,9 @@ ly_add_target( NAMESPACE Gem FILES_CMAKE qtforpython_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Source/Platform/${PAL_PLATFORM_NAME} BUILD_DEPENDENCIES PRIVATE Gem::QtForPython.Editor.Static diff --git a/Gems/QtForPython/Code/Source/Platform/Linux/InitializeEmbeddedPyside2.h b/Gems/QtForPython/Code/Source/Platform/Linux/InitializeEmbeddedPyside2.h new file mode 100644 index 0000000000..f27b54810f --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Linux/InitializeEmbeddedPyside2.h @@ -0,0 +1,60 @@ +/* + * 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 + * + */ +#pragma once + +#include +#include + +namespace QtForPython +{ + const char* s_libPythonLibraryFile = "libpython3.7m.so.1.0"; + const char* s_libPyside2LibraryFile = "libpyside2.abi3.so.5.14"; + const char* s_libShibokenLibraryFile = "libshiboken2.abi3.so.5.14"; + + class InitializeEmbeddedPyside2 + { + public: + InitializeEmbeddedPyside2() + { + m_libPythonLibraryFile = InitializeEmbeddedPyside2::LoadModule(s_libPythonLibraryFile); + m_libPyside2LibraryFile = InitializeEmbeddedPyside2::LoadModule(s_libPyside2LibraryFile); + m_libShibokenLibraryFile = InitializeEmbeddedPyside2::LoadModule(s_libShibokenLibraryFile); + } + virtual ~InitializeEmbeddedPyside2() + { + InitializeEmbeddedPyside2::UnloadModule(m_libShibokenLibraryFile); + InitializeEmbeddedPyside2::UnloadModule(m_libPyside2LibraryFile); + InitializeEmbeddedPyside2::UnloadModule(m_libPythonLibraryFile); + } + + private: + static void* LoadModule(const char* moduleToLoad) + { + void* moduleHandle = dlopen(moduleToLoad, RTLD_NOW | RTLD_GLOBAL); + if (!moduleHandle) + { + const char* loadError = dlerror(); + AZ_Error("QtForPython", false, "Unable to load python library %s for Pyside2: %s", moduleToLoad, + loadError ? loadError : "Unknown Error"); + } + return moduleHandle; + } + + static void UnloadModule(void* moduleHandle) + { + if (moduleHandle) + { + dlclose(moduleHandle); + } + } + + void* m_libPythonLibraryFile; + void* m_libPyside2LibraryFile; + void* m_libShibokenLibraryFile; + }; +} // namespace QtForPython diff --git a/Gems/QtForPython/Code/Source/Platform/Linux/PAL_linux.cmake b/Gems/QtForPython/Code/Source/Platform/Linux/PAL_linux.cmake index 236043e893..789d2afae2 100644 --- a/Gems/QtForPython/Code/Source/Platform/Linux/PAL_linux.cmake +++ b/Gems/QtForPython/Code/Source/Platform/Linux/PAL_linux.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED FALSE) +set(PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED TRUE) diff --git a/Gems/QtForPython/Code/Source/Platform/Linux/qtforpython_editor_linux_files.cmake b/Gems/QtForPython/Code/Source/Platform/Linux/qtforpython_editor_linux_files.cmake new file mode 100644 index 0000000000..54c588a247 --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Linux/qtforpython_editor_linux_files.cmake @@ -0,0 +1,11 @@ +# +# 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 +# +# + +set(FILES + InitializeEmbeddedPyside2.h +) diff --git a/Gems/QtForPython/Code/Source/Platform/Mac/InitializeEmbeddedPyside2.h b/Gems/QtForPython/Code/Source/Platform/Mac/InitializeEmbeddedPyside2.h new file mode 100644 index 0000000000..819764620b --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Mac/InitializeEmbeddedPyside2.h @@ -0,0 +1,18 @@ +/* + * 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 + * + */ +#pragma once + +namespace QtForPython +{ + class InitializeEmbeddedPyside2 + { + public: + InitializeEmbeddedPyside2() = default; + virtual ~InitializeEmbeddedPyside2() = default; + }; +} // namespace QtForPython diff --git a/Gems/QtForPython/Code/Source/Platform/Mac/qtforpython_editor_macos_files.cmake b/Gems/QtForPython/Code/Source/Platform/Mac/qtforpython_editor_macos_files.cmake new file mode 100644 index 0000000000..54c588a247 --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Mac/qtforpython_editor_macos_files.cmake @@ -0,0 +1,11 @@ +# +# 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 +# +# + +set(FILES + InitializeEmbeddedPyside2.h +) diff --git a/Gems/QtForPython/Code/Source/Platform/Windows/InitializeEmbeddedPyside2.h b/Gems/QtForPython/Code/Source/Platform/Windows/InitializeEmbeddedPyside2.h new file mode 100644 index 0000000000..819764620b --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Windows/InitializeEmbeddedPyside2.h @@ -0,0 +1,18 @@ +/* + * 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 + * + */ +#pragma once + +namespace QtForPython +{ + class InitializeEmbeddedPyside2 + { + public: + InitializeEmbeddedPyside2() = default; + virtual ~InitializeEmbeddedPyside2() = default; + }; +} // namespace QtForPython diff --git a/Gems/QtForPython/Code/Source/Platform/Windows/qtforpython_editor_windows_files.cmake b/Gems/QtForPython/Code/Source/Platform/Windows/qtforpython_editor_windows_files.cmake new file mode 100644 index 0000000000..54c588a247 --- /dev/null +++ b/Gems/QtForPython/Code/Source/Platform/Windows/qtforpython_editor_windows_files.cmake @@ -0,0 +1,11 @@ +# +# 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 +# +# + +set(FILES + InitializeEmbeddedPyside2.h +) diff --git a/Gems/QtForPython/Code/Source/QtForPythonModule.cpp b/Gems/QtForPython/Code/Source/QtForPythonModule.cpp index 1ed79310b4..ad39d0b82c 100644 --- a/Gems/QtForPython/Code/Source/QtForPythonModule.cpp +++ b/Gems/QtForPython/Code/Source/QtForPythonModule.cpp @@ -8,13 +8,17 @@ #include #include +#include #include +#include "InitializeEmbeddedPyside2.h" + namespace QtForPython { class QtForPythonModule : public AZ::Module + , private InitializeEmbeddedPyside2 { public: AZ_RTTI(QtForPythonModule, "{81545CD5-79FA-47CE-96F2-1A9C5D59B4B9}", AZ::Module); @@ -22,11 +26,13 @@ namespace QtForPython QtForPythonModule() : AZ::Module() + , InitializeEmbeddedPyside2() { m_descriptors.insert(m_descriptors.end(), { QtForPythonSystemComponent::CreateDescriptor(), }); } + ~QtForPythonModule() override = default; /** * Add required SystemComponents to the SystemEntity. diff --git a/Gems/QtForPython/Code/Source/QtForPythonSystemComponent.cpp b/Gems/QtForPython/Code/Source/QtForPythonSystemComponent.cpp index 83a158dd18..240beff78e 100644 --- a/Gems/QtForPython/Code/Source/QtForPythonSystemComponent.cpp +++ b/Gems/QtForPython/Code/Source/QtForPythonSystemComponent.cpp @@ -203,10 +203,6 @@ namespace QtForPython { QtBootstrapParameters params; -#if !defined(Q_OS_WIN) -#error Unsupported OS platform for this QtForPython gem -#endif - params.m_mainWindowId = 0; using namespace AzToolsFramework; QWidget* activeWindow = nullptr; diff --git a/Gems/QtForPython/Code/qtforpython_editor_macos_files.cmake b/Gems/QtForPython/Code/Source/qtforpython_editor_files.cmake similarity index 100% rename from Gems/QtForPython/Code/qtforpython_editor_macos_files.cmake rename to Gems/QtForPython/Code/Source/qtforpython_editor_files.cmake diff --git a/Gems/QtForPython/Code/qtforpython_editor_windows_files.cmake b/Gems/QtForPython/Code/qtforpython_editor_files.cmake similarity index 100% rename from Gems/QtForPython/Code/qtforpython_editor_windows_files.cmake rename to Gems/QtForPython/Code/qtforpython_editor_files.cmake diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 9cfa568b9a..024cccf337 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -80,26 +80,38 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) // ------- Macro Color / Normal ------- float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb; - [unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i) + + // There's a bug that shows up with an NVidia GTX 1660 Super card happening on driver versions as recent as 496.49 (10/26/21) in which + // the IN.m_uv values will intermittently "flicker" to 0.0 after entering and exiting game mode. + // (See https://github.com/o3de/o3de/issues/5014) + // This bug has only shown up on PCs when using the DX12 RHI. It doesn't show up with Vulkan or when capturing frames with PIX or + // RenderDoc. Our best guess is that it is a driver bug. The workaround is to use the IN.m_uv values in a calculation prior to the + // point that we actually use them for macroUv below. The "if(any(!isnan(IN.m_uv)))" seems to be sufficient for the workaround. The + // if statement will always be true, but just the act of reading these values in the if statement makes the values stable. Removing + // the if statement causes the flickering to occur using the steps documented in the bug. + if (any(!isnan(IN.m_uv))) { - float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin; - float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax; - float2 macroUv = lerp(macroUvMin, macroUvMax, IN.m_uv); - if (macroUv.x >= 0.0 && macroUv.x <= 1.0 && macroUv.y >= 0.0 && macroUv.y <= 1.0) + [unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i) { - if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 1) > 0) + float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin; + float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax; + float2 macroUv = lerp(macroUvMin, macroUvMax, IN.m_uv); + if (macroUv.x >= 0.0 && macroUv.x <= 1.0 && macroUv.y >= 0.0 && macroUv.y <= 1.0) { - macroColor = GetBaseColorInput(ObjectSrg::m_macroColorMap[i], TerrainMaterialSrg::m_sampler, macroUv, macroColor, true); + if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 1) > 0) + { + macroColor = GetBaseColorInput(ObjectSrg::m_macroColorMap[i], TerrainMaterialSrg::m_sampler, macroUv, macroColor, true); + } + if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 2) > 0) + { + bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX; + bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY; + bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor; + macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler, + macroUv, flipX, flipY, CreateIdentity3x3(), true, factor); + } + break; } - if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 2) > 0) - { - bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX; - bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY; - bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor; - macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler, - macroUv, flipX, flipY, CreateIdentity3x3(), true, factor); - } - break; } } diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp index 0027af011a..fdd971440e 100644 --- a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp @@ -10,6 +10,7 @@ #include <${Name}ModuleInterface.h> #include <${Name}EditorSystemComponent.h> +#include void Init${SanitizedCppName}Resources() { @@ -21,6 +22,7 @@ namespace ${SanitizedCppName} { class ${SanitizedCppName}EditorModule : public ${SanitizedCppName}ModuleInterface + , public AzToolsFramework::EmbeddedPython::PythonLoader { public: AZ_RTTI(${SanitizedCppName}EditorModule, "${ModuleClassId}", ${SanitizedCppName}ModuleInterface); diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index af7afff5dc..903364d8f5 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -45,3 +45,4 @@ ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-linux TARGETS astc-encoder PACKAGE_HASH 2ba97a06474d609945f0ab4419af1f6bbffdd294ca6b869f5fcebec75c573c0f) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-linux TARGETS ISPCTexComp PACKAGE_HASH 065fd12abe4247dde247330313763cf816c3375c221da030bdec35024947f259) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-linux TARGETS lz4 PACKAGE_HASH 5de3dbd3e2a3537c6555d759b3c5bb98e5456cf85c74ff6d046f809b7087290d) +ly_associate_package(PACKAGE_NAME pyside2-5.15.2-rev1-linux TARGETS pyside2 PACKAGE_HASH ec04291f3940e76ac817bc0c825f7a47f18d8760f0bdb8da4530732f2a69405e) diff --git a/cmake/SettingsRegistry.cmake b/cmake/SettingsRegistry.cmake index c677aba7cf..e8c14ac8a0 100644 --- a/cmake/SettingsRegistry.cmake +++ b/cmake/SettingsRegistry.cmake @@ -33,6 +33,34 @@ set(gems_json_template [[ [=[ }]=] ) +#!ly_detect_cycle_through_visitation: Detects if there is a cycle based on a list of visited +# items. If the passed item is in the list, then there is a cycle. +# \arg:item - item being checked for the cycle +# \arg:visited_items - list of visited items +# \arg:visited_items_var - list of visited items variable, "item" will be added to the list +# \arg:cycle(variable) - empty string if there is no cycle (an empty string in cmake evaluates +# to false). If there is a cycle a cycle dependency string detailing the sequence of items +# that produce a cycle, e.g. A --> B --> C --> A +# +function(ly_detect_cycle_through_visitation item visited_items visited_items_var cycle) + if(item IN_LIST visited_items) + unset(dependency_cycle_loop) + foreach(visited_item IN LISTS visited_items) + string(APPEND dependency_cycle_loop ${visited_item}) + if(visited_item STREQUAL item) + string(APPEND dependency_cycle_loop " (cycle starts)") + endif() + string(APPEND dependency_cycle_loop " --> ") + endforeach() + string(APPEND dependency_cycle_loop "${item} (cycle ends)") + set(${cycle} "${dependency_cycle_loop}" PARENT_SCOPE) + else() + set(cycle "" PARENT_SCOPE) # no cycles + endif() + list(APPEND visited_items ${item}) + set(${visited_items_var} "${visited_items}" PARENT_SCOPE) +endfunction() + #!ly_get_gem_load_dependencies: Retrieves the list of "load" dependencies for a target # Visits through only MANUALLY_ADDED_DEPENDENCIES of targets with a GEM_MODULE property # to determine which gems a target needs to load @@ -44,6 +72,13 @@ function(ly_get_gem_load_dependencies ly_GEM_LOAD_DEPENDENCIES ly_TARGET) if(NOT TARGET ${ly_TARGET}) return() # Nothing to do endif() + # Internally we use a third parameter to pass the list of targets that we have traversed. This is + # used to detect runtime cycles + if(ARGC EQUAL 3) + set(ly_CYCLE_DETECTION_TARGETS ${ARGV2}) + else() + set(ly_CYCLE_DETECTION_TARGETS "") + endif() # Optimize the search by caching gem load dependencies get_property(are_dependencies_cached GLOBAL PROPERTY LY_GEM_LOAD_DEPENDENCIES_${ly_TARGET} SET) @@ -54,6 +89,13 @@ function(ly_get_gem_load_dependencies ly_GEM_LOAD_DEPENDENCIES ly_TARGET) return() endif() + # detect cycles + unset(cycle_detected) + ly_detect_cycle_through_visitation(${ly_TARGET} "${ly_CYCLE_DETECTION_TARGETS}" ly_CYCLE_DETECTION_TARGETS cycle_detected) + if(cycle_detected) + message(FATAL_ERROR "Runtime dependency detected: ${cycle_detected}") + endif() + unset(all_gem_load_dependencies) # For load dependencies, we want to copy over the dependency and traverse them @@ -69,7 +111,7 @@ function(ly_get_gem_load_dependencies ly_GEM_LOAD_DEPENDENCIES ly_TARGET) # and recurse into its manually added dependencies if (is_gem_target) unset(dependencies) - ly_get_gem_load_dependencies(dependencies ${dealias_load_dependency}) + ly_get_gem_load_dependencies(dependencies ${dealias_load_dependency} "${ly_CYCLE_DETECTION_TARGETS}") list(APPEND all_gem_load_dependencies ${dependencies}) list(APPEND all_gem_load_dependencies ${dealias_load_dependency}) endif() diff --git a/scripts/o3de/o3de/disable_gem.py b/scripts/o3de/o3de/disable_gem.py index 01324d8500..158507fca1 100644 --- a/scripts/o3de/o3de/disable_gem.py +++ b/scripts/o3de/o3de/disable_gem.py @@ -69,7 +69,7 @@ def disable_gem_in_project(gem_name: str = None, return 1 gem_path = pathlib.Path(gem_path).resolve() # make sure this gem already exists if we're adding. We can always remove a gem. - if not gem_path.is_dir(): + if not gem_path.exists(): logger.error(f'Gem Path {gem_path} does not exist.') return 1 diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index 9b7c8cc782..dfec6b1aaa 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -48,26 +48,25 @@ def validate_downloaded_zip_sha256(download_uri_json_data: dict, download_zip_pa ' We cannot verify this is the actually the advertised object!!!') return 1 else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the f{manifest_json_name}.') - return 0 + with download_zip_path.open('rb') as f: + sha256B = hashlib.sha256(f.read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the f{manifest_json_name}.') + return 0 unzipped_manifest_json_data = unzip_manifest_json_data(download_zip_path, manifest_json_name) - # remove the sha256 if present in the advertised downloadable manifest json - # then compare it to the json in the zip, they should now be identical - try: - del download_uri_json_data['sha256'] - except KeyError as e: - pass + # do not include the data we know will not match/exist + for key in ['sha256','repo_name']: + if key in download_uri_json_data: + del download_uri_json_data[key] + if key in unzipped_manifest_json_data: + del unzipped_manifest_json_data[key] - sha256A = hashlib.sha256(json.dumps(download_uri_json_data, indent=4).encode('utf8')).hexdigest() - sha256B = hashlib.sha256(json.dumps(unzipped_manifest_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error('SECURITY VIOLATION: Downloaded manifest json does not match' - ' the advertised manifest json.') + if download_uri_json_data != unzipped_manifest_json_data: + logger.error(f'SECURITY VIOLATION: Downloaded {manifest_json_name} contents do not match' + ' the advertised manifest json contents.') return 0 return 1 @@ -102,7 +101,7 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: logger.error(f'Downloadable o3de object {object_name} not found.') return 1 - origin_uri = downloadable_object_data['originuri'] + origin_uri = downloadable_object_data['origin_uri'] parsed_uri = urllib.parse.urlparse(origin_uri) download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, force_overwrite, download_progress_callback) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 3ebb79fb27..5e64d856eb 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -9,7 +9,6 @@ import json import logging import pathlib -import shutil import urllib.parse import urllib.request import hashlib @@ -219,10 +218,10 @@ def search_repo(manifest_json_data: dict, json_key = 'gem_name' search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == gem_name else None elif isinstance(template_name, str) or isinstance(template_name, pathlib.PurePath): - o3de_object_uris = manifest_json_data['template'] + o3de_object_uris = manifest_json_data['templates'] manifest_json = 'template.json' json_key = 'template_name' - search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == template_name_name else None + search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == template_name else None elif isinstance(restricted_name, str) or isinstance(restricted_name, pathlib.PurePath): o3de_object_uris = manifest_json_data['restricted'] manifest_json = 'restricted.json'