Merge branch 'stabilization/2110' into Atom/santorac/MaterialEditorHandlesMissingTextures
This commit is contained in:
+36
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:954d7d0df47c840a24e313893800eb3126d0c0d47c3380926776b51833778db7
|
||||
oid sha256:aee1fd4d5264e5ef1676b507409ce70af6358cf1ff368d9aeb17f7b2597dfbca
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e81c19128f42ba362a2d5f3ccf159dfbc942d67ceeb1ac8c21f295a6fd9d2ce5
|
||||
oid sha256:d4787cdafbcc2fe71c1cb3f1da53a249db839a9df539a9e88be43ccd6d8e4d6a
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e20801213e065b6ea8c95ede81c23faa9b6dc70a2002dc5bced293e1bed989f
|
||||
oid sha256:5fac5bf41c9b16b6fbd762868e5cf514376af92d6ef7ebb9e819f024f1a3e1a7
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e250f812e594e5152bf2d6f23caa8b53b78276bfdf344d7a8d355dd96cb995c0
|
||||
oid sha256:7a23969670499524725535e8be7428b55b6f3e887cc24e2e903f7ea821a6d1a5
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:95be359041f8291c74b335297a4dfe9902a180510f24a181b15e1a5ba4d3b024
|
||||
oid sha256:2f1f4d8865c56ed7f96f339c39e5feb4e0dbc6c6a8b4a7843b4166381b06b00d
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:07e09d3eb5bf0cee3d9b3752aaad40f3ead1dcc5ddd837a6226fadde55d57274
|
||||
oid sha256:5d4ee5641e19eef08dd6b93d2f4054a1aae2165325416ed2cbf0b8243f2c0b06
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:118e43e4b915e262726183467cc4b82f244565213fea5b6bfe02be07f0851ab1
|
||||
oid sha256:55c8f0d1790bb12660b7557630efca297b2a1b59e6c93167a2563da79e0a8255
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc2ce3256a6552975962c9e113c52c1a22bf3817d417151f6f60640dd568e0fa
|
||||
oid sha256:082ff368b621e12b083d96562a0889b11a1d683767a74296cbe6d8732830e9e8
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:287d98890b35427688999760f9d066bcbff1a3bc9001534241dc212b32edabd8
|
||||
oid sha256:78cc62d89782899747875b41abee57c2efdfacf4c8af6511c88f82d76eaae4ca
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:66e91c92c868167c850078cd91714db47e10a96e23cc30191994486bd79c353f
|
||||
oid sha256:3d6719326f4dacae278d1723090ce1182193b793f250963af8be4b2c298e8841
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d950d173f5101820c5e18205401ca08ce5feeff2302ac2920b292750d86a8fa4
|
||||
oid sha256:9e492bb394fb18fb117f8a5b61cd2789922f9d6e88fc83189b5b6d59ffb1c3ef
|
||||
size 6220817
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72eddb7126eae0c839b933886e0fb69d78229f72d49ef13199de28df2b7879db
|
||||
oid sha256:caca85f7728f660daae36afc81d681ba2de2377c516eb3c637599de5c94012aa
|
||||
size 6220817
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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)
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
|
||||
@@ -8,42 +8,54 @@ import azlmbr.bus
|
||||
import azlmbr.asset
|
||||
import azlmbr.editor
|
||||
import azlmbr.math
|
||||
import azlmbr.legacy.general
|
||||
|
||||
def raise_and_stop(msg):
|
||||
print (msg)
|
||||
print('Starting mock asset tests')
|
||||
handler = azlmbr.editor.EditorEventBusHandler()
|
||||
|
||||
def on_notify_editor_initialized(args):
|
||||
# These tests are meant to check that the test_asset.mock source asset turned into
|
||||
# a test_asset.mock_asset product asset via the Python asset builder system
|
||||
mockAssetType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
|
||||
mockAssetPath = 'gem/pythontests/pythonassetbuilder/test_asset.mock_asset'
|
||||
assetId = azlmbr.asset.AssetCatalogRequestBus(azlmbr.bus.Broadcast, 'GetAssetIdByPath', mockAssetPath, mockAssetType, False)
|
||||
if (assetId.is_valid() is False):
|
||||
print(f'Mock AssetId is not valid! Got {assetId.to_string()} instead')
|
||||
else:
|
||||
print(f'Mock AssetId is valid!')
|
||||
|
||||
assetIdString = assetId.to_string()
|
||||
if (assetIdString.endswith(':528cca58') is False):
|
||||
print(f'Mock AssetId {assetIdString} has unexpected sub-id for {mockAssetPath}!')
|
||||
else:
|
||||
print(f'Mock AssetId has expected sub-id for {mockAssetPath}!')
|
||||
|
||||
print ('Mock asset exists')
|
||||
|
||||
# These tests detect if the geom_group.fbx file turns into a number of azmodel product assets
|
||||
def test_azmodel_product(generatedModelAssetPath):
|
||||
azModelAssetType = azlmbr.math.Uuid_CreateString('{2C7477B6-69C5-45BE-8163-BCD6A275B6D8}', 0)
|
||||
assetId = azlmbr.asset.AssetCatalogRequestBus(azlmbr.bus.Broadcast, 'GetAssetIdByPath', generatedModelAssetPath, azModelAssetType, False)
|
||||
assetIdString = assetId.to_string()
|
||||
if (assetId.is_valid()):
|
||||
print(f'AssetId found for asset ({generatedModelAssetPath}) found')
|
||||
else:
|
||||
print(f'Asset at path {generatedModelAssetPath} has unexpected asset ID ({assetIdString})!')
|
||||
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel')
|
||||
|
||||
# clear up notification handler
|
||||
global handler
|
||||
handler.disconnect()
|
||||
handler = None
|
||||
|
||||
print('Finished mock asset tests')
|
||||
azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'ExitNoPrompt')
|
||||
|
||||
# These tests are meant to check that the test_asset.mock source asset turned into
|
||||
# a test_asset.mock_asset product asset via the Python asset builder system
|
||||
mockAssetType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
|
||||
mockAssetPath = 'gem/pythontests/pythonassetbuilder/test_asset.mock_asset'
|
||||
assetId = azlmbr.asset.AssetCatalogRequestBus(azlmbr.bus.Broadcast, 'GetAssetIdByPath', mockAssetPath, mockAssetType, False)
|
||||
if (assetId.is_valid() is False):
|
||||
raise_and_stop(f'Mock AssetId is not valid! Got {assetId.to_string()} instead')
|
||||
|
||||
assetIdString = assetId.to_string()
|
||||
if (assetIdString.endswith(':528cca58') is False):
|
||||
raise_and_stop(f'Mock AssetId {assetIdString} has unexpected sub-id for {mockAssetPath}!')
|
||||
|
||||
print ('Mock asset exists')
|
||||
|
||||
# These tests detect if the geom_group.fbx file turns into a number of azmodel product assets
|
||||
def test_azmodel_product(generatedModelAssetPath):
|
||||
azModelAssetType = azlmbr.math.Uuid_CreateString('{2C7477B6-69C5-45BE-8163-BCD6A275B6D8}', 0)
|
||||
assetId = azlmbr.asset.AssetCatalogRequestBus(azlmbr.bus.Broadcast, 'GetAssetIdByPath', generatedModelAssetPath, azModelAssetType, False)
|
||||
assetIdString = assetId.to_string()
|
||||
if (assetId.is_valid()):
|
||||
print(f'AssetId found for asset ({generatedModelAssetPath}) found')
|
||||
else:
|
||||
raise_and_stop(f'Asset at path {generatedModelAssetPath} has unexpected asset ID ({assetIdString})!')
|
||||
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel')
|
||||
test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel')
|
||||
|
||||
azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'ExitNoPrompt')
|
||||
handler.connect()
|
||||
handler.add_callback('NotifyEditorInitialized', on_notify_editor_initialized)
|
||||
|
||||
+1
-2
@@ -12,13 +12,12 @@ class Tests():
|
||||
add_terrain_collider = ("Terrain Physics Heightfield Collider component added", "Failed to add a Terrain Physics Heightfield Collider component")
|
||||
box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions")
|
||||
configuration_changed = ("Terrain size changed successfully", "Failed terrain size change")
|
||||
no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
|
||||
#fmt: on
|
||||
|
||||
def TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges():
|
||||
"""
|
||||
Summary:
|
||||
Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree.
|
||||
Test aspects of the Terrain Physics Heightfield Collider through the BehaviorContext and the Property Tree.
|
||||
|
||||
Test Steps:
|
||||
Expected Behavior:
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
#fmt: off
|
||||
class Tests():
|
||||
create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity")
|
||||
create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity")
|
||||
create_test_ball = ("Ball created successfully", "Failed to create Ball")
|
||||
box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions")
|
||||
shape_changed = ("Shape changed successfully", "Failed Shape change")
|
||||
entity_added = ("Entity added successfully", "Failed Entity add")
|
||||
frequency_changed = ("Frequency changed successfully", "Failed Frequency change")
|
||||
shape_set = ("Shape set to Sphere successfully", "Failed to set Sphere shape")
|
||||
test_collision = ("Ball collided with terrain", "Ball failed to collide with terrain")
|
||||
no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
|
||||
#fmt: on
|
||||
|
||||
def Terrain_SupportsPhysics():
|
||||
"""
|
||||
Summary:
|
||||
Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree.
|
||||
|
||||
Test Steps:
|
||||
Expected Behavior:
|
||||
The Editor is stable there are no warnings or errors.
|
||||
|
||||
Test Steps:
|
||||
1) Load the base level
|
||||
2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
|
||||
2a) Create a ball at 600.0, 600.0, 46.0 - This position is bot too high over the heighfield so will collide in a reasonable time
|
||||
3) Start the Tracer to catch any errors and warnings
|
||||
4) Change the Axis Aligned Box Shape dimensions
|
||||
5) Set the Vegetation Shape reference to TestEntity1
|
||||
6) Set the FastNoise gradient frequency to 0.01
|
||||
7) Set the Gradient List to TestEntity2
|
||||
8) Set the PhysX Collider to Sphere mode
|
||||
9) Disable and Enable the Terrain Gradient List so that it is recognised
|
||||
10) Enter game mode and test if the ball hits the heightfield within 3 seconds
|
||||
11) Verify there are no errors and warnings in the logs
|
||||
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import TestHelper as helper, Report
|
||||
from editor_python_test_tools.utils import Report, Tracer
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
import azlmbr.math as azmath
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.editor as editor
|
||||
import math
|
||||
|
||||
SET_BOX_X_SIZE = 1024.0
|
||||
SET_BOX_Y_SIZE = 1024.0
|
||||
SET_BOX_Z_SIZE = 100.0
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Load the level
|
||||
helper.open_level("", "Base")
|
||||
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
|
||||
|
||||
#1a) Load the level components
|
||||
hydra.add_level_component("Terrain World")
|
||||
hydra.add_level_component("Terrain World Renderer")
|
||||
|
||||
# 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
|
||||
entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"]
|
||||
entity2_components_to_add = ["Vegetation Reference Shape", "Gradient Transform Modifier", "FastNoise Gradient"]
|
||||
ball_components_to_add = ["Sphere Shape", "PhysX Collider", "PhysX Rigid Body"]
|
||||
terrain_spawner_entity = hydra.Entity("TestEntity1")
|
||||
terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)
|
||||
Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid())
|
||||
height_provider_entity = hydra.Entity("TestEntity2")
|
||||
height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id)
|
||||
Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid())
|
||||
# 2a) Create a ball at 600.0, 600.0, 46.0 - This position is bot too high over the heighfield so will collide in a reasonable time
|
||||
ball = hydra.Entity("Ball")
|
||||
ball.create_entity(azmath.Vector3(600.0, 600.0, 46.0), ball_components_to_add)
|
||||
Report.result(Tests.create_test_ball, ball.id.IsValid())
|
||||
# Give everything a chance to finish initializing.
|
||||
general.idle_wait_frames(1)
|
||||
|
||||
# 3) Start the Tracer to catch any errors and warnings
|
||||
with Tracer() as section_tracer:
|
||||
# 4) Change the Axis Aligned Box Shape dimensions
|
||||
box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE)
|
||||
terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
|
||||
box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions")
|
||||
Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions)
|
||||
|
||||
# 5) Set the Vegetaion Shape reference to TestEntity1
|
||||
height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id)
|
||||
entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id")
|
||||
Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id)
|
||||
|
||||
# 6) Set the FastNoise Gradient frequency to 0.01
|
||||
Frequency = 0.01
|
||||
height_provider_entity.get_set_test(2, "Configuration|Frequency", Frequency)
|
||||
FrequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency")
|
||||
Report.result(Tests.frequency_changed, math.isclose(Frequency, FrequencyVal, abs_tol = 0.00001))
|
||||
|
||||
# 7) Set the Gradient List to TestEntity2
|
||||
pte = hydra.get_property_tree(terrain_spawner_entity.components[2])
|
||||
pte.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id)
|
||||
checkID = pte.get_container_item("Configuration|Gradient Entities", 0)
|
||||
Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id)
|
||||
|
||||
# 8) Set the PhysX Collider to Sphere mode
|
||||
shape = 0
|
||||
hydra.get_set_test(ball, 1, "Shape Configuration|Shape", shape)
|
||||
setShape = hydra.get_component_property_value(ball.components[1], "Shape Configuration|Shape")
|
||||
Report.result(Tests.shape_set, shape == setShape)
|
||||
|
||||
# 9) Disable and Enable the Terrain Gradient List so that it is recognised
|
||||
editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]])
|
||||
|
||||
general.enter_game_mode()
|
||||
|
||||
general.idle_wait_frames(1)
|
||||
|
||||
# 10) Enter game mode and test if the ball hits the heightfield within 3 seconds
|
||||
TIMEOUT = 3.0
|
||||
|
||||
class Collider:
|
||||
id = general.find_game_entity("Ball")
|
||||
touched_ground = False
|
||||
|
||||
terrain_id = general.find_game_entity("TestEntity1")
|
||||
|
||||
def on_collision_begin(args):
|
||||
other_id = args[0]
|
||||
if other_id.Equal(terrain_id):
|
||||
Report.info("Touched ground")
|
||||
Collider.touched_ground = True
|
||||
|
||||
handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
handler.connect(Collider.id)
|
||||
handler.add_callback("OnCollisionBegin", on_collision_begin)
|
||||
|
||||
helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
|
||||
Report.result(Tests.test_collision, Collider.touched_ground)
|
||||
|
||||
general.exit_game_mode()
|
||||
|
||||
# 11) Verify there are no errors and warnings in the logs
|
||||
helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0)
|
||||
for error_info in section_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
for assert_info in section_tracer.asserts:
|
||||
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Terrain_SupportsPhysics)
|
||||
|
||||
@@ -19,7 +19,9 @@ from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
#global_extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]
|
||||
|
||||
class test_AxisAlignedBoxShape_ConfigurationWorks(EditorSingleTest):
|
||||
from .EditorScripts import TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges as test_module
|
||||
|
||||
class test_Terrain_SupportsPhysics(EditorSingleTest):
|
||||
from .EditorScripts import Terrain_SupportsPhysics as test_module
|
||||
|
||||
@@ -17,7 +17,16 @@
|
||||
},
|
||||
"Component_[14126657869720434043]": {
|
||||
"$type": "EditorEntitySortComponent",
|
||||
"Id": 14126657869720434043
|
||||
"Id": 14126657869720434043,
|
||||
"ChildEntityOrderEntryArray": [
|
||||
{
|
||||
"EntityId": ""
|
||||
},
|
||||
{
|
||||
"EntityId": "",
|
||||
"SortIndex": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Component_[15230859088967841193]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
|
||||
@@ -547,13 +547,6 @@ QPoint Q2DViewport::WorldToView(const Vec3& wp) const
|
||||
QPoint p = QPoint(static_cast<int>(sp.x), static_cast<int>(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<int>(sp.x), static_cast<int>(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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<AzFramework::RotateCameraInput>(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<AzFramework::TranslateCameraInput>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CComponentEntityObject*>(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<float>(QHighDpiScaling::factor(windowHandle()->screen()));
|
||||
out.y /= static_cast<float>(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<int>((x / 100) * width);
|
||||
p.ry() = static_cast<int>((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<float>(screenPosition.m_x);
|
||||
*sy = static_cast<float>(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<float>(vp.x()), static_cast<float>(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<float>(vp.x()), static_cast<float>(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<float>(vp.x()), static_cast<float>(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();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -38,7 +38,9 @@ namespace UnitTest
|
||||
AzFramework::ViewportControllerListPtr m_controllerList;
|
||||
AZStd::unique_ptr<AZ::Entity> 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
|
||||
|
||||
|
||||
@@ -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<SandboxEditor::EditorModularViewportCameraComposer> m_editorModularViewportCameraComposer;
|
||||
};
|
||||
|
||||
const AzFramework::ViewportId ModularViewportCameraControllerFixture::TestViewportId = AzFramework::ViewportId(0);
|
||||
|
||||
TEST_F(ModularViewportCameraControllerFixture, MouseMovementDoesNotAccumulateExcessiveDriftInModularViewportCameraWithVaryingDeltaTime)
|
||||
{
|
||||
SandboxEditor::SetCameraCaptureCursorForLook(false);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
|
||||
#include "ViewportManipulatorController.h"
|
||||
|
||||
#include <AzCore/Script/ScriptTimePoint.h>
|
||||
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
|
||||
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Viewport/ScreenGeometry.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
|
||||
#include <AzFramework/Viewport/ScreenGeometry.h>
|
||||
#include <AzCore/Script/ScriptTimePoint.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
@@ -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<int>(position->m_normalizedPosition.GetX() * windowSize.m_width),
|
||||
aznumeric_cast<int>(position->m_normalizedPosition.GetY() * windowSize.m_height));
|
||||
|
||||
m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint;
|
||||
AZStd::optional<ProjectedViewportRay> 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
|
||||
|
||||
@@ -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<CursorEvent>(&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);
|
||||
}
|
||||
|
||||
@@ -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<typename MotionTag>
|
||||
struct MotionEvent
|
||||
@@ -334,6 +347,7 @@ namespace AzFramework
|
||||
AZStd::function<float()> m_rotateSpeedFn;
|
||||
AZStd::function<bool()> m_invertPitchFn;
|
||||
AZStd::function<bool()> m_invertYawFn;
|
||||
AZStd::function<bool()> m_constrainPitch;
|
||||
|
||||
private:
|
||||
InputChannelId m_rotateChannelId; //!< Input channel to begin the rotate camera input.
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
|
||||
#include "CameraState.h"
|
||||
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Math/Matrix3x4.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
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<CameraState>()->
|
||||
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<CameraState>()
|
||||
->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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace AzFramework
|
||||
serializeContext->Class<ScreenVector>()->
|
||||
Field("X", &ScreenVector::m_x)->
|
||||
Field("Y", &ScreenVector::m_y);
|
||||
|
||||
serializeContext->Class<ScreenSize>()->
|
||||
Field("Width", &ScreenSize::m_width)->
|
||||
Field("Height", &ScreenSize::m_height);
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -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<int>(AZStd::lround(aznumeric_cast<float>(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<int>(AZStd::lround(aznumeric_cast<float>(lhs.m_width) * rhs));
|
||||
lhs.m_height = aznumeric_cast<int>(AZStd::lround(aznumeric_cast<float>(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<float>(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<float>(screenVector.m_x), aznumeric_cast<float>(screenVector.m_y));
|
||||
}
|
||||
|
||||
//! Return an AZ::Vector2 from a ScreenSize.
|
||||
inline AZ::Vector2 Vector2FromScreenSize(const ScreenSize& screenSize)
|
||||
{
|
||||
return AZ::Vector2(aznumeric_cast<float>(screenSize.m_width), aznumeric_cast<float>(screenSize.m_height));
|
||||
}
|
||||
|
||||
//! Return a ScreenPoint from an AZ::Vector2.
|
||||
inline ScreenPoint ScreenPointFromVector2(const AZ::Vector2& vector2)
|
||||
{
|
||||
return ScreenPoint(aznumeric_cast<int>(AZStd::lround(vector2.GetX())), aznumeric_cast<int>(AZStd::lround(vector2.GetY())));
|
||||
}
|
||||
|
||||
//! Return a ScreenVector from an AZ::Vector2.
|
||||
inline ScreenVector ScreenVectorFromVector2(const AZ::Vector2& vector2)
|
||||
{
|
||||
return ScreenVector(aznumeric_cast<int>(AZStd::lround(vector2.GetX())), aznumeric_cast<int>(AZStd::lround(vector2.GetY())));
|
||||
}
|
||||
|
||||
//! Return a ScreenSize from an AZ::Vector2.
|
||||
inline ScreenSize ScreenSizeFromVector2(const AZ::Vector2& vector2)
|
||||
{
|
||||
return ScreenSize(aznumeric_cast<int>(AZStd::lround(vector2.GetX())), aznumeric_cast<int>(AZStd::lround(vector2.GetY())));
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Math/Frustum.h>
|
||||
#include <AzCore/Math/Matrix4x4.h>
|
||||
#include <AzCore/Math/MatrixUtils.h>
|
||||
#include <AzCore/Math/Vector4.h>
|
||||
#include <AzCore/Math/VectorConversions.h>
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
@@ -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)
|
||||
|
||||
@@ -104,9 +104,10 @@ namespace UnitTest
|
||||
AZStd::shared_ptr<AzFramework::OrbitCameraInput> 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
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AZTestShared/Math/MathTestHelpers.h>
|
||||
#include <AzCore/Math/SimdMath.h>
|
||||
#include <AzCore/Math/MatrixUtils.h>
|
||||
#include <AzCore/Math/Matrix4x4.h>
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/CursorState.h>
|
||||
#include <Tests/Utils/Printers.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
using AzFramework::CursorState;
|
||||
using AzFramework::ScreenVector;
|
||||
using AzFramework::ScreenPoint;
|
||||
using AzFramework::ScreenVector;
|
||||
|
||||
class CursorStateFixture : public ::testing::Test
|
||||
{
|
||||
|
||||
@@ -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 <AzFramework/Viewport/ScreenGeometry.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -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 <iosfwd>
|
||||
|
||||
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
|
||||
@@ -11,5 +11,7 @@ set(FILES
|
||||
Mocks/MockWindowRequests.h
|
||||
Utils/Utils.h
|
||||
Utils/Utils.cpp
|
||||
Utils/Printers.h
|
||||
Utils/Printers.cpp
|
||||
FrameworkApplicationFixture.h
|
||||
)
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ namespace AzManipulatorTestFramework
|
||||
// ViewportInteractionRequestBus overrides ...
|
||||
AzFramework::CameraState GetCameraState() override;
|
||||
AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override;
|
||||
AZStd::optional<AZ::Vector3> ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) override;
|
||||
AZStd::optional<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> ViewportScreenToWorldRay(
|
||||
AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override;
|
||||
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay(
|
||||
const AzFramework::ScreenPoint& screenPosition) override;
|
||||
float DeviceScalingFactor() override;
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
@@ -140,13 +140,12 @@ namespace AzManipulatorTestFramework
|
||||
return m_viewportId;
|
||||
}
|
||||
|
||||
AZStd::optional<AZ::Vector3> 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<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> ViewportInteraction::ViewportScreenToWorldRay(
|
||||
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteraction::ViewportScreenToWorldRay(
|
||||
[[maybe_unused]] const AzFramework::ScreenPoint& screenPosition)
|
||||
{
|
||||
return {};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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<Prefab::PrefabPublicInterface>::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();
|
||||
|
||||
+5
-1
@@ -448,7 +448,11 @@ namespace AzToolsFramework
|
||||
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
|
||||
componentMode.m_componentMode->GetComponentModeName().c_str());
|
||||
componentMode.m_componentMode->GetComponentModeName().c_str(),
|
||||
[]
|
||||
{
|
||||
ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode);
|
||||
});
|
||||
}
|
||||
|
||||
RefreshActions();
|
||||
|
||||
+5
-2
@@ -55,8 +55,11 @@ namespace AzToolsFramework
|
||||
GetEntityComponentIdPair(), elementIdsToDisplay);
|
||||
// create the component mode border with the specific name for this component mode
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
|
||||
GetComponentModeName());
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, GetComponentModeName(),
|
||||
[]
|
||||
{
|
||||
ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode);
|
||||
});
|
||||
// set the EntityComponentId for this ComponentMode to active in the ComponentModeViewportUi system
|
||||
ComponentModeViewportUiRequestBus::Event(
|
||||
GetComponentType(), &ComponentModeViewportUiRequestBus::Events::SetViewportUiActiveEntityComponentId,
|
||||
|
||||
+4
-1
@@ -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
|
||||
|
||||
@@ -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<AZ::Vector3> 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<ProjectedViewportRay> 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.
|
||||
|
||||
+5
-3
@@ -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)
|
||||
|
||||
+17
-4
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Manipulators/ScaleManipulators.h>
|
||||
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
|
||||
#include <AzToolsFramework/Maths/TransformUtils.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
|
||||
@@ -409,7 +410,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 +984,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId))
|
||||
{
|
||||
if (entityDataCache.IsVisibleEntitySelectableInViewport(*entityIndex))
|
||||
if (entityDataCache.IsVisibleEntityIndividuallySelectableInViewport(*entityIndex))
|
||||
{
|
||||
return *entityIndex;
|
||||
}
|
||||
@@ -1014,6 +1015,15 @@ namespace AzToolsFramework
|
||||
ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
|
||||
}
|
||||
|
||||
// leaves focus mode by focusing on the parent of the current perfab in the entity outliner
|
||||
static void LeaveFocusMode()
|
||||
{
|
||||
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get())
|
||||
{
|
||||
prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(GetEntityContextId());
|
||||
}
|
||||
}
|
||||
|
||||
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache)
|
||||
: m_entityDataCache(entityDataCache)
|
||||
{
|
||||
@@ -3674,7 +3684,8 @@ namespace AzToolsFramework
|
||||
case ViewportEditorMode::Focus:
|
||||
{
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode");
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode",
|
||||
LeaveFocusMode);
|
||||
}
|
||||
break;
|
||||
case ViewportEditorMode::Default:
|
||||
@@ -3703,12 +3714,14 @@ namespace AzToolsFramework
|
||||
if (editorModeState.IsModeActive(ViewportEditorMode::Focus))
|
||||
{
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode");
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode",
|
||||
LeaveFocusMode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ViewportEditorMode::Focus:
|
||||
{
|
||||
|
||||
ViewportUi::ViewportUiRequestBus::Event(
|
||||
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RemoveViewportBorder);
|
||||
}
|
||||
|
||||
+3
-5
@@ -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<size_t> EditorVisibleEntityDataCache::GetVisibleEntityIndexFromId(const AZ::EntityId entityId) const
|
||||
|
||||
+4
-1
@@ -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<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const;
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
{
|
||||
const static int HighlightBorderSize = 5;
|
||||
const static char* HighlightBorderColor = "#4A90E2";
|
||||
const static int HighlightBorderBackButtonMargin = 5;
|
||||
const static int HighlightBorderBackButtonIconSize = 20;
|
||||
const static char* HighlightBorderBackButtonIconFile = "X_axis.svg";
|
||||
|
||||
static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup)
|
||||
{
|
||||
@@ -62,6 +65,7 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
, m_fullScreenLayout(&m_uiOverlay)
|
||||
, m_uiOverlayLayout()
|
||||
, m_viewportBorderText(&m_uiOverlay)
|
||||
, m_viewportBorderBackButton(&m_uiOverlay)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -291,7 +295,8 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::CreateViewportBorder(const AZStd::string& borderTitle)
|
||||
void ViewportUiDisplay::CreateViewportBorder(
|
||||
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback)
|
||||
{
|
||||
const AZStd::string styleSheet = AZStd::string::format(
|
||||
"border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, ViewportUiTopBorderSize,
|
||||
@@ -302,6 +307,10 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin);
|
||||
m_viewportBorderText.setVisible(true);
|
||||
m_viewportBorderText.setText(borderTitle.c_str());
|
||||
|
||||
// only display the back button if a callback was provided
|
||||
m_viewportBorderBackButtonCallback = backButtonCallback;
|
||||
m_viewportBorderBackButton.setVisible(m_viewportBorderBackButtonCallback.has_value());
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::RemoveViewportBorder()
|
||||
@@ -311,6 +320,8 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
m_uiOverlayLayout.setContentsMargins(
|
||||
ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin,
|
||||
ViewportUiOverlayMargin);
|
||||
m_viewportBorderBackButtonCallback.reset();
|
||||
m_viewportBorderBackButton.setVisible(false);
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::PositionViewportUiElementFromWorldSpace(ViewportUiElementId elementId, const AZ::Vector3& pos)
|
||||
@@ -347,6 +358,8 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
|
||||
void ViewportUiDisplay::InitializeUiOverlay()
|
||||
{
|
||||
AZStd::string styleSheet;
|
||||
|
||||
m_uiMainWindow.setObjectName(QString("ViewportUiWindow"));
|
||||
ConfigureWindowForViewportUi(&m_uiMainWindow);
|
||||
m_uiMainWindow.setVisible(false);
|
||||
@@ -361,11 +374,37 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
m_fullScreenLayout.addLayout(&m_uiOverlayLayout, 0, 0, 1, 1);
|
||||
|
||||
// format the label which will appear on top of the highlight border
|
||||
AZStd::string styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor);
|
||||
styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor);
|
||||
m_viewportBorderText.setStyleSheet(styleSheet.c_str());
|
||||
m_viewportBorderText.setFixedHeight(ViewportUiTopBorderSize);
|
||||
m_viewportBorderText.setVisible(false);
|
||||
m_fullScreenLayout.addWidget(&m_viewportBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter);
|
||||
|
||||
// format the back button which will appear in the top right of the highlight border
|
||||
styleSheet = AZStd::string::format(
|
||||
"border: 0px; padding-left: %dpx; padding-right: %dpx", HighlightBorderBackButtonMargin, HighlightBorderBackButtonMargin);
|
||||
m_viewportBorderBackButton.setStyleSheet(styleSheet.c_str());
|
||||
m_viewportBorderBackButton.setVisible(false);
|
||||
QIcon backButtonIcon(QString(AZStd::string::format(":/stylesheet/img/UI20/toolbar/%s", HighlightBorderBackButtonIconFile).c_str()));
|
||||
m_viewportBorderBackButton.setIcon(backButtonIcon);
|
||||
m_viewportBorderBackButton.setIconSize(QSize(HighlightBorderBackButtonIconSize, HighlightBorderBackButtonIconSize));
|
||||
|
||||
// setup the handler for the back button to call the user provided callback (if any)
|
||||
QObject::connect(
|
||||
&m_viewportBorderBackButton, &QPushButton::clicked,
|
||||
[this]()
|
||||
{
|
||||
if (m_viewportBorderBackButtonCallback.has_value())
|
||||
{
|
||||
// we need to swap out the existing back button callback because it will be reset in RemoveViewportBorder()
|
||||
// so preserve the lifetime with this temporary callback until after the call to RemoveViewportBorder()
|
||||
AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback;
|
||||
m_viewportBorderBackButtonCallback.swap(backButtonCallback);
|
||||
RemoveViewportBorder();
|
||||
(*backButtonCallback)();
|
||||
}
|
||||
});
|
||||
m_fullScreenLayout.addWidget(&m_viewportBorderBackButton, 0, 0, Qt::AlignTop | Qt::AlignRight);
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::PrepareWidgetForViewportUi(QPointer<QWidget> widget)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <QLabel>
|
||||
#include <QMainWindow>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option")
|
||||
#include <QGridLayout>
|
||||
@@ -89,7 +90,7 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
AZStd::shared_ptr<QWidget> GetViewportUiElement(ViewportUiElementId elementId);
|
||||
bool IsViewportUiElementVisible(ViewportUiElementId elementId);
|
||||
|
||||
void CreateViewportBorder(const AZStd::string& borderTitle);
|
||||
void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback);
|
||||
void RemoveViewportBorder();
|
||||
|
||||
private:
|
||||
@@ -113,7 +114,10 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
QWidget m_uiOverlay; //!< The UI Overlay which displays Viewport UI Elements.
|
||||
QGridLayout m_fullScreenLayout; //!< The layout which extends across the full screen.
|
||||
ViewportUiDisplayLayout m_uiOverlayLayout; //!< The layout used for optionally anchoring Viewport UI Elements.
|
||||
QLabel m_viewportBorderText; //!< The text used for the viewport border.
|
||||
QLabel m_viewportBorderText; //!< The text used for the viewport highlight border.
|
||||
QPushButton m_viewportBorderBackButton; //!< The button to return from the viewport highlight border (only displayed if callback provided).
|
||||
AZStd::optional<ViewportUiBackButtonCallback>
|
||||
m_viewportBorderBackButtonCallback; //!< The optional callback for when the viewport highlight border back button is pressed.
|
||||
|
||||
QWidget* m_renderOverlay;
|
||||
QPointer<QWidget> m_fullScreenWidget; //!< Reference to the widget attached to m_fullScreenLayout if any.
|
||||
|
||||
@@ -240,9 +240,10 @@ namespace AzToolsFramework::ViewportUi
|
||||
}
|
||||
}
|
||||
|
||||
void ViewportUiManager::CreateViewportBorder(const AZStd::string& borderTitle)
|
||||
void ViewportUiManager::CreateViewportBorder(
|
||||
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback)
|
||||
{
|
||||
m_viewportUi->CreateViewportBorder(borderTitle);
|
||||
m_viewportUi->CreateViewportBorder(borderTitle, backButtonCallback);
|
||||
}
|
||||
|
||||
void ViewportUiManager::RemoveViewportBorder()
|
||||
|
||||
@@ -50,7 +50,8 @@ namespace AzToolsFramework::ViewportUi
|
||||
void RegisterTextFieldCallback(TextFieldId textFieldId, AZ::Event<AZStd::string>::Handler& handler) override;
|
||||
void RemoveTextField(TextFieldId textFieldId) override;
|
||||
void SetTextFieldVisible(TextFieldId textFieldId, bool visible) override;
|
||||
void CreateViewportBorder(const AZStd::string& borderTitle) override;
|
||||
void CreateViewportBorder(
|
||||
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback) override;
|
||||
void RemoveViewportBorder() override;
|
||||
void PressButton(ClusterId clusterId, ButtonId buttonId) override;
|
||||
void PressButton(SwitcherId switcherId, ButtonId buttonId) override;
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace AzToolsFramework::ViewportUi
|
||||
using SwitcherId = IdType<struct SwitcherIdType>;
|
||||
using TextFieldId = IdType<struct TextFieldIdType>;
|
||||
|
||||
//! Callback function for viewport UI back button.
|
||||
using ViewportUiBackButtonCallback = AZStd::function<void()>;
|
||||
|
||||
inline const ViewportUiElementId InvalidViewportUiElementId = ViewportUiElementId(0);
|
||||
inline const ButtonId InvalidButtonId = ButtonId(0);
|
||||
inline const ClusterId InvalidClusterId = ClusterId(0);
|
||||
@@ -95,9 +98,9 @@ namespace AzToolsFramework::ViewportUi
|
||||
virtual void RemoveTextField(TextFieldId textFieldId) = 0;
|
||||
//! Sets the visibility of the text field.
|
||||
virtual void SetTextFieldVisible(TextFieldId textFieldId, bool visible) = 0;
|
||||
//! Create the highlight border for Component Mode.
|
||||
virtual void CreateViewportBorder(const AZStd::string& borderTitle) = 0;
|
||||
//! Remove the highlight border for Component Mode.
|
||||
//! Create the highlight border with optional back button to exit the given editor mode.
|
||||
virtual void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback) = 0;
|
||||
//! Remove the highlight border.
|
||||
virtual void RemoveViewportBorder() = 0;
|
||||
//! Invoke a button press on a cluster.
|
||||
virtual void PressButton(ClusterId clusterId, ButtonId buttonId) = 0;
|
||||
|
||||
@@ -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
|
||||
|
||||
+20
@@ -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 <AzToolsFramework/API/PythonLoader.h>
|
||||
|
||||
namespace AzToolsFramework::EmbeddedPython
|
||||
{
|
||||
PythonLoader::PythonLoader()
|
||||
{
|
||||
}
|
||||
|
||||
PythonLoader::~PythonLoader()
|
||||
{
|
||||
}
|
||||
}
|
||||
+34
@@ -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 <AzToolsFramework/API/PythonLoader.h>
|
||||
#include <AzCore/Debug/Trace.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
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
|
||||
@@ -7,4 +7,5 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzToolsFramework/API/PythonLoader_Linux.cpp
|
||||
)
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp
|
||||
)
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../Common/Default/AzToolsFramework/API/PythonLoader_Default.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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiManager.h>
|
||||
|
||||
#include<Tests/BoundsTestComponent.h>
|
||||
#include <Tests/BoundsTestComponent.h>
|
||||
|
||||
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<BoundsTestComponent>();
|
||||
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<bool>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
|
||||
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
|
||||
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
|
||||
#include <Tests/Utils/Printers.h>
|
||||
|
||||
using namespace AzToolsFramework;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
#include <Tests/Utils/Printers.h>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <xxhash/xxhash.h>
|
||||
|
||||
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::IO::SizeType>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -32,6 +32,7 @@ ly_add_target(
|
||||
PUBLIC
|
||||
AZ::AzFramework
|
||||
AZ::AzToolsFramework
|
||||
3rdParty::xxhash
|
||||
)
|
||||
ly_add_source_properties(
|
||||
SOURCES AssetBuilderSDK/AssetBuilderSDK.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);
|
||||
}
|
||||
|
||||
@@ -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<AssetProcessor::IFileStateRequests>::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::IO::SizeType>(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)
|
||||
|
||||
@@ -238,7 +238,6 @@ namespace AssetUtilities
|
||||
// hashMsDelay is only for automated tests to test that writing to a file while it's hashing does not cause a crash.
|
||||
// hashMsDelay is not used in non-unit test builds.
|
||||
AZ::u64 GetFileHash(const char* filePath, bool force = false, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
inline constexpr AZ::u64 FileHashBufferSize = 1024 * 64;
|
||||
|
||||
//! Adjusts a timestamp to fix timezone settings and account for any precision adjustment needed
|
||||
AZ::u64 AdjustTimestamp(QDateTime timestamp);
|
||||
|
||||
@@ -72,12 +72,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum();
|
||||
}
|
||||
|
||||
void EngineScreenCtrl::NotifyCurrentScreen()
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// add all the gem repos into the hash
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& 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<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& 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",
|
||||
|
||||
@@ -53,10 +53,13 @@ namespace O3DE::ProjectManager
|
||||
Update(selectedIndices[0]);
|
||||
}
|
||||
|
||||
void SetLabelElidedText(QLabel* label, QString text)
|
||||
void SetLabelElidedText(QLabel* label, QString text, int labelWidth = 0)
|
||||
{
|
||||
QFontMetrics nameFontMetrics(label->font());
|
||||
int labelWidth = label->width();
|
||||
if (!labelWidth)
|
||||
{
|
||||
labelWidth = label->width();
|
||||
}
|
||||
|
||||
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
|
||||
if (labelWidth > 100)
|
||||
@@ -84,7 +87,8 @@ namespace O3DE::ProjectManager
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
m_summaryLabel->adjustSize();
|
||||
|
||||
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
|
||||
// Manually define remaining space to elide text because spacer would like to take all of the space
|
||||
SetLabelElidedText(m_licenseLinkLabel, m_model->GetLicenseText(modelIndex), width() - m_licenseLabel->width() - 35);
|
||||
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
|
||||
|
||||
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
|
||||
@@ -175,8 +179,8 @@ namespace O3DE::ProjectManager
|
||||
licenseHLayout->setAlignment(Qt::AlignLeft);
|
||||
m_mainLayout->addLayout(licenseHLayout);
|
||||
|
||||
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
licenseLabel->setText(tr("License: "));
|
||||
m_licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
m_licenseLabel->setText(tr("License: "));
|
||||
|
||||
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
|
||||
licenseHLayout->addWidget(m_licenseLinkLabel);
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_nameLabel = nullptr;
|
||||
QLabel* m_creatorLabel = nullptr;
|
||||
QLabel* m_summaryLabel = nullptr;
|
||||
QLabel* m_licenseLabel = nullptr;
|
||||
LinkLabel* m_licenseLinkLabel = nullptr;
|
||||
LinkLabel* m_directoryLinkLabel = nullptr;
|
||||
LinkLabel* m_documentationLinkLabel = nullptr;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <GemRepo/GemRepoInspector.h>
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
@@ -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));
|
||||
|
||||
@@ -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<GemInfo> GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex)
|
||||
{
|
||||
QVector<GemInfo> allGemInfos;
|
||||
QStringList repoGemPaths = GetIncludedGemPaths(modelIndex);
|
||||
QString repoUri = GetRepoUri(modelIndex);
|
||||
|
||||
for (const QString& gemPath : repoGemPaths)
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForRepo(repoUri);
|
||||
if (gemInfosResult.IsSuccess())
|
||||
{
|
||||
AZ::Outcome<GemInfo> 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<GemInfo>();
|
||||
}
|
||||
|
||||
bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
|
||||
|
||||
@@ -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<Tag> GetIncludedGemTags(const QModelIndex& modelIndex);
|
||||
static QVector<GemInfo> GetIncludedGemInfos(const QModelIndex& modelIndex);
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ProjectBuilderWorker.h>
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
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);
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Configure Gems..."), this, [this]() { emit EditProjectGems(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); });
|
||||
menu->addAction(tr("Open CMake GUI..."), this, [this]() { emit OpenCMakeGUI(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace O3DE::ProjectManager
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
void EditProjectGems(const QString& projectName);
|
||||
void CopyProject(const ProjectInfo& projectInfo);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
|
||||
@@ -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 <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 <QString>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager";
|
||||
|
||||
void SaveProjectManagerSettings();
|
||||
QString GetProjectBuiltSuccessfullyKey(const QString& projectName);
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QStandardPaths>
|
||||
#include <QScrollArea>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -33,11 +34,23 @@ namespace O3DE::ProjectManager
|
||||
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
|
||||
QFrame* projectSettingsFrame = new QFrame(this);
|
||||
projectSettingsFrame->setObjectName("projectSettings");
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
|
||||
// you cannot remove content margins in qss
|
||||
m_verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setMargin(0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
projectSettingsFrame->setLayout(vLayout);
|
||||
|
||||
QScrollArea* scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
vLayout->addWidget(scrollArea);
|
||||
|
||||
QWidget* scrollWidget = new QWidget(this);
|
||||
scrollArea->setWidget(scrollWidget);
|
||||
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
m_verticalLayout->setMargin(0);
|
||||
m_verticalLayout->setAlignment(Qt::AlignTop);
|
||||
scrollWidget->setLayout(m_verticalLayout);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), "", this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectNameUpdated);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
#include <AzCore/Platform.h>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -181,6 +183,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
|
||||
connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
|
||||
connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
@@ -269,17 +272,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,6 +470,14 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectInfo.m_path))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user