diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py
index 3d4d9ea419..54bc118f48 100644
--- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py
+++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py
@@ -81,7 +81,8 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
def launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, null_renderer=True,
- port_listener_timeout=120, log_monitor_timeout=300, remote_console_port=4600):
+ port_listener_timeout=120, log_monitor_timeout=300, remote_console_port=4600,
+ launch_ap=True):
"""
Runs the launcher with the specified level, and monitors Game.log for expected lines.
:param launcher: Configured launcher object to run test against.
@@ -92,6 +93,7 @@ def launch_and_validate_results_launcher(launcher, level, remote_console_instanc
:param port_listener_timeout: Timeout for verifying successful connection to Remote Console.
:param log_monitor_timeout: Timeout for monitoring for lines in Game.log
:param remote_console_port: The port used to communicate with the Remote Console.
+ :param launch_ap: Whether or not to launch AP. Defaults to True.
"""
def _check_for_listening_port(port):
@@ -110,7 +112,7 @@ def launch_and_validate_results_launcher(launcher, level, remote_console_instanc
launcher.args.extend(["-rhi=Null"])
# Start the Launcher
- with launcher.start():
+ with launcher.start(launch_ap=launch_ap):
# Ensure Remote Console can be reached
waiter.wait_for(
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt
index b3030e84ac..f1299ddc2d 100644
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt
@@ -14,8 +14,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
NAME AutomatedTesting::DynamicVegetationTests_Main
TEST_SERIAL
TEST_SUITE main
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark"
+ PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main.py
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
Legacy::Editor
@@ -27,104 +26,33 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationTests_Sandbox
+ NAME AutomatedTesting::DynamicVegetationTests_Periodic
TEST_SERIAL
- TEST_SUITE sandbox
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_sandbox"
+ TEST_SUITE periodic
+ PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Periodic.py
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
Legacy::Editor
+ AutomatedTesting.Assets
AutomatedTesting.GameLauncher
- AutomatedTesting.Assets
COMPONENT
LargeWorlds
)
ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationFilterTests_Periodic
+ NAME AutomatedTesting::DynamicVegetationTests_Main_Optimized
TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_filter"
+ TEST_SUITE main
+ PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main_Optimized.py
RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
+ AZ::AssetProcessor
+ Legacy::Editor
+ AutomatedTesting.Assets
+ AutomatedTesting.GameLauncher
COMPONENT
LargeWorlds
)
- ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationModifierTests_Periodic
- TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_modifier"
- RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
- COMPONENT
- LargeWorlds
- )
-
- ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationRegressionTests_Periodic
- TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_regression"
- RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
- COMPONENT
- LargeWorlds
- )
-
- ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationAreaTests_Periodic
- TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_area"
- RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
- COMPONENT
- LargeWorlds
- )
-
- ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationMiscTests_Periodic
- TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_misc"
- RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
- COMPONENT
- LargeWorlds
- )
-
- ly_add_pytest(
- NAME AutomatedTesting::DynamicVegetationSurfaceTagTests_Periodic
- TEST_SERIAL
- TEST_SUITE periodic
- PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg
- PYTEST_MARKS "SUITE_periodic and dynveg_surfacetagemitter"
- RUNTIME_DEPENDENCIES
- AZ::AssetProcessor
- Legacy::Editor
- AutomatedTesting.Assets
- COMPONENT
- LargeWorlds
- )
-
## LandscapeCanvas ##
ly_add_pytest(
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude.py
index 928d38ac5a..e404624c93 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude.py
@@ -5,119 +5,119 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C4814463 - Altitude Filter overrides function as expected
-C4847477 - Altitude Min/Max can be manually set
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ prefilter_instance_count = (
+ "Pre-filter instance counts are accurate",
+ "Unexpected number of pre-filter instances found"
+ )
+ postfilter_instance_count = (
+ "Post-filter instance counts are accurate",
+ "Unexpected number of post-filter instances found"
+ )
+ postfilter_overrides_instance_count = (
+ "Override instance counts are accurate",
+ "Unexpected number of override instances found"
+ )
-class TestAltitudeFilterComponentAndOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="AltitudeFilterComponentAndOverrides", args=["level"])
+def AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude():
+ """
+ Summary:
+ A new level is created. A spawner entity is added, along with a planting surface at 32 on Z, and another at 36
+ on Z. An Altitude Filter is added to the spawner entity, and Altitude Min/Max values are set. Instance counts
+ are validated. The same test is then performed for Altitude Filter overrides.
- def run_test(self):
- """
- Summary:
- A new level is created. A spawner entity is added, along with a planting surface at 32 on Z, and another at 36
- on Z. An Altitude Filter is added to the spawner entity, and Altitude Min/Max values are set. Instance counts
- are validated. The same test is then performed for Altitude Filter overrides.
+ Expected Behavior:
+ Instances are only spawned within the specified altitude ranges.
- Expected Behavior:
- Instances are only spawned within the specified altitude ranges.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create an instance spawner entity
+ 3) Create surfaces to plant on, one at 32 on Z and another at 36 on Z.
+ 4) Initial instance counts pre-filter are verified.
+ 5) Altitude Min/Max is set on the Vegetation Altitude Filter component.
+ 6) Instance counts post-filter are verified.
+ 7) Altitude Min/Max is set on descriptor overrides.
+ 8) Instance counts post-filter are verified.
- Test Steps:
- 1) Create a new level
- 2) Create an instance spawner entity
- 3) Create surfaces to plant on, one at 32 on Z and another at 36 on Z.
- 4) Initial instance counts pre-filter are verified.
- 5) Altitude Min/Max is set on the Vegetation Altitude Filter component.
- 6) Instance counts post-filter are verified.
- 7) Altitude Min/Max is set on descriptor overrides.
- 8) Instance counts post-filter are verified.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Add a Vegetation Altitude Filter
- spawner_entity.add_component("Vegetation Altitude Filter")
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 3) Add surfaces to plant on
- dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
- elevated_surface_center_point = math.Vector3(512.0, 512.0, 36.0)
- dynveg.create_surface_entity("Planting Surface Elevated", elevated_surface_center_point, 32.0, 32.0, 1.0)
+ # 2) Create a new entity with required vegetation area components
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
- # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Add a Vegetation Altitude Filter
+ spawner_entity.add_component("Vegetation Altitude Filter")
- # 4) Verify initial instance counts pre-filter
- num_expected = (40 * 40) * 2 # 20 instances per 16m per side x 2 surfaces
- spawner_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and spawner_success
+ # 3) Add surfaces to plant on
+ dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
+ elevated_surface_center_point = math.Vector3(512.0, 512.0, 36.0)
+ dynveg.create_surface_entity("Planting Surface Elevated", elevated_surface_center_point, 32.0, 32.0, 1.0)
- # 5) Set min/max vegetation altitude, instances should now only appear between 35-37m on the Z-axis
- spawner_entity.get_set_test(3, "Configuration|Altitude Min", 35)
- spawner_entity.get_set_test(3, "Configuration|Altitude Max", 37)
+ # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- # 6) Validate expected instance counts
- num_expected = 40 * 40 # Instances should now only plant on the elevated surface
- altitude_min_max_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and altitude_min_max_success
+ # 4) Verify initial instance counts pre-filter
+ num_expected = (40 * 40) * 2 # 20 instances per 16m per side x 2 surfaces
+ spawner_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.prefilter_instance_count, spawner_success)
- # Resize Spawner Entity's Box Shape component to allow monitoring for a different instance count
- box_size = math.Vector3(16.0, 16.0, 16.0)
- spawner_entity.get_set_test(1, "Box Shape|Box Configuration|Dimensions", box_size)
+ # 5) Set min/max vegetation altitude, instances should now only appear between 35-37m on the Z-axis
+ spawner_entity.get_set_test(3, "Configuration|Altitude Min", 35)
+ spawner_entity.get_set_test(3, "Configuration|Altitude Max", 37)
- # 7) Allow overrides on Altitude Filter and set Altitude Filter Min/Max overrides on descriptor
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Override Enabled", True)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Min", 35)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Max", 37)
+ # 6) Validate expected instance counts
+ num_expected = 40 * 40 # Instances should now only plant on the elevated surface
+ altitude_min_max_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.postfilter_instance_count, altitude_min_max_success)
- # 8) Validate expected instances at specified elevations
- num_expected = 20 * 20 # 20 instances per 16m per side
- overrides_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and overrides_success
+ # Resize Spawner Entity's Box Shape component to allow monitoring for a different instance count
+ box_size = math.Vector3(16.0, 16.0, 16.0)
+ spawner_entity.get_set_test(1, "Box Shape|Box Configuration|Dimensions", box_size)
+
+ # 7) Allow overrides on Altitude Filter and set Altitude Filter Min/Max overrides on descriptor
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Override Enabled", True)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Min", 35)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Altitude Filter|Max", 37)
+
+ # 8) Validate expected instances at specified elevations
+ num_expected = 20 * 20 # 20 instances per 16m per side
+ overrides_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.postfilter_overrides_instance_count, overrides_success)
-test = TestAltitudeFilterComponentAndOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py
index 2ef72f72eb..3fc6a0afde 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py
@@ -5,90 +5,88 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ preprocess_instance_count = (
+ "Pre-process instance counts are accurate",
+ "Unexpected number of pre-process instances found"
+ )
+ postprocess_instance_count = (
+ "Post-process instance counts are accurate",
+ "Unexpected number of post-process instances found"
+ )
-class TestAltitudeFilterFilterStageToggle(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="AltitudeFilter_FilterStageToggle", args=["level"])
+def AltitudeFilter_FilterStageToggle():
+ """
+ Summary:
+ Filter Stage toggle affects final vegetation position
- def run_test(self):
- """
- Summary:
- Filter Stage toggle affects final vegetation position
+ Expected Result:
+ Vegetation instances plant differently depending on the Filter Stage setting.
+ PostProcess should cause some number of plants that appear above and below the desired altitude range to disappear.
- Expected Result:
- Vegetation instances plant differently depending on the Filter Stage setting.
- PostProcess should cause some number of plants that appear above and below the desired altitude range to disappear.
+ :return: None
+ """
- :return: None
- """
+ import os
- PREPROCESS_INSTANCE_COUNT = 44
- POSTPROCESS_INSTANCE_COUNT = 34
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(512.0, 480.0, 38.0)
+ PREPROCESS_INSTANCE_COUNT = 44
+ POSTPROCESS_INSTANCE_COUNT = 34
- # Create basic vegetation entity
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add a Vegetation Altitude Filter to the vegetation area entity
- vegetation.add_component("Vegetation Altitude Filter")
+ # Create basic vegetation entity
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
- # Create Surface for instances to plant on
- dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0)
+ # Add a Vegetation Altitude Filter to the vegetation area entity
+ vegetation.add_component("Vegetation Altitude Filter")
- # Add entity with Mesh to replicate creation of hills
- hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 10.0)
+ # Create Surface for instances to plant on
+ dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0)
- # Set a Min Altitude of 38 and Max of 40 in Vegetation Altitude Filter
- vegetation.get_set_test(3, "Configuration|Altitude Min", 38.0)
- vegetation.get_set_test(3, "Configuration|Altitude Max", 40.0)
+ # Add entity with Mesh to replicate creation of hills
+ hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 10.0)
- # Create a new entity as a child of the vegetation area entity with Random Noise Gradient Generator, Gradient
- # Transform Modifier, and Box Shape component
- random_noise = hydra.Entity("random_noise")
- random_noise.create_entity(position, ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"])
- random_noise.set_test_parent_entity(vegetation)
+ # Set a Min Altitude of 38 and Max of 40 in Vegetation Altitude Filter
+ vegetation.get_set_test(3, "Configuration|Altitude Min", 38.0)
+ vegetation.get_set_test(3, "Configuration|Altitude Max", 40.0)
- # Add a Vegetation Position Modifier to the vegetation area entity.
- vegetation.add_component("Vegetation Position Modifier")
+ # Create a new entity as a child of the vegetation area entity with Random Noise Gradient Generator, Gradient
+ # Transform Modifier, and Box Shape component
+ random_noise = hydra.Entity("random_noise")
+ random_noise.create_entity(position, ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"])
+ random_noise.set_test_parent_entity(vegetation)
- # Pin the Random Noise entity to the Gradient Entity Id field of the Position Modifier's Gradient X
- vegetation.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", random_noise.id)
+ # Add a Vegetation Position Modifier to the vegetation area entity.
+ vegetation.add_component("Vegetation Position Modifier")
- # Toggle between PreProcess and PostProcess in Vegetation Altitude Filter
- vegetation.get_set_test(3, "Configuration|Filter Stage", 1)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 30.0, PREPROCESS_INSTANCE_COUNT), 2.0)
- self.log(f"Vegetation instances count equal to expected value for PREPROCESS filter stage: {result}")
- vegetation.get_set_test(3, "Configuration|Filter Stage", 2)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 30.0, POSTPROCESS_INSTANCE_COUNT), 2.0)
- self.log(f"Vegetation instances count equal to expected value for POSTPROCESS filter stage: {result}")
+ # Pin the Random Noise entity to the Gradient Entity Id field of the Position Modifier's Gradient X
+ vegetation.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", random_noise.id)
+
+ # Toggle between PreProcess and PostProcess in Vegetation Altitude Filter
+ vegetation.get_set_test(3, "Configuration|Filter Stage", 1)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 30.0, PREPROCESS_INSTANCE_COUNT), 2.0)
+ Report.result(Tests.preprocess_instance_count, result)
+ vegetation.get_set_test(3, "Configuration|Filter Stage", 2)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 30.0, POSTPROCESS_INSTANCE_COUNT), 2.0)
+ Report.result(Tests.postprocess_instance_count, result)
-test = TestAltitudeFilterFilterStageToggle()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AltitudeFilter_FilterStageToggle)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude.py
index 8139ec5cac..bcd42b7fbb 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude.py
@@ -5,104 +5,105 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ prefilter_instance_count = (
+ "Pre-filter instance counts are accurate",
+ "Unexpected number of pre-filter instances found"
+ )
+ postfilter_instance_count = (
+ "Post-filter instance counts are accurate",
+ "Unexpected number of post-filter instances found"
+ )
-class TestAltitudeFilterShapeSample(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="AltitudeFilterShapeSample", args=["level"])
+def AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude():
+ """
+ Summary:
+ A new level is created. A spawner entity is added, along with a planting surface at 32 on Z, and another at 36
+ on Z. An Altitude Filter is added to the spawner entity, and set to sample a shape entity. Instance counts are
+ validated.
- def run_test(self):
- """
- Summary:
- A new level is created. A spawner entity is added, along with a planting surface at 32 on Z, and another at 36
- on Z. An Altitude Filter is added to the spawner entity, and set to sample a shape entity. Instance counts are
- validated.
+ Expected Behavior:
+ Instances are only spawned within the altitude range specified by the sampled shape.
- Expected Behavior:
- Instances are only spawned within the altitude range specified by the sampled shape.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create an instance spawner entity
+ 3) Create surfaces to plant on, one at 32 on Z and another at 36 on Z.
+ 4) Initial instance counts pre-filter are verified.
+ 5) A new entity with shape is created, an sampled on the Vegetation Altitude Filter.
+ 6) Instance counts post-filter are verified.
- Test Steps:
- 1) Create a new level
- 2) Create an instance spawner entity
- 3) Create surfaces to plant on, one at 32 on Z and another at 36 on Z.
- 4) Initial instance counts pre-filter are verified.
- 5) A new entity with shape is created, an sampled on the Vegetation Altitude Filter.
- 6) Instance counts post-filter are verified.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, asset_path)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Add a Vegetation Altitude Filter
- spawner_entity.add_component("Vegetation Altitude Filter")
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 3) Add surfaces to plant on
- dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
- elevated_surface_center_point = math.Vector3(512.0, 512.0, 36.0)
- dynveg.create_surface_entity("Planting Surface Elevated", elevated_surface_center_point, 32.0, 32.0, 1.0)
+ # 2) Create a new entity with required vegetation area components
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, asset_path)
- # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Add a Vegetation Altitude Filter
+ spawner_entity.add_component("Vegetation Altitude Filter")
- # 4) Verify initial instance counts pre-filter
- num_expected = (20 * 20) * 2 # 20 instances per 16m per side x 2 surfaces
- spawner_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and spawner_success
+ # 3) Add surfaces to plant on
+ dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
+ elevated_surface_center_point = math.Vector3(512.0, 512.0, 36.0)
+ dynveg.create_surface_entity("Planting Surface Elevated", elevated_surface_center_point, 32.0, 32.0, 1.0)
- # 5) Create a new entity with a shape at 36 on the Z-axis, and pin the entity to the Vegetation Altitude Filter
- shape_sampler_center_point = math.Vector3(512.0, 512.0, 36.0)
- shape_sampler = hydra.Entity("Shape Sampler")
- shape_sampler.create_entity(
- shape_sampler_center_point,
- ["Box Shape"]
- )
- if shape_sampler.id.IsValid():
- print(f"'{shape_sampler.name}' created")
- spawner_entity.get_set_test(3, 'Configuration|Pin To Shape Entity Id', shape_sampler.id)
+ # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- # 6) Validate expected instance counts
- num_expected = 20 * 20 # Instances should now only plant on the elevated surface
- sampler_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and sampler_success
+ # 4) Verify initial instance counts pre-filter
+ num_expected = (20 * 20) * 2 # 20 instances per 16m per side x 2 surfaces
+ spawner_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.prefilter_instance_count, spawner_success)
+
+ # 5) Create a new entity with a shape at 36 on the Z-axis, and pin the entity to the Vegetation Altitude Filter
+ shape_sampler_center_point = math.Vector3(512.0, 512.0, 36.0)
+ shape_sampler = hydra.Entity("Shape Sampler")
+ shape_sampler.create_entity(
+ shape_sampler_center_point,
+ ["Box Shape"]
+ )
+ if shape_sampler.id.IsValid():
+ print(f"'{shape_sampler.name}' created")
+ spawner_entity.get_set_test(3, 'Configuration|Pin To Shape Entity Id', shape_sampler.id)
+
+ # 6) Validate expected instance counts
+ num_expected = 20 * 20 # Instances should now only plant on the elevated surface
+ sampler_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.postfilter_instance_count, sampler_success)
-test = TestAltitudeFilterShapeSample()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AreaComponentSlices_SliceCreationAndVisibilityToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AreaComponentSlices_SliceCreationAndVisibilityToggle.py
deleted file mode 100755
index 095024cca1..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AreaComponentSlices_SliceCreationAndVisibilityToggle.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""
-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
-"""
-
-import os
-import sys
-
-import azlmbr.math as math
-import azlmbr.legacy.general as general
-import azlmbr.slice as slice
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.asset as asset
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
-
-
-class TestAreaComponentsSliceCreationAndVisibilityToggle(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(
- self, log_prefix="AreaComponentSlices_SliceCreationAndVisibilityToggle", args=["level"]
- )
-
- def run_test(self):
- """
- Summary:
- C2627900 Verifies if a slice containing the component can be created.
- C2627905 A slice containing the Vegetation Layer Blender component can be created.
- C2627904: Hiding a slice containing the component clears any visuals from the Viewport.
-
- Expected Result:
- C2627900, C2627905: Slice is created, and is properly processed in the Asset Processor.
- C2627904: Vegetation area visuals are hidden from the Viewport.
-
- :return: None
- """
-
- def path_is_valid_asset(asset_path):
- asset_id = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", asset_path, math.Uuid(), False)
- return asset_id.invoke("IsValid")
-
- # 1) Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
-
- general.set_current_view_position(512.0, 480.0, 38.0)
-
- # 2) C2627900 Verifies if a slice containing the Vegetation Layer Spawner component can be created.
- # 2.1) Create basic vegetation entity
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- veg_1 = dynveg.create_vegetation_area("vegetation_1", position, 16.0, 16.0, 16.0, asset_path)
-
- # 2.2) Create slice from the entity
- slice_path = os.path.join("slices", "TestSlice_1.slice")
- slice.SliceRequestBus(bus.Broadcast, "CreateNewSlice", veg_1.id, slice_path)
-
- # 2.3) Verify if the slice has been created successfully
- self.wait_for_condition(lambda: path_is_valid_asset(slice_path), 5.0)
- self.log(
- f"Slice has been created successfully (entity with spawner component): {path_is_valid_asset(slice_path)}"
- )
-
- # 3) C2627904: Hiding a slice containing the component clears any visuals from the Viewport
- # 3.1) Create Surface for instances to plant on
- dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
-
- # 3.2) Initially verify instance count before hiding slice
- self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 400))
- self.log(
- f"Vegetation plants initially when slice is shown: {dynveg.validate_instance_count(position, 16.0, 400)}"
- )
-
- # 3.3) Hide the slice and verify instance count
- editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", veg_1.id, False)
- self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 0))
- self.log(f"Vegetation is cleared when slice is hidden: {dynveg.validate_instance_count(position, 16.0, 0)}")
-
- # 3.4) Unhide the slice
- editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", veg_1.id, True)
-
- # 4) C2627905 A slice containing the Vegetation Layer Blender component can be created.
- # 4.1) Create another vegetation entity to add to blender component
- veg_2 = dynveg.create_vegetation_area("vegetation_2", position, 1.0, 1.0, 1.0, "")
-
- # 4.2) Create entity with Vegetation Layer Blender
- components_to_add = ["Box Shape", "Vegetation Layer Blender"]
- blender_entity = hydra.Entity("blender_entity")
- blender_entity.create_entity(position, components_to_add)
-
- # 4.3) Pin both the vegetation areas to the blender entity
- pte = hydra.get_property_tree(blender_entity.components[1])
- path = "Configuration|Vegetation Areas"
- pte.update_container_item(path, 0, veg_1.id)
- pte.add_container_item(path, 1, veg_2.id)
-
- # 4.4) Drag the simple vegetation areas under the Vegetation Layer Blender entity to create an entity hierarchy.
- veg_1.set_test_parent_entity(blender_entity)
- veg_2.set_test_parent_entity(blender_entity)
-
- # 4.5) Create slice from blender entity
- slice_path = os.path.join("slices", "TestSlice_2.slice")
- slice.SliceRequestBus(bus.Broadcast, "CreateNewSlice", blender_entity.id, slice_path)
-
- # 4.6) Verify if the slice has been created successfully
- self.wait_for_condition(lambda: path_is_valid_asset(slice_path), 5.0)
- self.log(
- f"Slice has been created successfully (entity with blender component): {path_is_valid_asset(slice_path)}"
- )
-
-
-test = TestAreaComponentsSliceCreationAndVisibilityToggle()
-test.run()
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea.py
index 5242160b35..7f6ce87110 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea.py
@@ -5,162 +5,167 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.vegetation as vegetation
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ combined_instance_count_validation = (
+ "Combined instance counts are as expected",
+ "Found an unexpected number of instances"
+ )
+ replaced_asset_list_combined_instance_count_validation = (
+ "Combined instance counts are as expected after replacing an Asset List reference",
+ "Found an unexpected number of instances after replacing an Asset List reference"
+ )
+ removed_asset_lists_combined_instance_count_validation = (
+ "Instance counts are as expected after removing the referenced Asset Lists",
+ "Found an unexpected number of instances after removing the referenced Asset Lists"
+ )
-class TestAssetListCombiner(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="AssetListCombiner_CombinedDescriptors", args=["level"])
+def AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea():
+ """
+ Summary:
+ Combined descriptors appear as expected in a vegetation area. Also verifies remove/replace of assigned Asset
+ Lists.
- def run_test(self):
- """
- Summary:
- Combined descriptors appear as expected in a vegetation area. Also verifies remove/replace of assigned Asset
- Lists.
+ Expected Behavior:
+ Vegetation fills in the area using the assets assigned to both Vegetation Asset Lists.
- Expected Behavior:
- Vegetation fills in the area using the assets assigned to both Vegetation Asset Lists.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create 3 entities with Vegetation Asset List components set to spawn different descriptors
+ 3) Create a planting surface and add a Vegetation System Settings level component with instances set to spawn
+ on center instead of corner
+ 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
+ spawning empty assets
+ 5) Add 2 of the Asset List entities to the Vegetation Asset List Combiner component (PinkFlower and Empty)
+ 6) Create a Constant Gradient entity as a child of the spawner entity, and a Dither Gradient Modifier entity
+ as a child of the Constant Gradient entity, and configure for a checkerboard pattern
+ 7) Pin the Dither Gradient Entity to the Asset Weight Selector of the spawner entity
+ 8) Validate instance count with configured Asset List Combiner
+ 9) Replace the reference to the 2nd asset list on the Vegetation Asset List Combiner component and validate
+ instance count
+ 10) Remove the referenced Asset Lists on the Asset List Combiner, Disable/Re-enable the Asset List
+ Combiner component to force a refresh, and validate instance count
- Test Steps:
- 1) Create a new, temporary level
- 2) Create 3 entities with Vegetation Asset List components set to spawn different descriptors
- 3) Create a planting surface and add a Vegetation System Settings level component with instances set to spawn
- on center instead of corner
- 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
- spawning empty assets
- 5) Add 2 of the Asset List entities to the Vegetation Asset List Combiner component (PinkFlower and Empty)
- 6) Create a Constant Gradient entity as a child of the spawner entity, and a Dither Gradient Modifier entity
- as a child of the Constant Gradient entity, and configure for a checkerboard pattern
- 7) Pin the Dither Gradient Entity to the Asset Weight Selector of the spawner entity
- 8) Validate instance count with configured Asset List Combiner
- 9) Replace the reference to the 2nd asset list on the Vegetation Asset List Combiner component and validate
- instance count
- 10) Remove the referenced Asset Lists on the Asset List Combiner, Disable/Re-enable the Asset List
- Combiner component to force a refresh, and validate instance count
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- def create_asset_list_entity(name, center, dynamic_slice_asset_path):
- asset_list_entity = hydra.Entity(name)
- asset_list_entity.create_entity(
- center,
- ["Vegetation Asset List"]
- )
- if asset_list_entity.id.IsValid():
- print(f"'{asset_list_entity.name}' created")
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+ import azlmbr.vegetation as vegetation
- # Set the Asset List to a Dynamic Slice spawner with a specific slice asset selected
- dynamic_slice_spawner = vegetation.DynamicSliceInstanceSpawner()
- dynamic_slice_spawner.SetSliceAssetPath(dynamic_slice_asset_path)
- descriptor = hydra.get_component_property_value(asset_list_entity.components[0],
- "Configuration|Embedded Assets|[0]")
- descriptor.spawner = dynamic_slice_spawner
- asset_list_entity.get_set_test(0, "Configuration|Embedded Assets|[0]", descriptor)
- return asset_list_entity
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ def create_asset_list_entity(name, center, dynamic_slice_asset_path):
+ asset_list_entity = hydra.Entity(name)
+ asset_list_entity.create_entity(
+ center,
+ ["Vegetation Asset List"]
)
+ if asset_list_entity.id.IsValid():
+ print(f"'{asset_list_entity.name}' created")
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ # Set the Asset List to a Dynamic Slice spawner with a specific slice asset selected
+ dynamic_slice_spawner = vegetation.DynamicSliceInstanceSpawner()
+ dynamic_slice_spawner.SetSliceAssetPath(dynamic_slice_asset_path)
+ descriptor = hydra.get_component_property_value(asset_list_entity.components[0],
+ "Configuration|Embedded Assets|[0]")
+ descriptor.spawner = dynamic_slice_spawner
+ asset_list_entity.get_set_test(0, "Configuration|Embedded Assets|[0]", descriptor)
+ return asset_list_entity
- # 2) Create 3 entities with Vegetation Asset List components set to spawn different descriptors
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- asset_path2 = os.path.join("Slices", "PurpleFlower.dynamicslice")
- asset_list_entity = create_asset_list_entity("Asset List 1", center_point, asset_path)
- asset_list_entity2 = create_asset_list_entity("Asset List 2", center_point, None)
- asset_list_entity3 = create_asset_list_entity("Asset List 3", center_point, asset_path2)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Create a planting surface and add a Vegetation System Settings level component with instances set to spawn
- # on center instead of corner
- dynveg.create_surface_entity("Surface Entity", center_point, 32.0, 32.0, 1.0)
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
- # spawning empty assets
- spawner_entity = dynveg.create_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
- spawner_entity.remove_component("Vegetation Asset List")
- spawner_entity.add_component("Vegetation Asset List Combiner")
- spawner_entity.add_component("Vegetation Asset Weight Selector")
- spawner_entity.get_set_test(0, "Configuration|Allow Empty Assets", False)
+ # 2) Create 3 entities with Vegetation Asset List components set to spawn different descriptors
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ asset_path2 = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ asset_list_entity = create_asset_list_entity("Asset List 1", center_point, asset_path)
+ asset_list_entity2 = create_asset_list_entity("Asset List 2", center_point, None)
+ asset_list_entity3 = create_asset_list_entity("Asset List 3", center_point, asset_path2)
- # 5) Add the Asset List entities to the Vegetation Asset List Combiner component
- asset_list_entities = [asset_list_entity.id, asset_list_entity2.id]
- spawner_entity.get_set_test(2, "Configuration|Descriptor Providers", asset_list_entities)
+ # 3) Create a planting surface and add a Vegetation System Settings level component with instances set to spawn
+ # on center instead of corner
+ dynveg.create_surface_entity("Surface Entity", center_point, 32.0, 32.0, 1.0)
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- # 6) Create a Constant Gradient entity as a child of the spawner entity, and a Dither Gradient Modifier entity
- # as a child of the Constant Gradient entity, and configure for a checkerboard pattern
- components_to_add = ["Constant Gradient"]
- constant_gradient_entity = hydra.Entity("Constant Gradient Entity")
- constant_gradient_entity.create_entity(center_point, components_to_add, parent_id=spawner_entity.id)
- constant_gradient_entity.get_set_test(0, "Configuration|Value", 0.5)
+ # 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
+ # spawning empty assets
+ spawner_entity = dynveg.create_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
+ spawner_entity.remove_component("Vegetation Asset List")
+ spawner_entity.add_component("Vegetation Asset List Combiner")
+ spawner_entity.add_component("Vegetation Asset Weight Selector")
+ spawner_entity.get_set_test(0, "Configuration|Allow Empty Assets", False)
- components_to_add = ["Dither Gradient Modifier"]
- dither_gradient_entity = hydra.Entity("Dither Gradient Entity")
- dither_gradient_entity.create_entity(center_point, components_to_add, parent_id=constant_gradient_entity.id)
- dither_gradient_entity.get_set_test(0, "Configuration|Gradient|Gradient Entity Id", constant_gradient_entity.id)
+ # 5) Add the Asset List entities to the Vegetation Asset List Combiner component
+ asset_list_entities = [asset_list_entity.id, asset_list_entity2.id]
+ spawner_entity.get_set_test(2, "Configuration|Descriptor Providers", asset_list_entities)
- # 7) Pin the Dither Gradient Entity to the Asset Weight Selector of the spawner entity
- spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", dither_gradient_entity.id)
+ # 6) Create a Constant Gradient entity as a child of the spawner entity, and a Dither Gradient Modifier entity
+ # as a child of the Constant Gradient entity, and configure for a checkerboard pattern
+ components_to_add = ["Constant Gradient"]
+ constant_gradient_entity = hydra.Entity("Constant Gradient Entity")
+ constant_gradient_entity.create_entity(center_point, components_to_add, parent_id=spawner_entity.id)
+ constant_gradient_entity.get_set_test(0, "Configuration|Value", 0.5)
- # 8) Validate instance count. We should now have 200 instances in the spawner area as every other instance
- # should be an empty asset which the spawner is set to disallow
- num_expected = 20 * 20 / 2
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ components_to_add = ["Dither Gradient Modifier"]
+ dither_gradient_entity = hydra.Entity("Dither Gradient Entity")
+ dither_gradient_entity.create_entity(center_point, components_to_add, parent_id=constant_gradient_entity.id)
+ dither_gradient_entity.get_set_test(0, "Configuration|Gradient|Gradient Entity Id", constant_gradient_entity.id)
- # 9) Replace the reference to the 2nd asset list on the Vegetation Asset List Combiner component and validate
- # instance count. Should now be 400 instances as the empty spaces can now be claimed by the new descriptor
- spawner_entity.get_set_test(2, "Configuration|Descriptor Providers|[1]", asset_list_entity3.id)
- num_expected = 20 * 20
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # 7) Pin the Dither Gradient Entity to the Asset Weight Selector of the spawner entity
+ spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", dither_gradient_entity.id)
- # 10) Remove the referenced Asset Lists on the Asset List Combiner, Disable/Re-enable the Asset List
- # Combiner component to force a refresh, and validate instance count. We should now have 0 instances.
- pte = hydra.get_property_tree(spawner_entity.components[2])
- path = "Configuration|Descriptor Providers"
- pte.reset_container(path)
- # Component refresh is currently necessary due to container operations not causing a refresh (LY-120947)
- editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [spawner_entity.components[2]])
- editor.EditorComponentAPIBus(bus.Broadcast, "EnableComponents", [spawner_entity.components[2]])
- num_expected = 0
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # 8) Validate instance count. We should now have 200 instances in the spawner area as every other instance
+ # should be an empty asset which the spawner is set to disallow
+ num_expected = 20 * 20 / 2
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.combined_instance_count_validation, success)
+
+ # 9) Replace the reference to the 2nd asset list on the Vegetation Asset List Combiner component and validate
+ # instance count. Should now be 400 instances as the empty spaces can now be claimed by the new descriptor
+ spawner_entity.get_set_test(2, "Configuration|Descriptor Providers|[1]", asset_list_entity3.id)
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.replaced_asset_list_combined_instance_count_validation, success)
+
+ # 10) Remove the referenced Asset Lists on the Asset List Combiner, Disable/Re-enable the Asset List
+ # Combiner component to force a refresh, and validate instance count. We should now have 0 instances.
+ pte = hydra.get_property_tree(spawner_entity.components[2])
+ path = "Configuration|Descriptor Providers"
+ pte.reset_container(path)
+ # Component refresh is currently necessary due to container operations not causing a refresh (LY-120947)
+ editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [spawner_entity.components[2]])
+ editor.EditorComponentAPIBus(bus.Broadcast, "EnableComponents", [spawner_entity.components[2]])
+ num_expected = 0
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.removed_asset_lists_combined_instance_count_validation, success)
-test = TestAssetListCombiner()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetWeightSelector_InstancesExpressBasedOnWeight.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetWeightSelector_InstancesExpressBasedOnWeight.py
index 113bbc8ea8..53b45e8470 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetWeightSelector_InstancesExpressBasedOnWeight.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AssetWeightSelector_InstancesExpressBasedOnWeight.py
@@ -5,112 +5,110 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C6269654: Vegetation areas using weight selectors properly distribute instances according to Sort By Weight setting
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ highest_weight_instance_count = (
+ "Found the expected number of instances when sorting by highest weight",
+ "Found an unexpected number of instances when sorting by highest weight"
+ )
+ lowest_weight_instance_count = (
+ "Found the expected number of instances when sorting by lowest weight",
+ "Found an unexpected number of instances when sorting by lowest weight"
+ )
-class TestAssetWeightSelectorSortByWeight(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="AssetWeightSelector_SortByWeight", args=["level"])
+def AssetWeightSelector_InstancesExpressBasedOnWeight():
+ """
+ Summary:
+ Vegetation areas using weight selectors properly distribute instances according to Sort By Weight setting
- def run_test(self):
- """
- Summary:
- Vegetation areas using weight selectors properly distribute instances according to Sort By Weight setting
+ Expected Behavior:
+ Vegetation is planted in the area according to the generated gradient pattern.
+ Higher weight assets are more likely to express when "Descending (highest first)" is selected.
+ Lower weight assets are more likely to express when "Ascending (lowest first)" is selected.
- Expected Behavior:
- Vegetation is planted in the area according to the generated gradient pattern.
- Higher weight assets are more likely to express when "Descending (highest first)" is selected.
- Lower weight assets are more likely to express when "Ascending (lowest first)" is selected.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create instance spawner with 2 descriptors, one with an Empty Asset
+ 3) Create a planting surface
+ 4) Create a child entity of the instance spawner with a Constant Gradient component with default values (1.0)
+ 5) Pin the child entity to Vegetation Asset Weight Selector of the instance spawner entity
+ 6) Set first descriptor to a higher weight, and toggle off Allow Empty Assets
+ 7) Validate instance count with initial setup/sort values
+ 8) Change sort values and validate instance count
- Test Steps:
- 1) Create new level
- 2) Create instance spawner with 2 descriptors, one with an Empty Asset
- 3) Create a planting surface
- 4) Create a child entity of the instance spawner with a Constant Gradient component with default values (1.0)
- 5) Pin the child entity to Vegetation Asset Weight Selector of the instance spawner entity
- 6) Set first descriptor to a higher weight, and toggle off Allow Empty Assets
- 7) Validate instance count with initial setup/sort values
- 8) Change sort values and validate instance count
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import os
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # 2) Create a new instance spawner entity with multiple Dynamic Slice Instance Spawner descriptors, one set to a
- # valid slice entity, and one set to None
- spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
- desc_asset = hydra.get_component_property_value(spawner_entity.components[2],
- "Configuration|Embedded Assets")[0]
- desc_list = [desc_asset, desc_asset]
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets", desc_list)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[1]|Instance|Slice Asset", None)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Add an Asset Weight Selector component to the spawner entity
- spawner_entity.add_component("Vegetation Asset Weight Selector")
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Create a planting surface
- dynveg.create_surface_entity("Planting Surface", spawner_center_point, 32.0, 32.0, 1.0)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 4) Create a child entity of the spawner entity with a Constant Gradient component
- components_to_add = ["Constant Gradient"]
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
+ # 2) Create a new instance spawner entity with multiple Dynamic Slice Instance Spawner descriptors, one set to a
+ # valid slice entity, and one set to None
+ spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
+ desc_asset = hydra.get_component_property_value(spawner_entity.components[2],
+ "Configuration|Embedded Assets")[0]
+ desc_list = [desc_asset, desc_asset]
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets", desc_list)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[1]|Instance|Slice Asset", None)
- # 5) Pin the Constant Gradient to the Vegetation Asset Weight Selector
- spawner_entity.get_set_test(3, 'Configuration|Gradient|Gradient Entity Id', gradient_entity.id)
+ # Add an Asset Weight Selector component to the spawner entity
+ spawner_entity.add_component("Vegetation Asset Weight Selector")
- # 6) Set the first descriptor weight to a higher value and toggle off Allow Empty Assets on the Layer Spawner
- # component
- spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Weight', 50)
- spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
+ # 3) Create a planting surface
+ dynveg.create_surface_entity("Planting Surface", spawner_center_point, 32.0, 32.0, 1.0)
- # 7) Query for expected instances with default settings. We should have 0 instances with default Constant
- # Gradient setup sorting by higher weight first
- num_expected = 0
- initial_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = initial_success and self.test_success
+ # 4) Create a child entity of the spawner entity with a Constant Gradient component
+ components_to_add = ["Constant Gradient"]
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
- # 8) Sort by lowest weight first, and verify instance counts. We should now have 400 instances as the highest
- # priority instance won't be allowed to claim space due to "Allow Empty Assets" being False
- spawner_entity.get_set_test(3, 'Configuration|Sort By Weight', 1)
- num_expected = 20 * 20
- final_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = final_success and self.test_success
+ # 5) Pin the Constant Gradient to the Vegetation Asset Weight Selector
+ spawner_entity.get_set_test(3, 'Configuration|Gradient|Gradient Entity Id', gradient_entity.id)
+
+ # 6) Set the first descriptor weight to a higher value and toggle off Allow Empty Assets on the Layer Spawner
+ # component
+ spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Weight', 50)
+ spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
+
+ # 7) Query for expected instances with default settings. We should have 0 instances with default Constant
+ # Gradient setup sorting by higher weight first
+ num_expected = 0
+ initial_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.highest_weight_instance_count, initial_success)
+
+ # 8) Sort by lowest weight first, and verify instance counts. We should now have 400 instances as the highest
+ # priority instance won't be allowed to claim space due to "Allow Empty Assets" being False
+ spawner_entity.get_set_test(3, 'Configuration|Sort By Weight', 1)
+ num_expected = 20 * 20
+ final_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.lowest_weight_instance_count, final_success)
-test = TestAssetWeightSelectorSortByWeight()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(AssetWeightSelector_InstancesExpressBasedOnWeight)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius.py
index 12fb6d8eff..c2adffc6ca 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius.py
@@ -5,108 +5,118 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_counts = (
+ "Initial instance counts are as expected",
+ "Unexpected number of initial instances found"
+ )
+ instance_counts_1m = (
+ "Instance counts with 1 meters between instances are as expected",
+ "Unexpected number of instances found with 1 meters between instances"
+ )
+ instance_counts_2m = (
+ "Instance counts with 2 meters between instances are as expected",
+ "Unexpected number of instances found with 2 meters between instances"
+ )
+ instance_counts_16m = (
+ "Instance counts with 16 meters between instances are as expected",
+ "Unexpected number of instances found with 16 meters between instances"
+ )
-class TestDistanceBetweenFilterComponentOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="DistanceBetweenFilterComponentOverrides", args=["level"])
+def DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius():
+ """
+ Summary: Creates a level with a simple vegetation area. A Vegetation Distance Between Filter is
+ added and the min radius is changed as an override on the descriptor. Instance counts at specific points are
+ validated.
- def run_test(self):
- """
- Summary: Creates a level with a simple vegetation area. A Vegetation Distance Between Filter is
- added and the min radius is changed as an override on the descriptor. Instance counts at specific points are
- validated.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create a vegetation area
+ 3) Create a surface for planting
+ 4) Add the Vegetation System Settings component and setup for the test
+ 5-8) Add the Distance Between Filter, setup overrides on both the component and descriptor, and validate
+ expected instance counts with a few different Radius values
- Test Steps:
- 1) Create a new level
- 2) Create a vegetation area
- 3) Create a surface for planting
- 4) Add the Vegetation System Settings component and setup for the test
- 5-8) Add the Distance Between Filter, setup overrides on both the component and descriptor, and validate
- expected instance counts with a few different Radius values
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- instance_query_point_a = math.Vector3(512.5, 512.5, 32.0)
- instance_query_point_b = math.Vector3(514.0, 512.5, 32.0)
- instance_query_point_c = math.Vector3(515.0, 512.5, 32.0)
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- general.set_current_view_position(512.0, 480.0, 38.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components
- spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
- asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
+ instance_query_point_a = math.Vector3(512.5, 512.5, 32.0)
+ instance_query_point_b = math.Vector3(514.0, 512.5, 32.0)
+ instance_query_point_c = math.Vector3(515.0, 512.5, 32.0)
- # 3) Create a surface to plant on
- surface_center_point = math.Vector3(512.0, 512.0, 32.0)
- dynveg.create_surface_entity("Planting Surface", surface_center_point, 128.0, 128.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Density', 16)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # 5) Add a Vegetation Distance Between Filter, toggle overrides on both the component and descriptor,
- # and verify initial instance counts are accurate
- spawner_entity.add_component("Vegetation Distance Between Filter")
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Override Enabled", True)
- num_expected = 16 * 16
- initial_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and initial_success
+ # 2) Create a new entity with required vegetation area components
+ spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
+ asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
- # 6) Change Radius Min to 1.0, refresh, and verify instance counts are accurate
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 1.0)
- point_a_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
- point_b_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
- point_c_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 1), 5.0)
- self.test_success = self.test_success and point_a_success and point_b_success and point_c_success
+ # 3) Create a surface to plant on
+ surface_center_point = math.Vector3(512.0, 512.0, 32.0)
+ dynveg.create_surface_entity("Planting Surface", surface_center_point, 128.0, 128.0, 1.0)
- # 7) Change Radius Min to 2.0, refresh, and verify instance counts are accurate
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 2.0)
- point_a_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
- point_b_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
- point_c_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 0), 5.0)
- self.test_success = self.test_success and point_a_success and point_b_success and point_c_success
+ # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Density', 16)
- # 8) Change Radius Min to 16.0, refresh, and verify instance counts are accurate, only a single instance should plant
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 16.0)
- num_expected_instances = 1
- final_check_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and final_check_success
+ # 5) Add a Vegetation Distance Between Filter, toggle overrides on both the component and descriptor,
+ # and verify initial instance counts are accurate
+ spawner_entity.add_component("Vegetation Distance Between Filter")
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Override Enabled", True)
+ num_expected = 16 * 16
+ initial_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.initial_instance_counts, initial_success)
+
+ # 6) Change Radius Min to 1.0, refresh, and verify instance counts are accurate
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 1.0)
+ point_a_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
+ point_b_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
+ point_c_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 1), 5.0)
+ Report.result(Tests.instance_counts_1m, point_a_success and point_b_success and point_c_success)
+
+ # 7) Change Radius Min to 2.0, refresh, and verify instance counts are accurate
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 2.0)
+ point_a_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
+ point_b_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
+ point_c_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 0), 5.0)
+ Report.result(Tests.instance_counts_2m, point_a_success and point_b_success and point_c_success)
+
+ # 8) Change Radius Min to 16.0, refresh, and verify instance counts are accurate, only a single instance should plant
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min", 16.0)
+ num_expected_instances = 1
+ final_check_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(Tests.instance_counts_16m, final_check_success)
-test = TestDistanceBetweenFilterComponentOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius.py
index 5b978c6212..de0d9a14fe 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius.py
@@ -5,104 +5,113 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_counts = (
+ "Initial instance counts are as expected",
+ "Unexpected number of initial instances found"
+ )
+ instance_counts_1m = (
+ "Instance counts with 1 meters between instances are as expected",
+ "Unexpected number of instances found with 1 meters between instances"
+ )
+ instance_counts_2m = (
+ "Instance counts with 2 meters between instances are as expected",
+ "Unexpected number of instances found with 2 meters between instances"
+ )
+ instance_counts_16m = (
+ "Instance counts with 16 meters between instances are as expected",
+ "Unexpected number of instances found with 16 meters between instances"
+ )
-class TestDistanceBetweenFilterComponent(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="DistanceBetweenFilterComponent", args=["level"])
+def DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius():
+ """
+ Summary: Creates a level with a simple vegetation area. A Vegetation Distance Between Filter is
+ added and the min radius is changed. Instance counts at specific points are validated.
- def run_test(self):
- """
- Summary: Creates a level with a simple vegetation area. A Vegetation Distance Between Filter is
- added and the min radius is changed. Instance counts at specific points are validated.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create a vegetation area
+ 3) Create a surface for planting
+ 4) Add the Vegetation System Settings component and setup for the test
+ 5-8) Add the Distance Between Filter, and validate expected instance counts with a few different Radius values
- Test Steps:
- 1) Create a new level
- 2) Create a vegetation area
- 3) Create a surface for planting
- 4) Add the Vegetation System Settings component and setup for the test
- 5-8) Add the Distance Between Filter, and validate expected instance counts with a few different Radius values
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- instance_query_point_a = math.Vector3(512.5, 512.5, 32.0)
- instance_query_point_b = math.Vector3(514.0, 512.5, 32.0)
- instance_query_point_c = math.Vector3(515.0, 512.5, 32.0)
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- general.set_current_view_position(512.0, 480.0, 38.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components
- spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
- asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
+ instance_query_point_a = math.Vector3(512.5, 512.5, 32.0)
+ instance_query_point_b = math.Vector3(514.0, 512.5, 32.0)
+ instance_query_point_c = math.Vector3(515.0, 512.5, 32.0)
- # 3) Create a surface to plant on
- surface_center_point = math.Vector3(512.0, 512.0, 32.0)
- dynveg.create_surface_entity("Planting Surface", surface_center_point, 128.0, 128.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Density', 16)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # 5) Add a Vegetation Distance Between Filter and verify initial instance counts are accurate
- spawner_entity.add_component("Vegetation Distance Between Filter")
- num_expected = 16 * 16
- num_expected = 16 * 16
- initial_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and initial_success
+ # 2) Create a new entity with required vegetation area components
+ spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
+ asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
- # 6) Change Radius Min to 1.0, refresh, and verify instance counts are accurate
- spawner_entity.get_set_test(3, "Configuration|Radius Min", 1.0)
- point_a_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
- point_b_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
- point_c_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 1), 5.0)
- self.test_success = self.test_success and point_a_success and point_b_success and point_c_success
+ # 3) Create a surface to plant on
+ surface_center_point = math.Vector3(512.0, 512.0, 32.0)
+ dynveg.create_surface_entity("Planting Surface", surface_center_point, 128.0, 128.0, 1.0)
- # 7) Change Radius Min to 2.0, refresh, and verify instance counts are accurate
- spawner_entity.get_set_test(3, "Configuration|Radius Min", 2.0)
- point_a_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
- point_b_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
- point_c_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 0), 5.0)
- self.test_success = self.test_success and point_a_success and point_b_success and point_c_success
+ # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Density', 16)
- # 8) Change Radius Min to 16.0, refresh, and verify instance counts are accurate
- spawner_entity.get_set_test(3, "Configuration|Radius Min", 16.0)
- num_expected_instances = 1
- final_check_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = final_check_success and self.test_success
+ # 5) Add a Vegetation Distance Between Filter and verify initial instance counts are accurate
+ spawner_entity.add_component("Vegetation Distance Between Filter")
+ num_expected = 16 * 16
+ initial_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.initial_instance_counts, initial_success)
+
+ # 6) Change Radius Min to 1.0, refresh, and verify instance counts are accurate
+ spawner_entity.get_set_test(3, "Configuration|Radius Min", 1.0)
+ point_a_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
+ point_b_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
+ point_c_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 1), 5.0)
+ Report.result(Tests.instance_counts_1m, point_a_success and point_b_success and point_c_success)
+
+ # 7) Change Radius Min to 2.0, refresh, and verify instance counts are accurate
+ spawner_entity.get_set_test(3, "Configuration|Radius Min", 2.0)
+ point_a_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_a, 0.5, 1), 5.0)
+ point_b_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_b, 0.5, 0), 5.0)
+ point_c_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(instance_query_point_c, 0.5, 0), 5.0)
+ Report.result(Tests.instance_counts_2m, point_a_success and point_b_success and point_c_success)
+
+ # 8) Change Radius Min to 16.0, refresh, and verify instance counts are accurate
+ spawner_entity.get_set_test(3, "Configuration|Radius Min", 16.0)
+ num_expected_instances = 1
+ final_check_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(Tests.instance_counts_16m, final_check_success)
-test = TestDistanceBetweenFilterComponent()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks.py
index 173be62829..fcbf85c59d 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks.py
@@ -5,140 +5,178 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class BehaviorContextTests:
+ spawner_initialized = (
+ "Successfully initialized a Dynamic Slice Instance Spawner",
+ "Failed to initialize a Dynamic Slice Instance Spawner"
+ )
+ spawner_slice_asset_path_set = (
+ "Successfully set a Dynamic Slice asset path",
+ "Failed to set a Dynamic Slice asset path"
+ )
+ spawner_empty_slice_asset_path_set = (
+ "Successfully set an empty Dynamic Slice asset path",
+ "Failed to set an empty Dynamic Slice asset path"
+ )
+ desc_spawnertype_sets_spawner = (
+ "Setting spawnerType sets spawner too",
+ "Setting spawnerType failed to set spawner to expected value"
+ )
+ desc_spawner_sets_spawnertype = (
+ "Setting spawner sets spawnerType too",
+ "Setting spawner failed to set spawnerType to expected value"
+ )
-class TestDynamicSliceInstanceSpawner(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="DynamicSliceInstanceSpawner", args=["level"])
+class PropertyTreeTests:
+ entity_created = (
+ "Spawner entity created successfully",
+ "Failed to create spawner entity"
+ )
+ spawner_type_set = (
+ "Successfully set spawner type",
+ "Failed to set spawner type"
+ )
+ empty_instance_count_validation = (
+ "Expected number of empty instances planted",
+ "Unexpected number of empty instances planted"
+ )
+ no_instances_when_empty_disallowed = (
+ "No empty instances found when Empty Assets are not allowed",
+ "Unexpectedly found empty instances when Empty Assets are not allowed"
+ )
+ nonempty_asset_instance_count_validation = (
+ "Expected number of instances planted",
+ "Unexpected number of instances planted"
+ )
- def run_test(self):
- """
- Summary:
- Test aspects of the DynamicSliceInstanceSpawner through the BehaviorContext and the Property Tree.
- :return: None
- """
- # 1) Open an empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+def DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks():
+ """
+ Summary:
+ Test aspects of the DynamicSliceInstanceSpawner through the BehaviorContext and the Property Tree.
+
+ :return: None
+ """
+
+ import os
+
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
+
+ # Grab the UUID that we need for creating an Dynamic Slice Instance Spawner
+ dynamic_slice_spawner_uuid = azlmbr.math.Uuid_CreateString('{BBA5CC1E-B4CA-4792-89F7-93711E98FBD1}', 0)
+
+ # Grab a path to a test dynamic slice asset
+ test_slice_asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+
+ # 2) Test DynamicSliceInstanceSpawner BehaviorContext
+ behavior_context_test_success = True
+ dynamic_slice_spawner = azlmbr.vegetation.DynamicSliceInstanceSpawner()
+ behavior_context_test_success = behavior_context_test_success and (dynamic_slice_spawner is not None)
+ behavior_context_test_success = behavior_context_test_success and (dynamic_slice_spawner.typename == 'DynamicSliceInstanceSpawner')
+ Report.critical_result(BehaviorContextTests.spawner_initialized, behavior_context_test_success)
+ # Try to get/set the slice asset path with a valid asset
+ dynamic_slice_spawner.SetSliceAssetPath(test_slice_asset_path)
+ validate_path = dynamic_slice_spawner.GetSliceAssetPath()
+ # We expect the path to get lowercased and normalized with a forward slash, so we compare our result
+ # vs that instead of directly against test_slice_asset_path.
+ behavior_context_test_success = behavior_context_test_success and hydra.compare_values('slices/pinkflower.dynamicslice', validate_path, 'GetSliceAssetPath - valid')
+ Report.result(BehaviorContextTests.spawner_slice_asset_path_set, behavior_context_test_success)
+ # Try to get/set the slice asset path with an empty path
+ dynamic_slice_spawner.SetSliceAssetPath('')
+ validate_path = dynamic_slice_spawner.GetSliceAssetPath()
+ behavior_context_test_success = behavior_context_test_success and hydra.compare_values('', validate_path, 'GetSliceAssetPath - empty')
+ Report.result(BehaviorContextTests.spawner_empty_slice_asset_path_set, behavior_context_test_success)
+ Report.info(f'DynamicSliceInstanceSpawner() BehaviorContext test: {behavior_context_test_success}')
+
+ # 3) Test Descriptor BehaviorContext - setting spawnerType sets spawner too
+ spawner_type_test_success = True
+ descriptor = azlmbr.vegetation.Descriptor()
+ spawner_type_test_success = spawner_type_test_success and hydra.get_set_property_test(descriptor, 'spawnerType', dynamic_slice_spawner_uuid)
+ spawner_type_test_success = spawner_type_test_success and (descriptor.spawner.typename == 'DynamicSliceInstanceSpawner')
+ Report.result(BehaviorContextTests.desc_spawnertype_sets_spawner, spawner_type_test_success)
+ Report.info(f'Descriptor() BehaviorContext spawnerType test: {spawner_type_test_success}')
+
+ # 4) Test Descriptor BehaviorContext - setting spawner sets spawnerType too
+ spawner_test_success = True
+ descriptor = azlmbr.vegetation.Descriptor()
+ descriptor.spawner = dynamic_slice_spawner
+ spawner_test_success = spawner_test_success and (descriptor.spawnerType.Equal(dynamic_slice_spawner_uuid))
+ spawner_test_success = spawner_test_success and (descriptor.spawner.typename == 'DynamicSliceInstanceSpawner')
+ Report.result(BehaviorContextTests.desc_spawner_sets_spawnertype, spawner_test_success)
+ Report.info(f'Descriptor() BehaviorContext spawner test: {spawner_test_success}')
+
+ ### Setup for Property Tree set of tests
+
+ # Create a new entity with required vegetation area components
+ spawner_entity = hydra.Entity("Veg Area")
+ spawner_entity.create_entity(
+ math.Vector3(512.0, 512.0, 32.0),
+ ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List"]
)
- general.idle_wait(1.0)
- general.set_current_view_position(512.0, 480.0, 38.0)
+ Report.critical_result(PropertyTreeTests.entity_created, spawner_entity.id.IsValid())
- # Grab the UUID that we need for creating an Dynamic Slice Instance Spawner
- dynamic_slice_spawner_uuid = azlmbr.math.Uuid_CreateString('{BBA5CC1E-B4CA-4792-89F7-93711E98FBD1}', 0)
+ # Resize the Box Shape component
+ new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
+ box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
+ spawner_entity.get_set_test(1, box_dimensions_path, new_box_dimensions)
- # Grab a path to a test dynamic slice asset
- test_slice_asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ # Create a surface to plant on
+ dynveg.create_surface_entity("Surface Entity", math.Vector3(512.0, 512.0, 32.0), 1024.0, 1024.0, 1.0)
- # 2) Test DynamicSliceInstanceSpawner BehaviorContext
- behavior_context_test_success = True
- dynamic_slice_spawner = azlmbr.vegetation.DynamicSliceInstanceSpawner()
- behavior_context_test_success = behavior_context_test_success and (dynamic_slice_spawner is not None)
- behavior_context_test_success = behavior_context_test_success and (dynamic_slice_spawner.typename == 'DynamicSliceInstanceSpawner')
- # Try to get/set the slice asset path with a valid asset
- dynamic_slice_spawner.SetSliceAssetPath(test_slice_asset_path)
- validate_path = dynamic_slice_spawner.GetSliceAssetPath()
- # We expect the path to get lowercased and normalized with a forward slash, so we compare our result
- # vs that instead of directly against test_slice_asset_path.
- behavior_context_test_success = behavior_context_test_success and hydra.compare_values('slices/pinkflower.dynamicslice', validate_path, 'GetSliceAssetPath - valid')
- # Try to get/set the slice asset path with an empty path
- dynamic_slice_spawner.SetSliceAssetPath('')
- validate_path = dynamic_slice_spawner.GetSliceAssetPath()
- behavior_context_test_success = behavior_context_test_success and hydra.compare_values('', validate_path, 'GetSliceAssetPath - empty')
- self.test_success = self.test_success and behavior_context_test_success
- self.log(f'DynamicSliceInstanceSpawner() BehaviorContext test: {behavior_context_test_success}')
+ # 5) Descriptor Property Tree test: spawner type can be set
- # 3) Test Descriptor BehaviorContext - setting spawnerType sets spawner too
- spawner_type_test_success = True
- descriptor = azlmbr.vegetation.Descriptor()
- spawner_type_test_success = spawner_type_test_success and hydra.get_set_property_test(descriptor, 'spawnerType', dynamic_slice_spawner_uuid)
- spawner_type_test_success = spawner_type_test_success and (descriptor.spawner.typename == 'DynamicSliceInstanceSpawner')
- self.test_success = self.test_success and spawner_type_test_success
- self.log(f'Descriptor() BehaviorContext spawnerType test: {spawner_type_test_success}')
+ # - Validate the dynamic slice spawner type can be set correctly.
+ property_tree_success = True
+ property_tree_success = property_tree_success and spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Instance Spawner', dynamic_slice_spawner_uuid)
+ Report.result(PropertyTreeTests.spawner_type_set, property_tree_success)
- # 4) Test Descriptor BehaviorContext - setting spawner sets spawnerType too
- spawner_test_success = True
- descriptor = azlmbr.vegetation.Descriptor()
- descriptor.spawner = dynamic_slice_spawner
- spawner_test_success = spawner_test_success and (descriptor.spawnerType.Equal(dynamic_slice_spawner_uuid))
- spawner_test_success = spawner_test_success and (descriptor.spawner.typename == 'DynamicSliceInstanceSpawner')
- self.test_success = self.test_success and spawner_test_success
- self.log(f'Descriptor() BehaviorContext spawner test: {spawner_test_success}')
+ # This should result in 400 instances, since our box is 16 m x 16 m and by default the veg system plants
+ # 20 instances per 16 meters
+ spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', True)
+ num_expected_instances = 20 * 20
+ property_tree_success = property_tree_success and helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(PropertyTreeTests.empty_instance_count_validation, property_tree_success)
+ Report.info(f'Property Tree spawner type test: {property_tree_success}')
- ### Setup for Property Tree set of tests
+ # 6) Validate that the "Allow Empty Assets" setting affects the DynamicSliceInstanceSpawner
+ allow_empty_assets_success = True
+ # Since we have an empty slice path, we should have 0 instances once we disable 'Allow Empty Assets'
+ num_expected_instances = 0
+ allow_empty_assets_success = allow_empty_assets_success and spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
+ Report.info('Allow Empty Assets test:')
+ allow_empty_assets_success = allow_empty_assets_success and helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(PropertyTreeTests.no_instances_when_empty_disallowed, allow_empty_assets_success)
+ Report.info(f'Allow Empty Assets test: {allow_empty_assets_success}')
- # Create a new entity with required vegetation area components
- spawner_entity = hydra.Entity("Veg Area")
- spawner_entity.create_entity(
- math.Vector3(512.0, 512.0, 32.0),
- ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List"]
- )
- if (spawner_entity.id.IsValid()):
- self.log(f"'{spawner_entity.name}' created")
-
- # Resize the Box Shape component
- new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
- box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
- spawner_entity.get_set_test(1, box_dimensions_path, new_box_dimensions)
-
- # Create a surface to plant on
- dynveg.create_surface_entity("Surface Entity", math.Vector3(512.0, 512.0, 32.0), 1024.0, 1024.0, 1.0)
-
- # 5) Descriptor Property Tree test: spawner type can be set
-
- # - Validate the dynamic slice spawner type can be set correctly.
- property_tree_success = True
- property_tree_success = property_tree_success and spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Instance Spawner', dynamic_slice_spawner_uuid)
-
- # This should result in 400 instances, since our box is 16 m x 16 m and by default the veg system plants
- # 20 instances per 16 meters
- spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', True)
- num_expected_instances = 20 * 20
- property_tree_success = property_tree_success and self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and property_tree_success
- self.log(f'Property Tree spawner type test: {property_tree_success}')
-
- # 6) Validate that the "Allow Empty Assets" setting affects the DynamicSliceInstanceSpawner
- allow_empty_assets_success = True
- # Since we have an empty slice path, we should have 0 instances once we disable 'Allow Empty Assets'
- num_expected_instances = 0
- allow_empty_assets_success = allow_empty_assets_success and spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
- self.log('Allow Empty Assets test:')
- allow_empty_assets_success = allow_empty_assets_success and self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and allow_empty_assets_success
- self.log(f'Allow Empty Assets test: {allow_empty_assets_success}')
-
- # 7) Validate that with 'Allow Empty Assets' set to False, a non-empty slice asset gives us the number
- # of instances we expect.
- spawns_slices_success = True
- num_expected_instances = 20 * 20
- dynamic_slice_spawner.SetSliceAssetPath(test_slice_asset_path)
- spawns_slices_success = spawns_slices_success and spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
- descriptor = hydra.get_component_property_value(spawner_entity.components[2], 'Configuration|Embedded Assets|[0]')
- descriptor.spawner = dynamic_slice_spawner
- spawns_slices_success = spawns_slices_success and spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
- self.log('Spawn dynamic slices test:')
- spawns_slices_success = spawns_slices_success and self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and spawns_slices_success
- self.log(f'Spawn dynamic slices test: {spawns_slices_success}')
+ # 7) Validate that with 'Allow Empty Assets' set to False, a non-empty slice asset gives us the number
+ # of instances we expect.
+ spawns_slices_success = True
+ num_expected_instances = 20 * 20
+ dynamic_slice_spawner.SetSliceAssetPath(test_slice_asset_path)
+ spawns_slices_success = spawns_slices_success and spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
+ descriptor = hydra.get_component_property_value(spawner_entity.components[2], 'Configuration|Embedded Assets|[0]')
+ descriptor.spawner = dynamic_slice_spawner
+ spawns_slices_success = spawns_slices_success and spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
+ Report.info('Spawn dynamic slices test:')
+ spawns_slices_success = spawns_slices_success and helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(PropertyTreeTests.nonempty_asset_instance_count_validation, spawns_slices_success)
+ Report.info(f'Spawn dynamic slices test: {spawns_slices_success}')
-test = TestDynamicSliceInstanceSpawner()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py
index 6f2e711387..e51be58ec6 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py
@@ -5,99 +5,115 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.asset as asset
-import azlmbr.components as components
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.entity as entity
-import azlmbr.editor as editor
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ level_created = (
+ "Successfully created level",
+ "Failed to create level"
+ )
+ spawner_entity_created = (
+ "Spawner entity created successfully",
+ "Failed to create spawner entity"
+ )
+ surface_entity_created = (
+ "Surface entity created successfully",
+ "Failed to create surface entity"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Found an unexpected number of instances"
+ )
+ saved_and_exported = (
+ "Saved and exported level successfully",
+ "Failed to save and export level"
+ )
-class TestDynamicSliceInstanceSpawnerEmbeddedEditor(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="DynamicSliceInstanceSpawnerEmbeddedEditor", args=["level"])
+def DynamicSliceInstanceSpawner_Embedded_E2E():
+ """
+ Summary:
+ A new temporary level is created. Surface for planting is created. Simple vegetation area is created using
+ Dynamic Slice Instance Spawner type.
- def run_test(self):
- """
- Summary:
- A new temporary level is created. Surface for planting is created. Simple vegetation area is created using
- Dynamic Slice Instance Spawner type.
+ Expected Behavior:
+ Instances plant as expected in the assigned area.
- Expected Behavior:
- Instances plant as expected in the assigned area.
+ Test Steps:
+ 1) Create level
+ 2) Create a Vegetation Layer Spawner setup using Dynamic Slice Instance Spawner type assets
+ 3) Create a surface to plant on
+ 4) Verify expected instance counts
+ 5) Add a camera component looking at the planting area for visual debugging
+ 6) Save and export to engine
- Test Steps:
- 1) Create level
- 2) Create a Vegetation Layer Spawner setup using Dynamic Slice Instance Spawner type assets
- 3) Create a surface to plant on
- 4) Verify expected instance counts
- 5) Add a camera component looking at the planting area for visual debugging
- 6) Save and export to engine
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.asset as asset
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.entity as entity
+ import azlmbr.math as math
+ import azlmbr.paths as paths
- general.set_current_view_position(512.0, 480.0, 38.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0, asset_path)
- spawner_entity.add_component("Script Canvas")
- instance_counter_path = os.path.join("scriptcanvas", "instance_counter.scriptcanvas")
- instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
- math.Uuid(), False)
- spawner_entity.get_set_test(3, "Script Canvas Asset|Script Canvas Asset", instance_counter_script)
+ # 1) Create a new, temporary level
+ lvl_name = "tmp_level"
+ helper.init_idle()
+ level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
+ general.idle_wait(1.0)
+ Report.critical_result(Tests.level_created, level_created == 0)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # 3) Create a surface to plant on
- dynveg.create_surface_entity("Planting Surface", center_point, 128.0, 128.0, 1.0)
+ # 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0, asset_path)
+ spawner_entity.add_component("Script Canvas")
+ instance_counter_path = os.path.join("scriptcanvas", "instance_counter.scriptcanvas")
+ instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
+ math.Uuid(), False)
+ spawner_entity.get_set_test(3, "Script Canvas Asset|Script Canvas Asset", instance_counter_script)
+ Report.result(Tests.spawner_entity_created, spawner_entity.id.IsValid() and hydra.has_components(spawner_entity.id,
+ ["Script Canvas"]))
- # 4) Verify instance counts are accurate
- general.idle_wait(3.0) # Allow a few seconds for instances to spawn
- num_expected_instances = 20 * 20
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- num_found = azlmbr.areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ # 3) Create a surface to plant on
+ surface_entity = dynveg.create_surface_entity("Planting Surface", center_point, 128.0, 128.0, 1.0)
+ Report.result(Tests.surface_entity_created, surface_entity.id.IsValid())
- # 5) Move the default Camera entity for testing in the launcher
- cam_position = math.Vector3(512.0, 500.0, 35.0)
- search_filter = entity.SearchFilter()
- search_filter.names = ["Camera"]
- search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
- components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
+ # 4) Verify instance counts are accurate
+ num_expected_instances = 20 * 20
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 5.0)
+ Report.result(Tests.instance_count, success)
- # 6) Save and export to engine
- general.save_level()
- general.idle_wait(1.0)
- general.export_to_engine()
- general.idle_wait(1.0)
+ # 5) Move the default Camera entity for testing in the launcher
+ cam_position = math.Vector3(512.0, 500.0, 35.0)
+ search_filter = entity.SearchFilter()
+ search_filter.names = ["Camera"]
+ search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
+ components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
+
+ # 6) Save and export to engine
+ general.save_level()
+ general.export_to_engine()
+ pak_path = os.path.join(paths.devroot, "AutomatedTesting", "cache", "pc", "levels", lvl_name, "level.pak")
+ Report.result(Tests.saved_and_exported, os.path.exists(pak_path))
-test = TestDynamicSliceInstanceSpawnerEmbeddedEditor()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(DynamicSliceInstanceSpawner_Embedded_E2E)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py
index dfa4d8191e..7a0abdd969 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py
@@ -5,122 +5,137 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.legacy.general as general
-import azlmbr.asset as asset
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.entity as entity
-import azlmbr.editor as editor
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ level_created = (
+ "Successfully created level",
+ "Failed to create level"
+ )
+ spawner_entity_created = (
+ "Spawner entity created successfully",
+ "Failed to create spawner entity"
+ )
+ surface_entity_created = (
+ "Surface entity created successfully",
+ "Failed to create surface entity"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Found an unexpected number of instances"
+ )
+ saved_and_exported = (
+ "Saved and exported level successfully",
+ "Failed to save and export level"
+ )
-class TestDynamicSliceInstanceSpawnerExternalEditor(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="DynamicSliceInstanceSpawnerExternalEditor", args=["level"])
+def DynamicSliceInstanceSpawner_External_E2E():
+ """
+ Summary:
+ A new temporary level is created. Surface for planting is created. Simple vegetation area is created using
+ Dynamic Slice Instance Spawner type using external assets.
- def run_test(self):
- """
- Summary:
- A new temporary level is created. Surface for planting is created. Simple vegetation area is created using
- Dynamic Slice Instance Spawner type using external assets.
+ Expected Behavior:
+ Instances plant as expected in the assigned area.
- Expected Behavior:
- Instances plant as expected in the assigned area.
+ Test Steps:
+ 1) Create level
+ 2) Create a Vegetation Layer Spawner setup using Dynamic Slice Instance Spawner type assets
+ 3) Create a surface to plant on
+ 4) Verify expected instance counts
+ 5) Add a camera component looking at the planting area for visual debugging
+ 6) Save and export to engine
- Test Steps:
- 1) Create level
- 2) Create a Vegetation Layer Spawner setup using Dynamic Slice Instance Spawner type assets
- 3) Create a surface to plant on
- 4) Verify expected instance counts
- 5) Add a camera component looking at the planting area for visual debugging
- 6) Save and export to engine
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.asset as asset
+ import azlmbr.components as components
+ import azlmbr.editor as editor
+ import azlmbr.entity as entity
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
+ import azlmbr.paths as paths
- general.set_current_view_position(512.0, 480.0, 38.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components and switch the Vegetation Asset List Source
- # Type to External
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- veg_area_required_components = ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List",
- "Script Canvas"]
- new_entity_id = editor.ToolsApplicationRequestBus(
- bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId()
- )
- if new_entity_id.IsValid():
- self.log("Spawner entity created")
- spawner_entity = hydra.Entity("Spawner Entity", new_entity_id)
- spawner_entity.components = []
- for component in veg_area_required_components:
- spawner_entity.components.append(hydra.add_component(component, new_entity_id))
- hydra.get_set_test(spawner_entity, 2, "Configuration|Source Type", 1)
+ # 1) Create a new, temporary level
+ lvl_name = "tmp_level"
+ helper.init_idle()
+ level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
+ general.idle_wait(1.0)
+ Report.critical_result(Tests.level_created, level_created == 0)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add a Script Canvas component with instance_counter script for launcher tests
- instance_counter_path = os.path.join("scriptcanvas", "instance_counter.scriptcanvas")
- instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
- math.Uuid(), False)
- spawner_entity.get_set_test(3, "Script Canvas Asset|Script Canvas Asset", instance_counter_script)
+ # 2) Create a new entity with required vegetation area components and switch the Vegetation Asset List Source
+ # Type to External
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ veg_area_required_components = ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List",
+ "Script Canvas"]
+ new_entity_id = editor.ToolsApplicationRequestBus(
+ bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId()
+ )
+ spawner_entity = hydra.Entity("Spawner Entity", new_entity_id)
+ spawner_entity.components = []
+ for component in veg_area_required_components:
+ spawner_entity.components.append(hydra.add_component(component, new_entity_id))
+ hydra.get_set_test(spawner_entity, 2, "Configuration|Source Type", 1)
- # Assign a Vegetation Descriptor List asset to the Vegetation Asset List component
- descriptor_asset = asset.AssetCatalogRequestBus(
- bus.Broadcast, "GetAssetIdByPath", os.path.join("Assets", "VegDescriptorLists", "flower_pink.vegdescriptorlist"), math.Uuid(),
- False)
- hydra.get_set_test(spawner_entity, 2, "Configuration|External Assets", descriptor_asset)
+ # Add a Script Canvas component with instance_counter script for launcher tests
+ instance_counter_path = os.path.join("scriptcanvas", "instance_counter.scriptcanvas")
+ instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
+ math.Uuid(), False)
+ spawner_entity.get_set_test(3, "Script Canvas Asset|Script Canvas Asset", instance_counter_script)
+ Report.result(Tests.spawner_entity_created, spawner_entity.id.IsValid() and hydra.has_components(spawner_entity.id,
+ ["Script Canvas"]))
- # Resize the Box Shape component
- new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
- box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
- hydra.get_set_test(spawner_entity, 1, box_dimensions_path, new_box_dimensions)
+ # Assign a Vegetation Descriptor List asset to the Vegetation Asset List component
+ descriptor_asset = asset.AssetCatalogRequestBus(
+ bus.Broadcast, "GetAssetIdByPath", os.path.join("Assets", "VegDescriptorLists", "flower_pink.vegdescriptorlist"), math.Uuid(),
+ False)
+ hydra.get_set_test(spawner_entity, 2, "Configuration|External Assets", descriptor_asset)
- # 3) Create a surface to plant on
- dynveg.create_surface_entity("Planting Surface", entity_position, 128.0, 128.0, 1.0)
+ # Resize the Box Shape component
+ new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
+ box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
+ hydra.get_set_test(spawner_entity, 1, box_dimensions_path, new_box_dimensions)
- # 4) Verify instance counts are accurate
- general.idle_wait(3.0) # Allow a few seconds for instances to spawn
- num_expected_instances = 20 * 20
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- num_found = azlmbr.areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ # 3) Create a surface to plant on
+ surface_entity = dynveg.create_surface_entity("Planting Surface", entity_position, 128.0, 128.0, 1.0)
+ Report.result(Tests.surface_entity_created, surface_entity.id.IsValid())
- # 5) Move the default Camera entity for testing in the launcher
- cam_position = math.Vector3(512.0, 500.0, 35.0)
- search_filter = entity.SearchFilter()
- search_filter.names = ["Camera"]
- search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
- components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
+ # 4) Verify instance counts are accurate
+ num_expected_instances = 20 * 20
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 5.0)
+ Report.result(Tests.instance_count, success)
- # 6) Save and export to engine
- general.save_level()
- general.idle_wait(1.0)
- general.export_to_engine()
- general.idle_wait(1.0)
+ # 5) Move the default Camera entity for testing in the launcher
+ cam_position = math.Vector3(512.0, 500.0, 35.0)
+ search_filter = entity.SearchFilter()
+ search_filter.names = ["Camera"]
+ search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
+ components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
+
+ # 6) Save and export to engine
+ general.save_level()
+ general.export_to_engine()
+ pak_path = os.path.join(paths.devroot, "AutomatedTesting", "cache", "pc", "levels", lvl_name, "level.pak")
+ Report.result(Tests.saved_and_exported, os.path.exists(pak_path))
-test = TestDynamicSliceInstanceSpawnerExternalEditor()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(DynamicSliceInstanceSpawner_External_E2E)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/EmptyInstanceSpawner_EmptySpawnerWorks.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/EmptyInstanceSpawner_EmptySpawnerWorks.py
index 721d06f858..d0a51809b4 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/EmptyInstanceSpawner_EmptySpawnerWorks.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/EmptyInstanceSpawner_EmptySpawnerWorks.py
@@ -5,109 +5,131 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class BehaviorContextTests:
+ spawner_initialized = (
+ "Successfully initialized an Empty Instance Spawner",
+ "Failed to initialize an Empty Instance Spawner"
+ )
+ desc_spawnertype_sets_spawner = (
+ "Setting spawnerType sets spawner too",
+ "Setting spawnerType failed to set spawner to expected value"
+ )
+ desc_spawner_sets_spawnertype = (
+ "Setting spawner sets spawnerType too",
+ "Setting spawner failed to set spawnerType to expected value"
+ )
-class TestEmptyInstanceSpawner(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="EmptyInstanceSpawner", args=["level"])
+class PropertyTreeTests:
+ entity_created = (
+ "Spawner entity created successfully",
+ "Failed to create spawner entity"
+ )
+ spawner_type_set = (
+ "Successfully set spawner type",
+ "Failed to set spawner type"
+ )
+ empty_instance_count_validation = (
+ "Expected number of empty instances planted",
+ "Unexpected number of empty instances planted"
+ )
+ not_affected_by_allow_empty_assets = (
+ "Instance count unaffected by Allow Empty Assets toggle",
+ "Instance count was unexpectedly affected by Allow Empty Assets toggle"
+ )
- def run_test(self):
- """
- Summary:
- Test aspects of the EmptyInstanceSpawner through the BehaviorContext and the Property Tree.
- :return: None
- """
- # 1) Open an empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+def EmptyInstanceSpawner_EmptySpawnerWorks():
+ """
+ Summary:
+ Test aspects of the EmptyInstanceSpawner through the BehaviorContext and the Property Tree.
+
+ :return: None
+ """
+
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
+
+ # Grab the UUID that we need for creating an Empty Spawner
+ empty_spawner_uuid = azlmbr.math.Uuid_CreateString('{23C40FD4-A55F-4BD3-BE5B-DC5423F217C2}', 0)
+
+ # 2) Test EmptyInstanceSpawner BehaviorContext
+ behavior_context_test_success = True
+ empty_spawner = azlmbr.vegetation.EmptyInstanceSpawner()
+ behavior_context_test_success = behavior_context_test_success and (empty_spawner is not None)
+ behavior_context_test_success = behavior_context_test_success and (empty_spawner.typename == 'EmptyInstanceSpawner')
+ Report.critical_result(BehaviorContextTests.spawner_initialized, behavior_context_test_success)
+ Report.info(f'EmptyInstanceSpawner() BehaviorContext test: {behavior_context_test_success}')
+
+ # 3) Test Descriptor BehaviorContext - setting spawnerType sets spawner too
+ spawner_type_test_success = True
+ descriptor = azlmbr.vegetation.Descriptor()
+ spawner_type_test_success = spawner_type_test_success and hydra.get_set_property_test(descriptor, 'spawnerType', empty_spawner_uuid)
+ spawner_type_test_success = spawner_type_test_success and (descriptor.spawner.typename == 'EmptyInstanceSpawner')
+ Report.result(BehaviorContextTests.desc_spawnertype_sets_spawner, spawner_type_test_success)
+ Report.info(f'Descriptor() BehaviorContext spawnerType test: {spawner_type_test_success}')
+
+ # 4) Test Descriptor BehaviorContext - setting spawner sets spawnerType too
+ spawner_test_success = True
+ descriptor = azlmbr.vegetation.Descriptor()
+ descriptor.spawner = empty_spawner
+ spawner_test_success = spawner_test_success and (descriptor.spawnerType.Equal(empty_spawner_uuid))
+ spawner_test_success = spawner_test_success and (descriptor.spawner.typename == 'EmptyInstanceSpawner')
+ Report.result(BehaviorContextTests.desc_spawner_sets_spawnertype, spawner_test_success)
+ Report.info(f'Descriptor() BehaviorContext spawner test: {spawner_test_success}')
+
+ ### Setup for Property Tree set of tests
+
+ # Create a new entity with required vegetation area components
+ spawner_entity = hydra.Entity("Veg Area")
+ spawner_entity.create_entity(
+ math.Vector3(512.0, 512.0, 32.0),
+ ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List"]
)
- general.idle_wait(1.0)
- general.set_current_view_position(512.0, 480.0, 38.0)
+ Report.critical_result(PropertyTreeTests.entity_created, spawner_entity.id.IsValid())
- # Grab the UUID that we need for creating an Empty Spawner
- empty_spawner_uuid = azlmbr.math.Uuid_CreateString('{23C40FD4-A55F-4BD3-BE5B-DC5423F217C2}', 0)
+ # Resize the Box Shape component
+ new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
+ box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
+ spawner_entity.get_set_test(1, box_dimensions_path, new_box_dimensions)
- # 2) Test EmptyInstanceSpawner BehaviorContext
- behavior_context_test_success = True
- empty_spawner = azlmbr.vegetation.EmptyInstanceSpawner()
- behavior_context_test_success = behavior_context_test_success and (empty_spawner is not None)
- behavior_context_test_success = behavior_context_test_success and (empty_spawner.typename == 'EmptyInstanceSpawner')
- self.test_success = self.test_success and behavior_context_test_success
- self.log(f'EmptyInstanceSpawner() BehaviorContext test: {behavior_context_test_success}')
+ # Create a surface to plant on
+ dynveg.create_surface_entity("Surface Entity", math.Vector3(512.0, 512.0, 32.0), 1024.0, 1024.0, 1.0)
- # 3) Test Descriptor BehaviorContext - setting spawnerType sets spawner too
- spawner_type_test_success = True
- descriptor = azlmbr.vegetation.Descriptor()
- spawner_type_test_success = spawner_type_test_success and hydra.get_set_property_test(descriptor, 'spawnerType', empty_spawner_uuid)
- spawner_type_test_success = spawner_type_test_success and (descriptor.spawner.typename == 'EmptyInstanceSpawner')
- self.test_success = self.test_success and spawner_type_test_success
- self.log(f'Descriptor() BehaviorContext spawnerType test: {spawner_type_test_success}')
+ # 5) Descriptor Property Tree test: spawner type can be set
- # 4) Test Descriptor BehaviorContext - setting spawner sets spawnerType too
- spawner_test_success = True
- descriptor = azlmbr.vegetation.Descriptor()
- descriptor.spawner = empty_spawner
- spawner_test_success = spawner_test_success and (descriptor.spawnerType.Equal(empty_spawner_uuid))
- spawner_test_success = spawner_test_success and (descriptor.spawner.typename == 'EmptyInstanceSpawner')
- self.test_success = self.test_success and spawner_test_success
- self.log(f'Descriptor() BehaviorContext spawner test: {spawner_test_success}')
+ # - Validate the empty spawner type can be set correctly.
+ property_tree_success = True
+ property_tree_success = property_tree_success and spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Instance Spawner', empty_spawner_uuid)
+ Report.result(PropertyTreeTests.spawner_type_set, property_tree_success)
- ### Setup for Property Tree set of tests
+ # This should result in 400 instances, since our box is 16 m x 16 m and by default the veg system plants
+ # 20 instances per 16 meters
+ num_expected_instances = 20 * 20
+ property_tree_success = property_tree_success and helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(PropertyTreeTests.empty_instance_count_validation, property_tree_success)
+ Report.info(f'Property Tree spawner type test: {property_tree_success}')
- # Create a new entity with required vegetation area components
- spawner_entity = hydra.Entity("Veg Area")
- spawner_entity.create_entity(
- math.Vector3(512.0, 512.0, 32.0),
- ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List"]
- )
- if spawner_entity.id.IsValid():
- self.log(f"'{spawner_entity.name}' created")
-
- # Resize the Box Shape component
- new_box_dimensions = math.Vector3(16.0, 16.0, 16.0)
- box_dimensions_path = "Box Shape|Box Configuration|Dimensions"
- spawner_entity.get_set_test(1, box_dimensions_path, new_box_dimensions)
-
- # Create a surface to plant on
- dynveg.create_surface_entity("Surface Entity", math.Vector3(512.0, 512.0, 32.0), 1024.0, 1024.0, 1.0)
-
- # 5) Descriptor Property Tree test: spawner type can be set
-
- # - Validate the empty spawner type can be set correctly.
- property_tree_success = True
- property_tree_success = property_tree_success and spawner_entity.get_set_test(2, 'Configuration|Embedded Assets|[0]|Instance Spawner', empty_spawner_uuid)
-
- # This should result in 400 instances, since our box is 16 m x 16 m and by default the veg system plants
- # 20 instances per 16 meters
- num_expected_instances = 20 * 20
- property_tree_success = property_tree_success and self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and property_tree_success
- self.log(f'Property Tree spawner type test: {property_tree_success}')
-
- # 6) Validate that the "Allow Empty Assets" setting doesn't affect the EmptyInstanceSpawner
- allow_empty_assets_success = True
- spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
- allow_empty_assets_success = allow_empty_assets_success and self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
- self.test_success = self.test_success and allow_empty_assets_success
- self.log(f'Allow Empty Assets test: {allow_empty_assets_success}')
+ # 6) Validate that the "Allow Empty Assets" setting doesn't affect the EmptyInstanceSpawner
+ allow_empty_assets_success = True
+ spawner_entity.get_set_test(0, 'Configuration|Allow Empty Assets', False)
+ allow_empty_assets_success = allow_empty_assets_success and helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_instances), 5.0)
+ Report.result(PropertyTreeTests.not_affected_by_allow_empty_assets, allow_empty_assets_success)
+ Report.info(f'Allow Empty Assets test: {allow_empty_assets_success}')
-test = TestEmptyInstanceSpawner()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(EmptyInstanceSpawner_EmptySpawnerWorks)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/InstanceSpawnerPriority_LayerAndSubPriority.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/InstanceSpawnerPriority_LayerAndSubPriority.py
index e5ff0bf86f..98418c2432 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/InstanceSpawnerPriority_LayerAndSubPriority.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/InstanceSpawnerPriority_LayerAndSubPriority.py
@@ -5,111 +5,115 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.vegetation as vegetation
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_count = (
+ "Initial instance count is as expected",
+ "Initial instance count does not match expected results"
+ )
+ layer_priority_instance_count = (
+ "Instance count is as expected after updating layer priorities",
+ "Instance count does not match expected results after updating layer priorities"
+ )
+ sub_priority_instance_count = (
+ "Instance count is as expected after updating sub priorities",
+ "Instance count does not match expected results after updating sub priorities"
+ )
-class TestInstanceSpawnerPriority(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="InstanceSpawnerPriority", args=["level"])
+def InstanceSpawnerPriority_LayerAndSubPriority():
+ """
+ Summary:
+ A new level is created. An instance spawner area and blocker area are setup to overlap. Instance counts are
+ verified with the initial setup. Layer priority on the blocker area is set to lower than the instance spawner
+ area, and instance counts are re-verified.
- def run_test(self):
- """
- Summary:
- A new level is created. An instance spawner area and blocker area are setup to overlap. Instance counts are
- verified with the initial setup. Layer priority on the blocker area is set to lower than the instance spawner
- area, and instance counts are re-verified.
+ Expected Behavior:
+ Vegetation areas with a higher Layer Priority plant over those with a lower Layer Priority
- Expected Behavior:
- Vegetation areas with a higher Layer Priority plant over those with a lower Layer Priority
+ Test Steps:
+ 1) Open a simple level
+ 2) Create overlapping instance spawner and blocker areas
+ 3) Create a surface to plant on
+ 4) Validate initial instance counts in the spawner area
+ 5) Reduce the Layer Priority of the blocker area
+ 6) Validate instance counts in the spawner area
- Test Steps:
- 1) Create a new level
- 2) Create overlapping instance spawner and blocker areas
- 3) Create a surface to plant on
- 4) Validate initial instance counts in the spawner area
- 5) Reduce the Layer Priority of the blocker area
- 6) Validate instance counts in the spawner area
-
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- :return: None
- """
+ :return: None
+ """
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import os
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- # 2) Create overlapping areas: 1 instance spawner area, and 1 blocker area
- spawner_center_point = math.Vector3(508.0, 508.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 1.0,
- asset_path)
- blocker_center_point = math.Vector3(516.0, 516.0, 32.0)
- blocker_entity = dynveg.create_blocker_area("Instance Blocker", blocker_center_point, 16.0, 16.0, 1.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 3) Create a surface for planting
- planting_surface_center_point = math.Vector3(512.0, 512.0, 32.0)
- dynveg.create_surface_entity("Planting Surface", planting_surface_center_point, 64.0, 64.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 4) Validate the expected instance count with initial setup. GetAreaProductCount is used as
- # GetInstanceCountInAabb does not filter out blocked instances
- num_expected = (20 * 20) - (10 * 10) # 20 instances per 16m per side minus 1 blocked quadrant
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = self.test_success and result
+ # 2) Create overlapping areas: 1 instance spawner area, and 1 blocker area
+ spawner_center_point = math.Vector3(508.0, 508.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 1.0,
+ asset_path)
+ blocker_center_point = math.Vector3(516.0, 516.0, 32.0)
+ blocker_entity = dynveg.create_blocker_area("Instance Blocker", blocker_center_point, 16.0, 16.0, 1.0)
- # 5) Change the Instance Spawner area to a higher layer priority than the Instance Blocker
- blocker_entity.get_set_test(0, 'Configuration|Layer Priority', 0)
+ # 3) Create a surface for planting
+ planting_surface_center_point = math.Vector3(512.0, 512.0, 32.0)
+ dynveg.create_surface_entity("Planting Surface", planting_surface_center_point, 64.0, 64.0, 1.0)
- # 6) Validate the expected instance count with changed area priorities
- num_expected = 20 * 20 # 20 instances per 16m per side, no instances should be blocked at this point
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = self.test_success and result
+ # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- # 7) Revert Layer Priority changes so both areas are equal, and change Sub Priority to a higher value on the
- # Instance Spawner area
- blocker_entity.get_set_test(0, 'Configuration|Layer Priority', 1)
- spawner_entity.get_set_test(0, 'Configuration|Sub Priority', 100)
- blocker_entity.get_set_test(0, 'Configuration|Sub Priority', 1)
+ # 4) Validate the expected instance count with initial setup. GetAreaProductCount is used as
+ # GetInstanceCountInAabb does not filter out blocked instances
+ num_expected = (20 * 20) - (10 * 10) # 20 instances per 16m per side minus 1 blocked quadrant
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.initial_instance_count, result)
- # 8) Validate the expected instance count with changed area priorities
- num_expected = 20 * 20 # 20 instances per 16m per side, no instances should be blocked at this point
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = self.test_success and result
+ # 5) Change the Instance Spawner area to a higher layer priority than the Instance Blocker
+ blocker_entity.get_set_test(0, 'Configuration|Layer Priority', 0)
+
+ # 6) Validate the expected instance count with changed area priorities
+ num_expected = 20 * 20 # 20 instances per 16m per side, no instances should be blocked at this point
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.layer_priority_instance_count, result)
+
+ # 7) Revert Layer Priority changes so both areas are equal, and change Sub Priority to a higher value on the
+ # Instance Spawner area
+ blocker_entity.get_set_test(0, 'Configuration|Layer Priority', 1)
+ spawner_entity.get_set_test(0, 'Configuration|Sub Priority', 100)
+ blocker_entity.get_set_test(0, 'Configuration|Sub Priority', 1)
+
+ # 8) Validate the expected instance count with changed area priorities
+ num_expected = 20 * 20 # 20 instances per 16m per side, no instances should be blocked at this point
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.sub_priority_instance_count, result)
-test = TestInstanceSpawnerPriority()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(InstanceSpawnerPriority_LayerAndSubPriority)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py
index f03ea29613..f56c0b836e 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py
@@ -5,151 +5,161 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C2627906: A simple Vegetation Layer Blender area can be created
-"""
-import os
-from math import radians
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.asset as asset
-import azlmbr.areasystem as areasystem
-import azlmbr.legacy.general as general
-import azlmbr
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.math as math
-import azlmbr.entity as entity
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ level_created = (
+ "Successfully created level",
+ "Failed to create level"
+ )
+ blender_entity_created = (
+ "Blender entity created successfully",
+ "Failed to create Blender entity"
+ )
+ instance_count = (
+ "Found the expected number of instances in the Blender area",
+ "Found an unexpected number of instances in the Blender area"
+ )
+ instances_blended = (
+ "Instances from each spawner are blended as expected",
+ "Found an unexpected number of instances from each spawner"
+ )
+ saved_and_exported = (
+ "Saved and exported level successfully",
+ "Failed to save and export level"
+ )
-class TestVegLayerBlenderCreated(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="LayerBlender_E2E_Editor", args=["level"])
- self.screenshot_count = 0
+def LayerBlender_E2E_Editor():
+ """
+ Summary:
+ A temporary level is loaded. Two vegetation areas with different meshes are added and then
+ pinned to a vegetation blender. Screenshots are taken in the editor normal mode and in game mode.
- def run_test(self):
- """
- Summary:
- A temporary level is loaded. Two vegetation areas with different meshes are added and then
- pinned to a vegetation blender. Screenshots are taken in the editor normal mode and in game mode.
+ Expected Behavior:
+ The specified assets plant in the specified blend area and are visible in the Viewport in
+ Edit Mode, Game Mode.
- Expected Behavior:
- The specified assets plant in the specified blend area and are visible in the Viewport in
- Edit Mode, Game Mode.
+ Test Steps:
+ 1) Create level
+ 2) Create 2 vegetation areas with different meshes
+ 3) Create Blender entity and pin the vegetation areas
+ 4) Take screenshot in normal mode
+ 5) Create a new entity with a Camera component for testing in the launcher
+ 6) Save level and take screenshot in game mode
+ 7) Export to engine
- Test Steps:
- 1) Create level
- 2) Create 2 vegetation areas with different meshes
- 3) Create Blender entity and pin the vegetation areas
- 4) Take screenshot in normal mode
- 5) Create a new entity with a Camera component for testing in the launcher
- 6) Save level and take screenshot in game mode
- 7) Export to engine
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
+ from math import radians
- # 1) Create/prepare a new level and set an appropriate view of blender area
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.asset as asset
+ import azlmbr.areasystem as areasystem
+ import azlmbr.legacy.general as general
+ import azlmbr.paths as paths
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.math as math
+ import azlmbr.entity as entity
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create 2 vegetation areas with different meshes
- purple_position = math.Vector3(504.0, 512.0, 32.0)
- purple_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity_1 = dynveg.create_vegetation_area("Purple Spawner",
- purple_position,
- 16.0, 16.0, 1.0,
- purple_asset_path)
+ # 1) Create a new, temporary level
+ lvl_name = "tmp_level"
+ helper.init_idle()
+ level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
+ general.idle_wait(1.0)
+ Report.critical_result(Tests.level_created, level_created == 0)
- pink_position = math.Vector3(520.0, 512.0, 32.0)
- pink_asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity_2 = dynveg.create_vegetation_area("Pink Spawner",
- pink_position,
- 16.0, 16.0, 1.0,
- pink_asset_path)
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
- base_position = math.Vector3(512.0, 512.0, 32.0)
- dynveg.create_surface_entity("Surface Entity",
- base_position,
- 16.0, 16.0, 1.0)
+ # 2) Create 2 vegetation areas with different meshes
+ purple_position = math.Vector3(504.0, 512.0, 32.0)
+ purple_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity_1 = dynveg.create_vegetation_area("Purple Spawner",
+ purple_position,
+ 16.0, 16.0, 1.0,
+ purple_asset_path)
- hydra.add_level_component("Vegetation Debugger")
+ pink_position = math.Vector3(520.0, 512.0, 32.0)
+ pink_asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity_2 = dynveg.create_vegetation_area("Pink Spawner",
+ pink_position,
+ 16.0, 16.0, 1.0,
+ pink_asset_path)
- # 3) Create Blender entity and pin the vegetation areas. We also add and attach a Lua script to validate in the
- # launcher for the follow-up test
- blender_entity = hydra.Entity("Blender")
- blender_entity.create_entity(
- base_position,
- ["Box Shape", "Vegetation Layer Blender", "Lua Script"]
- )
- if blender_entity.id.IsValid():
- print(f"'{blender_entity.name}' created")
+ base_position = math.Vector3(512.0, 512.0, 32.0)
+ dynveg.create_surface_entity("Surface Entity",
+ base_position,
+ 16.0, 16.0, 1.0)
- blender_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(16.0, 16.0, 1.0))
- blender_entity.get_set_test(1, "Configuration|Vegetation Areas", [spawner_entity_1.id, spawner_entity_2.id])
- instance_counter_path = os.path.join("luascripts", "instance_counter_blender.lua")
- instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
- math.Uuid(), False)
- blender_entity.get_set_test(2, "Script properties|Asset", instance_counter_script)
+ hydra.add_level_component("Vegetation Debugger")
- # 4) Verify instances in blender area are equally represented by both descriptors
+ # 3) Create Blender entity and pin the vegetation areas. We also add and attach a Lua script to validate in the
+ # launcher for the follow-up test
+ blender_entity = hydra.Entity("Blender")
+ blender_entity.create_entity(
+ base_position,
+ ["Box Shape", "Vegetation Layer Blender", "Lua Script"]
+ )
+ Report.result(Tests.blender_entity_created, blender_entity.id.IsValid())
- # Wait for instances to spawn
- general.run_console('veg_debugClearAllAreas')
- num_expected = 20 * 20
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count(base_position, 8.0, num_expected), 5.0)
+ blender_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(16.0, 16.0, 1.0))
+ blender_entity.get_set_test(1, "Configuration|Vegetation Areas", [spawner_entity_1.id, spawner_entity_2.id])
+ instance_counter_path = os.path.join("luascripts", "instance_counter_blender.lua")
+ instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
+ math.Uuid(), False)
+ blender_entity.get_set_test(2, "Script properties|Asset", instance_counter_script)
- if self.test_success:
- box = math.Aabb_CreateCenterRadius(base_position, 8.0)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- pink_count = 0
- purple_count = 0
- for instance in instances:
- purple_asset_path = purple_asset_path.replace("\\", "/").lower()
- pink_asset_path = pink_asset_path.replace("\\", "/").lower()
- if instance.descriptor.spawner.GetSliceAssetPath() == pink_asset_path:
- pink_count += 1
- elif instance.descriptor.spawner.GetSliceAssetPath() == purple_asset_path:
- purple_count += 1
- self.test_success = pink_count == purple_count and (pink_count + purple_count == num_expected) and self.test_success
+ # 4) Verify instances in blender area are equally represented by both descriptors
- # 5) Move the default Camera entity for testing in the launcher
- cam_position = math.Vector3(500.0, 500.0, 47.0)
- cam_rot_degrees_vector = math.Vector3(radians(-55.0), radians(28.5), radians(-17.0))
- search_filter = entity.SearchFilter()
- search_filter.names = ["Camera"]
- search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
- components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
- azlmbr.components.TransformBus(bus.Event, "SetLocalRotation", search_entity_ids[0], cam_rot_degrees_vector)
+ # Wait for instances to spawn
+ general.run_console('veg_debugClearAllAreas')
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count(base_position, 8.0, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, success)
- # 6) Save and export level
- general.save_level()
- general.idle_wait(1.0)
- general.export_to_engine()
- general.idle_wait(1.0)
+ box = math.Aabb_CreateCenterRadius(base_position, 8.0)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ pink_count = 0
+ purple_count = 0
+ for instance in instances:
+ purple_asset_path = purple_asset_path.replace("\\", "/").lower()
+ pink_asset_path = pink_asset_path.replace("\\", "/").lower()
+ if instance.descriptor.spawner.GetSliceAssetPath() == pink_asset_path:
+ pink_count += 1
+ elif instance.descriptor.spawner.GetSliceAssetPath() == purple_asset_path:
+ purple_count += 1
+ Report.result(Tests.instances_blended, pink_count == purple_count and (pink_count + purple_count == num_expected))
+
+ # 5) Move the default Camera entity for testing in the launcher
+ cam_position = math.Vector3(500.0, 500.0, 47.0)
+ cam_rot_degrees_vector = math.Vector3(radians(-55.0), radians(28.5), radians(-17.0))
+ search_filter = entity.SearchFilter()
+ search_filter.names = ["Camera"]
+ search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)
+ components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position)
+ azlmbr.components.TransformBus(bus.Event, "SetLocalRotation", search_entity_ids[0], cam_rot_degrees_vector)
+
+ # 6) Save and export to engine
+ general.save_level()
+ general.export_to_engine()
+ pak_path = os.path.join(paths.devroot, "AutomatedTesting", "cache", "pc", "levels", lvl_name, "level.pak")
+ Report.result(Tests.saved_and_exported, os.path.exists(pak_path))
-test = TestVegLayerBlenderCreated()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerBlender_E2E_Editor)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlocker_InstancesBlockedInConfiguredArea.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlocker_InstancesBlockedInConfiguredArea.py
index 138ac27700..625b7d2265 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlocker_InstancesBlockedInConfiguredArea.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlocker_InstancesBlockedInConfiguredArea.py
@@ -5,102 +5,105 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.math as math
-import azlmbr.legacy.general as general
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_count = (
+ "Initial instance count is as expected",
+ "Unexpected number of initial instances found"
+ )
+ blocked_instance_count = (
+ "Expected number of instances found after configuring Blocker",
+ "Unexpected number of instances found after configuring Blocker"
+ )
-class TestLayerBlocker(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="LayerBlocker_InstancesBlocked", args=["level"])
- def run_test(self):
- """
- Summary:
- An empty level is created. A Vegetation Layer Spawner area is configured. A Vegetation Layer Blocker area is
- configured to block instances in the spawner area.
+def LayerBlocker_InstancesBlockedInConfiguredArea():
+ """
+ Summary:
+ An empty level is created. A Vegetation Layer Spawner area is configured. A Vegetation Layer Blocker area is
+ configured to block instances in the spawner area.
- Expected Behavior:
- Vegetation is blocked by the configured Blocker area.
+ Expected Behavior:
+ Vegetation is blocked by the configured Blocker area.
- Test Steps:
- 1. A new level is created
- 2. Vegetation Layer Spawner area is created
- 3. Planting surface is created
- 4. Vegetation System Settings level component is added, and Snap Mode set to center to ensure expected instance
- counts are accurate in the configured vegetation area
- 5. Initial instance counts pre-blocker are validated
- 6. A Vegetation Layer Blocker area is created, overlapping the spawner area
- 7. Post-blocker instance counts are validated
+ Test Steps:
+ 1. A simple level is opened
+ 2. Vegetation Layer Spawner area is created
+ 3. Planting surface is created
+ 4. Vegetation System Settings level component is added, and Snap Mode set to center to ensure expected instance
+ counts are accurate in the configured vegetation area
+ 5. Initial instance counts pre-blocker are validated
+ 6. A Vegetation Layer Blocker area is created, overlapping the spawner area
+ 7. Post-blocker instance counts are validated
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- :return: None
- """
+ :return: None
+ """
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import os
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.math as math
+ import azlmbr.legacy.general as general
- # 2) Create a new instance spawner entity
- spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 3) Create surface for planting on
- dynveg.create_surface_entity("Surface Entity", spawner_center_point, 32.0, 32.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 5) Verify initial instance counts
- num_expected = 20 * 20
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # 2) Create a new instance spawner entity
+ spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
- # 6) Create a new Vegetation Layer Blocker area overlapping the spawner area
- blocker_entity = hydra.Entity("Blocker Area")
- blocker_entity.create_entity(
- spawner_center_point,
- ["Vegetation Layer Blocker", "Box Shape"]
- )
- if blocker_entity.id.IsValid():
- print(f"'{blocker_entity.name}' created")
- blocker_entity.get_set_test(1, "Box Shape|Box Configuration|Dimensions",
- math.Vector3(3.0, 3.0, 3.0))
+ # 3) Create surface for planting on
+ dynveg.create_surface_entity("Surface Entity", spawner_center_point, 32.0, 32.0, 1.0)
- # 7) Validate instance counts post-blocker. 16 instances should now be blocked in the center of the spawner area
- num_expected = (20 * 20) - 16
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # 4) Add a Vegetation System Settings Level component and set Sector Point Snap Mode to Center
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+
+ # 5) Verify initial instance counts
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.initial_instance_count, success)
+
+ # 6) Create a new Vegetation Layer Blocker area overlapping the spawner area
+ blocker_entity = hydra.Entity("Blocker Area")
+ blocker_entity.create_entity(
+ spawner_center_point,
+ ["Vegetation Layer Blocker", "Box Shape"]
+ )
+ if blocker_entity.id.IsValid():
+ print(f"'{blocker_entity.name}' created")
+ blocker_entity.get_set_test(1, "Box Shape|Box Configuration|Dimensions",
+ math.Vector3(3.0, 3.0, 3.0))
+
+ # 7) Validate instance counts post-blocker. 16 instances should now be blocked in the center of the spawner area
+ num_expected = (20 * 20) - 16
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.blocked_instance_count, success)
-test = TestLayerBlocker()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerBlocker_InstancesBlockedInConfiguredArea)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py
index a52f91ae5f..8592692c4b 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py
@@ -5,85 +5,83 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ preprocess_instance_count = (
+ "Preprocess filter stage vegetation instance count is as expected",
+ "Preprocess filter stage instance count found an unexpected number of instances"
+ )
+ postprocess_instance_count = (
+ "Postprocess filter stage vegetation instance count is as expected",
+ "Postprocess filter stage instance count found an unexpected number of instances"
+ )
-class TestLayerSpawnerFilterStageToggle(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="LayerSpawner_FilterStageToggle", args=["level"])
+def LayerSpawner_FilterStageToggle():
+ """
+ Summary:
+ Filter Stage toggle affects final vegetation position.
- def run_test(self):
- """
- Summary:
- C4765973 Filter Stage toggle affects final vegetation position.
+ Expected Result:
+ Vegetation instances plant differently depending on the Filter Stage setting.
- Expected Result:
- Vegetation instances plant differently depending on the Filter Stage setting.
+ :return: None
+ """
- :return: None
- """
+ import os
- PREPROCESS_INSTANCE_COUNT = 21
- POSTPROCESS_INSTANCE_COUNT = 19
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
+ PREPROCESS_INSTANCE_COUNT = 21
+ POSTPROCESS_INSTANCE_COUNT = 19
- # Create a vegetation area with all needed components
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- vegetation_entity = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
- vegetation_entity.add_component("Vegetation Altitude Filter")
- vegetation_entity.add_component("Vegetation Position Modifier")
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create a child entity under vegetation area
- child_entity = hydra.Entity("child_entity")
- components_to_add = ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"]
- child_entity.create_entity(position, components_to_add, vegetation_entity.id)
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
- # Set the Gradient Id in X and Y direction
- vegetation_entity.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", child_entity.id)
- vegetation_entity.get_set_test(4, "Configuration|Position Y|Gradient|Gradient Entity Id", child_entity.id)
+ # Create a vegetation area with all needed components
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ vegetation_entity = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
+ vegetation_entity.add_component("Vegetation Altitude Filter")
+ vegetation_entity.add_component("Vegetation Position Modifier")
- # Set the min and max values for Altitude Filter
- vegetation_entity.get_set_test(3, "Configuration|Altitude Min", 34.0)
- vegetation_entity.get_set_test(3, "Configuration|Altitude Max", 38.0)
+ # Create a child entity under vegetation area
+ child_entity = hydra.Entity("child_entity")
+ components_to_add = ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"]
+ child_entity.create_entity(position, components_to_add, vegetation_entity.id)
- # Add entity with Mesh to replicate creation of hills and a flat surface to plant on
- dynveg.create_surface_entity("Flat Surface", position, 32.0, 32.0, 1.0)
- hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 4.0)
+ # Set the Gradient Id in X and Y direction
+ vegetation_entity.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", child_entity.id)
+ vegetation_entity.get_set_test(4, "Configuration|Position Y|Gradient|Gradient Entity Id", child_entity.id)
- # Set the filter stage to preprocess and postprocess respectively and verify instance count
- vegetation_entity.get_set_test(0, "Configuration|Filter Stage", 1)
- self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, PREPROCESS_INSTANCE_COUNT), 3.0)
- result = dynveg.validate_instance_count(position, 16.0, PREPROCESS_INSTANCE_COUNT)
- self.log(f"Preprocess filter stage vegetation instance count is as expected: {result}")
- vegetation_entity.get_set_test(0, "Configuration|Filter Stage", 2)
- self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, POSTPROCESS_INSTANCE_COUNT), 3.0)
- result = dynveg.validate_instance_count(position, 16.0, POSTPROCESS_INSTANCE_COUNT)
- self.log(f"Postprocess filter vegetation instance stage count is as expected: {result}")
+ # Set the min and max values for Altitude Filter
+ vegetation_entity.get_set_test(3, "Configuration|Altitude Min", 34.0)
+ vegetation_entity.get_set_test(3, "Configuration|Altitude Max", 38.0)
+
+ # Add entity with Mesh to replicate creation of hills and a flat surface to plant on
+ dynveg.create_surface_entity("Flat Surface", position, 32.0, 32.0, 1.0)
+ hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 4.0)
+
+ # Set the filter stage to preprocess and postprocess respectively and verify instance count
+ vegetation_entity.get_set_test(0, "Configuration|Filter Stage", 1)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, PREPROCESS_INSTANCE_COUNT), 3.0)
+ Report.result(Tests.preprocess_instance_count, result)
+ vegetation_entity.get_set_test(0, "Configuration|Filter Stage", 2)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, POSTPROCESS_INSTANCE_COUNT), 3.0)
+ Report.result(Tests.postprocess_instance_count, result)
-test = TestLayerSpawnerFilterStageToggle()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerSpawner_FilterStageToggle)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py
index 5e5c6410b0..649c7d0776 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py
@@ -5,118 +5,116 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.math as math
-import azlmbr.legacy.general as general
-import azlmbr.paths
-import azlmbr.surface_data as surface_data
-import azlmbr.vegetation as vegetation
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ inherit_behavior_checked = (
+ "Found no instances with Inherit Behavior checked as expected",
+ "Unexpectedly found instances with Inherit Behavior checked"
+ )
+ inherit_behavior_unchecked = (
+ "Found instances with Inherit Behavior unchecked as expected",
+ "Unexpectedly found no instances with Inherit Behavior unchecked"
+ )
-class TestLayerSpawnerInheritBehavior(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="LayerSpawner_InheritBehavior", args=["level"])
+def LayerSpawner_InheritBehaviorFlag():
+ """
+ Summary:
+ Verifies if Inherit Behavior Flag works as expected.
- def run_test(self):
- """
- Summary:
- C4762381 Verifies if Inherit Behavior Flag works as expected.
+ Expected Result:
+ The spawner with Inherit Behavior toggled off no longer obeys
+ Vegetation Surface Mask Filter of the Vegetation Layer Blender entity and plants on the surface.
- Expected Result:
- The spawner with Inherit Behavior toggled off no longer obeys
- Vegetation Surface Mask Filter of the Vegetation Layer Blender entity and plants on the surface.
+ :return: None
+ """
+ import os
- :return: None
- """
+ import azlmbr.math as math
+ import azlmbr.legacy.general as general
+ import azlmbr.surface_data as surface_data
+ import azlmbr.vegetation as vegetation
- SURFACE_TAG = "test_tag"
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- def set_dynamic_slice_asset(entity_obj, component_index, dynamic_slice_asset_path):
- dynamic_slice_spawner = vegetation.DynamicSliceInstanceSpawner()
- dynamic_slice_spawner.SetSliceAssetPath(dynamic_slice_asset_path)
- descriptor = hydra.get_component_property_value(
- entity_obj.components[component_index], "Configuration|Embedded Assets|[0]"
- )
- descriptor.spawner = dynamic_slice_spawner
- entity_obj.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
+ SURFACE_TAG = "test_tag"
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ def set_dynamic_slice_asset(entity_obj, component_index, dynamic_slice_asset_path):
+ dynamic_slice_spawner = vegetation.DynamicSliceInstanceSpawner()
+ dynamic_slice_spawner.SetSliceAssetPath(dynamic_slice_asset_path)
+ descriptor = hydra.get_component_property_value(
+ entity_obj.components[component_index], "Configuration|Embedded Assets|[0]"
)
+ descriptor.spawner = dynamic_slice_spawner
+ entity_obj.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
- general.set_current_view_position(512.0, 480.0, 38.0)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create Emitter entity and add the required components
- position = math.Vector3(512.0, 512.0, 32.0)
- emitter_entity = dynveg.create_surface_entity("emitter_entity", position, 16.0, 16.0, 1.0)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add surface tag to the Surface Tag Emitter
- tag = surface_data.SurfaceTag()
- tag.SetTag(SURFACE_TAG)
- pte = hydra.get_property_tree(emitter_entity.components[1])
- path = "Configuration|Generated Tags"
- pte.add_container_item(path, 0, tag)
- emitter_entity.get_set_test(1, "Configuration|Generated Tags|[0]", tag)
+ # Create Emitter entity and add the required components
+ position = math.Vector3(512.0, 512.0, 32.0)
+ emitter_entity = dynveg.create_surface_entity("emitter_entity", position, 16.0, 16.0, 1.0)
- # Create Blender entity and add required components
- components_to_add = ["Box Shape", "Vegetation Layer Blender"]
- blender_entity = hydra.Entity("blender_entity")
- blender_entity.create_entity(position, components_to_add)
- blender_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(16.0, 16.0, 1.0))
+ # Add surface tag to the Surface Tag Emitter
+ tag = surface_data.SurfaceTag()
+ tag.SetTag(SURFACE_TAG)
+ pte = hydra.get_property_tree(emitter_entity.components[1])
+ path = "Configuration|Generated Tags"
+ pte.add_container_item(path, 0, tag)
+ emitter_entity.get_set_test(1, "Configuration|Generated Tags|[0]", tag)
- # Create Vegetation area and assign a valid asset
- veg_1 = hydra.Entity("veg_1")
- veg_1.create_entity(
- position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
- )
- set_dynamic_slice_asset(veg_1, 2, os.path.join("Slices", "PinkFlower.dynamicslice"))
- veg_1.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)
+ # Create Blender entity and add required components
+ components_to_add = ["Box Shape", "Vegetation Layer Blender"]
+ blender_entity = hydra.Entity("blender_entity")
+ blender_entity.create_entity(position, components_to_add)
+ blender_entity.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(16.0, 16.0, 1.0))
- # Create second vegetation area and assign a valid asset
- veg_2 = hydra.Entity("veg_2")
- veg_2.create_entity(
- position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
- )
- set_dynamic_slice_asset(veg_2, 2, os.path.join("Slices", "PurpleFlower.dynamicslice"))
- veg_2.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)
+ # Create Vegetation area and assign a valid asset
+ veg_1 = hydra.Entity("veg_1")
+ veg_1.create_entity(
+ position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
+ )
+ set_dynamic_slice_asset(veg_1, 2, os.path.join("Slices", "PinkFlower.dynamicslice"))
+ veg_1.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)
- # Assign the vegetation areas to the Blender entity
- pte = hydra.get_property_tree(blender_entity.components[1])
- path = "Configuration|Vegetation Areas"
- pte.update_container_item(path, 0, veg_1.id)
- pte.add_container_item(path, 1, veg_2.id)
+ # Create second vegetation area and assign a valid asset
+ veg_2 = hydra.Entity("veg_2")
+ veg_2.create_entity(
+ position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
+ )
+ set_dynamic_slice_asset(veg_2, 2, os.path.join("Slices", "PurpleFlower.dynamicslice"))
+ veg_2.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)
- # Add Vegetation Surface Mask Filter to the blender entity and add a Exclusion tag
- tag = surface_data.SurfaceTag()
- tag.SetTag(SURFACE_TAG)
- blender_entity.add_component("Vegetation Surface Mask Filter")
- pte = hydra.get_property_tree(blender_entity.components[2])
- path = "Configuration|Exclusion|Surface Tags"
- pte.add_container_item(path, 0, tag)
- blender_entity.get_set_test(2, "Configuration|Exclusion|Surface Tags|[0]", tag)
+ # Assign the vegetation areas to the Blender entity
+ pte = hydra.get_property_tree(blender_entity.components[1])
+ path = "Configuration|Vegetation Areas"
+ pte.update_container_item(path, 0, veg_1.id)
+ pte.add_container_item(path, 1, veg_2.id)
- # Toggle Inherit Behavior flag and verify vegetation instances
- self.log(
- f"Vegetation is not planted when Inherit Behavior flag is checked: {dynveg.validate_instance_count(position, 16.0, 0)}"
- )
- veg_1.get_set_test(0, "Configuration|Inherit Behavior", False)
- self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 400), 2.0)
- self.log(
- f"Vegetation plant when Inherit Behavior flag is unchecked: {dynveg.validate_instance_count(position, 16.0, 400)}"
- )
+ # Add Vegetation Surface Mask Filter to the blender entity and add a Exclusion tag
+ tag = surface_data.SurfaceTag()
+ tag.SetTag(SURFACE_TAG)
+ blender_entity.add_component("Vegetation Surface Mask Filter")
+ pte = hydra.get_property_tree(blender_entity.components[2])
+ path = "Configuration|Exclusion|Surface Tags"
+ pte.add_container_item(path, 0, tag)
+ blender_entity.get_set_test(2, "Configuration|Exclusion|Surface Tags|[0]", tag)
+
+ # Toggle Inherit Behavior flag and verify vegetation instances
+ flag_checked_instance_count = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 0), 2.0)
+ Report.result(Tests.inherit_behavior_checked, flag_checked_instance_count)
+ veg_1.get_set_test(0, "Configuration|Inherit Behavior", False)
+ flag_unchecked_instance_count = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 400), 2.0)
+ Report.result(Tests.inherit_behavior_unchecked, flag_unchecked_instance_count)
-test = TestLayerSpawnerInheritBehavior()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerSpawner_InheritBehaviorFlag)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py
index 8310583fe6..0da200d87a 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py
@@ -5,134 +5,128 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr
-import azlmbr.legacy.general as general
-import azlmbr.entity as EntityId
-import azlmbr.math as math
+def LayerSpawner_InstancesPlantInAllSupportedShapes():
+ """
+ Summary:
+ The level is loaded and vegetation area is created. Then the Vegetation Reference Shape
+ component of vegetation area is pinned with entities of different shape components to check
+ if the vegetation plants in different shaped areas.
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ Expected Behavior:
+ Vegetation properly plants in areas of any shape.
+ Test Steps:
+ 1) Open a level
+ 2) Create basic vegetation area entity and set the properties
+ 3) Box Shape Entity: create, set properties and pin to vegetation
+ 4) Capsule Shape Entity: create, set properties and pin to vegetation
+ 5) Tube Shape Entity: create, set properties and pin to vegetation
+ 6) Sphere Shape Entity: create, set properties and pin to vegetation
+ 7) Cylinder Shape Entity: create, set properties and pin to vegetation
+ 8) Prism Shape Entity: create, set properties and pin to vegetation
+ 9) Compound Shape Entity: create, set properties and pin to vegetation
-class TestLayerSpawner_AllShapesPlant(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="TestLayerSpawner_AllShapesPlant", args=["level"])
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- def run_test(self):
- """
- Summary:
- The level is loaded and vegetation area is created. Then the Vegetation Reference Shape
- component of vegetation area is pinned with entities of different shape components to check
- if the vegetation plants in different shaped areas.
+ :return: None
+ """
- Expected Behavior:
- Vegetation properly plants in areas of any shape.
+ import os
- Test Steps:
- 1) Create level
- 2) Create basic vegetation area entity and set the properties
- 3) Box Shape Entity: create, set properties and pin to vegetation
- 4) Capsule Shape Entity: create, set properties and pin to vegetation
- 5) Tube Shape Entity: create, set properties and pin to vegetation
- 6) Sphere Shape Entity: create, set properties and pin to vegetation
- 7) Cylinder Shape Entity: create, set properties and pin to vegetation
- 8) Prism Shape Entity: create, set properties and pin to vegetation
- 9) Compound Shape Entity: create, set properties and pin to vegetation
+ import azlmbr.legacy.general as general
+ import azlmbr.entity as EntityId
+ import azlmbr.math as math
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- :return: None
- """
-
- def pin_shape_and_check_count(entity_id, count):
- hydra.get_set_test(vegetation, 2, "Configuration|Shape Entity Id", entity_id)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(vegetation.id,
- count), 2.0)
- self.test_success = self.test_success and result
-
- # 1) Create level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ def pin_shape_and_check_count(entity, count):
+ hydra.get_set_test(vegetation, 2, "Configuration|Shape Entity Id", entity.id)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(vegetation.id,
+ count), 2.0)
+ success = (
+ f"Found the expected number of instances in {entity.name} shape",
+ f"Unexpected number of instances found in {entity.name} shape"
)
+ Report.result(success, result)
- # 2) Create basic vegetation area entity and set the properties
- entity_position = math.Vector3(125.0, 136.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- vegetation = dynveg.create_vegetation_area("Instance Spawner",
- entity_position,
- 10.0, 10.0, 10.0,
- asset_path)
- vegetation.remove_component("Box Shape")
- vegetation.add_component("Vegetation Reference Shape")
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create surface for planting on
- dynveg.create_surface_entity("Surface Entity", entity_position, 60.0, 60.0, 1.0)
+ # 2) Create basic vegetation area entity and set the properties
+ entity_position = math.Vector3(125.0, 136.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ vegetation = dynveg.create_vegetation_area("Instance Spawner",
+ entity_position,
+ 10.0, 10.0, 10.0,
+ asset_path)
+ vegetation.remove_component("Box Shape")
+ vegetation.add_component("Vegetation Reference Shape")
- # Adjust camera to be close to the vegetation entity
- general.set_current_view_position(135.0, 102.0, 39.0)
- general.set_current_view_rotation(-15.0, 0, 0)
+ # Create surface for planting on
+ dynveg.create_surface_entity("Surface Entity", entity_position, 60.0, 60.0, 1.0)
- # 3) Box Shape Entity: create, set properties and pin to vegetation
- box = hydra.Entity("box")
- box.create_entity(math.Vector3(124.0, 126.0, 32.0), ["Box Shape"])
- new_box_dimension = math.Vector3(10.0, 10.0, 1.0)
- hydra.get_set_test(box, 0, "Box Shape|Box Configuration|Dimensions", new_box_dimension)
- # This and subsequent counts are the number of "PurpleFlower" that spawn in the shape with given dimensions
- pin_shape_and_check_count(box.id, 156)
+ # Adjust camera to be close to the vegetation entity
+ general.set_current_view_position(135.0, 102.0, 39.0)
+ general.set_current_view_rotation(-15.0, 0, 0)
- # 4) Capsule Shape Entity: create, set properties and pin to vegetation
- capsule = hydra.Entity("capsule")
- capsule.create_entity(math.Vector3(120.0, 142.0, 32.0), ["Capsule Shape"])
- hydra.get_set_test(capsule, 0, "Capsule Shape|Capsule Configuration|Height", 10.0)
- hydra.get_set_test(capsule, 0, "Capsule Shape|Capsule Configuration|Radius", 2.0)
- pin_shape_and_check_count(capsule.id, 20)
+ # 3) Box Shape Entity: create, set properties and pin to vegetation
+ box = hydra.Entity("Box")
+ box.create_entity(math.Vector3(124.0, 126.0, 32.0), ["Box Shape"])
+ new_box_dimension = math.Vector3(10.0, 10.0, 1.0)
+ hydra.get_set_test(box, 0, "Box Shape|Box Configuration|Dimensions", new_box_dimension)
+ # This and subsequent counts are the number of "PurpleFlower" that spawn in the shape with given dimensions
+ pin_shape_and_check_count(box, 156)
- # 5) Tube Shape Entity: create, set properties and pin to vegetation
- tube = hydra.Entity("tube")
- tube.create_entity(math.Vector3(124.0, 136.0, 32.0), ["Tube Shape", "Spline"])
- pin_shape_and_check_count(tube.id, 27)
+ # 4) Capsule Shape Entity: create, set properties and pin to vegetation
+ capsule = hydra.Entity("Capsule")
+ capsule.create_entity(math.Vector3(120.0, 142.0, 32.0), ["Capsule Shape"])
+ hydra.get_set_test(capsule, 0, "Capsule Shape|Capsule Configuration|Height", 10.0)
+ hydra.get_set_test(capsule, 0, "Capsule Shape|Capsule Configuration|Radius", 2.0)
+ pin_shape_and_check_count(capsule, 20)
- # 6) Sphere Shape Entity: create, set properties and pin to vegetation
- sphere = hydra.Entity("sphere")
- sphere.create_entity(math.Vector3(112.0, 143.0, 32.0), ["Sphere Shape"])
- hydra.get_set_test(sphere, 0, "Sphere Shape|Sphere Configuration|Radius", 5.0)
- pin_shape_and_check_count(sphere.id, 122)
+ # 5) Tube Shape Entity: create, set properties and pin to vegetation
+ tube = hydra.Entity("Tube")
+ tube.create_entity(math.Vector3(124.0, 136.0, 32.0), ["Tube Shape", "Spline"])
+ pin_shape_and_check_count(tube, 27)
- # 7) Cylinder Shape Entity: create, set properties and pin to vegetation
- cylinder = hydra.Entity("cylinder")
- cylinder.create_entity(math.Vector3(136.0, 143.0, 32.0), ["Cylinder Shape"])
- hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
- hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Height", 5.0)
- pin_shape_and_check_count(cylinder.id, 124)
+ # 6) Sphere Shape Entity: create, set properties and pin to vegetation
+ sphere = hydra.Entity("Sphere")
+ sphere.create_entity(math.Vector3(112.0, 143.0, 32.0), ["Sphere Shape"])
+ hydra.get_set_test(sphere, 0, "Sphere Shape|Sphere Configuration|Radius", 5.0)
+ pin_shape_and_check_count(sphere, 122)
- # 8) Prism Shape Entity: create, set properties and pin to vegetation
- polygon_prism = hydra.Entity("polygonprism")
- polygon_prism.create_entity(math.Vector3(127.0, 142.0, 32.0), ["Polygon Prism Shape"])
- pin_shape_and_check_count(polygon_prism.id, 20)
+ # 7) Cylinder Shape Entity: create, set properties and pin to vegetation
+ cylinder = hydra.Entity("Cylinder")
+ cylinder.create_entity(math.Vector3(136.0, 143.0, 32.0), ["Cylinder Shape"])
+ hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
+ hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Height", 5.0)
+ pin_shape_and_check_count(cylinder, 124)
- # 9) Compound Shape Entity: create, set properties and pin to vegetation
- compound = hydra.Entity("Compound")
- compound.create_entity(math.Vector3(125.0, 136.0, 32.0), ["Compound Shape"])
- pte = hydra.get_property_tree(compound.components[0])
- shapes = [box.id, capsule.id, tube.id, sphere.id, cylinder.id, polygon_prism.id]
- for index in range(6):
- pte.add_container_item("Configuration|Child Shape Entities", index, EntityId.EntityId())
- for index, element in enumerate(shapes):
- hydra.get_set_test(compound, 0, f"Configuration|Child Shape Entities|[{index}]", element)
- pin_shape_and_check_count(compound.id, 469)
+ # 8) Prism Shape Entity: create, set properties and pin to vegetation
+ polygon_prism = hydra.Entity("Polygon Prism")
+ polygon_prism.create_entity(math.Vector3(127.0, 142.0, 32.0), ["Polygon Prism Shape"])
+ pin_shape_and_check_count(polygon_prism, 20)
+
+ # 9) Compound Shape Entity: create, set properties and pin to vegetation
+ compound = hydra.Entity("Compound")
+ compound.create_entity(math.Vector3(125.0, 136.0, 32.0), ["Compound Shape"])
+ pte = hydra.get_property_tree(compound.components[0])
+ shapes = [box.id, capsule.id, tube.id, sphere.id, cylinder.id, polygon_prism.id]
+ for index in range(6):
+ pte.add_container_item("Configuration|Child Shape Entities", index, EntityId.EntityId())
+ for index, element in enumerate(shapes):
+ hydra.get_set_test(compound, 0, f"Configuration|Child Shape Entities|[{index}]", element)
+ pin_shape_and_check_count(compound, 469)
-test = TestLayerSpawner_AllShapesPlant()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerSpawner_InstancesPlantInAllSupportedShapes)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py
index f17956e066..02d30fb0f3 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py
@@ -5,115 +5,131 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ viewport_config_updated = (
+ "Viewport is now configured for test",
+ "Failed to configure viewport for test"
+ )
+ first_viewport_active_instance_count = (
+ "Expected number of instances found in left viewport",
+ "Unexpected number of instances found in left viewport"
+ )
+ second_viewport_inactive_instance_count = (
+ "No instances found in right viewport",
+ "Unexpectedly found instances in right viewport while not active"
+ )
+ first_viewport_inactive_instance_count = (
+ "No instances found in left viewport",
+ "Unexpectedly found instances in left viewport while not active"
+ )
+ second_viewport_active_instance_count = (
+ "Expected number of instances found in right viewport",
+ "Unexpected number of instances found in right viewport"
+ )
-class TestLayerSpawnerInstanceCameraRefresh(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="LayerSpawner_InstanceCameraRefresh", args=["level"])
+def LayerSpawner_InstancesRefreshUsingCorrectViewportCamera():
+ """
+ Summary:
+ Test that the Dynamic Vegetation System is using the current Editor viewport camera as the center
+ of the spawn area for vegetation. To verify this, we create two separate Editor viewports pointed
+ at two different vegetation areas, and verify that as we switch between active viewports, only the
+ area directly underneath that viewport's camera has vegetation.
+ """
- def run_test(self):
- """
- Summary:
- Test that the Dynamic Vegetation System is using the current Editor viewport camera as the center
- of the spawn area for vegetation. To verify this, we create two separate Editor viewports pointed
- at two different vegetation areas, and verify that as we switch between active viewports, only the
- area directly underneath that viewport's camera has vegetation.
- """
- # Create an empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=128,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
- # Set up a test environment to validate that switching viewports correctly changes which camera
- # the vegetation system uses.
- # The test environment consists of the following:
- # - two 32 x 32 x 1 box shapes located far apart that emit a surface with no tags
- # - two 32 x 32 x 32 vegetation areas that place vegetation on the boxes
+ import os
- # Initialize some constants for our test.
- # The boxes are intentionally shifted by 0.5 meters to ensure that we get a predictable number
- # of vegetation points. By default, vegetation plants on grid corners, so if our boxes are aligned
- # with grid corner points, the right/bottom edges will include more points than we might intuitively expect.
- # By shifting by 0.5 meters, the vegetation grid points don't fall on the box edges, making the total count
- # more predictable.
- first_entity_center_point = math.Vector3(0.5, 0.5, 100.0)
- # The second box needs to be far enough away from the first that the vegetation system will never spawn instances
- # in both at the same time.
- second_entity_center_point = math.Vector3(1024.5, 1024.5, 100.0)
- box_size = 32.0
- surface_height = 1.0
- # By default, vegetation spawns 20 instances per 16 meters, so for our box of 32 meters, we should have
- # ((20 instances / 16 m) * 32 m) ^ 2 instances.
- filled_vegetation_area_instance_count = (20 * 2) * (20 * 2)
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Change the Editor view to contain two viewports
- general.set_view_pane_layout(1)
- get_view_pane_layout_success = self.wait_for_condition(lambda: (general.get_view_pane_layout() == 1), 2)
- get_viewport_count_success = self.wait_for_condition(lambda: (general.get_viewport_count() == 2), 2)
- self.test_success = get_view_pane_layout_success and self.test_success
- self.test_success = get_viewport_count_success and self.test_success
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Set the view in the first viewport to point down at the first box
- general.set_active_viewport(0)
- self.wait_for_condition(lambda: general.get_active_viewport() == 0, 2)
- general.set_current_view_position(first_entity_center_point.x, first_entity_center_point.y,
- first_entity_center_point.z + 30.0)
- general.set_current_view_rotation(-85.0, 0.0, 0.0)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Set the view in the second viewport to point down at the second box
- general.set_active_viewport(1)
- self.wait_for_condition(lambda: general.get_active_viewport() == 1, 2)
- general.set_current_view_position(second_entity_center_point.x, second_entity_center_point.y,
- second_entity_center_point.z + 30.0)
- general.set_current_view_rotation(-85.0, 0.0, 0.0)
+ # Set up a test environment to validate that switching viewports correctly changes which camera
+ # the vegetation system uses.
+ # The test environment consists of the following:
+ # - two 32 x 32 x 1 box shapes located far apart that emit a surface with no tags
+ # - two 32 x 32 x 32 vegetation areas that place vegetation on the boxes
- # Create the "flat surface" entities to use as our vegetation surfaces
- first_surface_entity = dynveg.create_surface_entity("Surface 1", first_entity_center_point, box_size, box_size,
- surface_height)
- second_surface_entity = dynveg.create_surface_entity("Surface 2", second_entity_center_point, box_size, box_size,
- surface_height)
+ # Initialize some constants for our test.
+ # The boxes are intentionally shifted by 0.5 meters to ensure that we get a predictable number
+ # of vegetation points. By default, vegetation plants on grid corners, so if our boxes are aligned
+ # with grid corner points, the right/bottom edges will include more points than we might intuitively expect.
+ # By shifting by 0.5 meters, the vegetation grid points don't fall on the box edges, making the total count
+ # more predictable.
+ first_entity_center_point = math.Vector3(0.5, 0.5, 100.0)
+ # The second box needs to be far enough away from the first that the vegetation system will never spawn instances
+ # in both at the same time.
+ second_entity_center_point = math.Vector3(1024.5, 1024.5, 100.0)
+ box_size = 32.0
+ surface_height = 1.0
+ # By default, vegetation spawns 20 instances per 16 meters, so for our box of 32 meters, we should have
+ # ((20 instances / 16 m) * 32 m) ^ 2 instances.
+ filled_vegetation_area_instance_count = (20 * 2) * (20 * 2)
- # Create the two vegetation areas
- test_slice_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- first_veg_entity = dynveg.create_vegetation_area("Veg Area 1", first_entity_center_point, box_size, box_size,
- box_size, test_slice_asset_path)
- second_veg_entity = dynveg.create_vegetation_area("Veg Area 2", second_entity_center_point, box_size, box_size,
- box_size, test_slice_asset_path)
+ # Change the Editor view to contain two viewports
+ general.set_view_pane_layout(1)
+ get_view_pane_layout_success = helper.wait_for_condition(lambda: (general.get_view_pane_layout() == 1), 2)
+ get_viewport_count_success = helper.wait_for_condition(lambda: (general.get_viewport_count() == 2), 2)
+ Report.critical_result(Tests.viewport_config_updated, get_view_pane_layout_success and get_viewport_count_success)
- # When the first viewport is active, the first area should be full of instances, and the second should be empty
- general.set_active_viewport(0)
- viewport_0_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(first_entity_center_point,
- box_size / 2.0,
- filled_vegetation_area_instance_count), 5)
- self.test_success = viewport_0_success and self.test_success
- viewport_1_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(second_entity_center_point,
- box_size / 2.0, 0), 5)
- self.test_success = viewport_1_success and self.test_success
+ # Set the view in the first viewport to point down at the first box
+ general.set_active_viewport(0)
+ helper.wait_for_condition(lambda: general.get_active_viewport() == 0, 2)
+ general.set_current_view_position(first_entity_center_point.x, first_entity_center_point.y,
+ first_entity_center_point.z + 30.0)
+ general.set_current_view_rotation(-85.0, 0.0, 0.0)
- # When the second viewport is active, the second area should be full of instances, and the first should be empty
- general.set_active_viewport(1)
- viewport_0_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(first_entity_center_point,
- box_size / 2.0, 0), 5)
- self.test_success = viewport_0_success and self.test_success
- viewport_1_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(second_entity_center_point,
- box_size / 2.0,
- filled_vegetation_area_instance_count), 5)
- self.test_success = viewport_1_success and self.test_success
+ # Set the view in the second viewport to point down at the second box
+ general.set_active_viewport(1)
+ helper.wait_for_condition(lambda: general.get_active_viewport() == 1, 2)
+ general.set_current_view_position(second_entity_center_point.x, second_entity_center_point.y,
+ second_entity_center_point.z + 30.0)
+ general.set_current_view_rotation(-85.0, 0.0, 0.0)
+
+ # Create the "flat surface" entities to use as our vegetation surfaces
+ first_surface_entity = dynveg.create_surface_entity("Surface 1", first_entity_center_point, box_size, box_size,
+ surface_height)
+ second_surface_entity = dynveg.create_surface_entity("Surface 2", second_entity_center_point, box_size, box_size,
+ surface_height)
+
+ # Create the two vegetation areas
+ test_slice_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ first_veg_entity = dynveg.create_vegetation_area("Veg Area 1", first_entity_center_point, box_size, box_size,
+ box_size, test_slice_asset_path)
+ second_veg_entity = dynveg.create_vegetation_area("Veg Area 2", second_entity_center_point, box_size, box_size,
+ box_size, test_slice_asset_path)
+
+ # When the first viewport is active, the first area should be full of instances, and the second should be empty
+ general.set_active_viewport(0)
+ helper.wait_for_condition(lambda: general.get_active_viewport() == 0, 2)
+ viewport_0_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(first_entity_center_point,
+ box_size / 2.0,
+ filled_vegetation_area_instance_count), 5)
+ viewport_1_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(second_entity_center_point,
+ box_size / 2.0, 0), 5)
+ Report.result(Tests.first_viewport_active_instance_count, viewport_0_success)
+ Report.result(Tests.second_viewport_inactive_instance_count, viewport_1_success)
+
+ # When the second viewport is active, the second area should be full of instances, and the first should be empty
+ general.set_active_viewport(1)
+ helper.wait_for_condition(lambda: general.get_active_viewport() == 1, 2)
+ viewport_0_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(first_entity_center_point,
+ box_size / 2.0, 0), 5)
+ Report.result(Tests.first_viewport_inactive_instance_count, viewport_0_success)
+ viewport_1_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(second_entity_center_point,
+ box_size / 2.0,
+ filled_vegetation_area_instance_count), 5)
+ Report.result(Tests.second_viewport_active_instance_count, viewport_1_success)
-test = TestLayerSpawnerInstanceCameraRefresh()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(LayerSpawner_InstancesRefreshUsingCorrectViewportCamera)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py
index 47e9c857c2..415673c215 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py
@@ -5,92 +5,90 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os, sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.asset as asset
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ blocked_instance_count = (
+ "Instance count is as expected wih a Blocker setup",
+ "Found unexpected instances with a Blocker setup"
+ )
-class test_MeshBlocker_InstancesBlockedByMesh(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="MeshBlocker_InstancesBlockedByMesh", args=["level"])
+def MeshBlocker_InstancesBlockedByMesh():
+ """
+ Summary:
+ Level is created. An entity with a vegetation spawner and entity with vegetation blocker (Mesh) component are
+ added. Finally, the instance counts are checked to verify expected numbers after blocker is applied.
- def run_test(self):
- """
- Summary:
- Level is created. An entity with a vegetation spawner and entity with vegetation blocker (Mesh) component are
- added. Finally, the instance counts are checked to verify expected numbers after blocker is applied.
+ Expected Behavior:
+ The vegetation planted in the Spawner area is blocked by the Mesh of the Vegetation Blocker Mesh component.
- Expected Behavior:
- The vegetation planted in the Spawner area is blocked by the Mesh of the Vegetation Blocker Mesh component.
+ Test Steps:
+ --> Open a level
+ --> Create Spawner Entity
+ --> Create Surface Entity to spawn vegetation instances on
+ --> Create Blocker Entity with cube mesh
+ --> Verify spawned vegetation instance counts
- Test Steps:
- --> Create level
- --> Create Spawner Entity
- --> Create Surface Entity to spawn vegetation instances on
- --> Create Blocker Entity with cube mesh
- --> Verify spawned vegetation instance counts
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # Create a new level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.asset as asset
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
- entity_position,
- 10.0, 10.0, 10.0,
- asset_path)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create surface entity to plant on
- dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
- # Create blocker entity with cube mesh
- mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
- blocker_entity = hydra.Entity("Blocker Entity")
- blocker_entity.create_entity(entity_position,
- ["Vegetation Layer Blocker (Mesh)"])
- blocker_entity.add_component_of_type(mesh_type_id)
- if blocker_entity.id.IsValid():
- print(f"'{blocker_entity.name}' created")
- cubeId = asset.AssetCatalogRequestBus(
- bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(),
- False)
- blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", cubeId)
- components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 2.0)
+ # Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
+ entity_position,
+ 10.0, 10.0, 10.0,
+ asset_path)
- # Verify spawned instance counts are accurate after addition of Blocker Entity
- num_expected = 160 # Number of "PurpleFlower"s that plant on a 10 x 10 surface minus 2m blocker cube
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 2.0)
- self.test_success = self.test_success and result
+ # Create surface entity to plant on
+ dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
+
+ # Create blocker entity with cube mesh
+ mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
+ blocker_entity = hydra.Entity("Blocker Entity")
+ blocker_entity.create_entity(entity_position,
+ ["Vegetation Layer Blocker (Mesh)"])
+ blocker_entity.add_component_of_type(mesh_type_id)
+ if blocker_entity.id.IsValid():
+ print(f"'{blocker_entity.name}' created")
+ cubeId = asset.AssetCatalogRequestBus(
+ bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(),
+ False)
+ blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", cubeId)
+ components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 2.0)
+
+ # Verify spawned instance counts are accurate after addition of Blocker Entity
+ num_expected = 160 # Number of "PurpleFlower"s that plant on a 10 x 10 surface minus 2m blocker cube
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 2.0)
+ Report.result(Tests.blocked_instance_count, result)
-test = test_MeshBlocker_InstancesBlockedByMesh()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(MeshBlocker_InstancesBlockedByMesh)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py
index f5dad6b64d..be15c9967c 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py
@@ -5,104 +5,100 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import math as pymath
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr
-import azlmbr.asset as asset
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
+class Tests:
+ blocked_instance_count = (
+ "Instance count is as expected wih a Blocker setup",
+ "Found unexpected instances with a Blocker setup"
+ )
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+def MeshBlocker_InstancesBlockedByMeshHeightTuning():
+ """
+ Summary:
+ A temporary level is created, then a simple vegetation area is created. A blocker area is created and it is
+ verified that the tuning of the height percent blocker setting works as expected.
+
+ Expected Behavior:
+ Vegetation is blocked only around the trunk of the tree, while it still plants under the areas covered by branches.
+
+ Test Steps:
+ 1) Open a level
+ 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ 3) Create surface entity
+ 4) Create blocker entity with sphere mesh
+ 5) Adjust the height Min/Max percentage values of blocker
+ 6) Verify spawned instance counts are accurate after adjusting height Max percentage of Blocker Entity
+
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
+
+ :return: None
+ """
+
+ import os
+ import math as pymath
+
+ import azlmbr
+ import azlmbr.asset as asset
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
+
+ # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
+ entity_position,
+ 10.0, 10.0, 10.0,
+ asset_path)
+
+ # 3) Create surface entity to plant on
+ dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
+
+ # 4) Create blocker entity with rotated cube mesh
+ y_rotation = pymath.radians(45.0)
+ mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
+ blocker_entity = hydra.Entity("Blocker Entity")
+ blocker_entity.create_entity(entity_position,
+ ["Vegetation Layer Blocker (Mesh)"])
+ blocker_entity.add_component_of_type(mesh_type_id)
+ if blocker_entity.id.IsValid():
+ Report.info(f"'{blocker_entity.name}' created")
+ sphere_id = asset.AssetCatalogRequestBus(
+ bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(),
+ False)
+ blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_id)
+ components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 5.0)
+ components.TransformBus(bus.Event, "SetLocalRotation", blocker_entity.id, math.Vector3(0.0, y_rotation, 0.0))
+
+ # 5) Adjust the height Max percentage values of blocker
+ blocker_entity.get_set_test(0, "Configuration|Mesh Height Percent Max", 0.8)
+
+ # 6) Verify spawned instance counts are accurate after adjusting height Max percentage of Blocker Entity
+ # The number of "PurpleFlower" instances that plant on a 10 x 10 surface minus those blocked by the rotated at
+ # 80% max height factored in.
+ num_expected = 127
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.blocked_instance_count, result)
-class test_MeshBlocker_InstancesBlockedByMeshHeightTuning(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="MeshBlocker_InstancesBlockedByMeshHeightTuning", args=["level"])
+if __name__ == "__main__":
- def run_test(self):
- """
- Summary:
- A temporary level is created, then a simple vegetation area is created. A blocker area is created and it is
- verified that the tuning of the height percent blocker setting works as expected.
-
- Expected Behavior:
- Vegetation is blocked only around the trunk of the tree, while it still plants under the areas covered by branches.
-
- Test Steps:
- 1) Create level
- 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- 3) Create surface entity
- 4) Create blocker entity with sphere mesh
- 5) Adjust the height Min/Max percentage values of blocker
- 6) Verify spawned instance counts are accurate after adjusting height Max percentage of Blocker Entity
-
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
-
- :return: None
- """
-
- # 1) Create level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
-
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
-
- # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
- entity_position,
- 10.0, 10.0, 10.0,
- asset_path)
-
- # 3) Create surface entity to plant on
- dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
-
- # 4) Create blocker entity with rotated cube mesh
- y_rotation = pymath.radians(45.0)
- mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
- blocker_entity = hydra.Entity("Blocker Entity")
- blocker_entity.create_entity(entity_position,
- ["Vegetation Layer Blocker (Mesh)"])
- blocker_entity.add_component_of_type(mesh_type_id)
- if blocker_entity.id.IsValid():
- print(f"'{blocker_entity.name}' created")
- sphere_id = asset.AssetCatalogRequestBus(
- bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(),
- False)
- blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_id)
- components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 5.0)
- components.TransformBus(bus.Event, "SetLocalRotation", blocker_entity.id, math.Vector3(0.0, y_rotation, 0.0))
-
- # 5) Adjust the height Max percentage values of blocker
- blocker_entity.get_set_test(0, "Configuration|Mesh Height Percent Max", 0.8)
-
- # 6) Verify spawned instance counts are accurate after adjusting height Max percentage of Blocker Entity
- # The number of "PurpleFlower" instances that plant on a 10 x 10 surface minus those blocked by the rotated at
- # 80% max height factored in.
- num_expected = 127
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = self.test_success and result
-
-
-test = test_MeshBlocker_InstancesBlockedByMeshHeightTuning()
-test.run()
+ from editor_python_test_tools.utils import Report
+ Report.start_test(MeshBlocker_InstancesBlockedByMeshHeightTuning)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_DependentOnMeshComponent.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_DependentOnMeshComponent.py
index c093529888..cee8ebe5b6 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_DependentOnMeshComponent.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_DependentOnMeshComponent.py
@@ -5,93 +5,87 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.entity as EntityId
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
+class Tests:
+ new_entity_created = (
+ "Successfully created new entity",
+ "Failed to create new entity"
+ )
+ emitter_disabled_before_mesh = (
+ "Mesh Surface Tag Emitter is disabled without a Mesh component",
+ "Mesh Surface Tag Emitter is unexpectedly enabled without a Mesh component"
+ )
+ emitter_enabled_after_mesh = (
+ "Mesh Surface Tag Emitter is enabled after adding a Mesh component",
+ "Mesh Surface Tag Emitter is unexpectedly disabled after adding a Mesh component"
+ )
-class TestMeshSurfaceTagEmitter(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="MeshSurfaceTagEmitter_DependentOnMeshComponent", args=["level"])
+def MeshSurfaceTagEmitter_DependentOnMeshComponent():
+ """
+ Summary:
+ A New level is loaded. A New entity is created with component "Mesh Surface Tag Emitter". Adding a component
+ "Mesh" to the same entity.
- def run_test(self):
- """
- Summary:
- A New level is loaded. A New entity is created with component "Mesh Surface Tag Emitter". Adding a component
- "Mesh" to the same entity.
+ Expected Behavior:
+ Mesh Surface Tag Emitter is disabled until the required Mesh component is added to the entity.
- Expected Behavior:
- Mesh Surface Tag Emitter is disabled until the required Mesh component is added to the entity.
+ Test Steps:
+ 1) Open level
+ 2) Create a new entity with component "Mesh Surface Tag Emitter"
+ 3) Make sure Mesh Surface Tag Emitter is disabled
+ 4) Add Mesh to the same entity
+ 5) Make sure Mesh Surface Tag Emitter is enabled after adding Mesh
- Test Steps:
- 1) Open level
- 2) Create a new entity with component "Mesh Surface Tag Emitter"
- 3) Make sure Mesh Surface Tag Emitter is disabled
- 4) Add Mesh to the same entity
- 5) Make sure Mesh Surface Tag Emitter is enabled after adding Mesh
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.entity as EntityId
+ import azlmbr.math as math
- def is_component_enabled(EntityComponentIdPair):
- return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", EntityComponentIdPair)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 1) Open level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ def is_component_enabled(EntityComponentIdPair):
+ return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", EntityComponentIdPair)
- # 2) Create a new entity with component "Mesh Surface Tag Emitter"
- entity_position = math.Vector3(125.0, 136.0, 32.0)
- component_to_add = "Mesh Surface Tag Emitter"
- entity_id = editor.ToolsApplicationRequestBus(
- bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId()
- )
- meshentity = hydra.Entity("meshentity", entity_id)
- meshentity.components = []
- meshentity.components.append(hydra.add_component(component_to_add, entity_id))
- if entity_id.IsValid():
- print("New Entity Created")
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Make sure Mesh Surface Tag Emitter is disabled
- is_enabled = is_component_enabled(meshentity.components[0])
- self.test_success = self.test_success and not is_enabled
- if not is_enabled:
- print(f"{component_to_add} is Disabled")
- elif is_enabled:
- print(f"{component_to_add} is Enabled. But It should be disabled before adding Mesh")
+ # 2) Create a new entity with component "Mesh Surface Tag Emitter"
+ entity_position = math.Vector3(125.0, 136.0, 32.0)
+ component_to_add = "Mesh Surface Tag Emitter"
+ entity_id = editor.ToolsApplicationRequestBus(
+ bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId()
+ )
+ meshentity = hydra.Entity("meshentity", entity_id)
+ meshentity.components = []
+ meshentity.components.append(hydra.add_component(component_to_add, entity_id))
+ Report.critical_result(Tests.new_entity_created, entity_id.IsValid())
- # 4) Add Mesh to the same entity
- component = "Mesh"
- meshentity.components.append(hydra.add_component(component, entity_id))
+ # 3) Make sure Mesh Surface Tag Emitter is disabled
+ is_enabled = is_component_enabled(meshentity.components[0])
+ Report.result(Tests.emitter_disabled_before_mesh, not is_enabled)
- # 5) Make sure Mesh Surface Tag Emitter is enabled after adding Mesh
- is_enabled = is_component_enabled(meshentity.components[0])
- self.test_success = self.test_success and is_enabled
- if is_enabled:
- print(f"{component_to_add} is Enabled")
- elif not is_enabled:
- print(f"{component_to_add} is Disabled. But It should be enabled after adding Mesh")
+ # 4) Add Mesh to the same entity
+ component = "Mesh"
+ meshentity.components.append(hydra.add_component(component, entity_id))
+
+ # 5) Make sure Mesh Surface Tag Emitter is enabled after adding Mesh
+ is_enabled = is_component_enabled(meshentity.components[0])
+ Report.result(Tests.emitter_enabled_after_mesh, is_enabled)
-test = TestMeshSurfaceTagEmitter()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(MeshSurfaceTagEmitter_DependentOnMeshComponent)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py
index 83beb5d0c2..d7abc66106 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py
@@ -5,75 +5,71 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.surface_data as surface_data
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
+class Tests:
+ add_surface_tag = (
+ "Surface Tag added successfully",
+ "Failed to add Surface Tag"
+ )
+ remove_surface_tag = (
+ "Successfully removed Surface Tag",
+ "Failed to remove Surface Tag"
+ )
-class TestMeshSurfaceTagEmitter(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSucessfully",
- args=["level"])
+def MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully():
+ """
+ Summary:
+ An enity with Mesh Tag Emitter and a Mesh is added to the viewport to verify if we are able to
+ add/remove surface tags.
- def run_test(self):
- """
- Summary:
- An enity with Mesh Tag Emitter and a Mesh is added to the viewport to verify if we are able to
- add/remove surface tags.
+ Expected Behavior:
+ A new Surface Tag can be added and removed from the component.
- Expected Behavior:
- A new Surface Tag can be added and removed from the component.
+ Test Steps:
+ 1) Open level
+ 2) Create a new entity with components "Mesh Surface Tag Emitter", "Mesh"
+ 3) Add/ remove Surface Tags
- Test Steps:
- 1) Open level
- 2) Create a new entity with components "Mesh Surface Tag Emitter", "Mesh"
- 3) Add/ remove Surface Tags
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import azlmbr.math as math
+ import azlmbr.surface_data as surface_data
- # 1) Open level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with components "Mesh Surface Tag Emitter", "Mesh"
- entity_position = math.Vector3(125.0, 136.0, 32.0)
- components_to_add = ["Mesh Surface Tag Emitter", "Mesh"]
- entity = hydra.Entity("entity")
- entity.create_entity(entity_position, components_to_add)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Add/ remove Surface Tags
- tag = surface_data.SurfaceTag()
- tag.SetTag("water")
- pte = hydra.get_property_tree(entity.components[0])
- path = "Configuration|Generated Tags"
- pte.add_container_item(path, 0, tag)
- success = self.wait_for_condition(lambda: pte.get_container_count(path).GetValue() == 1, 5.0)
- self.test_success = self.test_success and success
- print(f"Added SurfaceTag: container count is {pte.get_container_count(path).GetValue()}")
- pte.remove_container_item(path, 0)
- success = self.wait_for_condition(lambda: pte.get_container_count(path).GetValue() == 0, 5.0)
- self.test_success = self.test_success and success
- print(f"Removed SurfaceTag: container count is {pte.get_container_count(path).GetValue()}")
+ # 2) Create a new entity with components "Mesh Surface Tag Emitter", "Mesh"
+ entity_position = math.Vector3(125.0, 136.0, 32.0)
+ components_to_add = ["Mesh Surface Tag Emitter", "Mesh"]
+ entity = hydra.Entity("entity")
+ entity.create_entity(entity_position, components_to_add)
+
+ # 3) Add/ remove Surface Tags
+ tag = surface_data.SurfaceTag()
+ tag.SetTag("water")
+ pte = hydra.get_property_tree(entity.components[0])
+ path = "Configuration|Generated Tags"
+ pte.add_container_item(path, 0, tag)
+ success = helper.wait_for_condition(lambda: pte.get_container_count(path).GetValue() == 1, 5.0)
+ Report.result(Tests.add_surface_tag, success)
+ pte.remove_container_item(path, 0)
+ success = helper.wait_for_condition(lambda: pte.get_container_count(path).GetValue() == 0, 5.0)
+ Report.result(Tests.remove_surface_tag, success)
-test = TestMeshSurfaceTagEmitter()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PhysXColliderSurfaceTagEmitter_E2E_Editor.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PhysXColliderSurfaceTagEmitter_E2E_Editor.py
index 361dbdaea9..b5739c2386 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PhysXColliderSurfaceTagEmitter_E2E_Editor.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PhysXColliderSurfaceTagEmitter_E2E_Editor.py
@@ -5,27 +5,30 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.asset as asset
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
+def PhysXColliderSurfaceTagEmitter_E2E_Editor():
+ """
+ Summary:
+ Test aspects of the PhysX Collider Surface Tag Emitter Component through the BehaviorContext and the Property
+ Tree.
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ :return: None
+ """
+ import os
-class TestPhysXColliderSurfaceTagEmitter(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="PhysXColliderSurfaceTagEmitter_E2E_Editor", args=["level"])
+ import azlmbr.asset as asset
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- def validate_behavior_context(self):
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ def validate_behavior_context():
# Verify that we can create the component through the BehaviorContext
behavior_context_test_success = True
test_component = azlmbr.surface_data.SurfaceDataColliderComponent()
@@ -38,163 +41,181 @@ class TestPhysXColliderSurfaceTagEmitter(EditorTestHelper):
provider_tag2 = azlmbr.surface_data.SurfaceTag('provider_tag2')
modifier_tag1 = azlmbr.surface_data.SurfaceTag('modifier_tag1')
modifier_tag2 = azlmbr.surface_data.SurfaceTag('modifier_tag2')
- behavior_context_test_success = behavior_context_test_success and hydra.get_set_property_test(test_component,
- 'providerTags',
- [provider_tag1,
- provider_tag2])
- behavior_context_test_success = behavior_context_test_success and hydra.get_set_property_test(test_component,
- 'modifierTags',
- [modifier_tag1,
- modifier_tag2])
- self.log(f'SurfaceDataColliderComponent() BehaviorContext test: {behavior_context_test_success}')
+ behavior_context_test_success = behavior_context_test_success and hydra.get_set_property_test(
+ test_component,
+ 'providerTags',
+ [provider_tag1,
+ provider_tag2])
+ behavior_context_test_success = behavior_context_test_success and hydra.get_set_property_test(
+ test_component,
+ 'modifierTags',
+ [modifier_tag1,
+ modifier_tag2])
+ Report.info(f'SurfaceDataColliderComponent() BehaviorContext test: {behavior_context_test_success}')
return behavior_context_test_success
- def run_test(self):
- """
- Summary:
- Test aspects of the PhysX Collider Surface Tag Emitter Component through the BehaviorContext and the Property Tree.
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- :return: None
- """
- # Create an empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ # Verify all of the BehaviorContext API:
+ behavior_context = (
+ "SurfaceDataColliderComponent() Behavior Context tests were successful",
+ "SurfaceDataColliderComponent() Behavior Context tests failed"
+ )
+ Report.result(behavior_context, validate_behavior_context())
+
+ # Set up a test environment to validate the PhysX Collider Surface Tag Emitter Component.
+ # The test environment will consist of the following:
+ # - a 32 x 32 x 1 box shape that emits a surface with no tags
+ # - a 32 x 32 x 32 vegetation area that will only place vegetation on surfaces with the 'test' tag
+ # With this setup, no vegetation will appear until a Surface Tag Emitter either emits new points with
+ # the correct tag, or modifies points on our box shape to emit the correct tag.
+
+ # Initialize some arbitrary constants for our test
+ entity_center_point = math.Vector3(512.0, 512.0, 100.0)
+ invalid_tag = azlmbr.surface_data.SurfaceTag('invalid')
+ surface_tag = azlmbr.surface_data.SurfaceTag('test')
+ test_box_size = 32.0
+ baseline_surface_height = 1.0
+ collider_radius = 4.0
+ collider_diameter = collider_radius * 2.0
+
+ # Set viewport view of area under test, and toggle helpers back on
+ general.set_current_view_position(512.0, 485.0, 110.0)
+ general.set_current_view_rotation(-35.0, 0.0, 0.0)
+ general.toggle_helpers()
+
+ # Create the "flat surface" entity to use as our baseline surface
+ dynveg.create_surface_entity("Baseline Surface", entity_center_point, 32.0, 32.0, 1.0)
+
+ # Create a new entity with required vegetation area components
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Veg Area", entity_center_point, 32.0, 32.0, 32.0, asset_path)
+
+ # Add a Vegetation Surface Mask Filter component to the spawner entity and set it to include the "test" tag
+ spawner_entity.add_component("Vegetation Surface Mask Filter")
+ spawner_entity.get_set_test(3, "Configuration|Inclusion|Surface Tags", [surface_tag])
+
+ # At this point, there should be 0 instances within our entire veg area
+ initial_instance_count = (
+ "Found no instances as expected with initial setup",
+ "Unexpected found instances with initial setup"
+ )
+ initial_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(entity_center_point, 16.0, 0),
+ 5.0)
+ Report.result(initial_instance_count, initial_success)
+
+ # Create an entity with a PhysX Collider and our PhysX Collider Surface Tag Emitter
+ collider_entity_created = (
+ "Successfully created a Collider entity",
+ "Failed to create Collider entity"
+ )
+ collider_entity = hydra.Entity("Collider Surface")
+ collider_entity.create_entity(
+ entity_center_point,
+ ["PhysX Collider", "PhysX Collider Surface Tag Emitter"]
)
+ Report.result(collider_entity_created, collider_entity.id.IsValid())
- # Verify all of the BehaviorContext API:
- self.test_success = self.test_success and self.validate_behavior_context()
+ # Set up the PhysX Collider so that each shape type (sphere, box, capsule) has the same test height.
+ hydra.get_set_test(collider_entity, 0, "Shape Configuration|Sphere|Radius", collider_radius)
+ hydra.get_set_test(collider_entity, 0, "Shape Configuration|Box|Dimensions", math.Vector3(collider_diameter,
+ collider_diameter,
+ collider_diameter))
+ hydra.get_set_test(collider_entity, 0, "Shape Configuration|Capsule|Height", collider_diameter)
- # Set up a test environment to validate the PhysX Collider Surface Tag Emitter Component.
- # The test environment will consist of the following:
- # - a 32 x 32 x 1 box shape that emits a surface with no tags
- # - a 32 x 32 x 32 vegetation area that will only place vegetation on surfaces with the 'test' tag
- # With this setup, no vegetation will appear until a Surface Tag Emitter either emits new points with
- # the correct tag, or modifies points on our box shape to emit the correct tag.
-
- # Initialize some arbitrary constants for our test
- entity_center_point = math.Vector3(512.0, 512.0, 100.0)
- invalid_tag = azlmbr.surface_data.SurfaceTag('invalid')
- surface_tag = azlmbr.surface_data.SurfaceTag('test')
- test_box_size = 32.0
- baseline_surface_height = 1.0
- collider_radius = 4.0
- collider_diameter = collider_radius * 2.0
-
- # Set viewport view of area under test, and toggle helpers back on
- general.set_current_view_position(512.0, 485.0, 110.0)
- general.set_current_view_rotation(-35.0, 0.0, 0.0)
- general.toggle_helpers()
-
- # Create the "flat surface" entity to use as our baseline surface
- dynveg.create_surface_entity("Baseline Surface", entity_center_point, 32.0, 32.0, 1.0)
-
- # Create a new entity with required vegetation area components
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Veg Area", entity_center_point, 32.0, 32.0, 32.0, asset_path)
-
- # Add a Vegetation Surface Mask Filter component to the spawner entity and set it to include the "test" tag
- spawner_entity.add_component("Vegetation Surface Mask Filter")
- spawner_entity.get_set_test(3, "Configuration|Inclusion|Surface Tags", [surface_tag])
-
- # At this point, there should be 0 instances within our entire veg area
- initial_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(entity_center_point, 16.0, 0),
- 5.0)
- self.test_success = self.test_success and initial_success
-
- # Create an entity with a PhysX Collider and our PhysX Collider Surface Tag Emitter
- collider_entity = hydra.Entity("Collider Surface")
- collider_entity.create_entity(
- entity_center_point,
- ["PhysX Collider", "PhysX Collider Surface Tag Emitter"]
- )
- if collider_entity.id.IsValid():
- self.log(f"'{collider_entity.name}' created")
-
- # Set up the PhysX Collider so that each shape type (sphere, box, capsule) has the same test height.
- hydra.get_set_test(collider_entity, 0, "Shape Configuration|Sphere|Radius", collider_radius)
- hydra.get_set_test(collider_entity, 0, "Shape Configuration|Box|Dimensions", math.Vector3(collider_diameter,
- collider_diameter,
- collider_diameter))
- hydra.get_set_test(collider_entity, 0, "Shape Configuration|Capsule|Height", collider_diameter)
-
- # Run through each collider shape type (sphere, box, capsule) and verify the surface generation
- # and surface modification of the PhysX Collision Surface Tag Emitter Component.
- for collider_shape in range(0, 3):
- hydra.get_set_test(collider_entity, 0, "Shape Configuration|Shape", collider_shape)
-
- # Test: Generate a new surface on the collider.
- # There should be one instance at the very top of the collider sphere, and none on the baseline surface
- # (We use a small query box to only check for one placed instance point)
- hydra.get_set_test(collider_entity, 1, "Configuration|Generated Tags", [surface_tag])
- hydra.get_set_test(collider_entity, 1, "Configuration|Extended Tags", [invalid_tag])
- top_point = math.Vector3(entity_center_point.x, entity_center_point.y, entity_center_point.z +
- collider_radius)
- baseline_surface_point = math.Vector3(entity_center_point.x, entity_center_point.y, entity_center_point.z +
- (baseline_surface_height / 2.0))
- top_point_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 1), 5.0)
- self.test_success = self.test_success and top_point_success
- baseline_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
- 0.25, 0), 5.0)
- self.test_success = self.test_success and baseline_success
-
- # Test: Modify an existing surface inside the collider.
- # There should be no instances at the very top of the collider sphere, and one on the baseline surface
- # within our query box.
- # (We use a small query box to only check for one placed instance point)
- hydra.get_set_test(collider_entity, 1, "Configuration|Generated Tags", [invalid_tag])
- hydra.get_set_test(collider_entity, 1, "Configuration|Extended Tags", [surface_tag])
- top_point_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 0), 5.0)
- self.test_success = self.test_success and top_point_success
- baseline_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
- 0.25, 1), 5.0)
- self.test_success = self.test_success and baseline_success
-
- # Setup collider entity with a PhysX Mesh
- test_physx_mesh_asset_id = asset.AssetCatalogRequestBus(
- bus.Broadcast, "GetAssetIdByPath", os.path.join("levels", "physics",
- "Material_PerFaceMaterialGetsCorrectMaterial",
- "test.pxmesh"), math.Uuid(), False)
-
- # Remove/re-add component due to LYN-5496
- collider_entity.remove_component("PhysX Collider")
- collider_entity.add_component("PhysX Collider")
- self.wait_for_condition(lambda: editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled',
- collider_entity.components[1]), 5.0)
- hydra.get_set_test(collider_entity, 1, "Shape Configuration|Shape", 7)
- hydra.get_set_test(collider_entity, 1, "Shape Configuration|Asset|PhysX Mesh", test_physx_mesh_asset_id)
-
- # Set the asset scale to match the test heights of the shapes tested
- asset_scale = math.Vector3(1.0, 1.0, 9.0)
- collider_entity.get_set_test(1, "Shape Configuration|Asset|Configuration|Asset Scale", asset_scale)
+ # Run through each collider shape type (sphere, box, capsule) and verify the surface generation
+ # and surface modification of the PhysX Collision Surface Tag Emitter Component.
+ for collider_shape in range(0, 3):
+ collider_shapes = {0: "Sphere", 1: "Box", 2: "Capsule"}
+ hydra.get_set_test(collider_entity, 0, "Shape Configuration|Shape", collider_shape)
# Test: Generate a new surface on the collider.
- # There should be one instance at the very top of the collider mesh, and none on the baseline surface
+ # There should be one instance at the very top of the collider sphere, and none on the baseline surface
# (We use a small query box to only check for one placed instance point)
- self.log("Starting PhysX Mesh Collider Test")
- hydra.get_set_test(collider_entity, 0, "Configuration|Generated Tags", [surface_tag])
- hydra.get_set_test(collider_entity, 0, "Configuration|Extended Tags", [invalid_tag])
- top_point_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 1), 5.0)
- self.test_success = self.test_success and top_point_success
- baseline_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
- 0.25, 0), 5.0)
- self.test_success = self.test_success and baseline_success
+ on_collider_top_point_count = (
+ f"Expected number of instances found on the top point for {collider_shapes[collider_shape]} shape",
+ f"Found an unexpected number of instances on the top point for {collider_shapes[collider_shape]} shape"
+ )
+ on_collider_baseline_count = (
+ f"Expected number of instances found on the baseline point for {collider_shapes[collider_shape]} shape",
+ f"Found an unexpected number of instances on the baseline point for {collider_shapes[collider_shape]} shape"
+ )
+ hydra.get_set_test(collider_entity, 1, "Configuration|Generated Tags", [surface_tag])
+ hydra.get_set_test(collider_entity, 1, "Configuration|Extended Tags", [invalid_tag])
+ top_point = math.Vector3(entity_center_point.x, entity_center_point.y, entity_center_point.z +
+ collider_radius)
+ baseline_surface_point = math.Vector3(entity_center_point.x, entity_center_point.y, entity_center_point.z +
+ (baseline_surface_height / 2.0))
+ top_point_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 1), 5.0)
+ Report.result(on_collider_top_point_count, top_point_success)
+ baseline_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
+ 0.25, 0), 5.0)
+ Report.result(on_collider_baseline_count, baseline_success)
# Test: Modify an existing surface inside the collider.
- # There should be no instances at the very top of the collider mesh, and none on the baseline surface within
- # our query box as PhysX meshes are treated as hollow shells, not solid volumes.
+ # There should be no instances at the very top of the collider sphere, and one on the baseline surface
+ # within our query box.
# (We use a small query box to only check for one placed instance point)
- hydra.get_set_test(collider_entity, 0, "Configuration|Generated Tags", [invalid_tag])
- hydra.get_set_test(collider_entity, 0, "Configuration|Extended Tags", [surface_tag])
- top_point_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 0), 5.0)
- self.test_success = self.test_success and top_point_success
- baseline_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
- 0.25, 0), 5.0)
- self.test_success = self.test_success and baseline_success
+ hydra.get_set_test(collider_entity, 1, "Configuration|Generated Tags", [invalid_tag])
+ hydra.get_set_test(collider_entity, 1, "Configuration|Extended Tags", [surface_tag])
+ top_point_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 0), 5.0)
+ Report.result(on_collider_top_point_count, top_point_success)
+ baseline_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
+ 0.25, 1), 5.0)
+ Report.result(on_collider_baseline_count, baseline_success)
+
+ # Setup collider entity with a PhysX Mesh
+ test_physx_mesh_asset_id = asset.AssetCatalogRequestBus(
+ bus.Broadcast, "GetAssetIdByPath", os.path.join("levels", "physics",
+ "Material_PerFaceMaterialGetsCorrectMaterial",
+ "test.pxmesh"), math.Uuid(), False)
+ collider_entity.remove_component("PhysX Collider")
+ collider_entity.add_component("PhysX Collider")
+ helper.wait_for_condition(lambda: editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled',
+ collider_entity.components[1]), 5.0)
+ hydra.get_set_test(collider_entity, 1, "Shape Configuration|Shape", 7)
+ hydra.get_set_test(collider_entity, 1, "Shape Configuration|Asset|PhysX Mesh", test_physx_mesh_asset_id)
+
+ # Set the asset scale to match the test heights of the shapes tested
+ asset_scale = math.Vector3(1.0, 1.0, 9.0)
+ collider_entity.get_set_test(1, "Shape Configuration|Asset|Configuration|Asset Scale", asset_scale)
+
+ # Test: Generate a new surface on the collider.
+ # There should be one instance at the very top of the collider mesh, and none on the baseline surface
+ # (We use a small query box to only check for one placed instance point)
+ Report.info("Starting PhysX Mesh Collider Test")
+ on_collider_top_point_count = (
+ f"Expected number of instances found on the top point for a PhysX Mesh",
+ f"Found an unexpected number of instances on the top point for a PhysX Mesh"
+ )
+ on_collider_baseline_count = (
+ f"Expected number of instances found on the baseline point for a PhysX Mesh",
+ f"Found an unexpected number of instances on the baseline point for a PhysX Mesh"
+ )
+ hydra.get_set_test(collider_entity, 0, "Configuration|Generated Tags", [surface_tag])
+ hydra.get_set_test(collider_entity, 0, "Configuration|Extended Tags", [invalid_tag])
+ top_point_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 1), 5.0)
+ Report.result(on_collider_top_point_count, top_point_success)
+ baseline_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
+ 0.25, 0), 5.0)
+ Report.result(on_collider_baseline_count, baseline_success)
+
+ # Test: Modify an existing surface inside the collider.
+ # There should be no instances at the very top of the collider mesh, and none on the baseline surface within
+ # our query box as PhysX meshes are treated as hollow shells, not solid volumes.
+ # (We use a small query box to only check for one placed instance point)
+ hydra.get_set_test(collider_entity, 0, "Configuration|Generated Tags", [invalid_tag])
+ hydra.get_set_test(collider_entity, 0, "Configuration|Extended Tags", [surface_tag])
+ top_point_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, 0.25, 0), 5.0)
+ Report.result(on_collider_top_point_count, top_point_success)
+ baseline_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(baseline_surface_point,
+ 0.25, 0), 5.0)
+ Report.result(on_collider_baseline_count, baseline_success)
-test = TestPhysXColliderSurfaceTagEmitter()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(PhysXColliderSurfaceTagEmitter_E2E_Editor)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_AutoSnapToSurfaceWorks.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_AutoSnapToSurfaceWorks.py
index a55e88488c..5a3ee70d22 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_AutoSnapToSurfaceWorks.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_AutoSnapToSurfaceWorks.py
@@ -5,136 +5,133 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.bus as bus
-import azlmbr.legacy.general as general
-import azlmbr.editor as editor
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_count = (
+ "Initial instance count is as expected",
+ "Found an unexpected number of initial instances"
+ )
+ autosnap_enabled_instance_count = (
+ "Found the expected number of instances with Auto Snap to Surface enabled",
+ "Found an unexpected number of instances with Auto Snap to Surface enabled"
+ )
+ autosnap_disabled_instance_count = (
+ "Found the expected number of instances with Auto Snap to Surface disabled",
+ "Found an unexpected number of instances with Auto Snap to Surface disabled"
+ )
-class TestPositionModifierAutoSnapToSurface(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="PositionModifier_AutoSnapToSurface", args=["level"])
+def PositionModifier_AutoSnapToSurfaceWorks():
+ """
+ Summary:
+ Instance spawner is setup to plant on a spherical mesh. Offsets are set on the x-axis, and checks are performed
+ to ensure instances plant where expected depending on the toggle setting.
- def run_test(self):
- """
- Summary:
- Instance spawner is setup to plant on a spherical mesh. Offsets are set on the x-axis, and checks are performed
- to ensure instances plant where expected depending on the toggle setting.
+ Expected Behavior:
+ Offset instances snap to the expected surface when Auto Snap to Surface is enabled, and offset away from surface
+ when it is disabled.
- Expected Behavior:
- Offset instances snap to the expected surface when Auto Snap to Surface is enabled, and offset away from surface
- when it is disabled.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create a new entity with required vegetation area components and a Position Modifier
+ 3) Create a spherical planting surface
+ 4) Verify initial instance counts pre-filter
+ 5) Create a child entity of the spawner entity with a Constant Gradient component and pin to spawner
+ 6) Set the Position Modifier offset to 5 on the x-axis
+ 7) Validate instance counts on top of and inside the sphere mesh with Auto Snap to Surface enabled
+ 8) Validate instance counts on top of and inside the sphere mesh with Auto Snap to Surface disabled
- Test Steps:
- 1) Create a new, temporary level
- 2) Create a new entity with required vegetation area components and a Position Modifier
- 3) Create a spherical planting surface
- 4) Verify initial instance counts pre-filter
- 5) Create a child entity of the spawner entity with a Constant Gradient component and pin to spawner
- 6) Set the Position Modifier offset to 5 on the x-axis
- 7) Validate instance counts on top of and inside the sphere mesh with Auto Snap to Surface enabled
- 8) Validate instance counts on top of and inside the sphere mesh with Auto Snap to Surface disabled
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- position_modifier_paths = ['Configuration|Position X|Range Min', 'Configuration|Position X|Range Max',
- 'Configuration|Position Y|Range Min', 'Configuration|Position Y|Range Max',
- 'Configuration|Position Z|Range Min', 'Configuration|Position Z|Range Max']
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components and a Position Modifier
- spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
+ position_modifier_paths = ['Configuration|Position X|Range Min', 'Configuration|Position X|Range Max',
+ 'Configuration|Position Y|Range Min', 'Configuration|Position Y|Range Max',
+ 'Configuration|Position Z|Range Min', 'Configuration|Position Z|Range Max']
- # Add a Vegetation Position Modifier and set offset values to 0
- spawner_entity.add_component("Vegetation Position Modifier")
- for path in position_modifier_paths:
- spawner_entity.get_set_test(3, path, 0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Create a spherical planting surface and a flat surface
- flat_entity = dynveg.create_surface_entity("Flat Surface", spawner_center_point, 32.0, 32.0, 1.0)
- hill_entity = dynveg.create_mesh_surface_entity_with_slopes("Planting Surface", spawner_center_point, 5.0)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # Disable the Flat Surface Box Shape component, and temporarily ignore initial instance counts due to LYN-2245
- editor.EditorComponentAPIBus(bus.Broadcast, 'DisableComponents', [flat_entity.components[0]])
- """
- # 4) Verify initial instance counts pre-filter
- num_expected = 121
- spawner_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and spawner_success
- """
+ # 2) Create a new entity with required vegetation area components and a Position Modifier
+ spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
- # 5) Create a child entity of the spawner entity with a Constant Gradient component and pin to spawner
- components_to_add = ["Constant Gradient"]
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
+ # Add a Vegetation Position Modifier and set offset values to 0
+ spawner_entity.add_component("Vegetation Position Modifier")
+ for path in position_modifier_paths:
+ spawner_entity.get_set_test(3, path, 0)
- # Pin the Constant Gradient to the X axis of the spawner's Position Modifier component
- spawner_entity.get_set_test(3, 'Configuration|Position X|Gradient|Gradient Entity Id', gradient_entity.id)
+ # 3) Create a spherical planting surface
+ hill_entity = dynveg.create_mesh_surface_entity_with_slopes("Planting Surface", spawner_center_point, 5.0)
- # 6) Set the Position Modifier offset to 2.5 on the x-axis
- spawner_entity.get_set_test(3, position_modifier_paths[0], 2.5)
- spawner_entity.get_set_test(3, position_modifier_paths[1], 2.5)
+ # 4) Verify initial instance counts pre-filter
+ num_expected = 29
+ spawner_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.initial_instance_count, spawner_success)
- # 7) Validate instance count at the top of the sphere mesh and inside the sphere mesh while Auto Snap to Surface
- # is enabled
- top_point = math.Vector3(512.0, 512.0, 37.0)
- inside_point = math.Vector3(512.0, 512.0, 35.0)
- radius = 0.5
- num_expected = 1
- self.log(f"Checking for instances in a {radius * 2}m area at {top_point.ToString()}")
- top_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, radius, num_expected),
- 5.0)
- self.test_success = top_success and self.test_success
- num_expected = 0
- self.log(f"Checking for instances in a {radius * 2}m area at {inside_point.ToString()}")
- inside_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(inside_point, radius,
- num_expected), 5.0)
- self.test_success = inside_success and self.test_success
+ # 5) Create a child entity of the spawner entity with a Constant Gradient component and pin to spawner
+ components_to_add = ["Constant Gradient"]
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
- # 8) Toggle off Auto Snap to Surface. Instances should now plant inside the sphere and no longer on top
- spawner_entity.get_set_test(3, "Configuration|Auto Snap To Surface", False)
- num_expected = 0
- self.log(f"Checking for instances in a {radius * 2}m area at {top_point.ToString()}")
- top_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, radius, num_expected),
- 5.0)
- self.test_success = top_success and self.test_success
- num_expected = 1
- self.log(f"Checking for instances in a {radius * 2}m area at {inside_point.ToString()}")
- inside_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(inside_point, radius,
- num_expected), 5.0)
- self.test_success = inside_success and self.test_success
+ # Pin the Constant Gradient to the X axis of the spawner's Position Modifier component
+ spawner_entity.get_set_test(3, 'Configuration|Position X|Gradient|Gradient Entity Id', gradient_entity.id)
+
+ # 6) Set the Position Modifier offset to 2.5 on the x-axis
+ spawner_entity.get_set_test(3, position_modifier_paths[0], 2.5)
+ spawner_entity.get_set_test(3, position_modifier_paths[1], 2.5)
+
+ # 7) Validate instance count at the top of the sphere mesh and inside the sphere mesh while Auto Snap to Surface
+ # is enabled
+ top_point = math.Vector3(512.0, 512.0, 37.0)
+ inside_point = math.Vector3(512.0, 512.0, 35.0)
+ radius = 0.5
+ num_expected = 1
+ Report.info(f"Checking for instances in a {radius * 2}m area at {top_point.ToString()}")
+ top_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, radius, num_expected),
+ 5.0)
+ num_expected = 0
+ Report.info(f"Checking for instances in a {radius * 2}m area at {inside_point.ToString()}")
+ inside_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(inside_point, radius,
+ num_expected), 5.0)
+ Report.result(Tests.autosnap_enabled_instance_count, top_success and inside_success)
+
+ # 8) Toggle off Auto Snap to Surface. Instances should now plant inside the sphere and no longer on top
+ spawner_entity.get_set_test(3, "Configuration|Auto Snap To Surface", False)
+ num_expected = 0
+ Report.info(f"Checking for instances in a {radius * 2}m area at {top_point.ToString()}")
+ top_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(top_point, radius, num_expected),
+ 5.0)
+ num_expected = 1
+ Report.info(f"Checking for instances in a {radius * 2}m area at {inside_point.ToString()}")
+ inside_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(inside_point, radius,
+ num_expected), 5.0)
+ Report.result(Tests.autosnap_disabled_instance_count, top_success and inside_success)
-test = TestPositionModifierAutoSnapToSurface()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(PositionModifier_AutoSnapToSurfaceWorks)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets.py
index 9597f79339..c4fa91f886 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets.py
@@ -5,159 +5,164 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import random
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_instance_count = (
+ "Initial instance count is as expected",
+ "Found an unexpected number of initial instances"
+ )
+ position_offset = (
+ "Found instances at all expected locations with Position Modifier offsets configured",
+ "Failed to find all expected instances at all locations with Position Modifier offsets configured"
+ )
+ position_offset_overrides = (
+ "Found instances at all expected locations with Position Modifier offset overrides configured",
+ "Failed to find all expected instances at all locations with Position Modifier offset overrides configured"
+ )
-class TestPositionModifierComponentAndOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="PositionModifierComponentAndOverrides_InstanceOffset", args=["level"])
+def PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets():
+ """
+ Summary: Range Min/Max in the Vegetation Position Modifier component and component overrides can be set for all
+ axes, and functions as expected when fed a gradient signal.
- def run_test(self):
- """
- Summary: Range Min/Max in the Vegetation Position Modifier component and component overrides can be set for all
- axes, and functions as expected when fed a gradient signal.
+ Expected Behavior: Instances are offset by the specified amount.
- Expected Behavior: Instances are offset by the specified amount.
+ Test Steps:
+ 1) Open an existing level
+ 2) Spawner area is setup with all necessary components
+ 3) Surface for planting is created
+ 4) Initial instance count validation pre-filter is performed
+ 5) An entity with a Constant Gradient of 1 is added as a child to the spawner entity, and pinned to the Position
+ Modifier Gradient Entity Id fields
+ 6) Sector size is adjusted on a Vegetation System Settings component to allow for offset instances to not fall
+ outside of the queried sector
+ 7) Random offsets are set for each axis of the Position Modifier component, and instance counts are validated
+ 8) Overrides are enabled on the Position Modifier component
+ 9) Random offsets are set for each axis of the descriptor's Position Modifier overrides, and instance counts
+ are validated
- Test Steps:
- 1) New test level is created
- 2) Spawner area is setup with all necessary components
- 3) Surface for planting is created
- 4) Initial instance count validation pre-filter is performed
- 5) An entity with a Constant Gradient of 1 is added as a child to the spawner entity, and pinned to the Position
- Modifier Gradient Entity Id fields
- 6) Sector size is adjusted on a Vegetation System Settings component to allow for offset instances to not fall
- outside of the queried sector
- 7) Random offsets are set for each axis of the Position Modifier component, and instance counts are validated
- 8) Overrides are enabled on the Position Modifier component
- 9) Random offsets are set for each axis of the descriptor's Position Modifier overrides, and instance counts
- are validated
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- position_modifier_paths = ['Configuration|Position X|Range Min', 'Configuration|Position X|Range Max',
- 'Configuration|Position Y|Range Min', 'Configuration|Position Y|Range Max',
- 'Configuration|Position Z|Range Min', 'Configuration|Position Z|Range Max']
+ import os
+ import random
- override_position_modifier_paths = ['Configuration|Embedded Assets|[0]|Position Modifier|Min X',
- 'Configuration|Embedded Assets|[0]|Position Modifier|Max X',
- 'Configuration|Embedded Assets|[0]|Position Modifier|Min Y',
- 'Configuration|Embedded Assets|[0]|Position Modifier|Max Y',
- 'Configuration|Embedded Assets|[0]|Position Modifier|Min Z',
- 'Configuration|Embedded Assets|[0]|Position Modifier|Max Z']
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- def generate_random_offset_list():
- offset_list = []
- while len(offset_list) < 10:
- offset = round(random.uniform(-8.0, 8.0), 2)
- if not -1.0 <= offset <= 1.0:
- offset_list.append(offset)
- print("List of values to test against = " + str(offset_list))
- return offset_list
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- def set_offset_and_verify_instance_counts(offset_to_test, center, is_override=False):
- print(f"Starting test with an offset of {offset_to_test}")
- # Set min/max values to the offset value
- if not is_override:
- for path in position_modifier_paths:
- spawner_entity.get_set_test(3, path, offset_to_test)
- else:
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Position Modifier|Override Enabled",
- True)
- for path in override_position_modifier_paths:
- spawner_entity.get_set_test(2, path, offset_to_test)
- center_point = math.Vector3(center.x + offset_to_test, center.y + offset_to_test, center.z + offset_to_test)
- radius = 0.5
- print(f"Querying for instances in a {radius * 2}m area around {center_point.ToString()}")
- offset_success = self.wait_for_condition(lambda: dynveg.validate_instance_count(center_point, radius, 1), 5.0)
- offset_success2 = self.wait_for_condition(lambda: dynveg.validate_instance_count(center, radius, 0), 5.0)
- self.test_success = offset_success and offset_success2 and self.test_success
+ position_modifier_paths = ['Configuration|Position X|Range Min', 'Configuration|Position X|Range Max',
+ 'Configuration|Position Y|Range Min', 'Configuration|Position Y|Range Max',
+ 'Configuration|Position Z|Range Min', 'Configuration|Position Z|Range Max']
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ override_position_modifier_paths = ['Configuration|Embedded Assets|[0]|Position Modifier|Min X',
+ 'Configuration|Embedded Assets|[0]|Position Modifier|Max X',
+ 'Configuration|Embedded Assets|[0]|Position Modifier|Min Y',
+ 'Configuration|Embedded Assets|[0]|Position Modifier|Max Y',
+ 'Configuration|Embedded Assets|[0]|Position Modifier|Min Z',
+ 'Configuration|Embedded Assets|[0]|Position Modifier|Max Z']
- # Set view of planting area for visual debugging
- general.set_current_view_position(16.0, -5.0, 32.0)
+ def generate_random_offset_list():
+ offset_list = []
+ while len(offset_list) < 10:
+ offset = round(random.uniform(-8.0, 8.0), 2)
+ if not -1.0 <= offset <= 1.0:
+ offset_list.append(offset)
+ Report.info("List of values to test against = " + str(offset_list))
+ return offset_list
- # 2) Create a new entity with required vegetation area components
- spawner_center_point = math.Vector3(16.0, 16.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 1.0, 1.0, 1.0, asset_path)
+ def set_offset_and_verify_instance_counts(offset_to_test, center, is_override=False):
+ Report.info(f"Starting test with an offset of {offset_to_test}")
+ # Set min/max values to the offset value
+ if not is_override:
+ for path in position_modifier_paths:
+ spawner_entity.get_set_test(3, path, offset_to_test)
+ else:
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Position Modifier|Override Enabled",
+ True)
+ for path in override_position_modifier_paths:
+ spawner_entity.get_set_test(2, path, offset_to_test)
+ center_point = math.Vector3(center.x + offset_to_test, center.y + offset_to_test, center.z + offset_to_test)
+ radius = 0.5
+ Report.info(f"Querying for instances in a {radius * 2}m area around {center_point.ToString()}")
+ offset_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(center_point, radius, 1), 5.0)
+ offset_success2 = helper.wait_for_condition(lambda: dynveg.validate_instance_count(center, radius, 0), 5.0)
+ return offset_success and offset_success2
- # Add a Vegetation Position Modifier and set offset values to 0
- spawner_entity.add_component("Vegetation Position Modifier")
- for path in position_modifier_paths:
- spawner_entity.get_set_test(3, path, 0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Add flat surface to plant on
- dynveg.create_surface_entity("Planting Surface", spawner_center_point, 32.0, 32.0, 0.0)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(16.0, -5.0, 32.0)
- # 4) Verify initial instance counts pre-filter
- num_expected = 1 # Single instance planted
- spawner_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = self.test_success and spawner_success
+ # 2) Create a new entity with required vegetation area components
+ spawner_center_point = math.Vector3(16.0, 16.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 1.0, 1.0, 1.0, asset_path)
- # 5) Create a child entity of the spawner entity with a Constant Gradient component
- components_to_add = ["Constant Gradient"]
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
+ # Add a Vegetation Position Modifier and set offset values to 0
+ spawner_entity.add_component("Vegetation Position Modifier")
+ for path in position_modifier_paths:
+ spawner_entity.get_set_test(3, path, 0)
- # Pin the Constant Gradient to each axis of the Position Modifier
- position_modifier_gradient_paths = ['Configuration|Position X|Gradient|Gradient Entity Id',
- 'Configuration|Position Y|Gradient|Gradient Entity Id',
- 'Configuration|Position Z|Gradient|Gradient Entity Id']
- for path in position_modifier_gradient_paths:
- spawner_entity.get_set_test(3, path, gradient_entity.id)
+ # 3) Add flat surface to plant on
+ dynveg.create_surface_entity("Planting Surface", spawner_center_point, 32.0, 32.0, 0.0)
- # 6) Add a Vegetation System Settings Level component and change sector size to 32 sq meters so instances can
- # offset to a greater range and still be validated
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- "Configuration|Area System Settings|Sector Size In Meters", 32)
- sector_size = hydra.get_component_property_value(veg_system_settings_component,
- "Configuration|Area System Settings|Sector Size In Meters")
- self.test_success = (sector_size == 32) and self.test_success
+ # 4) Verify initial instance counts pre-filter
+ num_expected = 1 # Single instance planted
+ spawner_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.initial_instance_count, spawner_success)
- # 7) Set offsets on all axes and verify instance counts
- offsets_to_test = generate_random_offset_list()
- for offset in offsets_to_test:
- if self.test_success:
- set_offset_and_verify_instance_counts(offset, spawner_center_point)
+ # 5) Create a child entity of the spawner entity with a Constant Gradient component
+ components_to_add = ["Constant Gradient"]
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(spawner_center_point, components_to_add, parent_id=spawner_entity.id)
- # 8) Toggle on allow overrides on the Position Modifier Component
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ # Pin the Constant Gradient to each axis of the Position Modifier
+ position_modifier_gradient_paths = ['Configuration|Position X|Gradient|Gradient Entity Id',
+ 'Configuration|Position Y|Gradient|Gradient Entity Id',
+ 'Configuration|Position Z|Gradient|Gradient Entity Id']
+ for path in position_modifier_gradient_paths:
+ spawner_entity.get_set_test(3, path, gradient_entity.id)
- # 9) Set offsets on all axes on descriptor overrides and verify instance counts
- for offset in offsets_to_test:
- if self.test_success:
- set_offset_and_verify_instance_counts(offset, spawner_center_point, is_override=True)
+ # 6) Add a Vegetation System Settings Level component and change sector size to 32 sq meters so instances can
+ # offset to a greater range and still be validated
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ "Configuration|Area System Settings|Sector Size In Meters", 32)
+
+ # 7) Set offsets on all axes and verify instance counts
+ offsets_to_test = generate_random_offset_list()
+ success = True
+ for offset in offsets_to_test:
+ success = success and set_offset_and_verify_instance_counts(offset, spawner_center_point)
+ Report.result(Tests.position_offset, success)
+
+ # 8) Toggle on allow overrides on the Position Modifier Component
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+
+ # 9) Set offsets on all axes on descriptor overrides and verify instance counts
+ success = True
+ for offset in offsets_to_test:
+ success = success and set_offset_and_verify_instance_counts(offset, spawner_center_point, is_override=True)
+ Report.result(Tests.position_offset_overrides, success)
-test = TestPositionModifierComponentAndOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifierOverrides_InstancesRotateWithinRange.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifierOverrides_InstancesRotateWithinRange.py
index b428459032..4d8019bb33 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifierOverrides_InstancesRotateWithinRange.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifierOverrides_InstancesRotateWithinRange.py
@@ -5,139 +5,138 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C4814460: A level with simple vegetation is created. A child entity with required components is then created,
-and pinned to the gradient entity id in Z direction for vegetation entity. The changes in vegetation area are observed.
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.bus as bus
-import azlmbr.areasystem as areasystem
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ gradient_entity_created = (
+ "Successfully created new Gradient entity",
+ "Failed to create Gradient entity"
+ )
+ non_override_rotation_check = (
+ "Instances are rotated at expected values after initial setup",
+ "Found unexpectedly rotated instances after initial setup"
+ )
+ override_rotation_check = (
+ "Instances are rotated at expected values after configuring overrides",
+ "Found unexpectedly rotated instances after configuring overrides"
+ )
-class TestRotationModifierOverrides_InstancesRotateWithinRange(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="RotationModifierOverrides_InstancesRotateWithinRange", args=["level"])
+def RotationModifierOverrides_InstancesRotateWithinRange():
+ """
+ Summary:
+ A level with simple vegetation is created. A child entity with required components is then created,
+ and pinned to the gradient entity id in Z direction for vegetation entity. The changes in vegetation
+ area are observed.
- def run_test(self):
- """
- Summary:
- A level with simple vegetation is created. A child entity with required components is then created,
- and pinned to the gradient entity id in Z direction for vegetation entity. The changes in vegetation
- area are observed.
+ Expected Behavior:
+ Vegetation instances all rotate randomly between 0-360 degrees on the Z-axis.
- Expected Behavior:
- Vegetation instances all rotate randomly between 0-360 degrees on the Z-axis.
+ Test Steps:
+ 1) Open a new level
+ 2) Create vegetation entity and add components
+ 3) Set properties for vegetation entity
+ 4) Create new child entity
+ 5) Pin the child entity to vegetation entity as gradient entity id
+ 6) Verify rotation without per-item overrides
+ 7) Verify rotation with per-item overrides
- Test Steps:
- 1) Create level
- 2) Create vegetation entity and add components
- 3) Set properties for vegetation entity
- 4) Create new child entity
- 5) Pin the child entity to vegetation entity as gradient entity id
- 6) Verify rotation without per-item overrides
- 7) Verify rotation with per-item overrides
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- def get_expected_rotation(min, max, gradient_value):
- return min + ((max - min) * gradient_value)
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+ import azlmbr.bus as bus
+ import azlmbr.areasystem as areasystem
- def validate_rotation(center, radius, num_expected, rot_degrees_vector):
- # Verify that every instance in the given area has the expected rotation.
- box = math.Aabb_CreateCenterRadius(center, radius)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- num_found = len(instances)
- num_validated = 0
- result = (num_found == num_expected)
- print(f'instance count validation: {result} (found={num_found}, expected={num_expected})')
- expected_rotation = math.Quaternion()
- expected_rotation.SetFromEulerDegrees(rot_degrees_vector)
- for instance in instances:
- is_close = instance.rotation.IsClose(expected_rotation)
- result = result and is_close
- if is_close:
- num_validated = num_validated + 1
- #else:
- # print(f'instance rotation validation: {is_close} (rotation={instance.rotation} expected={expected_rotation})')
- print(f'instance rotation validation: {result} (num_validated={num_validated})')
- return result
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 1) Create level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
- general.set_current_view_position(512.0, 480.0, 38.0)
+ def get_expected_rotation(min, max, gradient_value):
+ return min + ((max - min) * gradient_value)
- # 2) Create vegetation entity and add components
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0, asset_path)
- spawner_entity.add_component("Vegetation Rotation Modifier")
- # Our default vegetation settings places 20 instances per 16 meters, so we expect 20 * 20 total instances.
- num_expected = 20 * 20
- # This is technically twice as big as we need, but we want to make sure our query radius is large enough to discover every
- # instance we've created.
- area_radius = 16.0
+ def validate_rotation(center, radius, num_expected, rot_degrees_vector):
+ # Verify that every instance in the given area has the expected rotation.
+ box = math.Aabb_CreateCenterRadius(center, radius)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ num_found = len(instances)
+ num_validated = 0
+ result = (num_found == num_expected)
+ Report.info(f'instance count validation: {result} (found={num_found}, expected={num_expected})')
+ expected_rotation = math.Quaternion()
+ expected_rotation.SetFromEulerDegrees(rot_degrees_vector)
+ for instance in instances:
+ is_close = instance.rotation.IsClose(expected_rotation)
+ result = result and is_close
+ if is_close:
+ num_validated = num_validated + 1
+ Report.info(f'instance rotation validation: {result} (num_validated={num_validated})')
+ return result
- # Create surface to spawn on
- dynveg.create_surface_entity("Surface Entity", entity_position, 16.0, 16.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # 3) Set properties for the rotation override on the descriptor, but don't set "allow overrides" on the rotation modifier yet.
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Override Enabled", True)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Min Z", -70.0)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Max Z", 30.0)
+ # 2) Create vegetation entity and add components
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0, asset_path)
+ spawner_entity.add_component("Vegetation Rotation Modifier")
+ # Our default vegetation settings places 20 instances per 16 meters, so we expect 20 * 20 total instances.
+ num_expected = 20 * 20
+ # This is technically twice as big as we need, but we want to make sure our query radius is large enough to discover
+ # every instance we've created.
+ area_radius = 16.0
- # 4) Create new child entity with a constant gradient
- constant_gradient_value = 0.25
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(
- entity_position,
- ["Constant Gradient"],
- parent_id=spawner_entity.id
- )
- if gradient_entity.id.IsValid():
- self.log(f"'{gradient_entity.name}' created")
- gradient_entity.get_set_test(0, "Configuration|Value", constant_gradient_value)
+ # Create surface to spawn on
+ dynveg.create_surface_entity("Surface Entity", entity_position, 16.0, 16.0, 1.0)
- # 5) Pin the child entity to vegetation entity as gradient entity id
- spawner_entity.get_set_test(3, "Configuration|Rotation Z|Gradient|Gradient Entity Id", gradient_entity.id)
+ # 3) Set properties for the rotation override on the descriptor, but don't set "allow overrides" on the rotation
+ # modifier yet
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Override Enabled", True)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Min Z", -70.0)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Rotation Modifier|Max Z", 30.0)
- # 6) Verify that without per-item overrides, the rotation matches the one calculated from the default rotation range.
- general.idle_wait(1.0)
- rotation_degrees = get_expected_rotation(-180.0, 180.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(entity_position, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
- 5.0)
- self.test_success = self.test_success and rotation_success
+ # 4) Create new child entity with a constant gradient
+ constant_gradient_value = 0.25
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(
+ entity_position,
+ ["Constant Gradient"],
+ parent_id=spawner_entity.id
+ )
+ Report.critical_result(Tests.gradient_entity_created, gradient_entity.id.IsValid())
+ gradient_entity.get_set_test(0, "Configuration|Value", constant_gradient_value)
- # 7) Verify that with per-item overrides enabled, the rotation matches the one calculated from the override range.
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- rotation_degrees = get_expected_rotation(-70.0, 30.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(entity_position, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
- 5.0)
- self.test_success = self.test_success and rotation_success
+ # 5) Pin the child entity to vegetation entity as gradient entity id
+ spawner_entity.get_set_test(3, "Configuration|Rotation Z|Gradient|Gradient Entity Id", gradient_entity.id)
+
+ # 6) Verify that without per-item overrides, the rotation matches the one calculated from the default rotation range
+ general.idle_wait(1.0)
+ rotation_degrees = get_expected_rotation(-180.0, 180.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(entity_position, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
+ 5.0)
+ Report.result(Tests.non_override_rotation_check, rotation_success)
+
+ # 7) Verify that with per-item overrides enabled, the rotation matches the one calculated from the override range.
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ rotation_degrees = get_expected_rotation(-70.0, 30.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(entity_position, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
+ 5.0)
+ Report.result(Tests.override_rotation_check, rotation_success)
-test = TestRotationModifierOverrides_InstancesRotateWithinRange()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(RotationModifierOverrides_InstancesRotateWithinRange)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifier_InstancesRotateWithinRange.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifier_InstancesRotateWithinRange.py
index 6b6f66467b..c261415958 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifier_InstancesRotateWithinRange.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/RotationModifier_InstancesRotateWithinRange.py
@@ -5,207 +5,226 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.math as math
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.areasystem as areasystem
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ gradient_entity_created = (
+ "Successfully created new Gradient entity",
+ "Failed to create Gradient entity"
+ )
+ rotation_baseline = (
+ "Instances are not rotated after initial setup",
+ "Unexpectedly found rotated instances after initial setup"
+ )
+ x_axis_rotation_180 = (
+ "Instances rotated 180 degrees on X-axis as expected",
+ "Found unexpected instance rotation on X-axis"
+ )
+ x_axis_rotation_90 = (
+ "Instances rotated 90 degrees on X-axis as expected",
+ "Found unexpected instance rotation on X-axis"
+ )
+ y_axis_rotation_180 = (
+ "Instances rotated 180 degrees on Y-axis as expected",
+ "Found unexpected instance rotation on Y-axis"
+ )
+ y_axis_rotation_90 = (
+ "Instances rotated 90 degrees on Y-axis as expected",
+ "Found unexpected instance rotation on Y-axis"
+ )
+ z_axis_rotation_180 = (
+ "Instances rotated 180 degrees on Z-axis as expected",
+ "Found unexpected instance rotation on Z-axis"
+ )
+ z_axis_rotation_90 = (
+ "Instances rotated 90 degrees on Z-axis as expected",
+ "Found unexpected instance rotation on Z-axis"
+ )
-class TestRotationModifier_InstancesRotateWithinRange(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="RotationModifier_InstancesRotateWithinRange", args=["level"])
+def RotationModifier_InstancesRotateWithinRange():
+ """
+ Summary: Range Min/Max in the Vegetation Rotation Modifier component can be set for all axes,
+ and functions as expected when fed a gradient signal
- def run_test(self):
- """
- Summary: Range Min/Max in the Vegetation Rotation Modifier component can be set for all axes,
- and functions as expected when fed a gradient signal
-
- Vegetation Entity: Set in the middle of the level it holds a child entity and the following components:
- Vegetation Asset List
- Box Shape (size: <10, 10, 10>)
- Vegetation Layer Spawner
- Vegetation Rotation Modifier
- Rotation X (gradient: child, Range Min: Variable, Range Max: Variable)
- Rotation Y (gradient: child, Range Min: Variable, Range Max: Variable)
- Rotation Z (gradient: child, Range Min: Variable, Range Max: Variable)
+ Vegetation Entity: Set in the middle of the level it holds a child entity and the following components:
+ Vegetation Asset List
+ Box Shape (size: <10, 10, 10>)
+ Vegetation Layer Spawner
+ Vegetation Rotation Modifier
+ Rotation X (gradient: child, Range Min: Variable, Range Max: Variable)
+ Rotation Y (gradient: child, Range Min: Variable, Range Max: Variable)
+ Rotation Z (gradient: child, Range Min: Variable, Range Max: Variable)
- Child Entity: Child to Vegetation Entity has the following components:
- Box Shape (size: <10, 10, 10>)
- Gradient Transform Modifier
- Constant Gradient
+ Child Entity: Child to Vegetation Entity has the following components:
+ Box Shape (size: <10, 10, 10>)
+ Gradient Transform Modifier
+ Constant Gradient
- Expected Behavior: The vegetation area adjusts rotation based on the Constant Gradient component
- and the min and max values for each component. Min max of each axis is checked
+ Expected Behavior: The vegetation area adjusts rotation based on the Constant Gradient component
+ and the min and max values for each component. Min max of each axis is checked
- Test Steps:
- 1) Create level
- 2) Set up vegetation entities
- 3) X-axis Check
- 4) Y-axis Check
- 5) Z-axis Check
+ Test Steps:
+ 1) Open a new level
+ 2) Set up vegetation entities
+ 3) X-axis Check
+ 4) Y-axis Check
+ 5) Z-axis Check
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- :return: None
- """
+ :return: None
+ """
- # Test Constants
- LEVEL_CENTER = math.Vector3(512.0, 512.0, 32.0)
- constant_gradient_value = 0.15
+ import os
- # Helper Functions
- def change_range_max(axis, value):
- spawner_entity.get_set_test(3, f"Configuration|Rotation {axis}|Range Max", value)
+ import azlmbr.math as math
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.areasystem as areasystem
- def change_range_min(axis, value):
- spawner_entity.get_set_test(3, f"Configuration|Rotation {axis}|Range Min", value)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- def get_expected_rotation(min, max, gradient_value):
- return min + ((max - min) * gradient_value)
+ # Test Constants
+ LEVEL_CENTER = math.Vector3(512.0, 512.0, 32.0)
+ constant_gradient_value = 0.15
- def validate_rotation(center, radius, num_expected, rot_degrees_vector):
- # Verify that every instance in the given area has the expected rotation.
- box = math.Aabb_CreateCenterRadius(center, radius)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- num_found = len(instances)
- result = (num_found == num_expected)
- print(f'instance count validation: {result} (found={num_found}, expected={num_expected})')
- expected_rotation = math.Quaternion()
- expected_rotation.SetFromEulerDegrees(rot_degrees_vector)
- for instance in instances:
- result = result and instance.rotation.IsClose(expected_rotation)
- print(f'instance rotation validation: {result} (rotation={instance.rotation} expected={expected_rotation})')
- return result
+ # Helper Functions
+ def change_range_max(axis, value):
+ spawner_entity.get_set_test(3, f"Configuration|Rotation {axis}|Range Max", value)
+ def change_range_min(axis, value):
+ spawner_entity.get_set_test(3, f"Configuration|Rotation {axis}|Range Min", value)
- # Main Script
- # 1) Create Level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ def get_expected_rotation(min, max, gradient_value):
+ return min + ((max - min) * gradient_value)
+
+ def validate_rotation(center, radius, num_expected, rot_degrees_vector):
+ # Verify that every instance in the given area has the expected rotation.
+ box = math.Aabb_CreateCenterRadius(center, radius)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ num_found = len(instances)
+ result = (num_found == num_expected)
+ Report.info(f'instance count validation: {result} (found={num_found}, expected={num_expected})')
+ expected_rotation = math.Quaternion()
+ expected_rotation.SetFromEulerDegrees(rot_degrees_vector)
+ for instance in instances:
+ result = result and instance.rotation.IsClose(expected_rotation)
+ Report.info(f'instance rotation validation: {result} (rotation={instance.rotation} expected={expected_rotation})')
+ return result
+
+ # Main Script
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
+
+ # 2) Set up vegetation entities
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Spawner Entity", LEVEL_CENTER, 2.0, 2.0, 2.0, asset_path)
+
+ additional_components = [
+ "Vegetation Rotation Modifier"
+ ]
+ for component in additional_components:
+ spawner_entity.add_component(component)
+
+ # Create surface to spawn vegetation on
+ dynveg.create_surface_entity("Surface Entity", LEVEL_CENTER, 10.0, 10.0, 1.0)
+
+ # Create Gradient Entity
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(
+ LEVEL_CENTER,
+ ["Constant Gradient"],
+ parent_id=spawner_entity.id
+ )
+ Report.critical_result(Tests.gradient_entity_created, gradient_entity.id.IsValid())
+ gradient_entity.get_set_test(0, "Configuration|Value", constant_gradient_value)
+
+ # Vegetation Rotation Modifier
+ for axis in ["X", "Y", "Z"]:
+ spawner_entity.get_set_test(
+ 3, f"Configuration|Rotation {axis}|Gradient|Gradient Entity Id", gradient_entity.id
)
- general.set_current_view_position(512.0, 480.0, 38.0)
- # 2) Set up vegetation entities
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Spawner Entity", LEVEL_CENTER, 2.0, 2.0, 2.0, asset_path)
+ # Set up constants used across all the rotation checks
- additional_components = [
- "Vegetation Rotation Modifier"
- ]
- for component in additional_components:
- spawner_entity.add_component(component)
+ # Choose an area large enough to contain all of the instances we spawned.
+ area_center = LEVEL_CENTER
+ area_radius = 20.0
+ # We're spawning a 2x2 area, which will have 3 rows of 3 instances due to default vegetation system spacing, so
+ # we should have a total of 9 instances.
+ num_expected = 9
- # Create surface to spawn vegetation on
- dynveg.create_surface_entity("Surface Entity", LEVEL_CENTER, 10.0, 10.0, 1.0)
+ # 3) X-axis check
+ # baseline, verify that we initially have no rotation
+ change_range_min("Z", 0.0)
+ change_range_max("Z", 0.0)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, 0.0)), 5.0)
+ Report.result(Tests.rotation_baseline, rotation_success)
- # Create Gradient Entity
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(
- LEVEL_CENTER,
- ["Constant Gradient"],
- parent_id=spawner_entity.id
- )
- if gradient_entity.id.IsValid():
- self.log(f"'{gradient_entity.name}' created")
- gradient_entity.get_set_test(0, "Configuration|Value", constant_gradient_value)
+ # Adjust x-axis range min / max to (-180, 0).
+ # Because we have a constant gradient of 0.25, our actual rotation should be (min + (max - min) * gradient),
+ # or (-180 + (0 - -180) * 0.25)
+ change_range_min("X", -180.0)
+ rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(rotation_degrees, 0.0, 0.0)),
+ 5.0)
+ Report.result(Tests.x_axis_rotation_180, rotation_success)
- # Vegetation Rotation Modifier
- for axis in ["X", "Y", "Z"]:
- spawner_entity.get_set_test(
- 3, f"Configuration|Rotation {axis}|Gradient|Gradient Entity Id", gradient_entity.id
- )
+ # Set the min / max to (0, 90), with an expected result of (0 + (90 - 0) * 0.25)
+ change_range_min("X", 0.0)
+ change_range_max("X", 90.0)
+ rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(rotation_degrees, 0.0, 0.0)),
+ 5.0)
+ Report.result(Tests.x_axis_rotation_90, rotation_success)
+ change_range_max("X", 0.0)
- # Set up constants used across all the rotation checks
+ # 4) Y-axis check
+ change_range_min("Y", -180.0)
+ rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, rotation_degrees, 0.0)),
+ 5.0)
+ Report.result(Tests.y_axis_rotation_180, rotation_success)
- # Choose an area large enough to contain all of the instances we spawned.
- area_center = LEVEL_CENTER
- area_radius = 20.0
- # We're spawning a 2x2 area, which will have 3 rows of 3 instances due to default vegetation system spacing, so
- # we should have a total of 9 instances.
- num_expected = 9
-
- # 3) X-axis check
- general.idle_wait(3.0) # Allow mesh to load
+ change_range_min("Y", 0.0)
+ change_range_max("Y", 90.0)
+ rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, rotation_degrees, 0.0)),
+ 5.0)
+ Report.result(Tests.y_axis_rotation_90, rotation_success)
+ change_range_max("Y", 0.0)
- # baseline, verify that we initially have no rotation
- change_range_min("Z", 0.0)
- change_range_max("Z", 0.0)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, 0.0)),
- 5.0)
- self.test_success = self.test_success and rotation_success
+ # 5) Z-axis check
+ change_range_min("Z", -180.0)
+ rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
+ 5.0)
+ Report.result(Tests.z_axis_rotation_180, rotation_success)
- # Adjust x-axis range min / max to (-180, 0).
- # Because we have a constant gradient of 0.25, our actual rotation should be (min + (max - min) * gradient),
- # or (-180 + (0 - -180) * 0.25)
- change_range_min("X", -180.0)
- rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(rotation_degrees, 0.0, 0.0)),
- 5.0)
- self.test_success = self.test_success and rotation_success
-
- # Set the min / max to (0, 90), with an expected result of (0 + (90 - 0) * 0.25)
- change_range_min("X", 0.0)
- change_range_max("X", 90.0)
- rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(rotation_degrees, 0.0, 0.0)),
- 5.0)
- self.test_success = self.test_success and rotation_success
-
- change_range_max("X", 0.0)
-
- # 4) Y-axis check
- change_range_min("Y", -180.0)
- rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, rotation_degrees, 0.0)),
- 5.0)
- self.test_success = self.test_success and rotation_success
-
- change_range_min("Y", 0.0)
- change_range_max("Y", 90.0)
- rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, rotation_degrees, 0.0)),
- 5.0)
- self.test_success = self.test_success and rotation_success
-
- change_range_max("Y", 0.0)
-
- # 5) Z-axis check
- change_range_min("Z", -180.0)
- rotation_degrees = get_expected_rotation(-180.0, 0.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
- 5.0)
- self.test_success = self.test_success and rotation_success
-
- change_range_min("Z", 0.0)
- change_range_max("Z", 90.0)
- rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
- rotation_success = self.wait_for_condition(
- lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
- 5.0)
- self.test_success = self.test_success and rotation_success
+ change_range_min("Z", 0.0)
+ change_range_max("Z", 90.0)
+ rotation_degrees = get_expected_rotation(0.0, 90.0, constant_gradient_value)
+ rotation_success = helper.wait_for_condition(
+ lambda: validate_rotation(area_center, area_radius, num_expected, math.Vector3(0.0, 0.0, rotation_degrees)),
+ 5.0)
+ Report.result(Tests.z_axis_rotation_90, rotation_success)
-test = TestRotationModifier_InstancesRotateWithinRange()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(RotationModifier_InstancesRotateWithinRange)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifierOverrides_InstancesProperlyScale.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifierOverrides_InstancesProperlyScale.py
index d065c821bf..9f2359ebe2 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifierOverrides_InstancesProperlyScale.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifierOverrides_InstancesProperlyScale.py
@@ -5,153 +5,155 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.areasystem as areasystem
-import azlmbr.bus as bus
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
-
-# Constants
-CLOSE_ENOUGH_THRESHOLD = 0.01
+class Tests:
+ gradient_entity_created = (
+ "Successfully created Gradient entity",
+ "Failed to create Gradient entity"
+ )
+ scale_values_set = (
+ "Scale Min and Scale Max are set to 0.1 and 1.0 in Vegetation Asset List",
+ "Scale Min and Scale Max are not set to 0.1 and 1.0 in Vegetation Asset List"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Found an unexpected number of instances"
+ )
+ instances_properly_scaled = (
+ "All instances scaled within appropriate range",
+ "Found instances scaled outside of the appropriate range"
+ )
-class TestScaleModifierOverrides_InstancesProperlyScale(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="ScaleModifierOverrides_InstancesProperlyScale", args=["level"])
+def ScaleModifierOverrides_InstancesProperlyScale():
+ """
+ Summary:
+ A level is created, then as simple vegetation area is created. Vegetation Scale Modifier component is
+ added to the vegetation area. A new child entity is created with Random Noise Gradient Generator,
+ Gradient Transform Modifier, and Box Shape. Child entity is set as gradient entity id in Vegetation
+ Scale Modifier, and scale of instances is validated to fall within expected range.
- def run_test(self):
- """
- Summary:
- A level is created, then as simple vegetation area is created. Vegetation Scale Modifier component is
- added to the vegetation area. A new child entity is created with Random Noise Gradient Generator,
- Gradient Transform Modifier, and Box Shape. Child entity is set as gradient entity id in Vegetation
- Scale Modifier, and scale of instances is validated to fall within expected range.
+ Expected Behavior:
+ Vegetation instances have random scale between Range Min and Range Max applied.
- Expected Behavior:
- Vegetation instances have random scale between Range Min and Range Max applied.
+ Test Steps:
+ 1) Open an existing level
+ 2) Create a new entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ 3) Set a valid mesh asset on the Vegetation Asset List
+ 4) Add Vegetation Scale Modifier component to the vegetation and set the values
+ 5) Toggle on Scale Modifier Override and verify Scale Min and Scale Max are set 0.1 and 1.0
+ 6) Create a new child entity and add components
+ 7) Add child entity as gradient entity id in Vegetation Scale Modifier
+ 8) Validate scale of instances with a few different min/max override values
- Test Steps:
- 1) Create level
- 2) Create a new entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- 3) Set a valid mesh asset on the Vegetation Asset List
- 4) Add Vegetation Scale Modifier component to the vegetation and set the values
- 5) Toggle on Scale Modifier Override and verify Scale Min and Scale Max are set 0.1 and 1.0
- 6) Create a new child entity and add components
- 7) Add child entity as gradient entity id in Vegetation Scale Modifier
- 8) Validate scale of instances with a few different min/max override values
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- def set_and_validate_scale(entity, min_scale, max_scale):
- # Set Range Min/Max
- entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Min", min_scale)
- entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Max", max_scale)
+ import azlmbr.areasystem as areasystem
+ import azlmbr.bus as bus
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Clear all areas to force a refresh
- general.run_console('veg_debugClearAllAreas')
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Wait for instances to spawn
- num_expected = 20 * 20
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ # Constants
+ CLOSE_ENOUGH_THRESHOLD = 0.01
- # Validate scale values of instances
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- if len(instances) == num_expected:
- for instance in instances:
- if min_scale <= instance.scale <= max_scale:
- self.log("All instances scaled within appropriate range")
- return True
- self.log(f"Instance at {instance.position} scale is {instance.scale}. Expected between "
- f"{min_scale}/{max_scale}")
- return False
- self.log(f"Failed to find all instances! Found {len(instances)}, expected {num_expected}.")
- return False
+ def set_and_validate_scale(entity, min_scale, max_scale):
+ # Set Range Min/Max
+ entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Min", min_scale)
+ entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Max", max_scale)
- # 1) Create level and set an appropriate view of spawner area
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
+ # Refresh the planted instances
+ general.run_console("veg_DebugClearAllAreas")
+
+ # Check the initial instance count
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, success)
+
+ # Validate scale values of instances
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ for instance in instances:
+ if min_scale <= instance.scale <= max_scale:
+ return True
+ return False
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
+
+ # 2) Create a new entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 10.0, asset_path)
+
+ # Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
+ dynveg.create_surface_entity("Surface Entity", entity_position, 20.0, 20.0, 1.0)
+ hydra.add_level_component("Vegetation Debugger")
+
+ # 4) Add Vegetation Scale Modifier component to the vegetation and set the values
+ spawner_entity.add_component("Vegetation Scale Modifier")
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+
+ # 5) Toggle on Scale Modifier Override and verify Scale Min and Scale Max are set 0.1 and 1.0
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Override Enabled", True)
+ scale_min = float(
+ format(
+ (
+ hydra.get_component_property_value(
+ spawner_entity.components[2], "Configuration|Embedded Assets|[0]|Scale Modifier|Min"
+ )
+ ),
+ ".1f",
)
-
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
-
- # 2) Create a new entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 10.0, asset_path)
-
- # Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
- dynveg.create_surface_entity("Surface Entity", entity_position, 20.0, 20.0, 1.0)
- hydra.add_level_component("Vegetation Debugger")
-
- # 4) Add Vegetation Scale Modifier component to the vegetation and set the values
- spawner_entity.add_component("Vegetation Scale Modifier")
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
-
- # 5) Toggle on Scale Modifier Override and verify Scale Min and Scale Max are set 0.1 and 1.0
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Scale Modifier|Override Enabled", True)
- scale_min = float(
- format(
- (
- hydra.get_component_property_value(
- spawner_entity.components[2], "Configuration|Embedded Assets|[0]|Scale Modifier|Min"
- )
- ),
- ".1f",
- )
+ )
+ scale_max = float(
+ format(
+ (
+ hydra.get_component_property_value(
+ spawner_entity.components[2], "Configuration|Embedded Assets|[0]|Scale Modifier|Max"
+ )
+ ),
+ ".1f",
)
- scale_max = float(
- format(
- (
- hydra.get_component_property_value(
- spawner_entity.components[2], "Configuration|Embedded Assets|[0]|Scale Modifier|Max"
- )
- ),
- ".1f",
- )
- )
- if ((scale_max - 1.0) < CLOSE_ENOUGH_THRESHOLD) and ((scale_min - 0.1) < CLOSE_ENOUGH_THRESHOLD):
- self.log("Scale Min and Scale Max are set to 0.1 and 1.0 in Vegetation Asset List")
- else:
- self.log("Scale Min and Scale Max are not set to 0.1 and 1.0 in Vegetation Asset List")
+ )
+ Report.result(Tests.scale_values_set, ((scale_max - 1.0) < CLOSE_ENOUGH_THRESHOLD) and
+ ((scale_min - 0.1) < CLOSE_ENOUGH_THRESHOLD))
- # 6) Create a new child entity and add components
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(
- entity_position,
- ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"],
- parent_id=spawner_entity.id
- )
- if gradient_entity.id.IsValid():
- self.log(f"'{gradient_entity.name}' created")
+ # 6) Create a new child entity and add components
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(
+ entity_position,
+ ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"],
+ parent_id=spawner_entity.id
+ )
+ Report.result(Tests.gradient_entity_created, gradient_entity.id.IsValid())
- # 7) Add child entity as gradient entity id in Vegetation Scale Modifier
- spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", gradient_entity.id)
+ # 7) Add child entity as gradient entity id in Vegetation Scale Modifier
+ spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", gradient_entity.id)
- # 8) Validate instances are scaled properly via a few different Range Min/Max settings on the override
- self.test_success = set_and_validate_scale(spawner_entity, 0.1, 1.0) and self.test_success
- self.test_success = set_and_validate_scale(spawner_entity, 2.0, 2.5) and self.test_success
- self.test_success = set_and_validate_scale(spawner_entity, 1.0, 5.0) and self.test_success
+ # 8) Validate instances are scaled properly via a few different Range Min/Max settings on the override
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 0.1, 1.0))
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 2.0, 2.5))
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 1.0, 5.0))
-test = TestScaleModifierOverrides_InstancesProperlyScale()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(ScaleModifierOverrides_InstancesProperlyScale)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifier_InstancesProperlyScale.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifier_InstancesProperlyScale.py
index e579495e42..b2fadaa703 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifier_InstancesProperlyScale.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ScaleModifier_InstancesProperlyScale.py
@@ -5,123 +5,123 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.areasystem as areasystem
-import azlmbr.bus as bus
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ gradient_entity_created = (
+ "Successfully created Gradient entity",
+ "Failed to create Gradient entity"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Found an unexpected number of instances"
+ )
+ instances_properly_scaled = (
+ "All instances scaled within appropriate range",
+ "Found instances scaled outside of the appropriate range"
+ )
-class TestScaleModifier_InstancesProperlyScale(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="ScaleModifier_InstancesProperlyScale", args=["level"])
+def ScaleModifier_InstancesProperlyScale():
+ """
+ Summary:
+ A New level is created. A New entity is created with components Vegetation Layer Spawner, Vegetation Asset List,
+ Box Shape and Vegetation Scale Modifier. A New child entity is created with components Random Noise Gradient,
+ Gradient Transform Modifier, and Box Shape. Pin the Random Noise entity to the Gradient Entity Id field for
+ the Gradient group. Range Min and Range Max are set to few values and values are validated. Range Min and Range
+ Max are set to few other values and values are validated.
- def run_test(self):
- """
- Summary:
- A New level is created. A New entity is created with components Vegetation Layer Spawner, Vegetation Asset List,
- Box Shape and Vegetation Scale Modifier. A New child entity is created with components Random Noise Gradient,
- Gradient Transform Modifier, and Box Shape. Pin the Random Noise entity to the Gradient Entity Id field for
- the Gradient group. Range Min and Range Max are set to few values and values are validated. Range Min and Range
- Max are set to few other values and values are validated.
+ Expected Behavior:
+ Vegetation instances are scaled within Range Min/Range Max.
- Expected Behavior:
- Vegetation instances are scaled within Range Min/Range Max.
+ Test Steps:
+ 1) Open an existing level
+ 2) Create a new entity with components Vegetation Layer Spawner, Vegetation Asset List, Box Shape and
+ Vegetation Scale Modifier
+ 3) Create child entity with components Random Noise Gradient, Gradient Transform Modifier and Box Shape
+ 4) Pin the Random Noise entity to the Gradient Entity Id field for the Gradient group.
+ 5) Range Min/Max is set to few different values on the Vegetation Scale Modifier component and
+ scale of instances is validated
- Test Steps:
- 1) Create level
- 2) Create a new entity with components Vegetation Layer Spawner, Vegetation Asset List, Box Shape and
- Vegetation Scale Modifier
- 3) Create child entity with components Random Noise Gradient, Gradient Transform Modifier and Box Shape
- 4) Pin the Random Noise entity to the Gradient Entity Id field for the Gradient group.
- 5) Range Min/Max is set to few different values on the Vegetation Scale Modifier component and
- scale of instances is validated
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- def set_and_validate_scale(entity, min_scale, max_scale):
- # Set Range Min/Max
- entity.get_set_test(3, "Configuration|Range Min", min_scale)
- entity.get_set_test(3, "Configuration|Range Max", max_scale)
+ import azlmbr.areasystem as areasystem
+ import azlmbr.bus as bus
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Clear all areas to force a refresh
- general.run_console('veg_debugClearAllAreas')
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Wait for instances to spawn
- num_expected = 20 * 20
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ def set_and_validate_scale(entity, min_scale, max_scale):
+ # Set Range Min/Max
+ entity.get_set_test(3, "Configuration|Range Min", min_scale)
+ entity.get_set_test(3, "Configuration|Range Max", max_scale)
- # Validate scale values of instances
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- if len(instances) == num_expected:
- for instance in instances:
- if min_scale <= instance.scale <= max_scale:
- self.log("All instances scaled within appropriate range")
- return True
- self.log(f"Instance at {instance.position} scale is {instance.scale}. Expected between "
- f"{min_scale}/{max_scale}")
- return False
- self.log(f"Failed to find all instances! Found {len(instances)}, expected {num_expected}.")
- return False
+ # Refresh the planted instances
+ general.run_console("veg_DebugClearAllAreas")
- # 1) Create level and set an appropriate view of spawner area
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ # Check the initial instance count
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, success)
- general.set_current_view_position(500.49, 498.69, 46.66)
- general.set_current_view_rotation(-42.05, 0.00, -36.33)
+ # Validate scale values of instances
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ for instance in instances:
+ if min_scale <= instance.scale <= max_scale:
+ return True
+ return False
- # 2) Create a new entity with components Vegetation Layer Spawner, Vegetation Asset List, Box Shape and
- # Vegetation Scale Modifier
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0,
- asset_path)
- spawner_entity.add_component("Vegetation Scale Modifier")
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
- dynveg.create_surface_entity("Surface Entity", entity_position, 20.0, 20.0, 1.0)
- hydra.add_level_component("Vegetation Debugger")
+ general.set_current_view_position(500.49, 498.69, 46.66)
+ general.set_current_view_rotation(-42.05, 0.00, -36.33)
- # 3) Create child entity with components Random Noise Gradient, Gradient Transform Modifier and Box Shape
- gradient_entity = hydra.Entity("Gradient Entity")
- gradient_entity.create_entity(
- entity_position,
- ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"],
- parent_id=spawner_entity.id
- )
- if gradient_entity.id.IsValid():
- self.log(f"'{gradient_entity.name}' created")
+ # 2) Create a new entity with components Vegetation Layer Spawner, Vegetation Asset List, Box Shape and
+ # Vegetation Scale Modifier
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0,
+ asset_path)
+ spawner_entity.add_component("Vegetation Scale Modifier")
- # 4) Pin the Random Noise entity to the Gradient Entity Id field for the Gradient group.
- spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", gradient_entity.id)
+ # Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
+ dynveg.create_surface_entity("Surface Entity", entity_position, 20.0, 20.0, 1.0)
+ hydra.add_level_component("Vegetation Debugger")
- # 5) Set Range Min/Max on the Vegetation Scale Modifier component to diff values, and verify instance scale is
- # within bounds
- self.test_success = set_and_validate_scale(spawner_entity, 2.0, 4.0) and self.test_success
- self.test_success = set_and_validate_scale(spawner_entity, 12.0, 40.0) and self.test_success
- self.test_success = set_and_validate_scale(spawner_entity, 0.5, 2.5) and self.test_success
+ # 3) Create child entity with components Random Noise Gradient, Gradient Transform Modifier and Box Shape
+ gradient_entity = hydra.Entity("Gradient Entity")
+ gradient_entity.create_entity(
+ entity_position,
+ ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"],
+ parent_id=spawner_entity.id
+ )
+ Report.critical_result(Tests.gradient_entity_created, gradient_entity.id.IsValid())
+
+ # 4) Pin the Random Noise entity to the Gradient Entity Id field for the Gradient group.
+ spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", gradient_entity.id)
+
+ # 5) Set Range Min/Max on the Vegetation Scale Modifier component to diff values, and verify instance scale is
+ # within bounds
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 2.0, 4.0))
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 12.0, 40.0))
+ Report.result(Tests.instances_properly_scaled, set_and_validate_scale(spawner_entity, 0.5, 2.5))
-test = TestScaleModifier_InstancesProperlyScale()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(ScaleModifier_InstancesProperlyScale)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_FilterStageToggle.py
new file mode 100644
index 0000000000..1b9c6aa5ea
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_FilterStageToggle.py
@@ -0,0 +1,113 @@
+"""
+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
+"""
+
+
+class Tests:
+ instance_count_in_box_shape = (
+ "Only found instances in the configured Box Shape intersection area",
+ "Found instances outside of the configured Box Shape intersection area"
+ )
+ instance_count_in_cylinder_shape = (
+ "Only found instances in the configured Cylinder Shape intersection area",
+ "Found instances outside of the configured Cylinder Shape intersection area"
+ )
+ preprocess_instance_count = (
+ "Found the expected number of instances with preprocessing filter stage",
+ "Found an unexpected number of instances with preprocessing filter stage"
+ )
+ postprocess_instance_count = (
+ "Found the expected number of instances with postprocessing filter stage",
+ "Found an unexpected number of instances with postprocessing filter stage"
+ )
+
+
+def ShapeIntersectionFilter_FilterStageToggle():
+ """
+ Summary:
+ Filter Stage toggle affects final vegetation position
+
+ Expected Result:
+ Vegetation instances plant differently depending on the Filter Stage setting. With PreProcess, some vegetation
+ instances can appear on slopes outside the filtered values. With PostProcess, vegetation instances only appear on
+ the correct slope values.
+
+ :return: None
+ """
+
+ import os
+
+ import azlmbr.math as math
+ import azlmbr.legacy.general as general
+
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+
+ general.set_current_view_position(512.0, 480.0, 38.0)
+
+ # Create basic vegetation entity
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
+
+ # Create Surface for instances to plant on
+ dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0)
+
+ # Add a Vegetation Shape Intersection Filter to the vegetation area entity
+ vegetation.add_component("Vegetation Shape Intersection Filter")
+
+ # Create a new entity as a child of the vegetation area entity with Box Shape
+ box = hydra.Entity("box")
+ box.create_entity(position, ["Box Shape"])
+ box.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(8.0, 8.0, 1.0))
+
+ # Create a new entity as a child of the vegetation area entity with Cylinder Shape.
+ cylinder = hydra.Entity("cylinder")
+ cylinder.create_entity(position, ["Cylinder Shape"])
+ cylinder.get_set_test(0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
+ cylinder.get_set_test(0, "Cylinder Shape|Cylinder Configuration|Height", 5.0)
+ box.set_test_parent_entity(vegetation)
+ cylinder.set_test_parent_entity(vegetation)
+
+ # On the Shape Intersection Filter component, click the crosshair button, and add child entities one by one
+ vegetation.get_set_test(3, "Configuration|Shape Entity Id", box.id)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 8.0, 100), 2.0)
+ Report.result(Tests.instance_count_in_box_shape, result)
+ vegetation.get_set_test(3, "Configuration|Shape Entity Id", cylinder.id)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 100), 2.0)
+ Report.result(Tests.instance_count_in_cylinder_shape, result)
+
+ # Create a new entity as a child of the area entity with Random Noise Gradient, Gradient Transform Modifier,
+ # and Box Shape component
+ random_noise = hydra.Entity("random_noise")
+ random_noise.create_entity(position, ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"])
+ random_noise.set_test_parent_entity(vegetation)
+
+ # Add a Vegetation Position Modifier to the vegetation area entity
+ vegetation.add_component("Vegetation Position Modifier")
+
+ # Pin the Random Noise entity to the Gradient Entity Id field of the Position Modifier's Gradient X
+ vegetation.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", random_noise.id)
+
+ # Toggle between PreProcess and PostProcess
+ vegetation.get_set_test(3, "Configuration|Filter Stage", 1)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 117), 2.0)
+ Report.result(Tests.preprocess_instance_count, result)
+ vegetation.get_set_test(3, "Configuration|Filter Stage", 2)
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 122), 2.0)
+ Report.result(Tests.postprocess_instance_count, result)
+
+
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(ShapeIntersectionFilter_FilterStageToggle)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_InstancesPlantInAssignedShape.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_InstancesPlantInAssignedShape.py
index 83458fe153..e872b23054 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_InstancesPlantInAssignedShape.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/ShapeIntersectionFilter_InstancesPlantInAssignedShape.py
@@ -5,124 +5,126 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C4874094: Shape reference can be replaced/removed
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.bus as bus
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ instance_count_in_box_shape = (
+ "Only found instances in the configured Box Shape intersection area",
+ "Found instances outside of the configured Box Shape intersection area"
+ )
+ instance_count_in_cylinder_shape = (
+ "Only found instances in the configured Cylinder Shape intersection area",
+ "Found instances outside of the configured Cylinder Shape intersection area"
+ )
+ unfiltered_instance_count = (
+ "Found instances in the entire Spawner area with no filter set",
+ "Failed to find all expected instances in the Spawner area with no filter set"
+ )
-class TestShapeIntersectionFilter(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="ShapeIntersectionFilter_InstancePlanting", args=["level"])
+def ShapeIntersectionFilter_InstancesPlantInAssignedShape():
+ """
+ Summary:
+ A spawner area is created with a Vegetation Shape Intersection Filter. 2 different shape entities are created,
+ pinned to the Shape Intersection Filter, and instance counts are verified.
- def run_test(self):
- """
- Summary:
- A spawner area is created with a Vegetation Shape Intersection Filter. 2 different shape entities are created,
- pinned to the Shape Intersection Filter, and instance counts are verified.
+ Expected Behavior:
+ The Shape Entity Id reference can be successfully set/updated. Instances spawn only in the specified shape area.
- Expected Behavior:
- The Shape Entity Id reference can be successfully set/updated. Instances spawn only in the specified shape area.
+ Test Steps:
+ 1) Open an existing level, and set view for visual debugging
+ 2) Create an instance spawner and planting surface
+ 3) Create child entity with Box Shape
+ 4) Create child entity with Cylinder Shape
+ 5) Assign the Intersection Filter to the Box Shape and validate instance counts
+ 6) Assign the Intersection Filter to the Cylinder Shape and validate instance counts
+ 7) Remove the shape reference on the Intersection Filter and validate instance counts
- Test Steps:
- 1) Create a new level, and set view for visual debugging
- 2) Create an instance spawner and planting surface
- 3) Create child entity with Box Shape
- 4) Create child entity with Cylinder Shape
- 5) Assign the Intersection Filter to the Box Shape and validate instance counts
- 6) Assign the Intersection Filter to the Cylinder Shape and validate instance counts
- 7) Remove the shape reference on the Intersection Filter and validate instance counts
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import os
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.bus as bus
+ import azlmbr.math as math
- # 2) Create a new entity with required vegetation area components and Vegetation Shape Intersection Filter
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0,
- asset_path)
- spawner_entity.add_component("Vegetation Shape Intersection Filter")
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Create a planting surface
- dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 3) Create a child entity with Box Shape
- components_to_add = ["Box Shape"]
- box_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
- box = hydra.Entity("Box", box_id)
- box.components = []
- for component in components_to_add:
- box.components.append(hydra.add_component(component, box_id))
- new_box_dimension = math.Vector3(5.0, 5.0, 5.0)
- hydra.get_set_test(box, 0, "Box Shape|Box Configuration|Dimensions", new_box_dimension)
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 4) Create a child entity with Cylinder Shape
- components_to_add = ["Cylinder Shape"]
- cylinder_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
- cylinder = hydra.Entity("Cylinder", cylinder_id)
- cylinder.components = []
- for component in components_to_add:
- cylinder.components.append(hydra.add_component(component, cylinder_id))
- hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
+ # 2) Create a new entity with required vegetation area components and Vegetation Shape Intersection Filter
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0,
+ asset_path)
+ spawner_entity.add_component("Vegetation Shape Intersection Filter")
- # 5) Set the Intersection Filter's Shape Entity Id to the Box Shape entity
- spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", box_id)
+ # Create a planting surface
+ dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
- # Validate instance counts. Instances should only plant in the Box Shape area
- num_expected = 49
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
-
- # 6) Set the Intersection Filter's Shape Entity Id to the Cylinder Shape entity
- spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", cylinder_id)
+ # 3) Create a child entity with Box Shape
+ components_to_add = ["Box Shape"]
+ box_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
+ box = hydra.Entity("Box", box_id)
+ box.components = []
+ for component in components_to_add:
+ box.components.append(hydra.add_component(component, box_id))
+ new_box_dimension = math.Vector3(5.0, 5.0, 5.0)
+ hydra.get_set_test(box, 0, "Box Shape|Box Configuration|Dimensions", new_box_dimension)
- # Validate instance counts. Instances should only plant in the Cylinder Shape area
- num_expected = 121
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # 4) Create a child entity with Cylinder Shape
+ components_to_add = ["Cylinder Shape"]
+ cylinder_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
+ cylinder = hydra.Entity("Cylinder", cylinder_id)
+ cylinder.components = []
+ for component in components_to_add:
+ cylinder.components.append(hydra.add_component(component, cylinder_id))
+ hydra.get_set_test(cylinder, 0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
- # 7) Clear the Intersection Filter's Shape Entity Id reference
- spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", None)
+ # 5) Set the Intersection Filter's Shape Entity Id to the Box Shape entity
+ spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", box_id)
- # Validate instance counts. Instances should now fill the entire spawner_entity's area
- num_expected = 20 * 20
- success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 5.0)
- self.test_success = success and self.test_success
+ # Validate instance counts. Instances should only plant in the Box Shape area
+ num_expected = 49
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.instance_count_in_box_shape, success)
+
+ # 6) Set the Intersection Filter's Shape Entity Id to the Cylinder Shape entity
+ spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", cylinder_id)
+
+ # Validate instance counts. Instances should only plant in the Cylinder Shape area
+ num_expected = 121
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.instance_count_in_cylinder_shape, success)
+
+ # 7) Clear the Intersection Filter's Shape Entity Id reference
+ spawner_entity.get_set_test(3, "Configuration|Shape Entity Id", None)
+
+ # Validate instance counts. Instances should now fill the entire spawner_entity's area
+ num_expected = 20 * 20
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 5.0)
+ Report.result(Tests.unfiltered_instance_count, success)
-test = TestShapeIntersectionFilter()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(ShapeIntersectionFilter_InstancesPlantInAssignedShape)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment.py
index abd3dc35d5..5855972f0c 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment.py
@@ -5,121 +5,132 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-from math import radians
-import sys
-import azlmbr.areasystem as areasystem
-import azlmbr.asset as asset
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ surface_entity_created = (
+ "Successfully created Surface entity",
+ "Failed to create Surface entity"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Unexpected number of instances found"
+ )
+ instances_aligned_0 = (
+ "All instances are pointed straight up",
+ "Found instances not aligned to surface pointing straight up"
+ )
+ instances_aligned_1 = (
+ "All instances are planted perpendicularly to the surface",
+ "Found instances not aligned to surface perpendicularly"
+ )
-class TestSlopeAlignmentModifierOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SlopeAlignmentModifierOverrides", args=["level"])
+def SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment():
+ """
+ Summary:
+ Verifies instances properly align to surfaces based on configuration of descriptor overrides of the
+ Vegetation Slope Alignment Modifier component.
- def run_test(self):
- """
- Summary:
- C4814459 Verifies instances properly align to surfaces based on configuration of descriptor overrides of the
- Vegetation Slope Alignment Modifier component.
+ :return: None
+ """
- :return: None
- """
+ import os
+ from math import radians
- def verify_proper_alignment(instance, rot_degrees_vec):
- expected_rotation = math.Quaternion()
- expected_rotation.SetFromEulerDegrees(rot_degrees_vec)
- if instance.alignment.IsClose(expected_rotation):
- return True
- self.log(f"Expected rotation of {expected_rotation}, Found {instance.alignment}")
- return False
+ import azlmbr.areasystem as areasystem
+ import azlmbr.asset as asset
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(512.0, 480.0, 38.0)
+ def verify_proper_alignment(instance, rot_degrees_vec):
+ expected_rotation = math.Quaternion()
+ expected_rotation.SetFromEulerDegrees(rot_degrees_vec)
+ if instance.alignment.IsClose(expected_rotation):
+ return True
+ Report.info(f"Expected rotation of {expected_rotation}, Found {instance.alignment}")
+ return False
- # Create a spawner entity setup with all needed components
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create a sloped mesh surface for the instances to plant on
- center_point = math.Vector3(502.0, 512.0, 24.0)
- mesh_asset_path = os.path.join("objects", "_primitives", "_box_1x1.azmodel")
- mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(),
- False)
- rotation = math.Vector3(0.0, radians(45.0), 0.0)
- surface_entity = hydra.Entity("Surface Entity")
- surface_entity.create_entity(
- center_point,
- ["Mesh", "Mesh Surface Tag Emitter"]
- )
- if surface_entity.id.IsValid():
- print(f"'{surface_entity.name}' created")
- hydra.get_set_test(surface_entity, 0, "Controller|Configuration|Mesh Asset", mesh_asset)
- components.TransformBus(bus.Event, "SetLocalRotation", surface_entity.id, rotation)
- components.TransformBus(bus.Event, "SetLocalUniformScale", surface_entity.id, 30.0)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add a Vegetation Debugger component to allow refreshing instances
- hydra.add_level_component("Vegetation Debugger")
+ # Create a spawner entity setup with all needed components
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
- # Add Vegetation Slope Alignment Modifier to the spawner entity and toggle on Allow Per-Item Overrides
- spawner_entity.add_component("Vegetation Slope Alignment Modifier")
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ # Create a sloped mesh surface for the instances to plant on
+ center_point = math.Vector3(502.0, 512.0, 24.0)
+ mesh_asset_path = os.path.join("objects", "_primitives", "_box_1x1.azmodel")
+ mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(),
+ False)
+ rotation = math.Vector3(0.0, radians(45.0), 0.0)
+ surface_entity = hydra.Entity("Surface Entity")
+ surface_entity.create_entity(
+ center_point,
+ ["Mesh", "Mesh Surface Tag Emitter"]
+ )
+ Report.critical_result(Tests.surface_entity_created, surface_entity.id.IsValid())
+ hydra.get_set_test(surface_entity, 0, "Controller|Configuration|Mesh Asset", mesh_asset)
+ components.TransformBus(bus.Event, "SetLocalRotation", surface_entity.id, rotation)
+ components.TransformBus(bus.Event, "SetLocalUniformScale", surface_entity.id, 30.0)
- # Toggle on Surface Slope Alignment Override Enabled on the Vegetation Asset List component
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Override Enabled",
- True)
+ # Add a Vegetation Debugger component to allow refreshing instances
+ hydra.add_level_component("Vegetation Debugger")
- # Set Surface Slope Alignment Override Min and Max to 0 and validate instance alignment
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 0.0)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 0.0)
+ # Add Vegetation Slope Alignment Modifier to the spawner entity and toggle on Allow Per-Item Overrides
+ spawner_entity.add_component("Vegetation Slope Alignment Modifier")
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- # Verify instances are have planted and are aligned to slope as expected
- num_expected = 20 * 20
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ # Toggle on Surface Slope Alignment Override Enabled on the Vegetation Asset List component
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Override Enabled",
+ True)
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ # Set Surface Slope Alignment Override Min and Max to 0 and validate instance alignment
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 0.0)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 0.0)
- if self.test_success and num_expected == len(instances):
- for instance in instances:
- self.test_success = verify_proper_alignment(instance,
- math.Vector3(0.0, 0.0, 0.0)) and self.test_success
+ # Verify instances are have planted and are aligned to slope as expected
+ num_expected = 20 * 20
+ instances_planted = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, instances_planted)
- # Set Surface Slope Alignment Min and Max to 1 and validate instance alignment
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Min", 1.0)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 1.0)
- general.run_console('veg_debugClearAllAreas')
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ success = True
+ for instance in instances:
+ success = verify_proper_alignment(instance, math.Vector3(0.0, 0.0, 0.0))
+ Report.result(Tests.instances_aligned_0, success)
- if self.test_success and num_expected == len(instances):
- for instance in instances:
- self.test_success = verify_proper_alignment(instance, math.Vector3(0.0, 45.0, 0.0)) and \
- self.test_success
+ # Set Surface Slope Alignment Min and Max to 1 and validate instance alignment
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Min", 1.0)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max", 1.0)
+ general.run_console('veg_debugClearAllAreas')
+ instances_planted = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, instances_planted)
+
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+
+ success = True
+ for instance in instances:
+ success = verify_proper_alignment(instance, math.Vector3(0.0, 45.0, 0.0))
+ Report.result(Tests.instances_aligned_1, success)
-test = TestSlopeAlignmentModifierOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifier_InstanceSurfaceAlignment.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifier_InstanceSurfaceAlignment.py
index 6e9e36957f..11427b9b0e 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifier_InstanceSurfaceAlignment.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeAlignmentModifier_InstanceSurfaceAlignment.py
@@ -5,127 +5,139 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-from math import radians
-import sys
-import azlmbr.areasystem as areasystem
-import azlmbr.asset as asset
-import azlmbr.bus as bus
-import azlmbr.components as components
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ surface_entity_created = (
+ "Successfully created Surface entity",
+ "Failed to create Surface entity"
+ )
+ instance_count = (
+ "Found the expected number of instances",
+ "Unexpected number of instances found"
+ )
+ instances_aligned_1 = (
+ "All instances are planted perpendicularly to the surface",
+ "Found instances not aligned to surface perpendicularly"
+ )
+ instances_aligned_0 = (
+ "All instances are pointed straight up",
+ "Found instances not aligned to surface pointing straight up"
+ )
-class TestSlopeAlignmentModifier(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SlopeAlignmentModifier", args=["level"])
+def SlopeAlignmentModifier_InstanceSurfaceAlignment():
+ """
+ Summary:
+ Verifies instances properly align to surfaces based on configuration of the Vegetation Slope Alignment
+ Modifier.
- def run_test(self):
- """
- Summary:
- C4896941 Verifies instances properly align to surfaces based on configuration of the Vegetation Slope Alignment
- Modifier.
+ :return: None
+ """
- :return: None
- """
+ import os
+ from math import radians
- def verify_proper_alignment(instance, rot_degrees_vec):
- expected_rotation = math.Quaternion()
- expected_rotation.SetFromEulerDegrees(rot_degrees_vec)
- if instance.alignment.IsClose(expected_rotation):
- return True
- self.log(f"Expected rotation of {expected_rotation}, Found {instance.alignment}")
- return False
+ import azlmbr.areasystem as areasystem
+ import azlmbr.asset as asset
+ import azlmbr.bus as bus
+ import azlmbr.components as components
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(512.0, 480.0, 38.0)
+ def verify_proper_alignment(instance, rot_degrees_vec):
+ expected_rotation = math.Quaternion()
+ expected_rotation.SetFromEulerDegrees(rot_degrees_vec)
+ if instance.alignment.IsClose(expected_rotation):
+ return True
+ Report.info(f"Expected rotation of {expected_rotation}, Found {instance.alignment}")
+ return False
- # Create a spawner entity setup with all needed components
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Create a sloped mesh surface for the instances to plant on
- center_point = math.Vector3(502.0, 512.0, 24.0)
- mesh_asset_path = os.path.join("objects", "_primitives", "_box_1x1.azmodel")
- mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(),
- False)
- rotation = math.Vector3(0.0, radians(45.0), 0.0)
- surface_entity = hydra.Entity("Surface Entity")
- surface_entity.create_entity(
- center_point,
- ["Mesh", "Mesh Surface Tag Emitter"]
- )
- if surface_entity.id.IsValid():
- print(f"'{surface_entity.name}' created")
- hydra.get_set_test(surface_entity, 0, "Controller|Configuration|Mesh Asset", mesh_asset)
- components.TransformBus(bus.Event, "SetLocalRotation", surface_entity.id, rotation)
- components.TransformBus(bus.Event, "SetLocalUniformScale", surface_entity.id, 30.0)
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add a Vegetation Debugger component to allow refreshing instances
- hydra.add_level_component("Vegetation Debugger")
+ # Create a spawner entity setup with all needed components
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
- # Add Vegetation Slope Alignment Modifier to the spawner entity
- spawner_entity.add_component("Vegetation Slope Alignment Modifier")
+ # Create a sloped mesh surface for the instances to plant on
+ center_point = math.Vector3(502.0, 512.0, 24.0)
+ mesh_asset_path = os.path.join("objects", "_primitives", "_box_1x1.azmodel")
+ mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(),
+ False)
+ rotation = math.Vector3(0.0, radians(45.0), 0.0)
+ surface_entity = hydra.Entity("Surface Entity")
+ surface_entity.create_entity(
+ center_point,
+ ["Mesh", "Mesh Surface Tag Emitter"]
+ )
+ Report.critical_result(Tests.surface_entity_created, surface_entity.id.IsValid())
+ hydra.get_set_test(surface_entity, 0, "Controller|Configuration|Mesh Asset", mesh_asset)
+ components.TransformBus(bus.Event, "SetLocalRotation", surface_entity.id, rotation)
+ components.TransformBus(bus.Event, "SetLocalUniformScale", surface_entity.id, 30.0)
- # Set Alignment Coefficient Min/Max to 1 on the Slope Alignment Modifier
- spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Min", 1.0)
- spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Max", 1.0)
+ # Add a Vegetation Debugger component to allow refreshing instances
+ hydra.add_level_component("Vegetation Debugger")
- # Create new child entity with a Constant Gradient
- child_vegetation_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
- child_vegetation = hydra.Entity("Child Vegetation Entity", child_vegetation_id)
- components_to_add = ["Constant Gradient"]
- child_vegetation.components = []
- for component in components_to_add:
- child_vegetation.components.append(hydra.add_component(component, child_vegetation_id))
+ # Add Vegetation Slope Alignment Modifier to the spawner entity
+ spawner_entity.add_component("Vegetation Slope Alignment Modifier")
- # Reference the Constant Gradient on the Slope Alignment Modifier component
- spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", child_vegetation_id)
+ # Set Alignment Coefficient Min/Max to 1 on the Slope Alignment Modifier
+ spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Min", 1.0)
+ spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Max", 1.0)
- # Verify instances are have planted and are aligned to slope as expected
- num_expected = 20 * 20
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ # Create new child entity with a Constant Gradient
+ child_vegetation_id = editor.ToolsApplicationRequestBus(bus.Broadcast, "CreateNewEntity", spawner_entity.id)
+ child_vegetation = hydra.Entity("Child Vegetation Entity", child_vegetation_id)
+ components_to_add = ["Constant Gradient"]
+ child_vegetation.components = []
+ for component in components_to_add:
+ child_vegetation.components.append(hydra.add_component(component, child_vegetation_id))
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ # Reference the Constant Gradient on the Slope Alignment Modifier component
+ spawner_entity.get_set_test(3, "Configuration|Gradient|Gradient Entity Id", child_vegetation_id)
- if self.test_success and num_expected == len(instances):
- for instance in instances:
- self.test_success = verify_proper_alignment(instance, math.Vector3(0.0, 45.0, 0.0)) and \
- self.test_success
+ # Verify instances are have planted and are aligned to slope as expected
+ num_expected = 20 * 20
+ instances_planted = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, instances_planted)
- # Change Min/Max to 0.0 and verify proper alignment
- spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Min", 0.0)
- spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Max", 0.0)
- general.run_console('veg_debugClearAllAreas')
- self.test_success = self.test_success and self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
- box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+ success = True
+ for instance in instances:
+ success = verify_proper_alignment(instance, math.Vector3(0.0, 45.0, 0.0))
+ Report.result(Tests.instances_aligned_1, success)
- if self.test_success and num_expected == len(instances):
- for instance in instances:
- self.test_success = verify_proper_alignment(instance, math.Vector3(0.0, 0.0, 0.0)) and self.test_success
+ # Change Min/Max to 0.0 and verify proper alignment
+ spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Min", 0.0)
+ spawner_entity.get_set_test(3, "Configuration|Alignment Coefficient Max", 0.0)
+ general.run_console('veg_debugClearAllAreas')
+ instances_planted = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.critical_result(Tests.instance_count, instances_planted)
+
+ box = azlmbr.shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
+ instances = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', box)
+
+ success = True
+ for instance in instances:
+ success = verify_proper_alignment(instance, math.Vector3(0.0, 0.0, 0.0))
+ Report.result(Tests.instances_aligned_0, success)
-test = TestSlopeAlignmentModifier()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SlopeAlignmentModifier_InstanceSurfaceAlignment)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope.py
index d66716cee3..ee0851d552 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope.py
@@ -5,121 +5,122 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C4874096 - Slope Min/Max properties can be set, and properly affect planted vegetation
-C4814464 - Slope Filter overrides function as expected
-"""
-import os
-import sys
-
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ prefilter_instance_count = (
+ "Found the expected number of instances before applying the Slope Filter",
+ "Found an unexpected number of instances before applying the Slope Filter"
+ )
+ postfilter_instance_count = (
+ "Found the expected number of instances after applying the Slope Filter",
+ "Found an unexpected number of instances after applying the Slope Filter"
+ )
+ postfilter_overrides_instance_count = (
+ "Found the expected number of instances after applying descriptor overrides to the Slope Filter",
+ "Found an unexpected number of instances after applying descriptor overrides to the Slope Filter"
+ )
-class TestSlopeFilterComponentAndOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SlopeFilter_InstancesPlantOnValidSlope", args=["level"])
+def SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes():
+ """
+ Summary:
+ An existing level is opened. A spawner entity is added, along with a flat planting surface at 32 on Z, and sphere
+ mesh at 38 on Z to provide a sloped surface. A Slope Filter is added to the spawner entity, and Slope Min/Max
+ values are set. Instance counts are validated. The same test is then performed for Slope Filter overrides.
- def run_test(self):
- """
- Summary:
- A new level is created. A spawner entity is added, along with a flat planting surface at 32 on Z, and sphere
- mesh at 38 on Z to provide a sloped surface. A Slope Filter is added to the spawner entity, and Slope Min/Max
- values are set. Instance counts are validated. The same test is then performed for Slope Filter overrides.
+ Expected Behavior:
+ Instances plant only on surfaces that fall between the Slope Filter Min/Max settings
- Expected Behavior:
- Instances plant only on surfaces that fall between the Slope Filter Min/Max settings
+ Test Steps:
+ 1) Open an existing level
+ 2) Create an instance spawner entity
+ 3) Create surfaces to plant on, one at 32 on Z and another sloped surface at 38 on Z.
+ 4) Initial instance counts pre-filter are verified.
+ 5) Slope Min/Max values are set on the Slope Filter component
+ 6) Instance counts are validated
+ 7) Setup for overrides tests
+ 8) Slope Min/Max values are set on the descriptor overrides
+ 9) Instance counts are validated
- Test Steps:
- 1) Create a new level
- 2) Create an instance spawner entity
- 3) Create surfaces to plant on, one at 32 on Z and another sloped surface at 38 on Z.
- 4) Initial instance counts pre-filter are verified.
- 5) Slope Min/Max values are set on the Slope Filter component
- 6) Instance counts are validated
- 7) Setup for overrides tests
- 8) Slope Min/Max values are set on the descriptor overrides
- 9) Instance counts are validated
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 475.0, 38.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new entity with required vegetation area components
- center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Add a Vegetation Slope Filter
- spawner_entity.add_component("Vegetation Slope Filter")
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 475.0, 38.0)
- # 3) Add surfaces to plant on. This will include a flat surface and a sphere mesh to provide a sloped surface
- dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
- sloped_surface_center = math.Vector3(512.0, 512.0, 38.0)
- dynveg.create_mesh_surface_entity_with_slopes("Sloped Planting Surface", sloped_surface_center, 10.0)
+ # 2) Create a new entity with required vegetation area components
+ center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
- # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
- 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
+ # Add a Vegetation Slope Filter
+ spawner_entity.add_component("Vegetation Slope Filter")
- # 4) Validate instance counts pre-filter
- num_expected_flat_surface = 40 * 40 # 20x20 instances per 16m
- num_expected_slopes_pre_filter = 120 # Unfiltered planting on the top of the sphere mesh
- num_expected = num_expected_flat_surface + num_expected_slopes_pre_filter
- initial_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
- spawner_entity.id, num_expected), 5.0)
- self.test_success = initial_success and self.test_success
+ # 3) Add surfaces to plant on. This will include a flat surface and a sphere mesh to provide a sloped surface
+ dynveg.create_surface_entity("Planting Surface", center_point, 32.0, 32.0, 1.0)
+ sloped_surface_center = math.Vector3(512.0, 512.0, 38.0)
+ dynveg.create_mesh_surface_entity_with_slopes("Sloped Planting Surface", sloped_surface_center, 10.0)
- # 5) Change Slope Min/Max on the Vegetation Slope Filter component
- spawner_entity.get_set_test(3, "Configuration|Slope Min", 20)
- spawner_entity.get_set_test(3, "Configuration|Slope Max", 45)
+ # Set instances to spawn on a center snap point to avoid unexpected instances around the edges of the box shape
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", veg_system_settings_component,
+ 'Configuration|Area System Settings|Sector Point Snap Mode', 1)
- # 6) Validate instance counts post-filter: instances should only plant on slopes between 20-45 degrees
- num_expected_slopes_post_filter = 48
- slope_min_max_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
- spawner_entity.id, num_expected_slopes_post_filter), 5.0)
- self.test_success = slope_min_max_success and self.test_success
+ # 4) Validate instance counts pre-filter
+ num_expected_flat_surface = 40 * 40 # 20x20 instances per 16m
+ num_expected_slopes_pre_filter = 120 # Unfiltered planting on the top of the sphere mesh
+ num_expected = num_expected_flat_surface + num_expected_slopes_pre_filter
+ initial_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
+ spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.prefilter_instance_count, initial_success)
- # 7) Setup for overrides on the Slope Filter component and the spawner entity's descriptor
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Override Enabled", True)
+ # 5) Change Slope Min/Max on the Vegetation Slope Filter component
+ spawner_entity.get_set_test(3, "Configuration|Slope Min", 20)
+ spawner_entity.get_set_test(3, "Configuration|Slope Max", 45)
- # 8) Set Slope Filter Min/Max overrides on the spawner entity's descriptor
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Min", 5)
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Max", 20)
+ # 6) Validate instance counts post-filter: instances should only plant on slopes between 20-45 degrees
+ num_expected_slopes_post_filter = 48
+ slope_min_max_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
+ spawner_entity.id, num_expected_slopes_post_filter), 5.0)
+ Report.result(Tests.postfilter_instance_count, slope_min_max_success)
- # 9) Validate instance counts post-filter: instances should only plant on slopes between 5-20 degrees
- num_expected_slopes_post_filter_overrides = 12
- overrides_min_max_success = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
- spawner_entity.id, num_expected_slopes_post_filter_overrides), 5.0)
- self.test_success = overrides_min_max_success and self.test_success
+ # 7) Setup for overrides on the Slope Filter component and the spawner entity's descriptor
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Override Enabled", True)
+
+ # 8) Set Slope Filter Min/Max overrides on the spawner entity's descriptor
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Min", 5)
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]|Slope Filter|Max", 20)
+
+ # 9) Validate instance counts post-filter: instances should only plant on slopes between 5-20 degrees
+ num_expected_slopes_post_filter_overrides = 12
+ overrides_min_max_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
+ spawner_entity.id, num_expected_slopes_post_filter_overrides), 5.0)
+ Report.result(Tests.postfilter_overrides_instance_count, overrides_min_max_success)
-test = TestSlopeFilterComponentAndOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_FilterStageToggle.py
deleted file mode 100755
index 33275f2992..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SlopeFilter_FilterStageToggle.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""
-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
-"""
-
-import os
-import sys
-import azlmbr.math as math
-import azlmbr.bus as bus
-import azlmbr.paths
-import azlmbr.editor as editor
-import azlmbr.entity as EntityId
-import azlmbr.components as components
-import azlmbr.legacy.general as general
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
-
-class TestSlopeFilterFilterStageToggle(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SlopeFilter_FilterStageToggle", args=["level"])
-
- def run_test(self):
- """
- Summary:
- Filter Stage toggle affects final vegetation position
-
- Expected Result:
- Vegetation instances plant differently depending on the Filter Stage setting. With PreProcess, some vegetation instances can
- appear on slopes outside the filtered values. With PostProcess, vegetation instances only appear on the correct slope values.
-
- :return: None
- """
-
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
-
- general.set_current_view_position(512.0, 480.0, 38.0)
-
- # Create basic vegetation entity
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
-
- # Create Surface for instances to plant on
- dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0)
-
- # Add a Vegetation Shape Intersection Filter to the vegetation area entity
- vegetation.add_component("Vegetation Shape Intersection Filter")
-
- # Create a new entity as a child of the vegetation area entity with Box Shape
- box = hydra.Entity("box")
- box.create_entity(position, ["Box Shape"])
- box.get_set_test(0, "Box Shape|Box Configuration|Dimensions", math.Vector3(8.0, 8.0, 1.0))
-
- # Create a new entity as a child of the vegetation area entity with Cylinder Shape.
- cylinder = hydra.Entity("cylinder")
- cylinder.create_entity(position, ["Cylinder Shape"])
- cylinder.get_set_test(0, "Cylinder Shape|Cylinder Configuration|Radius", 5.0)
- cylinder.get_set_test(0, "Cylinder Shape|Cylinder Configuration|Height", 5.0)
- box.set_test_parent_entity(vegetation)
- cylinder.set_test_parent_entity(vegetation)
-
- # # On the Vegetation Shape Intersection Filter component, click the crosshair button, and add child entities one by one
- vegetation.get_set_test(3, "Configuration|Shape Entity Id", box.id)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 8.0, 100), 2.0)
- self.log(f"Vegetation plant only in the areas where the Box overlaps with the vegetation area's boundaries: {result}")
- vegetation.get_set_test(3, "Configuration|Shape Entity Id", cylinder.id)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 100), 2.0)
- self.log(f"Vegetation plant only in the areas where the Cylinder overlaps with the vegetation area's boundaries: {result}")
-
- # Create a new entity as a child of the vegetation area entity with Random Noise Gradient Generator, Gradient Transform Modifier,
- # and Box Shape component
- random_noise = hydra.Entity("random_noise")
- random_noise.create_entity(position, ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"])
- random_noise.set_test_parent_entity(vegetation)
-
- # Add a Vegetation Position Modifier to the vegetation area entity
- vegetation.add_component("Vegetation Position Modifier")
-
- # Pin the Random Noise entity to the Gradient Entity Id field of the Position Modifier's Gradient X
- vegetation.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", random_noise.id)
-
- # Toggle between PreProcess and PostProcess
- vegetation.get_set_test(3, "Configuration|Filter Stage", 1)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 117), 2.0)
- self.log(f"Vegetation instances count equal to expected value for PREPROCESS filter stage: {result}")
- vegetation.get_set_test(3, "Configuration|Filter Stage", 2)
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count(position, 5.0, 122), 2.0)
- self.log(f"Vegetation instances count equal to expected value for POSTPROCESS filter stage: {result}")
-
-test = TestSlopeFilterFilterStageToggle()
-test.run()
\ No newline at end of file
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SpawnerSlices_SliceCreationAndVisibilityToggleWorks.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SpawnerSlices_SliceCreationAndVisibilityToggleWorks.py
new file mode 100644
index 0000000000..1658ffc532
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SpawnerSlices_SliceCreationAndVisibilityToggleWorks.py
@@ -0,0 +1,126 @@
+"""
+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
+"""
+
+
+class Tests:
+ spawner_slice_created = (
+ "Spawner slice created successfully",
+ "Failed to create Spawner slice"
+ )
+ instance_count_unhidden = (
+ "Initial instance counts are as expected",
+ "Found an unexpected number of initial instances"
+ )
+ instance_count_hidden = (
+ "Instance counts upon hiding the Spawner slice are as expected",
+ "Unexpectedly found instances with the Spawner slice hidden"
+ )
+ blender_slice_created = (
+ "Blender slice created successfully",
+ "Failed to create Blender slice"
+ )
+
+
+def SpawnerSlices_SliceCreationAndVisibilityToggleWorks():
+ """
+ Summary:
+ C2627900 Verifies if a slice containing the component can be created.
+ C2627905 A slice containing the Vegetation Layer Blender component can be created.
+ C2627904: Hiding a slice containing the component clears any visuals from the Viewport.
+
+ Expected Result:
+ C2627900, C2627905: Slice is created, and is properly processed in the Asset Processor.
+ C2627904: Vegetation area visuals are hidden from the Viewport.
+
+ :return: None
+ """
+
+ import os
+
+ import azlmbr.math as math
+ import azlmbr.legacy.general as general
+ import azlmbr.slice as slice
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.asset as asset
+
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ def path_is_valid_asset(asset_path):
+ asset_id = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", asset_path, math.Uuid(), False)
+ return asset_id.invoke("IsValid")
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+ general.set_current_view_position(512.0, 480.0, 38.0)
+
+ # 2) C2627900 Verifies if a slice containing the Vegetation Layer Spawner component can be created.
+ # 2.1) Create basic vegetation entity
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ veg_1 = dynveg.create_vegetation_area("vegetation_1", position, 16.0, 16.0, 16.0, asset_path)
+
+ # 2.2) Create slice from the entity
+ slice_path = os.path.join("slices", "TestSlice_1.slice")
+ slice.SliceRequestBus(bus.Broadcast, "CreateNewSlice", veg_1.id, slice_path)
+
+ # 2.3) Verify if the slice has been created successfully
+ spawner_slice_success = helper.wait_for_condition(lambda: path_is_valid_asset(slice_path), 5.0)
+ Report.result(Tests.spawner_slice_created, spawner_slice_success)
+
+ # 3) C2627904: Hiding a slice containing the component clears any visuals from the Viewport
+ # 3.1) Create Surface for instances to plant on
+ dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
+
+ # 3.2) Initially verify instance count before hiding slice
+ initial_count_success = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 400), 5.0)
+ Report.result(Tests.instance_count_unhidden, initial_count_success)
+
+ # 3.3) Hide the slice and verify instance count
+ editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", veg_1.id, False)
+ hidden_instance_count = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 16.0, 0), 5.0)
+ Report.result(Tests.instance_count_hidden, hidden_instance_count)
+
+ # 3.4) Unhide the slice
+ editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", veg_1.id, True)
+
+ # 4) C2627905 A slice containing the Vegetation Layer Blender component can be created.
+ # 4.1) Create another vegetation entity to add to blender component
+ veg_2 = dynveg.create_vegetation_area("vegetation_2", position, 1.0, 1.0, 1.0, "")
+
+ # 4.2) Create entity with Vegetation Layer Blender
+ components_to_add = ["Box Shape", "Vegetation Layer Blender"]
+ blender_entity = hydra.Entity("blender_entity")
+ blender_entity.create_entity(position, components_to_add)
+
+ # 4.3) Pin both the vegetation areas to the blender entity
+ pte = hydra.get_property_tree(blender_entity.components[1])
+ path = "Configuration|Vegetation Areas"
+ pte.update_container_item(path, 0, veg_1.id)
+ pte.add_container_item(path, 1, veg_2.id)
+
+ # 4.4) Drag the simple vegetation areas under the Vegetation Layer Blender entity to create an entity hierarchy.
+ veg_1.set_test_parent_entity(blender_entity)
+ veg_2.set_test_parent_entity(blender_entity)
+
+ # 4.5) Create slice from blender entity
+ slice_path = os.path.join("slices", "TestSlice_2.slice")
+ slice.SliceRequestBus(bus.Broadcast, "CreateNewSlice", blender_entity.id, slice_path)
+
+ # 4.6) Verify if the slice has been created successfully
+ blender_slice_success = helper.wait_for_condition(lambda: path_is_valid_asset(slice_path), 5.0)
+ Report.result(Tests.blender_slice_created, blender_slice_success)
+
+
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SpawnerSlices_SliceCreationAndVisibilityToggleWorks)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceDataRefreshes_RemainsStable.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceDataRefreshes_RemainsStable.py
index 2f08d3963d..1ad4f305c3 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceDataRefreshes_RemainsStable.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceDataRefreshes_RemainsStable.py
@@ -5,99 +5,97 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.legacy.general as general
-import azlmbr.math as math
+class Tests:
+ editor_remains_stable = (
+ "Editor did not crash following rapid surface data updates",
+ "Editor crashed"
+ )
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+def SurfaceDataRefreshes_RemainsStable():
+ """
+ Summary:
+ The Vegetation Area System can intermittently crash when updating surface data and moving the camera
+ around rapidly. The situation occurs across multiple frames - the surface data updates, which triggers a bunch
+ of sector updates getting added to the update queue. Then in a subsequent frame, there is no active vegetation
+ area or surface data updates, which triggers "delete all sectors". The "delete all" wasn't deleting entries from
+ the update queue, so any unprocessed updates would continue to get processed. If any of those updates referenced
+ a sector that no longer exists, because the camera changed position, then it would assert and crash.
+
+ To repro this bug, this test loads an empty level with a large box shape emitting a surface, and then runs a tight
+ loop of camera movements and "surface changed" events that invalidate all surface points. Because this is a timing
+ issue, there's no guarantee that the test below will successfully cause the condition to occur, but it successfully
+ crashed every time it was tested locally prior to the bugfix.
+
+ :return: None
+ """
+
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+
+ world_center = math.Vector3(512.0, 512.0, 32.0)
+
+ # Add an entity with a 1024 x 1024 box centered at 512,512.
+ surface_entity = dynveg.create_surface_entity("Surface Data", world_center, 1024.0, 1024.0, 1.0)
+
+ # Move the camera to the world center
+ general.set_current_view_position(world_center.x, world_center.y, world_center.z)
+
+ # 2) Perform the test. Since the conditions are extremely timing related, and every machine
+ # running the test can have different timing conditions, we run through a set of different
+ # combinations to try and cause the crash under as many scenarios as possible
+
+ loops_per_surface_changed = [3, 5, 5]
+ loops_per_camera_reset = [20, 20, 20]
+ camera_speed_per_loop = [10.0, 10.0, 15.0]
+
+ # Setting test success to false to make sure the toggle at the end accurately conveys the loop being successful
+ test_success = False
+
+ # Loop through all our attempted timing test cases to cause the crash pretty consistently.
+ for test_case in range(0,3):
+ Report.info(f'Starting test case {test_case}')
+ Report.info(f'Loops per surface changed: {loops_per_surface_changed[test_case]}')
+ Report.info(f'Loops per camera reset: {loops_per_camera_reset[test_case]}')
+ Report.info(f'Camera speed per loop: {camera_speed_per_loop[test_case]}')
+ for test_counter in range(0, 100):
+
+ # Every N loops, invalidate the entire set of surface data. It's mostly just important for this
+ # not to happen *every* iteration, since we need the vegetation system to bounce between having
+ # dirty surface points that cause sectors to be refreshed, and having no dirty surface points or
+ # active surface areas to trigger a "delete all sectors" condition.
+ if (test_counter % loops_per_surface_changed[test_case]) == 0:
+ azlmbr.surface_data.SurfaceDataSystemNotificationBus(azlmbr.bus.Broadcast,
+ 'OnSurfaceChanged',
+ surface_entity.id,
+ azlmbr.math.Aabb(),
+ azlmbr.math.Aabb())
+
+ # Move the camera back and forth along the X axis at just the right speed to invalidate sectors that are
+ # queued for updating but haven't updated yet, so that when they try to update they crash.
+ x_pos = world_center.x + ((test_counter % loops_per_camera_reset[test_case]) * camera_speed_per_loop[test_case])
+ general.set_current_view_position(x_pos, world_center.y, world_center.z)
+
+ Report.info(f'{test_counter}: {x_pos}')
+
+ # Give a little processing time each iteration.
+ general.idle_wait(0.01)
+
+ # If we haven't crashed, then we've succeeded.
+ test_success = True
+ Report.result(Tests.editor_remains_stable, test_success)
-class TestSurfaceDataRefreshes_RemainsStable(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SurfaceDataRefreshes_RemainsStable", args=["level"])
+if __name__ == "__main__":
- def run_test(self):
- """
- Summary:
- The Vegetation Area System can intermittently crash when updating surface data and moving the camera
- around rapidly. The situation occurs across multiple frames - the surface data updates, which triggers a bunch
- of sector updates getting added to the update queue. Then in a subsequent frame, there is no active vegetation
- area or surface data updates, which triggers "delete all sectors". The "delete all" wasn't deleting entries from
- the update queue, so any unprocessed updates would continue to get processed. If any of those updates referenced
- a sector that no longer exists, because the camera changed position, then it would assert and crash.
-
- To repro this bug, this test creates an empty level with a large box shape emitting a surface, and then runs a tight
- loop of camera movements and "surface changed" events that invalidate all surface points. Because this is a timing
- issue, there's no guarantee that the test below will successfully cause the condition to occur, but it successfully
- crashed every time it was tested locally prior to the bugfix.
-
- :return: None
- """
- # 1) Create a test level with the needed test setup
- self.test_success = self.create_level(
- self.get_arg('level'),
- heightmap_resolution=128,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=128,
- use_terrain=False)
-
- world_center = math.Vector3(512.0, 512.0, 32.0)
-
- # Add an entity with a 1024 x 1024 box centered at 512,512.
- surface_entity = dynveg.create_surface_entity("Surface Data", world_center, 1024.0, 1024.0, 1.0)
-
- # Move the camera to the world center
- general.set_current_view_position(world_center.x, world_center.y, world_center.z)
-
- # 2) Perform the test. Since the conditions are extremely timing related, and every machine
- # running the test can have different timing conditions, we run through a set of different
- # combinations to try and cause the crash under as many scenarios as possible
-
- loops_per_surface_changed = [3, 5, 5]
- loops_per_camera_reset = [20, 20, 20]
- camera_speed_per_loop = [10.0, 10.0, 15.0]
-
- # Setting test success to false to make sure the toggle at the end accurately conveys the loop being successful
- self.test_success = False
-
- # Loop through all our attempted timing test cases to cause the crash pretty consistently.
- for test_case in range(0,3):
- self.log(f'Starting test case {test_case}')
- self.log(f'Loops per surface changed: {loops_per_surface_changed[test_case]}')
- self.log(f'Loops per camera reset: {loops_per_camera_reset[test_case]}')
- self.log(f'Camera speed per loop: {camera_speed_per_loop[test_case]}')
- for test_counter in range (0,100):
-
- # Every N loops, invalidate the entire set of surface data. It's mostly just important for this
- # not to happen *every* iteration, since we need the vegetation system to bounce between having
- # dirty surface points that cause sectors to be refreshed, and having no dirty surface points or
- # active surface areas to trigger a "delete all sectors" condition.
- if (test_counter % loops_per_surface_changed[test_case]) == 0:
- azlmbr.surface_data.SurfaceDataSystemNotificationBus(azlmbr.bus.Broadcast,
- 'OnSurfaceChanged',
- surface_entity.id,
- azlmbr.math.Aabb(),
- azlmbr.math.Aabb())
-
- # Move the camera back and forth along the X axis at just the right speed to invalidate sectors that are
- # queued for updating but haven't updated yet, so that when they try to update they crash.
- x_pos = world_center.x + ((test_counter % loops_per_camera_reset[test_case]) * camera_speed_per_loop[test_case])
- general.set_current_view_position(x_pos, world_center.y, world_center.z)
-
- self.log(f'{test_counter}: {x_pos}')
-
- # Give a little processing time each iteration.
- general.idle_wait(0.01)
-
- # If we haven't crashed, then we've succeeded.
- self.test_success = True
-
-
-test = TestSurfaceDataRefreshes_RemainsStable()
-test.run()
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SurfaceDataRefreshes_RemainsStable)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected.py
index 87aa7946b3..ba0f3e05e3 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected.py
@@ -5,153 +5,160 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C3711666: Multiple Descriptors with different Surface Mask Filter overrides plant as expected.
-"""
-import os
-import sys
-
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.surface_data as surface_data
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_surface_validation = (
+ "Found all expected instances on all surfaces with initial setup",
+ "Found an unexpected number of instances on all surfaces with initial setup"
+ )
+ surface_a_validation = (
+ "Found the expected number of instances on Surface A",
+ "Found an unexpected number of instances on Surface A"
+ )
+ surface_b_validation = (
+ "Found the expected number of instances on Surface B",
+ "Found an unexpected number of instances on Surface B"
+ )
+ surface_c_validation = (
+ "Found the expected number of instances on Surface C",
+ "Found an unexpected number of instances on Surface C"
+ )
-class TestSurfaceMaskFilterMultipleOverrides(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SurfaceMaskFilter_MultipleDescriptorOverrides", args=["level"])
+def SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected():
+ """
+ Summary:
+ A new level is created. An instance spawner with 3 descriptors is created. 3 planting surfaces of different
+ sizes are created and different surface tags are applied to each. Descriptor surface mask filter overrides are
+ set and instance counts are validated.
- def run_test(self):
- """
- Summary:
- A new level is created. An instance spawner with 3 descriptors is created. 3 planting surfaces of different
- sizes are created and different surface tags are applied to each. Descriptor surface mask filter overrides are
- set and instance counts are validated.
+ Expected Behavior:
+ Instances plant on surfaces based on surface mask filter overrides.
- Expected Behavior:
- Instances plant on surfaces based on surface mask filter overrides.
+ Test Steps:
+ 1) Open an existing level
+ 2) An instance spawner with 3 descriptors is created, and a Surface Mask Filter is added to the entity
+ 3) 3 surfaces of different sizes are created, and set to emit different tags
+ 4) Pre-test validation of instances
+ 5) Test 1 setup and validation: Inclusion tag matching surface a is set on a single descriptor
+ 6) Test 2 setup and validation: Inclusion tag matching surface b is set on a single descriptor
+ 7) Test 3 setup and validation: Inclusion tag matching surface c is set on a single descriptor
- Test Steps:
- 1) A new level is created
- 2) An instance spawner with 3 descriptors is created, and a Surface Mask Filter is added to the entity
- 3) 3 surfaces of different sizes are created, and set to emit different tags
- 4) Pre-test validation of instances
- 5) Test 1 setup and validation: Inclusion tag matching surface a is set on a single descriptor
- 6) Test 2 setup and validation: Inclusion tag matching surface b is set on a single descriptor
- 7) Test 3 setup and validation: Inclusion tag matching surface c is set on a single descriptor
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
- surface_tag_list = [surface_data.SurfaceTag("test_tag"), surface_data.SurfaceTag("test_tag2"),
- surface_data.SurfaceTag("test_tag3")]
+ import os
- # 1) Create a new, temporary level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+ import azlmbr.surface_data as surface_data
- # Set view of planting area for visual debugging
- general.set_current_view_position(512.0, 500.0, 38.0)
- general.set_current_view_rotation(-20.0, 0.0, 0.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # 2) Create a new instance spawner entity with multiple Dynamic Slice Instance Spawner descriptors
- spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
- asset_path)
- asset_list_component = spawner_entity.components[2]
- desc_asset = hydra.get_component_property_value(asset_list_component,
- "Configuration|Embedded Assets")[0]
- desc_list = [desc_asset, desc_asset, desc_asset]
- spawner_entity.get_set_test(2, "Configuration|Embedded Assets", desc_list)
-
- # Add a Surface Mask Filter component to the spawner entity and toggle on Allow Overrides
- spawner_entity.add_component("Vegetation Surface Mask Filter")
- spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
+ surface_tag_list = [surface_data.SurfaceTag("test_tag"), surface_data.SurfaceTag("test_tag2"),
+ surface_data.SurfaceTag("test_tag3")]
- # 3) Create 3 surfaces for planting, spaced out vertically, and set expected instance counts for each surface
- surface_entity_a = dynveg.create_surface_entity("Surface Entity A", math.Vector3(512.0, 512.0, 32.0),
- 16.0, 16.0, 1.0)
- num_expected_surface_a = 20 * 20 # 20x20 instances on a 16x16 meter surface
- surface_entity_b = dynveg.create_surface_entity("Surface Entity B", math.Vector3(512.0, 512.0, 35.0),
- 12.0, 12.0, 1.0)
- num_expected_surface_b = 15 * 15 # 15x15 instances on a 12x12 meter surface
- surface_entity_c = dynveg.create_surface_entity("Surface Entity C", math.Vector3(512.0, 512.0, 38.0),
- 8.0, 8.0, 1.0)
- num_expected_surface_c = 10 * 10 # 10x10 instances on a 8x8 meter surface
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Set each surface to emit a different tag
- surface_entity_a.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[0]])
- surface_entity_b.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[1]])
- surface_entity_c.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[2]])
+ # Set view of planting area for visual debugging
+ general.set_current_view_position(512.0, 500.0, 38.0)
+ general.set_current_view_rotation(-20.0, 0.0, 0.0)
- # 4) Initial Validation: Validate instance count in the spawner area. Instances should plant on all surfaces
- num_expected = num_expected_surface_a + num_expected_surface_b + num_expected_surface_c
- initial_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
- self.test_success = initial_success and self.test_success
+ # 2) Create a new instance spawner entity with multiple Dynamic Slice Instance Spawner descriptors
+ spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
+ asset_path)
+ asset_list_component = spawner_entity.components[2]
+ desc_asset = hydra.get_component_property_value(asset_list_component,
+ "Configuration|Embedded Assets")[0]
+ desc_list = [desc_asset, desc_asset, desc_asset]
+ spawner_entity.get_set_test(2, "Configuration|Embedded Assets", desc_list)
- # 5)
- # Test #1 Setup: Set test_tag to inclusion list for descriptor 1. Set other descriptors to exclude all surfaces
+ # Add a Surface Mask Filter component to the spawner entity and toggle on Allow Overrides
+ spawner_entity.add_component("Vegetation Surface Mask Filter")
+ spawner_entity.get_set_test(3, "Configuration|Allow Per-Item Overrides", True)
- # Toggle on Display Per-Item Overrides and Surface Mask Filter Override for each descriptor
- for index in range(3):
- spawner_entity.get_set_test(2, f"Configuration|Embedded Assets|[{index}]|Display Per-Item Overrides", True)
- spawner_entity.get_set_test(2,
- f"Configuration|Embedded Assets|[{index}]|Surface Mask Filter|Override Mode", 1)
+ # 3) Create 3 surfaces for planting, spaced out vertically, and set expected instance counts for each surface
+ surface_entity_a = dynveg.create_surface_entity("Surface Entity A", math.Vector3(512.0, 512.0, 32.0),
+ 16.0, 16.0, 1.0)
+ num_expected_surface_a = 20 * 20 # 20x20 instances on a 16x16 meter surface
+ surface_entity_b = dynveg.create_surface_entity("Surface Entity B", math.Vector3(512.0, 512.0, 35.0),
+ 12.0, 12.0, 1.0)
+ num_expected_surface_b = 15 * 15 # 15x15 instances on a 12x12 meter surface
+ surface_entity_c = dynveg.create_surface_entity("Surface Entity C", math.Vector3(512.0, 512.0, 38.0),
+ 8.0, 8.0, 1.0)
+ num_expected_surface_c = 10 * 10 # 10x10 instances on a 8x8 meter surface
+ # Set each surface to emit a different tag
+ surface_entity_a.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[0]])
+ surface_entity_b.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[1]])
+ surface_entity_c.get_set_test(1, "Configuration|Generated Tags", [surface_tag_list[2]])
+
+ # 4) Initial Validation: Validate instance count in the spawner area. Instances should plant on all surfaces
+ num_expected = num_expected_surface_a + num_expected_surface_b + num_expected_surface_c
+ initial_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected), 5.0)
+ Report.result(Tests.initial_surface_validation, initial_success)
+
+ # 5)
+ # Test #1 Setup: Set test_tag to inclusion list for descriptor 1. Set other descriptors to exclude all surfaces
+
+ # Toggle on Display Per-Item Overrides and Surface Mask Filter Override for each descriptor
+ for index in range(3):
+ spawner_entity.get_set_test(2, f"Configuration|Embedded Assets|[{index}]|Display Per-Item Overrides", True)
spawner_entity.get_set_test(2,
- "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
- [surface_tag_list[0]])
- spawner_entity.get_set_test(2,
- "Configuration|Embedded Assets|[1]|Surface Mask Filter|Exclusion Tags",
- surface_tag_list)
- spawner_entity.get_set_test(2,
- "Configuration|Embedded Assets|[2]|Surface Mask Filter|Exclusion Tags",
- surface_tag_list)
+ f"Configuration|Embedded Assets|[{index}]|Surface Mask Filter|Override Mode", 1)
- # Test #1 Validation: Validate instance count. Should only plant on a single surface for 400 instances
- test_1_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_a), 5.0)
- self.test_success = test_1_success and self.test_success
+ spawner_entity.get_set_test(2,
+ "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
+ [surface_tag_list[0]])
+ spawner_entity.get_set_test(2,
+ "Configuration|Embedded Assets|[1]|Surface Mask Filter|Exclusion Tags",
+ surface_tag_list)
+ spawner_entity.get_set_test(2,
+ "Configuration|Embedded Assets|[2]|Surface Mask Filter|Exclusion Tags",
+ surface_tag_list)
- # 6)
- # Test #2 Setup: Set test_tag2 to inclusion for descriptor 1.
- spawner_entity.get_set_test(2,
- "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
- [surface_tag_list[1]])
+ # Test #1 Validation: Validate instance count. Should only plant on a single surface for 400 instances
+ test_1_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_a), 5.0)
+ Report.result(Tests.surface_a_validation, test_1_success)
- # Test #2 Validation: Validate instance count. Should only plant on a single surface for 225 instances
- test_2_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_b), 5.0)
- self.test_success = test_2_success and self.test_success
+ # 6)
+ # Test #2 Setup: Set test_tag2 to inclusion for descriptor 1.
+ spawner_entity.get_set_test(2,
+ "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
+ [surface_tag_list[1]])
- # 7)
- # Test #3 Setup: Set test_tag3 to inclusion for descriptor 1.
- spawner_entity.get_set_test(2,
- "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
- [surface_tag_list[2]])
+ # Test #2 Validation: Validate instance count. Should only plant on a single surface for 225 instances
+ test_2_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_b), 5.0)
+ Report.result(Tests.surface_b_validation, test_2_success)
- # Test #3 Validation: Validate instance count. Should only plant on a single surface for 100 instances
- test_3_success = self.wait_for_condition(
- lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_c), 5.0)
- self.test_success = test_3_success and self.test_success
+ # 7)
+ # Test #3 Setup: Set test_tag3 to inclusion for descriptor 1.
+ spawner_entity.get_set_test(2,
+ "Configuration|Embedded Assets|[0]|Surface Mask Filter|Inclusion Tags",
+ [surface_tag_list[2]])
+
+ # Test #3 Validation: Validate instance count. Should only plant on a single surface for 100 instances
+ test_3_success = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id, num_expected_surface_c), 5.0)
+ Report.result(Tests.surface_c_validation, test_3_success)
-test = TestSurfaceMaskFilterMultipleOverrides()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
index 1baec7694d..fee62c04d1 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
@@ -5,66 +5,61 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.surface_data as surface_data
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
+class Tests:
+ tags_same_value_equal = (
+ "Two Surface Tags of the same value evaluated as equal",
+ "Two Surface Tags of the same value unexpectedly evaluated as unequal"
+ )
+ tags_different_value_unequal = (
+ "Two Surface Tags of different values evaluated as unequal",
+ "Two Surface Tags of different values unexpectedly evaluated as equal"
+ )
-class TestSurfaceMaskFilter_BasicSurfaceTagCreation(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="TestSurfaceMaskFilter_BasicSurfaceTagCreation", args=["level"])
-
- def run_test(self):
- """
- Summary:
- Verifies basic surface tag value equality
+def SurfaceMaskFilter_BasicSurfaceTagCreation():
+ """
+ Summary:
+ Verifies basic surface tag value equality
- Expected Behavior:
- Surface tags of the same name are equal, and different names aren't.
+ Expected Behavior:
+ Surface tags of the same name are equal, and different names aren't.
- Test Steps:
- 1) Open level
- 2) Create 2 new surface tags of identical names and verify they resolve as equal.
- 3) Create another new tag of a different name and verify they resolve as different.
+ Test Steps:
+ 1) Open level
+ 2) Create 2 new surface tags of identical names and verify they resolve as equal.
+ 3) Create another new tag of a different name and verify they resolve as different.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- :return: None
- """
- self.log("SurfaceTag test started")
-
- # Create a level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
-
- tag1 = surface_data.SurfaceTag()
- tag2 = surface_data.SurfaceTag()
-
- # Test 1: Verify that two tags with the same value are equal
- tag1.SetTag('equal_test')
- tag2.SetTag('equal_test')
- self.log("SurfaceTag equal tag comparison is {} expected True".format(tag1.Equal(tag2)))
- self.test_success = self.test_success and tag1.Equal(tag2)
-
- # Test 2: Verify that two tags with different values are not equal
- tag2.SetTag('not_equal_test')
- self.log("SurfaceTag not equal tag comparison is {} expected False".format(tag1.Equal(tag2)))
- self.test_success = self.test_success and not tag1.Equal(tag2)
-
- self.log("SurfaceTag test finished")
+ :return: None
+ """
+
+ import azlmbr.surface_data as surface_data
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
+
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
+
+ tag1 = surface_data.SurfaceTag()
+ tag2 = surface_data.SurfaceTag()
+
+ # Test 1: Verify that two tags with the same value are equal
+ tag1.SetTag('equal_test')
+ tag2.SetTag('equal_test')
+ Report.result(Tests.tags_same_value_equal, tag1.Equal(tag2))
+
+ # Test 2: Verify that two tags with different values are not equal
+ tag2.SetTag('not_equal_test')
+ Report.result(Tests.tags_different_value_unequal, not tag1.Equal(tag2))
-test = TestSurfaceMaskFilter_BasicSurfaceTagCreation()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SurfaceMaskFilter_BasicSurfaceTagCreation)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_ExclusionList.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_ExclusionList.py
index 1ce7962e6a..6438124698 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_ExclusionList.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_ExclusionList.py
@@ -4,147 +4,141 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C2561342: Exclusive Surface Masks tags function
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.areasystem as areasystem
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.shape as shape
-import azlmbr.surface_data as surface_data
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ default_exclusion_weight = (
+ "Found the expected number of instances with Exclusion Weight set to defaults",
+ "Found an unexpected number of instances with Exclusion Weight set to defaults"
+ )
+ exclusion_weight_below_one = (
+ "Found the expected number of instances with Exclusion Weight set below 1",
+ "Found an unexpected number of instances with Exclusion Weight set below 1"
+ )
-class TestExclusiveSurfaceMasksTag(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SurfaceMaskFilter_ExclusionList", args=["level"])
+def SurfaceMaskFilter_ExclusionList():
+ """
+ Summary:
+ New level is created and set up with surface shapes with varying surface tags. A simple vegetation area has been
+ created and Vegetation Surface Mask Filter component is added to entity with terrain hole exclusion tag.
- def run_test(self):
- """
- Summary:
- New level is created and set up with surface shapes with varying surface tags. A simple vegetation area has been
- created and Vegetation Surface Mask Filter component is added to entity with terrain hole exclusion tag.
+ Expected Behavior:
+ With default Exclusion settings, vegetation does not plant over the terrain holes.
+ With Exclusion Weight Max below 1.0, vegetation plants over the terrain holes.
- Expected Behavior:
- With default Exclusion settings, vegetation does not plant over the terrain holes.
- With Exclusion Weight Max below 1.0, vegetation plants over the terrain holes.
+ Test Steps:
+ 1) Open an existing level
+ 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ 3) Add a Vegetation Surface Mask Filter component to the entity.
+ 4) Create 2 surface entities to represent terrain and terrain hole surfaces
+ 5) Add an Exclusion List tag to the component, and set it to terrainHole.
+ 6) Check spawn count with default Exclusion Weights
+ 7) Check spawn count with Exclusion Weight Max set below 1.0
- Test Steps:
- 1) Create a new level.
- 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- 3) Add a Vegetation Surface Mask Filter component to the entity.
- 4) Create 2 surface entities to represent terrain and terrain hole surfaces
- 5) Add an Exclusion List tag to the component, and set it to terrainHole.
- 6) Check spawn count with default Exclusion Weights
- 7) Check spawn count with Exclusion Weight Max set below 1.0
-
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- :return: None
- """
+ :return: None
+ """
- def update_surface_tag_exclusion_list(Entity, component_index, surface_tag):
- tag_list = [surface_data.SurfaceTag()]
+ import os
- # assign list with one surface tag to exclusion list
- hydra.get_set_test(Entity, component_index, "Configuration|Exclusion|Surface Tags", tag_list)
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+ import azlmbr.surface_data as surface_data
- # set that one surface tag element to required surface tag
- component = Entity.components[component_index]
- path = "Configuration|Exclusion|Surface Tags|[0]|Surface Tag"
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
- new_value = hydra.get_component_property_value(component, path)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- if new_value == surface_tag:
- self.log(f"Exclusive surface mask filter of {surface_tag} is added successfully")
- else:
- self.log(f"Failed to add an Exclusive surface mask filter of {surface_tag}")
+ def update_surface_tag_exclusion_list(Entity, component_index, surface_tag):
+ tag_list = [surface_data.SurfaceTag()]
- def update_generated_surface_tag(Entity, component_index, surface_tag):
- tag_list = [surface_data.SurfaceTag()]
+ # assign list with one surface tag to exclusion list
+ hydra.get_set_test(Entity, component_index, "Configuration|Exclusion|Surface Tags", tag_list)
- # assign list with one surface tag to Generated Tags list
- hydra.get_set_test(Entity, component_index, "Configuration|Generated Tags", tag_list)
+ # set that one surface tag element to required surface tag
+ component = Entity.components[component_index]
+ path = "Configuration|Exclusion|Surface Tags|[0]|Surface Tag"
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
+ new_value = hydra.get_component_property_value(component, path)
- # set that one surface tag element to required surface tag
- component = Entity.components[component_index]
- path = "Configuration|Generated Tags|[0]|Surface Tag"
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
- new_value = hydra.get_component_property_value(component, path)
+ if new_value == surface_tag:
+ Report.info(f"Exclusive surface mask filter of {surface_tag} is added successfully")
+ else:
+ Report.info(f"Failed to add an Exclusive surface mask filter of {surface_tag}")
- if new_value == surface_tag:
- self.log(f"Generated surface tag of {surface_tag} is added successfully")
- else:
- self.log(f"Failed to add Generated surface tag of {surface_tag}")
+ def update_generated_surface_tag(Entity, component_index, surface_tag):
+ tag_list = [surface_data.SurfaceTag()]
- # 1) Create a new level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ # assign list with one surface tag to Generated Tags list
+ hydra.get_set_test(Entity, component_index, "Configuration|Generated Tags", tag_list)
- general.set_current_view_position(512.0, 480.0, 38.0)
+ # set that one surface tag element to required surface tag
+ component = Entity.components[component_index]
+ path = "Configuration|Generated Tags|[0]|Surface Tag"
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
+ new_value = hydra.get_component_property_value(component, path)
- # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
- entity_position,
- 10.0, 10.0, 10.0,
- asset_path)
+ if new_value == surface_tag:
+ Report.info(f"Generated surface tag of {surface_tag} is added successfully")
+ else:
+ Report.info(f"Failed to add Generated surface tag of {surface_tag}")
- # 3) Add a Vegetation Surface Mask Filter component to the entity.
- spawner_entity.add_component("Vegetation Surface Mask Filter")
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # 4) Create 2 surface entities to represent terrain and terrain hole surfaces
- surface_tags: dict = {"terrainHole": 1327698037, "terrain": 3363197873}
- entity_position = math.Vector3(510.0, 512.0, 32.0)
- surface_entity_1 = dynveg.create_surface_entity("Surface Entity 1",
- entity_position,
- 10.0, 10.0, 1.0)
- update_generated_surface_tag(surface_entity_1, 1, surface_tags["terrainHole"])
+ general.set_current_view_position(512.0, 480.0, 38.0)
- entity_position = math.Vector3(520.0, 512.0, 32.0)
- surface_entity_2 = dynveg.create_surface_entity("Surface Entity 2",
- entity_position,
- 10.0, 10.0, 1.0)
- update_generated_surface_tag(surface_entity_2, 1, surface_tags["terrain"])
+ # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
+ entity_position,
+ 10.0, 10.0, 10.0,
+ asset_path)
- # 5) Add an Exclusion List tag to the component, and set it to "terrainHole".
- update_surface_tag_exclusion_list(spawner_entity, 3, surface_tags["terrainHole"])
+ # 3) Add a Vegetation Surface Mask Filter component to the entity.
+ spawner_entity.add_component("Vegetation Surface Mask Filter")
- # 6) Check spawn count with default Exclusion Weights
- general.idle_wait(2.0) # Allow a few seconds for instances to spawn
- num_expected_instances = 39
- box = shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- num_found = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ # 4) Create 2 surface entities to represent terrain and terrain hole surfaces
+ surface_tags: dict = {"terrainHole": 1327698037, "terrain": 3363197873}
+ entity_position = math.Vector3(510.0, 512.0, 32.0)
+ surface_entity_1 = dynveg.create_surface_entity("Surface Entity 1",
+ entity_position,
+ 10.0, 10.0, 1.0)
+ update_generated_surface_tag(surface_entity_1, 1, surface_tags["terrainHole"])
- # 7) Check spawn count with Exclusion Weight Max set below 1.0
- hydra.get_set_test(spawner_entity, 3, "Configuration|Exclusion|Weight Max", 0.9)
- general.idle_wait(2.0) # Allow a few seconds for instances to spawn
- num_expected_instances = 169
- num_found = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ entity_position = math.Vector3(520.0, 512.0, 32.0)
+ surface_entity_2 = dynveg.create_surface_entity("Surface Entity 2",
+ entity_position,
+ 10.0, 10.0, 1.0)
+ update_generated_surface_tag(surface_entity_2, 1, surface_tags["terrain"])
+
+ # 5) Add an Exclusion List tag to the component, and set it to "terrainHole".
+ update_surface_tag_exclusion_list(spawner_entity, 3, surface_tags["terrainHole"])
+
+ # 6) Check spawn count with default Exclusion Weights
+ num_expected_instances = 39
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 2.0)
+ Report.result(Tests.default_exclusion_weight, success)
+
+ # 7) Check spawn count with Exclusion Weight Max set below 1.0
+ hydra.get_set_test(spawner_entity, 3, "Configuration|Exclusion|Weight Max", 0.9)
+ num_expected_instances = 169
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 2.0)
+ Report.result(Tests.exclusion_weight_below_one, success)
-test = TestExclusiveSurfaceMasksTag()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SurfaceMaskFilter_ExclusionList)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_InclusionList.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_InclusionList.py
index e394bc0cde..bbd1235abc 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_InclusionList.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_InclusionList.py
@@ -4,148 +4,142 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-C2561341: Inclusive Surface Masks tags function
-"""
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-import azlmbr.areasystem as areasystem
-import azlmbr.bus as bus
-import azlmbr.editor as editor
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-import azlmbr.shape as shape
-import azlmbr.surface_data as surface_data
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ default_inclusion_weight = (
+ "Found the expected number of instances with Inclusion Weight set to defaults",
+ "Found an unexpected number of instances with Inclusion Weight set to defaults"
+ )
+ inclusion_weight_below_one = (
+ "Found the expected number of instances with Inclusion Weight set below 1",
+ "Found an unexpected number of instances with Inclusion Weight set below 1"
+ )
-class TestInclusiveSurfaceMasksTag(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SurfaceMaskFilter_InclusionList", args=["level"])
- def run_test(self):
- """
- Summary:
- New level is created and set up with surface shapes with varying surface tags. A simple vegetation area has been
- created and Vegetation Surface Mask Filter component is added to entity with terrain hole inclusion tag.
+def SurfaceMaskFilter_InclusionList():
+ """
+ Summary:
+ New level is created and set up with surface shapes with varying surface tags. A simple vegetation area has been
+ created and Vegetation Surface Mask Filter component is added to entity with terrain hole inclusion tag.
- Expected Behavior:
- With default Inclusion Weights, vegetation draws over the terrain holes.
- With Inclusion Weight Max set below 1.0, vegetation stops drawing over the terrain holes.
+ Expected Behavior:
+ With default Inclusion Weights, vegetation draws over the terrain holes.
+ With Inclusion Weight Max set below 1.0, vegetation stops drawing over the terrain holes.
- Test Steps:
- 1) Create a new level
- 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- 3) Add a Vegetation Surface Mask Filter component to the entity.
- 4) Create 2 surface entities to represent terrain and terrain hole surfaces
- 5) Add an Inclusion List tag to the component, and set it to "terrainHole".
- 6) Check spawn count with default Inclusion Weights
- 7) Check spawn count with Inclusion Weight Max set below 1.0
-
- Note:
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ Test Steps:
+ 1) Open an existing level
+ 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ 3) Add a Vegetation Surface Mask Filter component to the entity.
+ 4) Create 2 surface entities to represent terrain and terrain hole surfaces
+ 5) Add an Inclusion List tag to the component, and set it to "terrainHole".
+ 6) Check spawn count with default Inclusion Weights
+ 7) Check spawn count with Inclusion Weight Max set below 1.0
- :return: None
- """
+ Note:
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- def update_surface_tag_inclusion_list(Entity, component_index, surface_tag):
- tag_list = [surface_data.SurfaceTag()]
+ :return: None
+ """
- # assign list with one surface tag to inclusion list
- hydra.get_set_test(Entity, component_index, "Configuration|Inclusion|Surface Tags", tag_list)
+ import os
- # set that one surface tag element to required surface tag
- component = Entity.components[component_index]
- path = "Configuration|Inclusion|Surface Tags|[0]|Surface Tag"
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
- new_value = hydra.get_component_property_value(component, path)
+ import azlmbr.bus as bus
+ import azlmbr.editor as editor
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
+ import azlmbr.surface_data as surface_data
- if new_value == surface_tag:
- print("Inclusive surface mask filter of terrainHole is added successfully")
- else:
- print("Failed to add an Inclusive surface mask filter of terrainHole")
- general.idle_wait(2.0)
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- def update_generated_surface_tag(Entity, component_index, surface_tag):
- tag_list = [surface_data.SurfaceTag()]
+ def update_surface_tag_inclusion_list(Entity, component_index, surface_tag):
+ tag_list = [surface_data.SurfaceTag()]
- # assign list with one surface tag to Generated Tags list
- hydra.get_set_test(Entity, component_index, "Configuration|Generated Tags", tag_list)
+ # assign list with one surface tag to inclusion list
+ hydra.get_set_test(Entity, component_index, "Configuration|Inclusion|Surface Tags", tag_list)
- # set that one surface tag element to required surface tag
- component = Entity.components[component_index]
- path = "Configuration|Generated Tags|[0]|Surface Tag"
- editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
- new_value = hydra.get_component_property_value(component, path)
+ # set that one surface tag element to required surface tag
+ component = Entity.components[component_index]
+ path = "Configuration|Inclusion|Surface Tags|[0]|Surface Tag"
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
+ new_value = hydra.get_component_property_value(component, path)
- if new_value == surface_tag:
- self.log(f"Generated surface tag of {surface_tag} is added successfully")
- else:
- self.log(f"Failed to add Generated surface tag of {surface_tag}")
+ if new_value == surface_tag:
+ Report.info("Inclusive surface mask filter of terrainHole is added successfully")
+ else:
+ Report.info("Failed to add an Inclusive surface mask filter of terrainHole")
- # 1) Create a new level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ def update_generated_surface_tag(Entity, component_index, surface_tag):
+ tag_list = [surface_data.SurfaceTag()]
- general.set_current_view_position(512.0, 480.0, 38.0)
+ # assign list with one surface tag to Generated Tags list
+ hydra.get_set_test(Entity, component_index, "Configuration|Generated Tags", tag_list)
- # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
- entity_position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
- entity_position,
- 10.0, 10.0, 10.0,
- asset_path)
+ # set that one surface tag element to required surface tag
+ component = Entity.components[component_index]
+ path = "Configuration|Generated Tags|[0]|Surface Tag"
+ editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", component, path, surface_tag)
+ new_value = hydra.get_component_property_value(component, path)
- # 3) Add a Vegetation Surface Mask Filter component to the entity.
- spawner_entity.add_component("Vegetation Surface Mask Filter")
+ if new_value == surface_tag:
+ Report.info(f"Generated surface tag of {surface_tag} is added successfully")
+ else:
+ Report.info(f"Failed to add Generated surface tag of {surface_tag}")
- # 4) Create 2 surface entities to represent terrain and terrain hole surfaces
- surface_tags: dict = {"terrainHole": 1327698037, "terrain": 3363197873}
- entity_position = math.Vector3(510.0, 512.0, 32.0)
- surface_entity_1 = dynveg.create_surface_entity("Surface Entity 1",
- entity_position,
- 10.0, 10.0, 1.0)
- update_generated_surface_tag(surface_entity_1, 1, surface_tags["terrainHole"])
+ # 1) Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- entity_position = math.Vector3(520.0, 512.0, 32.0)
- surface_entity_2 = dynveg.create_surface_entity("Surface Entity 2",
- entity_position,
- 10.0, 10.0, 1.0)
- update_generated_surface_tag(surface_entity_2, 1, surface_tags["terrain"])
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # 5) Add an Inclusion List tag to the component, and set it to "terrainHole".
- update_surface_tag_inclusion_list(spawner_entity, 3, surface_tags["terrainHole"])
+ # 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
+ entity_position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
+ entity_position,
+ 10.0, 10.0, 10.0,
+ asset_path)
- # 6) Check spawn count with default Inclusion Weights
- general.idle_wait(2.0) # Allow a few seconds for instances to spawn
- num_expected_instances = 130
- box = shape.ShapeComponentRequestsBus(bus.Event, 'GetEncompassingAabb', spawner_entity.id)
- num_found = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ # 3) Add a Vegetation Surface Mask Filter component to the entity.
+ spawner_entity.add_component("Vegetation Surface Mask Filter")
- # 7) Check spawn count with Inclusion Weight Max set below 1.0
- hydra.get_set_test(spawner_entity, 3, "Configuration|Inclusion|Weight Max", 0.9)
- general.idle_wait(2.0) # Allow a few seconds for instances to update
- num_expected_instances = 0
- num_found = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstanceCountInAabb', box)
- self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances")
- self.test_success = self.test_success and num_found == num_expected_instances
+ # 4) Create 2 surface entities to represent terrain and terrain hole surfaces
+ surface_tags: dict = {"terrainHole": 1327698037, "terrain": 3363197873}
+ entity_position = math.Vector3(510.0, 512.0, 32.0)
+ surface_entity_1 = dynveg.create_surface_entity("Surface Entity 1",
+ entity_position,
+ 10.0, 10.0, 1.0)
+ update_generated_surface_tag(surface_entity_1, 1, surface_tags["terrainHole"])
+
+ entity_position = math.Vector3(520.0, 512.0, 32.0)
+ surface_entity_2 = dynveg.create_surface_entity("Surface Entity 2",
+ entity_position,
+ 10.0, 10.0, 1.0)
+ update_generated_surface_tag(surface_entity_2, 1, surface_tags["terrain"])
+
+ # 5) Add an Inclusion List tag to the component, and set it to "terrainHole".
+ update_surface_tag_inclusion_list(spawner_entity, 3, surface_tags["terrainHole"])
+
+ # 6) Check spawn count with default Inclusion Weights
+ num_expected_instances = 130
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 2.0)
+ Report.result(Tests.default_inclusion_weight, success)
+
+ # 7) Check spawn count with Inclusion Weight Max set below 1.0
+ hydra.get_set_test(spawner_entity, 3, "Configuration|Inclusion|Weight Max", 0.9)
+ num_expected_instances = 0
+ success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected_instances), 2.0)
+ Report.result(Tests.inclusion_weight_below_one, success)
-test = TestInclusiveSurfaceMasksTag()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SurfaceMaskFilter_InclusionList)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorPointDensity.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorPointDensity.py
index f690e175de..d808d3f20b 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorPointDensity.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorPointDensity.py
@@ -5,89 +5,90 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.editor as editor
-import azlmbr.bus as bus
-import azlmbr.legacy.general as general
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_density_instance_count = (
+ "Found the expected number of instances with default sector density",
+ "Found an unexpected number of instances with default sector density"
+ )
+ configured_density_instance_count = (
+ "Found the expected number of instances with a sector density of 10",
+ "Found an unexpected number of instances with a sector density of 10"
+ )
-class TestSystemSettingsSectorPointDensity(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SystemSettings_SectorPointDensity", args=["level"])
+def SystemSettings_SectorPointDensity():
+ """
+ Summary:
+ Sector Point Density increases/reduces the number of vegetation points within a sector
- def run_test(self):
- """
- Summary:
- Sector Point Density increases/reduces the number of vegetation points within a sector
+ Expected Result:
+ Default value for Sector Point Density is 20.
+ 20 vegetation meshes appear on each side of the established vegetation area with the default value.
+ When altered, the specified number of vegetation meshes along a side of a vegetation area matches the value set
+ in Sector Point Density.
- Expected Result:
- Default value for Sector Point Density is 20.
- 20 vegetation meshes appear on each side of the established vegetation area with the default value.
- When altered, the specified number of vegetation meshes along a side of a vegetation area matches the value set
- in Sector Point Density.
+ :return: None
+ """
- :return: None
- """
+ import os
- INSTANCE_COUNT_BEFORE_DENSITY_CHANGE = 400
- INSTANCE_COUNT_AFTER_DENSITY_CHANGE = 100
+ import azlmbr.math as math
+ import azlmbr.editor as editor
+ import azlmbr.bus as bus
+ import azlmbr.legacy.general as general
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(512.0, 480.0, 38.0)
+ INSTANCE_COUNT_BEFORE_DENSITY_CHANGE = 400
+ INSTANCE_COUNT_AFTER_DENSITY_CHANGE = 100
- # Create basic vegetation entity
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
- dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Count the number of vegetation meshes along one side of the new vegetation area. #
- result = self.wait_for_condition(
- lambda: dynveg.validate_instance_count(position, 8.0, INSTANCE_COUNT_BEFORE_DENSITY_CHANGE), 2.0
- )
- self.log(f"Vegetation instances count equal to expected value before changing sector point density: {result}")
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Add the Vegetation Debugger component to the Level Inspector
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ # Create basic vegetation entity
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
+ dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
- # Change Sector Point Density to 10
- editor.EditorComponentAPIBus(
- bus.Broadcast,
- "SetComponentProperty",
- veg_system_settings_component,
- "Configuration|Area System Settings|Sector Point Snap Mode",
- 1,
- )
- editor.EditorComponentAPIBus(
- bus.Broadcast,
- "SetComponentProperty",
- veg_system_settings_component,
- "Configuration|Area System Settings|Sector Point Density",
- 10,
- )
+ # Count the number of vegetation instances in the vegetation area
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 8.0,
+ INSTANCE_COUNT_BEFORE_DENSITY_CHANGE), 2.0)
+ Report.result(Tests.initial_density_instance_count, result)
- # Count the number of vegetation meshes along one side of the new vegetation area.
- result = self.wait_for_condition(
- lambda: dynveg.validate_instance_count(position, 8.0, INSTANCE_COUNT_AFTER_DENSITY_CHANGE), 2.0
- )
- self.log(f"Vegetation instances count equal to expected value after changing sector point density: {result}")
+ # Add the Vegetation Debugger component to the Level Inspector
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+
+ # Change Sector Point Density to 10
+ editor.EditorComponentAPIBus(
+ bus.Broadcast,
+ "SetComponentProperty",
+ veg_system_settings_component,
+ "Configuration|Area System Settings|Sector Point Snap Mode",
+ 1,
+ )
+ editor.EditorComponentAPIBus(
+ bus.Broadcast,
+ "SetComponentProperty",
+ veg_system_settings_component,
+ "Configuration|Area System Settings|Sector Point Density",
+ 10,
+ )
+
+ # Count the number of vegetation instances in the vegetation area
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count(position, 8.0,
+ INSTANCE_COUNT_AFTER_DENSITY_CHANGE), 2.0)
+ Report.result(Tests.configured_density_instance_count, result)
-test = TestSystemSettingsSectorPointDensity()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SystemSettings_SectorPointDensity)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorSize.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorSize.py
index 61aaedcf7c..fb660c964a 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorSize.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SystemSettings_SectorSize.py
@@ -5,88 +5,91 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-import os
-import sys
-import azlmbr.math as math
-import azlmbr.paths
-import azlmbr.editor as editor
-import azlmbr.bus as bus
-import azlmbr.legacy.general as general
-sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
-import editor_python_test_tools.hydra_editor_utils as hydra
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ initial_sector_size_instance_count = (
+ "Found the expected number of instances with default sector size",
+ "Found an unexpected number of instances with default sector size"
+ )
+ configured_sector_size_instance_count = (
+ "Found the expected number of instances with a sector size of 10",
+ "Found an unexpected number of instances with a sector size of 10"
+ )
-class TestSystemSettingsSectorSize(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix="SystemSettings_SectorSize", args=["level"])
+def SystemSettings_SectorSize():
+ """
+ Summary:
+ Sector Size In Meters increases/reduces the size of a sector
- def run_test(self):
- """
- Summary:
- Sector Size In Meters increases/reduces the size of a sector
+ Expected Result:
+ The number of spawned vegetation meshes inside the vegetation area is identical after updating the Sector Size
- Expected Result:
- The number of spawned vegetation meshes inside the vegetation area is identical after updating the Sector Size
+ :return: None
+ """
- :return: None
- """
+ import os
- VEGETATION_INSTANCE_COUNT = 400
+ import azlmbr.math as math
+ import azlmbr.editor as editor
+ import azlmbr.bus as bus
+ import azlmbr.legacy.general as general
- # Create empty level
- self.test_success = self.create_level(
- self.args["level"],
- heightmap_resolution=1024,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=4096,
- use_terrain=False,
- )
+ import editor_python_test_tools.hydra_editor_utils as hydra
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- general.set_current_view_position(512.0, 480.0, 38.0)
+ VEGETATION_INSTANCE_COUNT = 400
- # Create basic vegetation entity
- position = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
- vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
- dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Add the Vegetation Debugger component to the Level Inspector
- veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
+ general.set_current_view_position(512.0, 480.0, 38.0)
- # Count the number of vegetation meshes along one side of the new vegetation area.
- result = self.wait_for_condition(
- lambda: dynveg.validate_instance_count(position, 8.0, VEGETATION_INSTANCE_COUNT), 2.0
- )
- self.log(f"Vegetation instances count equal to expected value before changing sector size: {result}")
+ # Create basic vegetation entity
+ position = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
+ vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
+ dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
- # Change Sector Size in Meters to 10.
- editor.EditorComponentAPIBus(
- bus.Broadcast,
- "SetComponentProperty",
- veg_system_settings_component,
- "Configuration|Area System Settings|Sector Point Snap Mode",
- 1,
- )
- editor.EditorComponentAPIBus(
- bus.Broadcast,
- "SetComponentProperty",
- veg_system_settings_component,
- "Configuration|Area System Settings|Sector Size In Meters",
- 10,
- )
+ # Add the Vegetation Debugger component to the Level Inspector
+ veg_system_settings_component = hydra.add_level_component("Vegetation System Settings")
- # Alter the Box Shape to be 10,10,1
- vegetation.get_set_test(1, "Box Shape|Box Configuration|Dimensions", math.Vector3(10.0, 10.0, 1.0))
+ # Count the number of vegetation instances in the vegetation area
+ result = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count(position, 8.0, VEGETATION_INSTANCE_COUNT), 2.0
+ )
+ Report.result(Tests.initial_sector_size_instance_count, result)
- # Count the number of vegetation meshes along one side of the new vegetation area.
- result = self.wait_for_condition(
- lambda: dynveg.validate_instance_count(position, 5.0, VEGETATION_INSTANCE_COUNT), 2.0
- )
- self.log(f"Vegetation instances count equal to expected value after changing sector size: {result}")
+ # Change Sector Size in Meters to 10.
+ editor.EditorComponentAPIBus(
+ bus.Broadcast,
+ "SetComponentProperty",
+ veg_system_settings_component,
+ "Configuration|Area System Settings|Sector Point Snap Mode",
+ 1,
+ )
+ editor.EditorComponentAPIBus(
+ bus.Broadcast,
+ "SetComponentProperty",
+ veg_system_settings_component,
+ "Configuration|Area System Settings|Sector Size In Meters",
+ 10,
+ )
+
+ # Alter the Box Shape to be 10,10,1
+ vegetation.get_set_test(1, "Box Shape|Box Configuration|Dimensions", math.Vector3(10.0, 10.0, 1.0))
+
+ # Count the number of vegetation instances in the vegetation area
+ result = helper.wait_for_condition(
+ lambda: dynveg.validate_instance_count(position, 5.0, VEGETATION_INSTANCE_COUNT), 2.0
+ )
+ Report.result(Tests.configured_sector_size_instance_count, result)
-test = TestSystemSettingsSectorSize()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(SystemSettings_SectorSize)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
index 6f40d04854..a0657f3949 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
@@ -5,89 +5,85 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
-"""
-This script tests for regressions of "vegetation instances don't despawn correctly
-when the camera moves beyond the range of all active vegetation areas".
-This creates a new level and a vegetation area with 400 instances.
-The expectation is that we will have 400 instances in that area when the camera is centered on it,
-and 0 instances when the camera is moved sufficiently far away.
-"""
-
-import sys, os
-
-import azlmbr.legacy.general as general
-import azlmbr.math as math
-
-sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests'))
-from editor_python_test_tools.editor_test_helper import EditorTestHelper
-from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+class Tests:
+ instance_validation_close = (
+ "Instance count is as expected when within range of Spawner",
+ "Instance count was unexpected when within range of Spawner"
+ )
+ instance_validation_far = (
+ "No instances found when out of range of Spawner",
+ "Instances still found when out of range of Spawner"
+ )
-class TestVegetationInstances_DespawnWhenOutOfRange(EditorTestHelper):
- def __init__(self):
- EditorTestHelper.__init__(self, log_prefix='VegetationInstances_DespawnWhenOutOfRange', args=['level'])
+def VegetationInstances_DespawnWhenOutOfRange():
+ """
+ Summary:
+ Verifies that vegetation instances properly spawn/despawn based on camera range.
- def run_test(self):
- """
- Summary:
- Verifies that vegetation instances properly spawn/despawn based on camera range.
+ Expected Behavior:
+ Vegetation instances despawn when out of camera range.
- Expected Behavior:
- Vegetation instances despawn when out of camera range.
+ Test Steps:
+ 1) Open a simple level
+ 2) Create a simple vegetation area, and set the view position near the spawner. Verify instances plant.
+ 3) Move the view position away from the spawner. Verify instances despawn.
- Test Steps:
- 1) Create a new level
- 2) Create a simple vegetation area, and set the view position near the spawner. Verify instances plant.
- 3) Move the view position away from the spawner. Verify instances despawn.
+ Note:
+ - This test file must be called from the Open 3D Engine Editor command terminal
+ - Any passed and failed tests are written to the Editor.log file.
+ Parsing the file or running a log_monitor are required to observe the test results.
- Note:
- - This test file must be called from the Open 3D Engine Editor command terminal
- - Any passed and failed tests are written to the Editor.log file.
- Parsing the file or running a log_monitor are required to observe the test results.
+ :return: None
+ """
- :return: None
- """
+ import os
- # Create a new level
- self.test_success = self.create_level(
- self.get_arg('level'),
- heightmap_resolution=128,
- heightmap_meters_per_pixel=1,
- terrain_texture_resolution=128,
- use_terrain=False)
+ import azlmbr.legacy.general as general
+ import azlmbr.math as math
- # Create vegetation layer spawner
- world_center = math.Vector3(512.0, 512.0, 32.0)
- asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
- spawner_entity = dynveg.create_vegetation_area("Spawner Instance", world_center, 16.0, 16.0, 16.0, asset_path)
+ from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
+ from editor_python_test_tools.utils import Report
+ from editor_python_test_tools.utils import TestHelper as helper
- # Create a surface to spawn on
- dynveg.create_surface_entity("Spawner Entity", world_center, 16.0, 16.0, 1.0)
+ # Open an existing simple level
+ helper.init_idle()
+ helper.open_level("Physics", "Base")
- # Get the root position of our veg area and use it to position our camera.
- # This is useful both to ensure that vegetation is spawned where we're querying and to
- # visually verify the number of instances in each box
- position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", spawner_entity.id)
- general.set_current_view_position(position.x, position.y, position.z + 30.0)
- general.set_current_view_rotation(-90.0, 0.0, 0.0)
+ # Create vegetation layer spawner
+ world_center = math.Vector3(512.0, 512.0, 32.0)
+ asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
+ spawner_entity = dynveg.create_vegetation_area("Spawner Instance", world_center, 16.0, 16.0, 16.0, asset_path)
- # When centered over the veg area, we expect to find 400 instances.
- # (16x16 area, 20 points per 16 meters)
- num_expected = 400
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 2.0)
- self.test_success = self.test_success and result
+ # Create a surface to spawn on
+ dynveg.create_surface_entity("Spawner Entity", world_center, 16.0, 16.0, 1.0)
- # Move sufficiently far away from the veg area that it should all despawn.
- general.set_current_view_position(position.x - 1000.0, position.y - 1000.0, position.z + 30.0)
+ # Get the root position of our veg area and use it to position our camera.
+ # This is useful both to ensure that vegetation is spawned where we're querying and to
+ # visually verify the number of instances in each box
+ position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", spawner_entity.id)
+ general.set_current_view_position(position.x, position.y, position.z + 30.0)
+ general.set_current_view_rotation(-90.0, 0.0, 0.0)
- # We now expect to find 0 instances. If the bug exists, we will find 400 still.
- num_expected = 0
- result = self.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
- num_expected), 2.0)
- self.test_success = self.test_success and result
+ # When centered over the veg area, we expect to find 400 instances.
+ # (16x16 area, 20 points per 16 meters)
+ num_expected = 400
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 2.0)
+ Report.result(Tests.instance_validation_close, result)
+
+ # Move sufficiently far away from the veg area that it should all despawn.
+ general.set_current_view_position(position.x - 1000.0, position.y - 1000.0, position.z + 30.0)
+
+ # We now expect to find 0 instances. If the bug exists, we will find 400 still.
+ num_expected = 0
+ result = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(spawner_entity.id,
+ num_expected), 2.0)
+ Report.result(Tests.instance_validation_far, result)
-test = TestVegetationInstances_DespawnWhenOutOfRange()
-test.run()
+if __name__ == "__main__":
+
+ from editor_python_test_tools.utils import Report
+ Report.start_test(VegetationInstances_DespawnWhenOutOfRange)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py
new file mode 100644
index 0000000000..4c02c887ef
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py
@@ -0,0 +1,27 @@
+"""
+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
+"""
+
+import os
+import pytest
+import sys
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../../automatedtesting_shared')
+from base import TestAutomationBase
+
+
+@pytest.mark.SUITE_main
+@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+@pytest.mark.parametrize("project", ["AutomatedTesting"])
+class TestAutomation(TestAutomationBase):
+
+ def test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_EmptyInstanceSpawner_EmptySpawnerWorks(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import EmptyInstanceSpawner_EmptySpawnerWorks as test_module
+ self._run_test(request, workspace, editor, test_module)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py
new file mode 100644
index 0000000000..ded2dda4e9
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py
@@ -0,0 +1,172 @@
+"""
+Copyright (c) Contributors to the Open 3D Engine Project.
+For complete copyright and license terms please see the LICENSE at the root of this distribution.
+
+SPDX-License-Identifier: Apache-2.0 OR MIT
+"""
+
+import os
+import pytest
+
+import ly_test_tools.environment.file_system as file_system
+from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
+
+
+@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
+@pytest.mark.SUITE_main
+@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+@pytest.mark.parametrize("project", ["AutomatedTesting"])
+class TestAutomation(EditorTestSuite):
+
+ class test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(EditorParallelTest):
+ from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module
+
+ class test_EmptyInstanceSpawner_EmptySpawnerWorks(EditorParallelTest):
+ from .EditorScripts import EmptyInstanceSpawner_EmptySpawnerWorks as test_module
+
+ class test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
+ from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module
+
+ class test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
+ from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module
+
+ class test_AltitudeFilter_FilterStageToggle(EditorParallelTest):
+ from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module
+
+ class test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(EditorSingleTest):
+ # Custom teardown to remove slice asset created during test
+ def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
+ file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
+ "TestSlice_1.slice")], True, True)
+ file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
+ "TestSlice_2.slice")], True, True)
+ from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
+
+ class test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(EditorParallelTest):
+ from .EditorScripts import AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea as test_module
+
+ class test_AssetWeightSelector_InstancesExpressBasedOnWeight(EditorParallelTest):
+ from .EditorScripts import AssetWeightSelector_InstancesExpressBasedOnWeight as test_module
+
+ @pytest.mark.skip(reason="https://github.com/o3de/o3de/issues/4155")
+ class test_DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius(EditorParallelTest):
+ from .EditorScripts import DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius as test_module
+
+ @pytest.mark.skip(reason="https://github.com/o3de/o3de/issues/4155")
+ class test_DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius(EditorParallelTest):
+ from .EditorScripts import DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius as test_module
+
+ class test_SurfaceDataRefreshes_RemainsStable(EditorParallelTest):
+ from .EditorScripts import SurfaceDataRefreshes_RemainsStable as test_module
+
+ class test_VegetationInstances_DespawnWhenOutOfRange(EditorParallelTest):
+ from .EditorScripts import VegetationInstances_DespawnWhenOutOfRange as test_module
+
+ class test_InstanceSpawnerPriority_LayerAndSubPriority_HigherValuesPlantOverLower(EditorParallelTest):
+ from .EditorScripts import InstanceSpawnerPriority_LayerAndSubPriority as test_module
+
+ class test_LayerBlocker_InstancesBlockedInConfiguredArea(EditorParallelTest):
+ from .EditorScripts import LayerBlocker_InstancesBlockedInConfiguredArea as test_module
+
+ class test_LayerSpawner_InheritBehaviorFlag(EditorParallelTest):
+ from .EditorScripts import LayerSpawner_InheritBehaviorFlag as test_module
+
+ class test_LayerSpawner_InstancesPlantInAllSupportedShapes(EditorParallelTest):
+ from .EditorScripts import LayerSpawner_InstancesPlantInAllSupportedShapes as test_module
+
+ class test_LayerSpawner_FilterStageToggle(EditorParallelTest):
+ from .EditorScripts import LayerSpawner_FilterStageToggle as test_module
+
+ @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2038")
+ class test_LayerSpawner_InstancesRefreshUsingCorrectViewportCamera(EditorParallelTest):
+ from .EditorScripts import LayerSpawner_InstancesRefreshUsingCorrectViewportCamera as test_module
+
+ class test_MeshBlocker_InstancesBlockedByMesh(EditorParallelTest):
+ from .EditorScripts import MeshBlocker_InstancesBlockedByMesh as test_module
+
+ class test_MeshBlocker_InstancesBlockedByMeshHeightTuning(EditorParallelTest):
+ from .EditorScripts import MeshBlocker_InstancesBlockedByMeshHeightTuning as test_module
+
+ class test_MeshSurfaceTagEmitter_DependentOnMeshComponent(EditorParallelTest):
+ from .EditorScripts import MeshSurfaceTagEmitter_DependentOnMeshComponent as test_module
+
+ class test_MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(EditorParallelTest):
+ from .EditorScripts import MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully as test_module
+
+ class test_PhysXColliderSurfaceTagEmitter_E2E_Editor(EditorParallelTest):
+ from .EditorScripts import PhysXColliderSurfaceTagEmitter_E2E_Editor as test_module
+
+ class test_PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets(EditorParallelTest):
+ from .EditorScripts import PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets as test_module
+
+ class test_PositionModifier_AutoSnapToSurfaceWorks(EditorParallelTest):
+ from .EditorScripts import PositionModifier_AutoSnapToSurfaceWorks as test_module
+
+ class test_RotationModifier_InstancesRotateWithinRange(EditorParallelTest):
+ from .EditorScripts import RotationModifier_InstancesRotateWithinRange as test_module
+
+ class test_RotationModifierOverrides_InstancesRotateWithinRange(EditorParallelTest):
+ from .EditorScripts import RotationModifierOverrides_InstancesRotateWithinRange as test_module
+
+ class test_ScaleModifier_InstancesProperlyScale(EditorParallelTest):
+ from .EditorScripts import ScaleModifier_InstancesProperlyScale as test_module
+
+ class test_ScaleModifierOverrides_InstancesProperlyScale(EditorParallelTest):
+ from .EditorScripts import ScaleModifierOverrides_InstancesProperlyScale as test_module
+
+ class test_ShapeIntersectionFilter_InstancesPlantInAssignedShape(EditorParallelTest):
+ from .EditorScripts import ShapeIntersectionFilter_InstancesPlantInAssignedShape as test_module
+
+ class test_ShapeIntersectionFilter_FilterStageToggle(EditorParallelTest):
+ from .EditorScripts import ShapeIntersectionFilter_FilterStageToggle as test_module
+
+ class test_SlopeAlignmentModifier_InstanceSurfaceAlignment(EditorParallelTest):
+ from .EditorScripts import SlopeAlignmentModifier_InstanceSurfaceAlignment as test_module
+
+ class test_SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment(EditorParallelTest):
+ from .EditorScripts import SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment as test_module
+
+ class test_SurfaceMaskFilter_BasicSurfaceTagCreation(EditorParallelTest):
+ from .EditorScripts import SurfaceMaskFilter_BasicSurfaceTagCreation as test_module
+
+ class test_SurfaceMaskFilter_ExclusiveSurfaceTags_Function(EditorParallelTest):
+ from .EditorScripts import SurfaceMaskFilter_ExclusionList as test_module
+
+ class test_SurfaceMaskFilter_InclusiveSurfaceTags_Function(EditorParallelTest):
+ from .EditorScripts import SurfaceMaskFilter_InclusionList as test_module
+
+ class test_SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected(EditorParallelTest):
+ from .EditorScripts import SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected as test_module
+
+ class test_SystemSettings_SectorPointDensity(EditorParallelTest):
+ from .EditorScripts import SystemSettings_SectorPointDensity as test_module
+
+ class test_SystemSettings_SectorSize(EditorParallelTest):
+ from .EditorScripts import SystemSettings_SectorSize as test_module
+
+ class test_SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes(EditorParallelTest):
+ from .EditorScripts import SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope as test_module
+
+ class test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(EditorSingleTest):
+ from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module
+
+ # Custom teardown to remove test level created during test
+ def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
+ file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
+ True, True)
+
+ class test_DynamicSliceInstanceSpawner_External_E2E_Editor(EditorSingleTest):
+ from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module
+
+ # Custom teardown to remove test level created during test
+ def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
+ file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
+ True, True)
+
+ class test_LayerBlender_E2E_Editor(EditorSingleTest):
+ from .EditorScripts import LayerBlender_E2E_Editor as test_module
+
+ # Custom teardown to remove test level created during test
+ def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
+ file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
+ True, True)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py
new file mode 100644
index 0000000000..2780c0f471
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py
@@ -0,0 +1,283 @@
+"""
+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
+"""
+
+import os
+import pytest
+import sys
+
+import ly_test_tools.environment.waiter as waiter
+import ly_test_tools.environment.file_system as file_system
+import editor_python_test_tools.hydra_test_utils as hydra
+from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../../automatedtesting_shared')
+from base import TestAutomationBase
+
+
+@pytest.fixture
+def remove_test_slice(request, workspace, project):
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_1.slice")], True,
+ True)
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_2.slice")], True,
+ True)
+
+ def teardown():
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_1.slice")], True,
+ True)
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_2.slice")], True,
+ True)
+ request.addfinalizer(teardown)
+
+
+@pytest.fixture
+def remote_console_instance(request):
+ console = RemoteConsole()
+
+ def teardown():
+ if console.connected:
+ console.stop()
+
+ request.addfinalizer(teardown)
+ return console
+
+
+@pytest.mark.SUITE_periodic
+@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+@pytest.mark.parametrize("project", ["AutomatedTesting"])
+class TestAutomation(TestAutomationBase):
+
+ def test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_AltitudeFilter_FilterStageToggle(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(self, request, workspace, editor, remove_test_slice, launcher_platform):
+ from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_AssetWeightSelector_InstancesExpressBasedOnWeight(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import AssetWeightSelector_InstancesExpressBasedOnWeight as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
+ def test_DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
+ def test_DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SurfaceDataRefreshes_RemainsStable(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SurfaceDataRefreshes_RemainsStable as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_VegetationInstances_DespawnWhenOutOfRange(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import VegetationInstances_DespawnWhenOutOfRange as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_InstanceSpawnerPriority_LayerAndSubPriority_HigherValuesPlantOverLower(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import InstanceSpawnerPriority_LayerAndSubPriority as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_LayerBlocker_InstancesBlockedInConfiguredArea(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import LayerBlocker_InstancesBlockedInConfiguredArea as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_LayerSpawner_InheritBehaviorFlag(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import LayerSpawner_InheritBehaviorFlag as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_LayerSpawner_InstancesPlantInAllSupportedShapes(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import LayerSpawner_InstancesPlantInAllSupportedShapes as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_LayerSpawner_FilterStageToggle(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import LayerSpawner_FilterStageToggle as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2038")
+ def test_LayerSpawner_InstancesRefreshUsingCorrectViewportCamera(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import LayerSpawner_InstancesRefreshUsingCorrectViewportCamera as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_MeshBlocker_InstancesBlockedByMesh(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import MeshBlocker_InstancesBlockedByMesh as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_MeshBlocker_InstancesBlockedByMeshHeightTuning(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import MeshBlocker_InstancesBlockedByMeshHeightTuning as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_MeshSurfaceTagEmitter_DependentOnMeshComponent(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import MeshSurfaceTagEmitter_DependentOnMeshComponent as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_PhysXColliderSurfaceTagEmitter_E2E_Editor(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import PhysXColliderSurfaceTagEmitter_E2E_Editor as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_PositionModifier_AutoSnapToSurfaceWorks(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import PositionModifier_AutoSnapToSurfaceWorks as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_RotationModifier_InstancesRotateWithinRange(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import RotationModifier_InstancesRotateWithinRange as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_RotationModifierOverrides_InstancesRotateWithinRange(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import RotationModifierOverrides_InstancesRotateWithinRange as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_ScaleModifier_InstancesProperlyScale(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import ScaleModifier_InstancesProperlyScale as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_ScaleModifierOverrides_InstancesProperlyScale(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import ScaleModifierOverrides_InstancesProperlyScale as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_ShapeIntersectionFilter_InstancesPlantInAssignedShape(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import ShapeIntersectionFilter_InstancesPlantInAssignedShape as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_ShapeIntersectionFilter_FilterStageToggle(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import ShapeIntersectionFilter_FilterStageToggle as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SlopeAlignmentModifier_InstanceSurfaceAlignment(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SlopeAlignmentModifier_InstanceSurfaceAlignment as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SurfaceMaskFilter_BasicSurfaceTagCreation(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SurfaceMaskFilter_BasicSurfaceTagCreation as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SurfaceMaskFilter_ExclusiveSurfaceTags_Function(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SurfaceMaskFilter_ExclusionList as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SurfaceMaskFilter_InclusiveSurfaceTags_Function(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SurfaceMaskFilter_InclusionList as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SystemSettings_SectorPointDensity(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SystemSettings_SectorPointDensity as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SystemSettings_SectorSize(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SystemSettings_SectorSize as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ def test_SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes(self, request, workspace, editor, launcher_platform):
+ from .EditorScripts import SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+
+@pytest.mark.SUITE_periodic
+@pytest.mark.parametrize("project", ["AutomatedTesting"])
+@pytest.mark.parametrize("level", ["tmp_level"])
+class TestAutomationE2E(TestAutomationBase):
+
+ # The following tests must run in order, please do not move tests out of order
+
+ @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+ def test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(self, request, workspace, project, level, editor, launcher_platform):
+ # Ensure our test level does not already exist
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
+
+ from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.parametrize("launcher_platform", ['windows'])
+ def test_DynamicSliceInstanceSpawner_Embedded_E2E_Launcher(self, workspace, launcher, level,
+ remote_console_instance, project, launcher_platform):
+
+ expected_lines = [
+ "Instances found in area = 400"
+ ]
+
+ hydra.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, launch_ap=False)
+
+ # Cleanup our temp level
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
+
+ @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+ def test_DynamicSliceInstanceSpawner_External_E2E_Editor(self, request, workspace, project, level, editor, launcher_platform):
+ # Ensure our test level does not already exist
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
+
+ from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.parametrize("launcher_platform", ['windows'])
+ def test_DynamicSliceInstanceSpawner_External_E2E_Launcher(self, workspace, launcher, level,
+ remote_console_instance, project, launcher_platform):
+
+ expected_lines = [
+ "Instances found in area = 400"
+ ]
+
+ hydra.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, launch_ap=False)
+
+ # Cleanup our temp level
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
+
+ @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
+ def test_LayerBlender_E2E_Editor(self, request, workspace, project, level, editor, launcher_platform):
+ # Ensure our test level does not already exist
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
+
+ from .EditorScripts import LayerBlender_E2E_Editor as test_module
+ self._run_test(request, workspace, editor, test_module)
+
+ @pytest.mark.parametrize("launcher_platform", ['windows'])
+ @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4170")
+ def test_LayerBlender_E2E_Launcher(self, workspace, launcher, level,
+ remote_console_instance, project, launcher_platform):
+
+ launcher.args.extend(["-rhi=Null"])
+ launcher.start(launch_ap=False)
+ assert launcher.is_alive(), "Launcher failed to start"
+
+ # Wait for test script to quit the launcher. If wait_for returns exc, test was not successful
+ waiter.wait_for(lambda: not launcher.is_alive(), timeout=300)
+
+ # Verify launcher quit successfully and did not crash
+ ret_code = launcher.get_returncode()
+ assert ret_code == 0, "Test failed. See Game.log for details"
+
+ # Cleanup our temp level
+ file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AltitudeFilter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AltitudeFilter.py
deleted file mode 100755
index ec8c225d3b..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AltitudeFilter.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestAltitudeFilter(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id('C4814463', 'C4847477')
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "'Planting Surface Elevated' created",
- "instance count validation: True (found=3200, expected=3200)",
- "instance count validation: True (found=1600, expected=1600)",
- "instance count validation: True (found=400, expected=400)",
- "AltitudeFilterComponentAndOverrides: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4847476")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "'Planting Surface Elevated' created",
- "instance count validation: True (found=800, expected=800)",
- "'Shape Sampler' created",
- "instance count validation: True (found=400, expected=400)",
- "AltitudeFilterShapeSample: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4847478")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_AltitudeFilter_FilterStageToggle(self, request, editor, level, workspace, launcher_platform):
- cfg_args = [level]
-
- expected_lines = [
- "AltitudeFilter_FilterStageToggle: test started",
- "AltitudeFilter_FilterStageToggle: Vegetation instances count equal to expected value for PREPROCESS filter stage: True",
- "AltitudeFilter_FilterStageToggle: Vegetation instances count equal to expected value for POSTPROCESS filter stage: True",
- "AltitudeFilter_FilterStageToggle: result=SUCCESS",
- ]
-
- unexpected_lines = [
- "AltitudeFilter_FilterStageToggle: Vegetation instances count equal to expected value for PREPROCESS filter stage: False",
- "AltitudeFilter_FilterStageToggle: Vegetation instances count equal to expected value for POSTPROCESS filter stage: False",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AltitudeFilter_FilterStageToggle.py",
- expected_lines=expected_lines,
- unexpected_lines=unexpected_lines,
- cfg_args=cfg_args
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AreaComponentSlices.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AreaComponentSlices.py
deleted file mode 100755
index 7c72105957..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AreaComponentSlices.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestAreaComponents(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Cleanup the test slices
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_1.slice")], True, True)
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_2.slice")], True, True)
-
- def teardown():
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Cleanup the test slices
- file_system.delete(
- [os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_1.slice")], True, True
- )
- file_system.delete(
- [os.path.join(workspace.paths.engine_root(), project, "slices", "TestSlice_2.slice")], True, True
- )
-
- request.addfinalizer(teardown)
-
- @pytest.mark.test_case_id("C2627900", "C2627905", "C2627904")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_AreaComponents_SliceCreationVisibilityToggleWorks(self, request, editor, level, workspace,
- launcher_platform):
- cfg_args = [level]
-
- expected_lines = [
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: test started",
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: Slice has been created successfully (entity with spawner component): True",
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: Vegetation plants initially when slice is shown: True",
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: Vegetation is cleared when slice is hidden: True",
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: Slice has been created successfully (entity with blender component): True",
- "AreaComponentSlices_SliceCreationAndVisibilityToggle: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AreaComponentSlices_SliceCreationAndVisibilityToggle.py",
- expected_lines=expected_lines,
- cfg_args=cfg_args
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetListCombiner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetListCombiner.py
deleted file mode 100755
index 5deaa9199e..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetListCombiner.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestAssetListCombiner(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4762374", "C4762373")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Asset List 1' created",
- "'Asset List 2' created",
- "'Asset List 3' created",
- "'Surface Entity' created",
- "'Spawner Entity' created",
- "Spawner Entity Configuration|Descriptor Providers: SUCCESS",
- "Spawner Entity Configuration|Gradient|Gradient Entity Id: SUCCESS",
- "instance count validation: True (found=200, expected=200.0)",
- "Spawner Entity Configuration|Descriptor Providers|[1]: SUCCESS",
- "instance count validation: True (found=400, expected=400)",
- "instance count validation: True (found=0, expected=0)",
- "AssetListCombiner_CombinedDescriptors: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea.py",
- expected_lines=expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetWeightSelector.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetWeightSelector.py
deleted file mode 100755
index f3cb2f4647..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_AssetWeightSelector.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-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
-"""
-
-"""
-C6269654: Vegetation areas using weight selectors properly distribute instances according to Sort By Weight setting
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestAssetWeightSelector(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C6269654", "C4762368")
- @pytest.mark.SUITE_sandbox
- @pytest.mark.dynveg_filter
- def test_AssetWeightSelector_InstancesExpressBasedOnWeight(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "Instance Spawner Configuration|Embedded Assets|[1]|Instance|Slice Asset: SUCCESS",
- "'Planting Surface' created",
- "Configuration|Embedded Assets|[0]|Weight set to 50.0",
- "Instance Spawner Configuration|Allow Empty Assets: SUCCESS",
- "AssetWeightSelector_SortByWeight: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "AssetWeightSelector_InstancesExpressBasedOnWeight.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DistanceBetweenFilter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DistanceBetweenFilter.py
deleted file mode 100755
index d525e4a599..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DistanceBetweenFilter.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestDistanceBetweenFilter(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4851066")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
- def test_DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "Configuration|Radius Min set to 1.0",
- "Configuration|Radius Min set to 2.0",
- "Configuration|Radius Min set to 16.0",
- "DistanceBetweenFilterComponent: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4814458")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
- def test_DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min set to 1.0",
- "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min set to 2.0",
- "Configuration|Embedded Assets|[0]|Distance Between Filter (Radius)|Radius Min set to 16.0",
- "DistanceBetweenFilterComponentOverrides: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynVeg_Regressions.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynVeg_Regressions.py
deleted file mode 100755
index b15e1d552d..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynVeg_Regressions.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class Test_DynVeg_Regressions(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- # delete temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Setup - add the teardown finalizer
- request.addfinalizer(teardown)
-
- # Make sure the temp level doesn't already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C29470845")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_regression
- def test_SurfaceDataRefreshes_RemainsStable(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "SurfaceDataRefreshes_RemainsStable: test started",
- "SurfaceDataRefreshes_RemainsStable: test finished",
- "SurfaceDataRefreshes_RemainsStable: result=SUCCESS"
- ]
-
- unexpected_lines = [
- "Sector update mode is 'RebuildSurfaceCache' but sector doesn't exist"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- 'SurfaceDataRefreshes_RemainsStable.py',
- expected_lines=expected_lines,
- unexpected_lines=unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.SUITE_periodic
- def test_VegetationInstances_DespawnWhenOutOfRange(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "VegetationInstances_DespawnWhenOutOfRange: test started",
- "VegetationInstances_DespawnWhenOutOfRange: test finished",
- "VegetationInstances_DespawnWhenOutOfRange: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- 'VegetationInstances_DespawnWhenOutOfRange.py',
- expected_lines=expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py
deleted file mode 100755
index 0f83f44094..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py
+++ /dev/null
@@ -1,140 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import ly_test_tools._internal.pytest_plugin as internal_plugin
-import editor_python_test_tools.hydra_test_utils as hydra
-from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-class TestDynamicSliceInstanceSpawner(object):
-
- @pytest.fixture
- def remote_console_instance(self, request):
- console = RemoteConsole()
-
- def teardown():
- if console.connected:
- console.stop()
-
- request.addfinalizer(teardown)
-
- return console
-
- @pytest.mark.test_case_id("C28851763")
- @pytest.mark.SUITE_main
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
- def test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(self, request, editor, level, workspace, project,
- launcher_platform):
-
- # Skip test if running against Debug build
- if "debug" in internal_plugin.build_directory:
- pytest.skip("Does not execute against debug builds.")
-
- # Ensure temp level does not already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- cfg_args = [level]
-
- expected_lines = [
- "DynamicSliceInstanceSpawner: test started",
- "DynamicSliceInstanceSpawner: test finished",
- "DynamicSliceInstanceSpawner: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(request, test_directory, editor,
- 'DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks.py',
- expected_lines=expected_lines, cfg_args=cfg_args)
-
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id('C2574330')
- @pytest.mark.BAT
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
- def test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(self, workspace, request, editor, level, project,
- launcher_platform):
- # Ensure temp level does not already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "DynamicSliceInstanceSpawnerEmbeddedEditor: Expected 400 instances - Found 400 instances",
- "DynamicSliceInstanceSpawnerEmbeddedEditor: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(request, test_directory, editor, "DynamicSliceInstanceSpawner_Embedded_E2E.py",
- expected_lines, cfg_args=[level])
-
- @pytest.mark.test_case_id('C2574330')
- @pytest.mark.BAT
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows'])
- def test_DynamicSliceInstanceSpawner_Embedded_E2E_Launcher(self, workspace, launcher, level,
- remote_console_instance, project, launcher_platform):
-
- expected_lines = [
- "Instances found in area = 400"
- ]
-
- hydra.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines)
-
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id('C4762367')
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
- def test_DynamicSliceInstanceSpawner_External_E2E_Editor(self, workspace, request, editor, level, project,
- launcher_platform):
- # Ensure temp level does not already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- expected_lines = [
- "Spawner entity created",
- "'Planting Surface' created",
- "DynamicSliceInstanceSpawnerExternalEditor: Expected 400 instances - Found 400 instances",
- "DynamicSliceInstanceSpawnerExternalEditor: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(request, test_directory, editor, "DynamicSliceInstanceSpawner_External_E2E.py",
- expected_lines, cfg_args=[level])
-
- @pytest.mark.test_case_id('C4762367')
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows'])
- def test_DynamicSliceInstanceSpawner_External_E2E_Launcher(self, workspace, launcher, level,
- remote_console_instance, project, launcher_platform):
-
- expected_lines = [
- "Instances found in area = 400"
- ]
-
- hydra.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines)
-
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_EmptyInstanceSpawner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_EmptyInstanceSpawner.py
deleted file mode 100755
index 1fc88b816f..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_EmptyInstanceSpawner.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import ly_test_tools._internal.pytest_plugin as internal_plugin
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestEmptyInstanceSpawner(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C28851762")
- @pytest.mark.SUITE_main
- @pytest.mark.dynveg_area
- def test_EmptyInstanceSpawner_EmptySpawnerWorks(self, request, editor, level, launcher_platform):
-
- # Skip test if running against Debug build
- if "debug" in internal_plugin.build_directory:
- pytest.skip("Does not execute against debug builds.")
-
- cfg_args = [level]
-
- expected_lines = [
- "EmptyInstanceSpawner: test started",
- "EmptyInstanceSpawner: test finished",
- "EmptyInstanceSpawner: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(request, test_directory, editor, 'EmptyInstanceSpawner_EmptySpawnerWorks.py',
- expected_lines=expected_lines, cfg_args=cfg_args)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_InstanceSpawnerPriority.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_InstanceSpawnerPriority.py
deleted file mode 100755
index 955c20a37d..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_InstanceSpawnerPriority.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-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
-"""
-
-"""
-C5747383: Vegetation areas with a higher Layer Priority plant over those with a lower Layer Priority
-C4762382: Vegetation areas with a higher Sub Priority plant over those with a lower Sub Priority
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestInstanceSpawnerPriority(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C5747383", "C4762382")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_InstanceSpawnerPriority_LayerAndSubPriority_HigherValuesPlantOverLower(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Instance Blocker' created",
- "'Planting Surface' created",
- "Instance Blocker Configuration|Layer Priority: SUCCESS",
- "Instance Spawner Configuration|Sub Priority: SUCCESS",
- "Instance Blocker Configuration|Sub Priority: SUCCESS",
- "InstanceSpawnerPriority: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "InstanceSpawnerPriority_LayerAndSubPriority.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py
deleted file mode 100755
index 13f2a569c1..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-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
-"""
-
-"""
-C2627906: A simple Vegetation Layer Blender area can be created.
-The specified assets plant in the specified blend area and are visible in the Viewport in
-Edit Mode, Game Mode.
-"""
-
-import os
-import pytest
-
-pytest.importorskip("ly_test_tools")
-
-import ly_remote_console.remote_console_commands as remote_console_commands
-import ly_test_tools.environment.waiter as waiter
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-remote_console_port = 4600
-listener_timeout = 120
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-class TestLayerBlender(object):
-
- @pytest.fixture
- def remote_console_instance(self, request):
- console = remote_console_commands.RemoteConsole()
-
- def teardown():
- if console.connected:
- console.stop()
-
- request.addfinalizer(teardown)
-
- return console
-
- @pytest.mark.test_case_id("C2627906")
- @pytest.mark.BAT
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
- def test_LayerBlender_E2E_Editor(self, workspace, request, editor, project, level, launcher_platform):
- # Make sure temp level doesn't already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- expected_lines = [
- "'Purple Spawner' created",
- "'Pink Spawner' created",
- "'Surface Entity' created",
- "Entity has a Vegetation Layer Spawner component",
- "Entity has a Vegetation Asset List component",
- "Entity has a Box Shape component",
- "Purple Spawner Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Pink Spawner Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Purple Spawner Configuration|Embedded Assets|[0]: SUCCESS",
- "Pink Spawner Configuration|Embedded Assets|[0]: SUCCESS",
- "'Blender' created",
- "Entity has a Vegetation Layer Blender component",
- "Entity has a Box Shape component",
- "Blender Configuration|Vegetation Areas: SUCCESS",
- "Blender Box Shape|Box Configuration|Dimensions: SUCCESS",
- "LayerBlender_E2E_Editor: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerBlender_E2E_Editor.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2627906")
- @pytest.mark.BAT
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.parametrize("launcher_platform", ['windows'])
- def test_LayerBlender_E2E_Launcher(self, workspace, project, launcher, level, remote_console_instance,
- launcher_platform):
-
- launcher.args.extend(["-rhi=Null"])
- launcher.start()
- assert launcher.is_alive(), "Launcher failed to start"
-
- # Wait for test script to quit the launcher. If wait_for returns exc, test was not successful
- waiter.wait_for(lambda: not launcher.is_alive(), timeout=300)
-
- # Verify launcher quit successfully and did not crash
- ret_code = launcher.get_returncode()
- assert ret_code == 0, "Test failed. See Game.log for details"
-
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlocker.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlocker.py
deleted file mode 100755
index d0889f17a4..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlocker.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestLayerBlocker(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C2793772")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- def test_LayerBlocker_InstancesBlockedInConfiguredArea(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Surface Entity' created",
- "instance count validation: True (found=400, expected=400)",
- "'Blocker Area' created",
- "instance count validation: True (found=384, expected=384)",
- "LayerBlocker_InstancesBlocked: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerBlocker_InstancesBlockedInConfiguredArea.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py
deleted file mode 100755
index 65d994f841..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py
+++ /dev/null
@@ -1,142 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestLayerSpawner(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- def teardown():
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- request.addfinalizer(teardown)
-
- @pytest.mark.test_case_id("C4762381")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_LayerSpawner_InheritBehaviorFlag(self, request, editor, level, workspace, launcher_platform):
-
- expected_lines = [
- "LayerSpawner_InheritBehavior: test started",
- "LayerSpawner_InheritBehavior: Vegetation is not planted when Inherit Behavior flag is checked: True",
- "LayerSpawner_InheritBehavior: Vegetation plant when Inherit Behavior flag is unchecked: True",
- "LayerSpawner_InheritBehavior: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerSpawner_InheritBehaviorFlag.py",
- expected_lines=expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2802020")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_LayerSpawner_InstancesPlantInAllSupportedShapes(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Surface Entity' created",
- "Entity has a Vegetation Reference Shape component",
- "Entity has a Box Shape component",
- "box Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Entity has a Capsule Shape component",
- "capsule Capsule Shape|Capsule Configuration|Height: SUCCESS",
- "capsule Capsule Shape|Capsule Configuration|Radius: SUCCESS",
- "Entity has a Tube Shape component",
- "Entity has a Spline component",
- "Entity has a Sphere Shape component",
- "sphere Sphere Shape|Sphere Configuration|Radius: SUCCESS",
- "Entity has a Cylinder Shape component",
- "cylinder Cylinder Shape|Cylinder Configuration|Radius: SUCCESS",
- "cylinder Cylinder Shape|Cylinder Configuration|Height: SUCCESS",
- "Entity has a Polygon Prism Shape component",
- "Entity has a Compound Shape component",
- "Compound Configuration|Child Shape Entities|[0]: SUCCESS",
- "Compound Configuration|Child Shape Entities|[1]: SUCCESS",
- "Compound Configuration|Child Shape Entities|[2]: SUCCESS",
- "Compound Configuration|Child Shape Entities|[3]: SUCCESS",
- "Compound Configuration|Child Shape Entities|[4]: SUCCESS",
- "Compound Configuration|Child Shape Entities|[5]: SUCCESS",
- "Instance Spawner Configuration|Shape Entity Id: SUCCESS",
- "TestLayerSpawner_AllShapesPlant: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerSpawner_InstancesPlantInAllSupportedShapes.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4765973")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_LayerSpawner_FilterStageToggle(self, request, editor, level, workspace, launcher_platform):
-
- expected_lines = [
- "LayerSpawner_FilterStageToggle: test started",
- "LayerSpawner_FilterStageToggle: Preprocess filter stage vegetation instance count is as expected: True",
- "LayerSpawner_FilterStageToggle: Postprocess filter vegetation instance stage count is as expected: True",
- "LayerSpawner_FilterStageToggle: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerSpawner_FilterStageToggle.py",
- expected_lines=expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C30000751")
- @pytest.mark.SUITE_sandbox
- @pytest.mark.dynveg_misc
- @pytest.mark.skip # https://github.com/o3de/o3de/issues/2038
- def test_LayerSpawner_InstancesRefreshUsingCorrectViewportCamera(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "LayerSpawner_InstanceCameraRefresh: test started",
- "LayerSpawner_InstanceCameraRefresh: test finished",
- "LayerSpawner_InstanceCameraRefresh: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py",
- expected_lines,
- cfg_args=[level],
- null_renderer=False
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshBlocker.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshBlocker.py
deleted file mode 100755
index 197ab6e585..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshBlocker.py
+++ /dev/null
@@ -1,89 +0,0 @@
-"""
-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
-"""
-
-
-import logging
-import os
-import pytest
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-logger = logging.getLogger(__name__)
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestMeshBlocker(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, editor, project, level):
- pass
-
- def teardown():
- # delete temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Setup - add the teardown finalizer
- request.addfinalizer(teardown)
- # Make sure the temp level doesn't already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- """
- C3980834: A simple Vegetation Blocker Mesh can be created
- """
- @pytest.mark.test_case_id("C3980834")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.xfail # LYN-3273
- def test_MeshBlocker_InstancesBlockedByMesh(self, request, editor, level, launcher_platform):
- expected_lines = [
- "'Instance Spawner' created",
- "'Surface Entity' created",
- "'Blocker Entity' created",
- "instance count validation: True (found=160, expected=160)",
- "MeshBlocker_InstancesBlockedByMesh: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "MeshBlocker_InstancesBlockedByMesh.py",
- expected_lines,
- cfg_args=[level]
- )
-
- """
- C4766030: Mesh Height Percent Min/Max values can be set to fine tune the blocked area
- """
- @pytest.mark.test_case_id("C4766030")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_area
- @pytest.mark.xfail # LYN-3273
- def test_MeshBlocker_InstancesBlockedByMeshHeightTuning(self, request, editor, level, launcher_platform):
- expected_lines = [
- "'Instance Spawner' created",
- "'Surface Entity' created",
- "'Blocker Entity' created",
- "Blocker Entity Configuration|Mesh Height Percent Max: SUCCESS",
- "instance count validation: True (found=127, expected=127)",
- "MeshBlocker_InstancesBlockedByMeshHeightTuning: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "MeshBlocker_InstancesBlockedByMeshHeightTuning.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshSurfaceTagEmitter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshSurfaceTagEmitter.py
deleted file mode 100755
index fd3a2f6514..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_MeshSurfaceTagEmitter.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestMeshSurfaceTagEmitter(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C2908172")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_surfacetagemitter
- def test_MeshSurfaceTagEmitter_DependentOnMeshComponent(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "Entity has a Mesh Surface Tag Emitter component",
- "New Entity Created",
- "Mesh Surface Tag Emitter is Disabled",
- "Entity has a Mesh component",
- "Mesh Surface Tag Emitter is Enabled",
- "MeshSurfaceTagEmitter_DependentOnMeshComponent: result=SUCCESS"
- ]
-
- unexpected_lines = [
- "Mesh Surface Tag Emitter is Enabled. But It should be disabled before adding Mesh",
- "Mesh Surface Tag Emitter is Disabled. But It should be enabled after adding Mesh",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "MeshSurfaceTagEmitter_DependentOnMeshComponent.py",
- expected_lines,
- unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2908174")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_surfacetagemitter
- def test_MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "Added SurfaceTag: container count is 1",
- "Removed SurfaceTag: container count is 0",
- "MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSucessfully: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PhysXColliderSurfaceTagEmitter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PhysXColliderSurfaceTagEmitter.py
deleted file mode 100755
index 03b7a968f8..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PhysXColliderSurfaceTagEmitter.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize('project', ['AutomatedTesting'])
-@pytest.mark.parametrize('level', ['tmp_level'])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestPhysXColliderSurfaceTagEmitter(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C29053640")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_surfacetagemitter
- def test_PhysXColliderSurfaceTagEmitter_E2E_Editor(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "PhysXColliderSurfaceTagEmitter_E2E_Editor: test started",
- "PhysXColliderSurfaceTagEmitter_E2E_Editor: test finished",
- "PhysXColliderSurfaceTagEmitter_E2E_Editor: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "PhysXColliderSurfaceTagEmitter_E2E_Editor.py",
- expected_lines=expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PositionModifier.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PositionModifier.py
deleted file mode 100755
index ebbd58557e..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_PositionModifier.py
+++ /dev/null
@@ -1,79 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestPositionModifier(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4874099", "C4814461")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- def test_PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "Vegetation Position Modifier component was added to entity",
- "'Planting Surface' created",
- "Entity has a Constant Gradient component",
- "PositionModifierComponentAndOverrides_InstanceOffset: result=SUCCESS",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4874100")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_PositionModifier_AutoSnapToSurfaceWorks(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "Instance Spawner Configuration|Position X|Range Min: SUCCESS",
- "Instance Spawner Configuration|Position X|Range Max: SUCCESS",
- "PositionModifier_AutoSnapToSurface: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "PositionModifier_AutoSnapToSurfaceWorks.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_RotationModifier.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_RotationModifier.py
deleted file mode 100755
index 1dea332f7d..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_RotationModifier.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""
-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
-"""
-
-import logging
-import os
-import pytest
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-logger = logging.getLogger(__name__)
-
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestRotationModifier(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- # delete temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Setup - add the teardown finalizer
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4896922")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- def test_RotationModifier_InstancesRotateWithinRange(self, request, editor, level, launcher_platform) -> None:
- """
- Launches editor and run test script to test that rotation modifier works for all axis.
- Manual test case: C4896922
- """
-
- expected_lines = [
- "'Spawner Entity' created",
- "'Surface Entity' created",
- "'Gradient Entity' created",
- "Entity has a Vegetation Asset List component",
- "Entity has a Vegetation Layer Spawner component",
- "Entity has a Vegetation Rotation Modifier component",
- "Entity has a Box Shape component",
- "Entity has a Constant Gradient component",
- "RotationModifier_InstancesRotateWithinRange: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "RotationModifier_InstancesRotateWithinRange.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4814460")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- def test_RotationModifierOverrides_InstancesRotateWithinRange(self, request, editor, level, launcher_platform) -> None:
-
- expected_lines = [
- "'Spawner Entity' created",
- "'Surface Entity' created",
- "'Gradient Entity' created",
- "Entity has a Vegetation Layer Spawner component",
- "Entity has a Vegetation Asset List component",
- "Spawner Entity Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Entity has a Vegetation Rotation Modifier component",
- "Spawner Entity Configuration|Embedded Assets|[0]|Rotation Modifier|Override Enabled: SUCCESS",
- "Spawner Entity Configuration|Allow Per-Item Overrides: SUCCESS",
- "Entity has a Constant Gradient component",
- "Entity has a Box Shape component",
- "Spawner Entity Configuration|Rotation Z|Gradient|Gradient Entity Id: SUCCESS",
- "RotationModifierOverrides_InstancesRotateWithinRange: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "RotationModifierOverrides_InstancesRotateWithinRange.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ScaleModifier.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ScaleModifier.py
deleted file mode 100755
index 62f6c0bbad..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ScaleModifier.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""
-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
-"""
-
-"""
-C4814462: Vegetation instances have random scale between 0.1 and 1.0 applied.
-"""
-
-import os
-import pytest
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestScaleOverrideWorksSuccessfully(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4814462")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- def test_ScaleModifierOverrides_InstancesProperlyScale(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Spawner Entity' created",
- "'Surface Entity' created",
- "Entity has a Vegetation Scale Modifier component",
- "'Gradient Entity' created",
- "Scale Min and Scale Max are set to 0.1 and 1.0 in Vegetation Asset List",
- "Entity has a Random Noise Gradient component",
- "Entity has a Gradient Transform Modifier component",
- "Entity has a Box Shape component",
- "Spawner Entity Configuration|Gradient|Gradient Entity Id: SUCCESS",
- "ScaleModifierOverrides_InstancesProperlyScale: result=SUCCESS"
- ]
-
- unexpected_lines = ["Scale Min and Scale Max are not set to 0.1 and 1.0 in Vegetation Asset List"]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "ScaleModifierOverrides_InstancesProperlyScale.py",
- expected_lines,
- unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4896937")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- def test_ScaleModifier_InstancesProperlyScale(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Spawner Entity' created",
- "Entity has a Vegetation Scale Modifier component",
- "'Surface Entity' created",
- "'Gradient Entity' created",
- "Spawner Entity Configuration|Gradient|Gradient Entity Id: SUCCESS",
- "Spawner Entity Configuration|Range Min: SUCCESS",
- "Spawner Entity Configuration|Range Max: SUCCESS",
- "ScaleModifier_InstancesProperlyScale: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "ScaleModifier_InstancesProperlyScale.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ShapeIntersectionFilter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ShapeIntersectionFilter.py
deleted file mode 100755
index d2ed0318ca..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_ShapeIntersectionFilter.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip('ly_test_tools')
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), 'EditorScripts')
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestShapeIntersectionFilter(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4874094")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_ShapeIntersectionFilter_InstancesPlantInAssignedShape(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "instance count validation: True (found=49, expected=49)",
- "instance count validation: True (found=121, expected=121)",
- "instance count validation: True (found=400, expected=400)",
- "ShapeIntersectionFilter_InstancePlanting: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "ShapeIntersectionFilter_InstancesPlantInAssignedShape.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeAlignmentModifier.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeAlignmentModifier.py
deleted file mode 100755
index 3a01521d8e..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeAlignmentModifier.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-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
-"""
-
-"""
-C4896941 - Surface Alignment functions as expected
-C4814459 - Surface Alignment overrides function as expected
-"""
-
-import os
-import pytest
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestSlopeAlignmentModifier(object):
-
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C4896941")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_SlopeAlignmentModifier_InstanceSurfaceAlignment(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "Vegetation Slope Alignment Modifier component was added to entity",
- "Instance Spawner Configuration|Alignment Coefficient Min: SUCCESS",
- "Constant Gradient component was added to entity",
- "Instance Spawner Configuration|Gradient|Gradient Entity Id: SUCCESS",
- "SlopeAlignmentModifier: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SlopeAlignmentModifier_InstanceSurfaceAlignment.py",
- expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C4814459")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_modifier
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "Instance Spawner Configuration|Allow Per-Item Overrides: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]|Surface Slope Alignment|Override Enabled: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]|Surface Slope Alignment|Max: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]|Surface Slope Alignment|Min: SUCCESS",
- "SlopeAlignmentModifierOverrides: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeFilter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeFilter.py
deleted file mode 100755
index 3065b2df61..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SlopeFilter.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestSlopeFilter(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- def teardown():
- # Cleanup our temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
- request.addfinalizer(teardown)
-
- @pytest.mark.test_case_id("C4874097")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_SlopeFilter_FilterStageToggle(self, request, editor, level, workspace, launcher_platform):
- cfg_args = [level]
-
- expected_lines = [
- "SlopeFilter_FilterStageToggle: test started",
- "SlopeFilter_FilterStageToggle: Vegetation plant only in the areas where the Box overlaps with the vegetation area's boundaries: True",
- "SlopeFilter_FilterStageToggle: Vegetation plant only in the areas where the Cylinder overlaps with the vegetation area's boundaries: True",
- "SlopeFilter_FilterStageToggle: Vegetation instances count equal to expected value for PREPROCESS filter stage: True",
- "SlopeFilter_FilterStageToggle: Vegetation instances count equal to expected value for POSTPROCESS filter stage: True",
- "SlopeFilter_FilterStageToggle: result=SUCCESS",
- ]
-
- unexpected_lines = [
- "SlopeFilter_FilterStageToggle: Vegetation plant only in the areas where the Box overlaps with the vegetation area's boundaries: False",
- "SlopeFilter_FilterStageToggle: Vegetation plant only in the areas where the Cylinder overlaps with the vegetation area's boundaries: False",
- "SlopeFilter_FilterStageToggle: Vegetation instances count equal to expected value for PREPROCESS filter stage: False",
- "SlopeFilter_FilterStageToggle: Vegetation instances count equal to expected value for POSTPROCESS filter stage: False",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SlopeFilter_FilterStageToggle.py",
- expected_lines=expected_lines,
- unexpected_lines=unexpected_lines,
- cfg_args=cfg_args
- )
-
- @pytest.mark.test_case_id("C4814464", "C4874096")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2303")
- def test_SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Planting Surface' created",
- "'Sloped Planting Surface' created",
- "instance count validation: True (found=1720, expected=1720)",
- "Instance Spawner Configuration|Slope Min: SUCCESS",
- "Instance Spawner Configuration|Slope Max: SUCCESS",
- "instance count validation: True (found=48, expected=48)",
- "Instance Spawner Configuration|Embedded Assets|[0]|Slope Filter|Min: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]|Slope Filter|Max: SUCCESS",
- "instance count validation: True (found=12, expected=12)",
- "SlopeFilter_InstancesPlantOnValidSlope: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SurfaceMaskFilter.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SurfaceMaskFilter.py
deleted file mode 100755
index 7101a8e286..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SurfaceMaskFilter.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-
-import editor_python_test_tools.hydra_test_utils as hydra
-import ly_test_tools.environment.file_system as file_system
-
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestSurfaceMaskFilter(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- # delete temp level
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Setup - add the teardown finalizer
- request.addfinalizer(teardown)
-
- # Make sure the temp level doesn't already exist
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- # Simple validation test to ensure that SurfaceTag can be created, set to a value, and compared to another SurfaceTag.
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_SurfaceMaskFilter_BasicSurfaceTagCreation(self, request, level, editor, launcher_platform):
-
- expected_lines = [
- "SurfaceTag test started",
- "SurfaceTag equal tag comparison is True expected True",
- "SurfaceTag not equal tag comparison is False expected False",
- "SurfaceTag test finished",
- "TestSurfaceMaskFilter_BasicSurfaceTagCreation: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- 'SurfaceMaskFilter_BasicSurfaceTagCreation.py',
- expected_lines=expected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2561342")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_SurfaceMaskFilter_ExclusiveSurfaceTags_Function(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "Instance Spawner Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]: SUCCESS",
- "'Surface Entity 1' created",
- "Surface Entity 1 Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Surface Entity 1 Configuration|Generated Tags: SUCCESS",
- "'Surface Entity 2' created",
- "Surface Entity 2 Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Surface Entity 2 Configuration|Generated Tags: SUCCESS",
- "SurfaceMaskFilter_ExclusionList: Expected 39 instances - Found 39 instances",
- "Instance Spawner Configuration|Exclusion|Weight Max: SUCCESS",
- "SurfaceMaskFilter_ExclusionList: Expected 169 instances - Found 169 instances",
- "SurfaceMaskFilter_ExclusionList: result=SUCCESS"
- ]
-
- unexpected_lines = ["Failed to add an Exclusive surface mask filter of terrainHole"]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SurfaceMaskFilter_ExclusionList.py",
- expected_lines,
- unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2561341")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_SurfaceMaskFilter_InclusiveSurfaceTags_Function(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "Instance Spawner Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Instance Spawner Configuration|Embedded Assets|[0]: SUCCESS",
- "'Surface Entity 1' created",
- "Surface Entity 1 Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Surface Entity 1 Configuration|Generated Tags: SUCCESS",
- "'Surface Entity 2' created",
- "Surface Entity 2 Box Shape|Box Configuration|Dimensions: SUCCESS",
- "Surface Entity 2 Configuration|Generated Tags: SUCCESS",
- "SurfaceMaskFilter_InclusionList: Expected 130 instances - Found 130 instances",
- "Instance Spawner Configuration|Inclusion|Weight Max: SUCCESS",
- "SurfaceMaskFilter_InclusionList: Expected 0 instances - Found 0 instances",
- "SurfaceMaskFilter_InclusionList: result=SUCCESS"
- ]
-
- unexpected_lines = ["Failed to add an Inclusive surface mask filter of terrainHole"]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SurfaceMaskFilter_InclusionList.py",
- expected_lines,
- unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C3711666")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_filter
- def test_SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected(self, request, editor, level,
- launcher_platform):
-
- expected_lines = [
- "'Instance Spawner' created",
- "'Surface Entity A' created",
- "'Surface Entity B' created",
- "'Surface Entity C' created",
- "instance count validation: True (found=725, expected=725)",
- "instance count validation: True (found=400, expected=400)",
- "instance count validation: True (found=225, expected=225)",
- "instance count validation: True (found=100, expected=100)",
- "SurfaceMaskFilter_MultipleDescriptorOverrides: result=SUCCESS"
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected.py",
- expected_lines,
- cfg_args=[level]
- )
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SystemSettings.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SystemSettings.py
deleted file mode 100755
index 0b00a1fa83..0000000000
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_SystemSettings.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""
-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
-"""
-
-import os
-import pytest
-import logging
-
-# Bail on the test if ly_test_tools doesn't exist.
-pytest.importorskip("ly_test_tools")
-import ly_test_tools.environment.file_system as file_system
-import editor_python_test_tools.hydra_test_utils as hydra
-
-logger = logging.getLogger(__name__)
-test_directory = os.path.join(os.path.dirname(__file__), "EditorScripts")
-
-
-@pytest.mark.parametrize("project", ["AutomatedTesting"])
-@pytest.mark.parametrize("level", ["tmp_level"])
-@pytest.mark.usefixtures("automatic_process_killer")
-@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
-class TestSystemSettings(object):
- @pytest.fixture(autouse=True)
- def setup_teardown(self, request, workspace, project, level):
- def teardown():
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- request.addfinalizer(teardown)
-
- file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
-
- @pytest.mark.test_case_id("C2646869")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_SystemSettings_SectorPointDensity(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "SystemSettings_SectorPointDensity: test started",
- "SystemSettings_SectorPointDensity: Vegetation instances count equal to expected value before changing sector point density: True",
- "SystemSettings_SectorPointDensity: Vegetation instances count equal to expected value after changing sector point density: True",
- "SystemSettings_SectorPointDensity: result=SUCCESS",
- ]
-
- unexpected_lines = [
- "SystemSettings_SectorPointDensity: Vegetation instances count equal to expected value before changing sector point density: False",
- "SystemSettings_SectorPointDensity: Vegetation instances count equal to expected value after changing sector point density: False",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SystemSettings_SectorPointDensity.py",
- expected_lines,
- unexpected_lines=unexpected_lines,
- cfg_args=[level]
- )
-
- @pytest.mark.test_case_id("C2646870")
- @pytest.mark.SUITE_periodic
- @pytest.mark.dynveg_misc
- def test_SystemSettings_SectorSize(self, request, editor, level, launcher_platform):
-
- expected_lines = [
- "SystemSettings_SectorSize: test started",
- "SystemSettings_SectorSize: Vegetation instances count equal to expected value before changing sector size: True",
- "SystemSettings_SectorSize: Vegetation instances count equal to expected value after changing sector size: True",
- "SystemSettings_SectorSize: result=SUCCESS",
- ]
-
- unexpected_lines = [
- "SystemSettings_SectorSize: Vegetation instances count equal to expected value before changing sector size: False",
- "SystemSettings_SectorSize: Vegetation instances count equal to expected value after changing sector size: False",
- ]
-
- hydra.launch_and_validate_results(
- request,
- test_directory,
- editor,
- "SystemSettings_SectorSize.py",
- expected_lines,
- unexpected_lines=unexpected_lines,
- cfg_args=[level]
- )
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43b0dd240e..e659270f84 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,11 +29,11 @@ include(cmake/PAL.cmake)
include(cmake/PALTools.cmake)
include(cmake/RuntimeDependencies.cmake)
include(cmake/Configurations.cmake) # Requires to be after PAL so we get platform variable definitions
-include(cmake/Install.cmake)
include(cmake/Dependencies.cmake)
include(cmake/Deployment.cmake)
include(cmake/3rdParty.cmake)
include(cmake/LYPython.cmake)
+include(cmake/Install.cmake)
include(cmake/LYWrappers.cmake)
include(cmake/Gems.cmake)
include(cmake/UnitTest.cmake)
diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui
index df7474d9d4..a345438aed 100644
--- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui
+++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui
@@ -140,9 +140,6 @@
QAbstractItemView::ScrollPerPixel
-
- false
-
true
@@ -201,6 +198,11 @@
AzToolsFramework/AssetBrowser/Search/SearchWidget.h
1
+
+ AzQtComponents::TableView
+ QTreeView
+ AzQtComponents/Components/Widgets/TableView.h
+
AzToolsFramework::AssetBrowser::AssetBrowserTreeView
QTreeView
@@ -214,7 +216,7 @@
AzToolsFramework::AssetBrowser::AssetBrowserTableView
- QTableView
+ AzQtComponents::TableView
AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h
diff --git a/Code/Editor/Core/QtEditorApplication_linux.cpp b/Code/Editor/Core/QtEditorApplication_linux.cpp
index fa39609308..175bef0238 100644
--- a/Code/Editor/Core/QtEditorApplication_linux.cpp
+++ b/Code/Editor/Core/QtEditorApplication_linux.cpp
@@ -8,11 +8,21 @@
#include "QtEditorApplication.h"
+#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
+#include
+#endif
+
namespace Editor
{
- bool EditorQtApplication::nativeEventFilter(const QByteArray& , void* , long* )
+ bool EditorQtApplication::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
- // TODO_KDAB_LINUX
+ if (GetIEditor()->IsInGameMode())
+ {
+#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
+ AzFramework::LinuxXcbEventHandlerBus::Broadcast(&AzFramework::LinuxXcbEventHandler::HandleXcbEvent, static_cast(message));
+#endif
+ return true;
+ }
return false;
}
}
diff --git a/Code/Editor/EditorToolsApplication.cpp b/Code/Editor/EditorToolsApplication.cpp
index 1e5d747e4a..26e608f657 100644
--- a/Code/Editor/EditorToolsApplication.cpp
+++ b/Code/Editor/EditorToolsApplication.cpp
@@ -34,10 +34,14 @@ namespace EditorInternal
: ToolsApplication(argc, argv)
{
EditorToolsApplicationRequests::Bus::Handler::BusConnect();
+ AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
+ AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
}
EditorToolsApplication::~EditorToolsApplication()
{
+ AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect();
+ AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
EditorToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop();
}
@@ -48,7 +52,6 @@ namespace EditorInternal
return m_StartupAborted;
}
-
void EditorToolsApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
@@ -274,5 +277,14 @@ namespace EditorInternal
Exit();
}
-}
+ AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorToolsApplication::QueryKeyboardModifiers()
+ {
+ return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
+ }
+ AZStd::chrono::milliseconds EditorToolsApplication::EditorViewportInputTimeNow()
+ {
+ const auto now = AZStd::chrono::high_resolution_clock::now();
+ return AZStd::chrono::time_point_cast(now).time_since_epoch();
+ }
+} // namespace EditorInternal
diff --git a/Code/Editor/EditorToolsApplication.h b/Code/Editor/EditorToolsApplication.h
index 422772cbc4..d4e6223445 100644
--- a/Code/Editor/EditorToolsApplication.h
+++ b/Code/Editor/EditorToolsApplication.h
@@ -7,7 +7,9 @@
*/
#pragma once
+
#include
+#include
#include "Core/EditorMetricsPlainTextNameRegistration.h"
#include "EditorToolsApplicationAPI.h"
@@ -19,6 +21,8 @@ namespace EditorInternal
class EditorToolsApplication
: public AzToolsFramework::ToolsApplication
, public EditorToolsApplicationRequests::Bus::Handler
+ , public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
+ , public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler
{
public:
EditorToolsApplication(int* argc, char*** argv);
@@ -44,6 +48,12 @@ namespace EditorInternal
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
+ // EditorModifierKeyRequestBus overrides ...
+ AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
+
+ // EditorViewportInputTimeNowRequestBus overrides ...
+ AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
+
protected:
// From EditorToolsApplicationRequests
bool OpenLevel(AZStd::string_view levelName) override;
diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp
index 1ed901e514..5b5b52d28f 100644
--- a/Code/Editor/EditorViewportWidget.cpp
+++ b/Code/Editor/EditorViewportWidget.cpp
@@ -744,11 +744,15 @@ void EditorViewportWidget::RenderAll()
{
namespace AztfVi = AzToolsFramework::ViewportInteraction;
+ AztfVi::KeyboardModifiers keyboardModifiers;
+ AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
+ keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
+
m_debugDisplay->DepthTestOff();
m_manipulatorManager->DrawManipulators(
*m_debugDisplay, GetCameraState(),
BuildMouseInteractionInternal(
- AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), QueryKeyboardModifiers(),
+ AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers,
BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos())))));
m_debugDisplay->DepthTestOn();
}
@@ -959,12 +963,13 @@ QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu()
bool EditorViewportWidget::ShowingWorldSpace()
{
- return QueryKeyboardModifiers().Shift();
-}
+ namespace AztfVi = AzToolsFramework::ViewportInteraction;
-AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorViewportWidget::QueryKeyboardModifiers()
-{
- return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
+ AztfVi::KeyboardModifiers keyboardModifiers;
+ AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
+ keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
+
+ return keyboardModifiers.Shift();
}
void EditorViewportWidget::SetViewportId(int id)
@@ -1039,7 +1044,6 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
{
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
- AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
m_viewportUi.ConnectViewportUiBus(GetViewportId());
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect();
@@ -1050,7 +1054,6 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus()
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect();
m_viewportUi.DisconnectViewportUiBus();
- AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect();
}
diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h
index 6b46524394..49930a2a13 100644
--- a/Code/Editor/EditorViewportWidget.h
+++ b/Code/Editor/EditorViewportWidget.h
@@ -92,7 +92,6 @@ class SANDBOX_API EditorViewportWidget final
, private AzFramework::InputSystemCursorConstraintRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler
- , private AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
, private AZ::RPI::SceneNotificationBus::Handler
{
@@ -212,9 +211,6 @@ private:
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector& visibleEntities) override;
- // EditorModifierKeyRequestBus overrides ...
- AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
-
// Camera::EditorCameraRequestBus overrides ...
void SetViewFromEntityPerspective(const AZ::EntityId& entityId) override;
void SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) override;
diff --git a/Code/Editor/Include/IEditorClassFactory.h b/Code/Editor/Include/IEditorClassFactory.h
index 0827dc96dc..dd47f803e2 100644
--- a/Code/Editor/Include/IEditorClassFactory.h
+++ b/Code/Editor/Include/IEditorClassFactory.h
@@ -14,8 +14,10 @@
#define CRYINCLUDE_EDITOR_INCLUDE_IEDITORCLASSFACTORY_H
#pragma once
+#include
#include
#include
+#include
#define DEFINE_UUID(l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
static const GUID uuid() { return { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; }
@@ -34,7 +36,7 @@ struct IUnknown
#endif
#define __uuidof(T) T::uuid()
-#if defined(AZ_PLATFORM_LINUX)
+#if defined(AZ_PLATFORM_LINUX) || defined(AZ_PLATFORM_MAC)
# ifndef _REFGUID_DEFINED
# define _REFGUID_DEFINED
@@ -65,7 +67,7 @@ enum
};
#endif
-#endif // defined(AZ_PLATFORM_LINUX)
+#endif // defined(AZ_PLATFORM_LINUX) || defined(AZ_PLATFORM_MAC)
#include "SandboxAPI.h"
diff --git a/Code/Editor/Platform/Mac/EditorEntitlements.plist b/Code/Editor/Platform/Mac/EditorEntitlements.plist
new file mode 100644
index 0000000000..cefa2bf93b
--- /dev/null
+++ b/Code/Editor/Platform/Mac/EditorEntitlements.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.cs.disable-library-validation
+
+
+
diff --git a/Code/Editor/Platform/Mac/editor_mac.cmake b/Code/Editor/Platform/Mac/editor_mac.cmake
index fdccfafb46..eed955f2e4 100644
--- a/Code/Editor/Platform/Mac/editor_mac.cmake
+++ b/Code/Editor/Platform/Mac/editor_mac.cmake
@@ -12,28 +12,5 @@ set_target_properties(Editor PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/gui_info.plist
RESOURCE ${CMAKE_CURRENT_LIST_DIR}/Images.xcassets
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME EditorAppIcon
+ ENTITLEMENT_FILE_PATH ${CMAKE_CURRENT_LIST_DIR}/EditorEntitlements.plist
)
-
-# We cannot use ly_add_target here because we're already including this file from inside ly_add_target
-# So we need to setup target, dependencies and install logic manually.
-add_executable(EditorDummy Platform/Mac/main_dummy.cpp)
-add_executable(AZ::EditorDummy ALIAS EditorDummy)
-
-ly_target_link_libraries(EditorDummy
- PRIVATE
- AZ::AzCore
- AZ::AzFramework)
-
-ly_add_dependencies(Editor EditorDummy)
-
-# Store the aliased target into a DIRECTORY property
-set_property(DIRECTORY APPEND PROPERTY LY_DIRECTORY_TARGETS AZ::EditorDummy)
-
-# Store the directory path in a GLOBAL property so that it can be accessed
-# in the layout install logic. Skip if the directory has already been added
-get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES)
-if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories)
- set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
-endif()
-
-ly_install_add_install_path_setreg(Editor)
\ No newline at end of file
diff --git a/Code/Editor/Platform/Mac/gui_info.plist b/Code/Editor/Platform/Mac/gui_info.plist
index cc87cbbb47..5b5f94e977 100644
--- a/Code/Editor/Platform/Mac/gui_info.plist
+++ b/Code/Editor/Platform/Mac/gui_info.plist
@@ -3,7 +3,7 @@
CFBundleExecutable
- EditorDummy
+ Editor
CFBundleIdentifier
org.O3DE.Editor
CFBundlePackageType
diff --git a/Code/Editor/Platform/Mac/main_dummy.cpp b/Code/Editor/Platform/Mac/main_dummy.cpp
deleted file mode 100644
index 348a32ab47..0000000000
--- a/Code/Editor/Platform/Mac/main_dummy.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-int main(int argc, char* argv[])
-{
- // Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
- AZ::ComponentApplication::Descriptor desc;
- AZ::ComponentApplication application;
- application.Create(desc);
-
- AZStd::vector envVars;
-
- const char* homePath = std::getenv("HOME");
- envVars.push_back(AZStd::string::format("HOME=%s", homePath));
-
- if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
- {
- const char* dyldLibPathOrig = std::getenv("DYLD_LIBRARY_PATH");
- AZStd::string dyldSearchPath = AZStd::string::format("DYLD_LIBRARY_PATH=%s", dyldLibPathOrig);
- if (AZ::IO::FixedMaxPath projectModulePath;
- settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
- {
- dyldSearchPath.append(":");
- dyldSearchPath.append(projectModulePath.c_str());
- }
-
- if (AZ::IO::FixedMaxPath installedBinariesFolder;
- settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
- {
- if (AZ::IO::FixedMaxPath engineRootFolder;
- settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
- {
- installedBinariesFolder = engineRootFolder / installedBinariesFolder;
- dyldSearchPath.append(":");
- dyldSearchPath.append(installedBinariesFolder.c_str());
- }
- }
- envVars.push_back(dyldSearchPath);
- }
-
- AZStd::string commandArgs;
- for (int i = 1; i < argc; i++)
- {
- commandArgs.append(argv[i]);
- commandArgs.append(" ");
- }
-
- AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
- AZ::IO::Path processPath{ AZ::IO::PathView(AZ::Utils::GetExecutableDirectory()) };
- processPath /= "Editor";
- processLaunchInfo.m_processExecutableString = AZStd::move(processPath.Native());
- processLaunchInfo.m_commandlineParameters = commandArgs;
- processLaunchInfo.m_environmentVariables = &envVars;
- processLaunchInfo.m_showWindow = true;
-
- AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
-
- application.Destroy();
-
- return 0;
-}
-
diff --git a/Code/Editor/Plugins/EditorCommon/CMakeLists.txt b/Code/Editor/Plugins/EditorCommon/CMakeLists.txt
index 2aff57cfb1..cd9f2e79c7 100644
--- a/Code/Editor/Plugins/EditorCommon/CMakeLists.txt
+++ b/Code/Editor/Plugins/EditorCommon/CMakeLists.txt
@@ -51,4 +51,5 @@ ly_add_target(
AZ::AzCore
AZ::AzToolsFramework
AZ::AzQtComponents
+ Legacy::EditorCore
)
diff --git a/Code/Framework/AzCore/AzCore/Debug/Profiler.h b/Code/Framework/AzCore/AzCore/Debug/Profiler.h
index 8af48e47f6..56103e8314 100644
--- a/Code/Framework/AzCore/AzCore/Debug/Profiler.h
+++ b/Code/Framework/AzCore/AzCore/Debug/Profiler.h
@@ -59,6 +59,20 @@ namespace AZStd
namespace AZ::Debug
{
+ // interface for externally defined profiler systems
+ class Profiler
+ {
+ public:
+ AZ_RTTI(Profiler, "{3E5D6329-72D1-41BA-9158-68A349D1A4D5}");
+
+ Profiler() = default;
+ virtual ~Profiler() = default;
+
+ // support for the extra macro args (e.g. format strings) will come in a later PR
+ virtual void BeginRegion(const Budget* budget, const char* eventName) = 0;
+ virtual void EndRegion(const Budget* budget) = 0;
+ };
+
class ProfileScope
{
public:
diff --git a/Code/Framework/AzCore/AzCore/Debug/Profiler.inl b/Code/Framework/AzCore/AzCore/Debug/Profiler.inl
index 8ca8368ce1..74c0f553c4 100644
--- a/Code/Framework/AzCore/AzCore/Debug/Profiler.inl
+++ b/Code/Framework/AzCore/AzCore/Debug/Profiler.inl
@@ -6,6 +6,8 @@
*
*/
+#include
+
namespace AZ::Debug
{
template
@@ -22,9 +24,11 @@ namespace AZ::Debug
PIXBeginEvent(PIX_COLOR_INDEX(budget->Crc() & 0xff), eventName, args...);
#endif
budget->BeginProfileRegion();
-// TODO: injecting instrumentation for other profilers
-// NOTE: external profiler registration won't occur inline in a header necessarily in this manner, but the exact mechanism
-// will be introduced in a future PR
+
+ if (auto profiler = AZ::Interface::Get(); profiler)
+ {
+ profiler->BeginRegion(budget, eventName);
+ }
#endif
}
@@ -39,6 +43,10 @@ namespace AZ::Debug
#if defined(USE_PIX)
PIXEndEvent();
#endif
+ if (auto profiler = AZ::Interface::Get(); profiler)
+ {
+ profiler->EndRegion(budget);
+ }
#endif
}
diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp
index 5cf854d49f..1ca6a7c4c7 100644
--- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp
+++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp
@@ -72,6 +72,7 @@ namespace AZ
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
+ bool fileFound = false;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
@@ -79,6 +80,23 @@ namespace AZ
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
+ fileFound = true;
+ }
+ }
+ if (!fileFound)
+ {
+ if (AZ::IO::FixedMaxPath installedBinariesPath;
+ settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
+ {
+ if (AZ::IO::FixedMaxPath engineRootFolder;
+ settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
+ {
+ installedBinariesPath = engineRootFolder / installedBinariesPath / fullFilePath;
+ if (AZ::IO::SystemFile::Exists(installedBinariesPath.c_str()))
+ {
+ m_fileName.assign(installedBinariesPath.c_str(), installedBinariesPath.Native().size());
+ }
+ }
}
}
}
diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp
index 9aff67a00f..2e31936057 100644
--- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp
+++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include
namespace AZ
{
@@ -39,6 +40,14 @@ namespace AZ
AZ::IO::FixedMaxPath path{homePath};
return path.Native();
}
+
+ struct passwd* pass = getpwuid(getuid());
+ if (pass)
+ {
+ AZ::IO::FixedMaxPath path{pass->pw_dir};
+ return path.Native();
+ }
+
return {};
}
diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
index c94d588a90..d2a81102dc 100644
--- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
@@ -181,7 +181,7 @@ namespace AZ::IO::ArchiveInternal
return 0;
}
- nTotal = (AZStd::min)(nTotal, GetFileSize() - m_nCurSeek);
+ nTotal = AZStd::min(nTotal, GetFileSize() - m_nCurSeek);
int64_t nReadBytes = GetFile()->ReadData(pDest, m_nCurSeek, nTotal);
if (nReadBytes == -1)
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
index b909689c22..1b3645630e 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
@@ -9,8 +9,19 @@
#include
#include
+#include
+
namespace AzFramework
{
+ ClickDetector::ClickDetector()
+ {
+ m_timeNowFn = []
+ {
+ const auto now = AZStd::chrono::high_resolution_clock::now();
+ return AZStd::chrono::time_point_cast(now).time_since_epoch();
+ };
+ }
+
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
const auto previousDetectionState = m_detectionState;
@@ -26,11 +37,13 @@ namespace AzFramework
if (clickEvent == ClickEvent::Down)
{
- const auto now = std::chrono::steady_clock::now();
+ const auto now = m_timeNowFn();
if (m_tryBeginTime)
{
- const std::chrono::duration diff = now - m_tryBeginTime.value();
- if (diff.count() < m_doubleClickInterval)
+ using FloatingPointSeconds = AZStd::chrono::duration;
+
+ const auto diff = now - m_tryBeginTime.value();
+ if (FloatingPointSeconds(diff).count() < m_doubleClickInterval)
{
return ClickOutcome::Nil;
}
@@ -43,7 +56,8 @@ namespace AzFramework
}
else if (clickEvent == ClickEvent::Up)
{
- const auto clickOutcome = [detectionState = m_detectionState] {
+ const auto clickOutcome = [detectionState = m_detectionState]
+ {
if (detectionState == DetectionState::WaitingForMove)
{
return ClickOutcome::Click;
@@ -66,4 +80,9 @@ namespace AzFramework
return ClickOutcome::Nil;
}
+
+ void ClickDetector::OverrideTimeNowFn(AZStd::function timeNowFn)
+ {
+ m_timeNowFn = AZStd::move(timeNowFn);
+ }
} // namespace AzFramework
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
index f95924550a..70bdeb4619 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
@@ -8,6 +8,7 @@
#pragma once
+#include
#include
#include
@@ -21,10 +22,9 @@ namespace AzFramework
//! (mouse down with movement and then mouse up).
class ClickDetector
{
- //! Alias for recording time of mouse down events
- using Time = std::chrono::time_point;
-
public:
+ ClickDetector();
+
//! Internal representation of click event (map from external event for this when
//! calling DetectClick).
enum class ClickEvent
@@ -51,6 +51,10 @@ namespace AzFramework
void SetDoubleClickInterval(float doubleClickInterval);
//! Override the dead zone before a 'move' outcome will be triggered.
void SetDeadZone(float deadZone);
+ //! Override how the current time is retrieved.
+ //! This is helpful to override when it comes to simulating different passages of
+ //! time to avoid double click issues in tests for example.
+ void OverrideTimeNowFn(AZStd::function timeNowFn);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -65,7 +69,9 @@ namespace AzFramework
float m_deadZone = 2.0f; //!< How far to move before a click is cancelled (when Move will fire).
float m_doubleClickInterval = 0.4f; //!< Default double click interval, can be overridden.
DetectionState m_detectionState; //!< Internal state of ClickDetector.
- AZStd::optional
diff --git a/Code/Tools/AssetProcessor/Platform/Mac/main_dummy.cpp b/Code/Tools/AssetProcessor/Platform/Mac/main_dummy.cpp
deleted file mode 100644
index 3eed37e555..0000000000
--- a/Code/Tools/AssetProcessor/Platform/Mac/main_dummy.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-int main(int argc, char* argv[])
-{
- // Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
- AZ::ComponentApplication::Descriptor desc;
- AZ::ComponentApplication application;
- application.Create(desc);
-
- AZStd::vector envVars;
-
- const char* homePath = std::getenv("HOME");
- envVars.push_back(AZStd::string::format("HOME=%s", homePath));
-
- if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
- {
- const char* dyldLibPathOrig = std::getenv("DYLD_LIBRARY_PATH");
- AZStd::string dyldSearchPath = AZStd::string::format("DYLD_LIBRARY_PATH=%s", dyldLibPathOrig);
- if (AZ::IO::FixedMaxPath projectModulePath;
- settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
- {
- dyldSearchPath.append(":");
- dyldSearchPath.append(projectModulePath.c_str());
- }
-
- if (AZ::IO::FixedMaxPath installedBinariesFolder;
- settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
- {
- if (AZ::IO::FixedMaxPath engineRootFolder;
- settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
- {
- installedBinariesFolder = engineRootFolder / installedBinariesFolder;
- dyldSearchPath.append(":");
- dyldSearchPath.append(installedBinariesFolder.c_str());
- }
- }
- envVars.push_back(dyldSearchPath);
- }
-
- AZStd::string commandArgs;
- for (int i = 1; i < argc; i++)
- {
- commandArgs.append(argv[i]);
- commandArgs.append(" ");
- }
-
- AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
- AZ::IO::Path processPath{ AZ::IO::PathView(AZ::Utils::GetExecutableDirectory()) };
- processPath /= "AssetProcessor";
- processLaunchInfo.m_processExecutableString = AZStd::move(processPath.Native());
- processLaunchInfo.m_commandlineParameters = commandArgs;
- processLaunchInfo.m_environmentVariables = &envVars;
- processLaunchInfo.m_showWindow = true;
-
- AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
-
- application.Destroy();
-
- return 0;
-}
-
diff --git a/Code/Tools/BundleLauncher/CMakeLists.txt b/Code/Tools/BundleLauncher/CMakeLists.txt
new file mode 100644
index 0000000000..812cb21099
--- /dev/null
+++ b/Code/Tools/BundleLauncher/CMakeLists.txt
@@ -0,0 +1,24 @@
+#
+# 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
+#
+#
+
+# This is the launcher that will be used by the O3DE_SDK.app bundle
+# generated by the cmake install process for Mac.
+if(NOT ${PAL_PLATFORM_NAME} STREQUAL Mac)
+ return()
+endif()
+
+ly_add_target(
+ NAME O3DE_SDK EXECUTABLE
+ NAMESPACE AZ
+ FILES_CMAKE
+ O3DE_SDK_files.cmake
+ BUILD_DEPENDENCIES
+ PRIVATE
+ AZ::AzCore
+ AZ::AzFramework
+)
diff --git a/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp b/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp
new file mode 100644
index 0000000000..0c362ac829
--- /dev/null
+++ b/Code/Tools/BundleLauncher/O3DE_SDK_Launcher.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+int main(int argc, char* argv[])
+{
+ // We need to pass in the engine path since we won't be able to find it by searching upwards.
+ // We can't use any containers that use our custom allocator till after the call to ComponentApplication::Create()
+ AZ::IO::FixedMaxPath processPath = AZ::Utils::GetExecutableDirectory();
+ AZ::IO::FixedMaxPath enginePath = (processPath / "../Engine").LexicallyNormal();
+ auto enginePathParam = AZ::SettingsRegistryInterface::FixedValueString::format(R"(--engine-path="%s")", enginePath.c_str());
+ // Uses the fixed_vector deduction guide to determine the type is AZStd::fixed_vector
+ AZStd::fixed_vector commandLineParams{ processPath.Native().data(), enginePathParam.data() };
+
+
+ // Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
+ AZ::ComponentApplication application(static_cast(commandLineParams.size()), commandLineParams.data());
+ application.Create(AZ::ComponentApplication::Descriptor());
+
+ AZ::IO::FixedMaxPath installedBinariesFolder;
+ if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+ {
+ if (settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
+ {
+ installedBinariesFolder = enginePath / installedBinariesFolder;
+ }
+ }
+
+ AZ::IO::FixedMaxPath shellPath = "/bin/sh";
+ AZStd::string parameters = AZStd::string::format("-c \"export LY_CMAKE_PATH=/usr/local/bin && \"%s/python/get_python.sh\"\"", enginePath.c_str());
+ AzFramework::ProcessLauncher::ProcessLaunchInfo shellProcessLaunch;
+ shellProcessLaunch.m_processExecutableString = AZStd::move(shellPath.Native());
+ shellProcessLaunch.m_commandlineParameters = parameters;
+ shellProcessLaunch.m_showWindow = true;
+ shellProcessLaunch.m_workingDirectory = enginePath.String();
+ AZStd::unique_ptr shellProcess(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
+ shellProcess->WaitForProcessToExit(120);
+ shellProcess.reset();
+
+ AZ::IO::FixedMaxPath projectManagerPath = installedBinariesFolder/"o3de.app"/"Contents"/"MacOS"/"o3de";
+ AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
+ processLaunchInfo.m_processExecutableString = AZStd::move(projectManagerPath.Native());
+ processLaunchInfo.m_showWindow = true;
+ AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
+
+ application.Destroy();
+
+ return 0;
+}
+
diff --git a/Code/Tools/BundleLauncher/O3DE_SDK_files.cmake b/Code/Tools/BundleLauncher/O3DE_SDK_files.cmake
new file mode 100644
index 0000000000..04989c9df2
--- /dev/null
+++ b/Code/Tools/BundleLauncher/O3DE_SDK_files.cmake
@@ -0,0 +1,11 @@
+#
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+#
+
+set(FILES
+ O3DE_SDK_Launcher.cpp
+)
diff --git a/Code/Tools/BundleLauncher/info.plist b/Code/Tools/BundleLauncher/info.plist
new file mode 100644
index 0000000000..5b3dd43b37
--- /dev/null
+++ b/Code/Tools/BundleLauncher/info.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ CFBundleExecutable
+ O3DE_SDK
+ CFBundleIdentifier
+ org.O3DE.O3DE_SDK
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ ????
+ NSHumanReadableCopyright
+ Copyright (c) Contributors to the Open 3D Engine Project.
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/Code/Tools/CMakeLists.txt b/Code/Tools/CMakeLists.txt
index 66c43e53e5..8107089433 100644
--- a/Code/Tools/CMakeLists.txt
+++ b/Code/Tools/CMakeLists.txt
@@ -20,3 +20,4 @@ add_subdirectory(GridHub)
add_subdirectory(Standalone)
add_subdirectory(TestImpactFramework)
add_subdirectory(ProjectManager)
+add_subdirectory(BundleLauncher)
diff --git a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac.cmake b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac.cmake
index 7a325ca97e..5cd1fb5a22 100644
--- a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac.cmake
+++ b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac.cmake
@@ -5,3 +5,4 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
+
diff --git a/Code/Tools/ProjectManager/Resources/Delete.svg b/Code/Tools/ProjectManager/Resources/Delete.svg
new file mode 100644
index 0000000000..f932c71544
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/Delete.svg
@@ -0,0 +1,4 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/Edit.svg b/Code/Tools/ProjectManager/Resources/Edit.svg
new file mode 100644
index 0000000000..3ee9bbbfae
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/Edit.svg
@@ -0,0 +1,7 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
index 2e93e9eca9..30bcc1ace5 100644
--- a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
@@ -35,5 +35,8 @@
Backgrounds/DefaultBackground.jpg
Backgrounds/FtueBackground.jpg
FeatureTagClose.svg
+ Refresh.svg
+ Edit.svg
+ Delete.svg
diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss
index 17c5077d83..30117d6636 100644
--- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss
@@ -518,3 +518,82 @@ QProgressBar::chunk {
font-size: 12px;
font-weight: 600;
}
+
+/************** Engine **************/
+
+#engineTab::tab-bar {
+ left: 60px;
+}
+
+#engineTabBar::tab {
+ height: 50px;
+ background-color: transparent;
+ font-weight: 400;
+ font-size: 18px;
+ min-width: 160px;
+}
+
+#engineTabBar::tab:selected {
+ border-bottom: 3px solid #94D2FF;
+ color: #94D2FF;
+ font-weight: 600;
+}
+#engineTabBar::tab:hover {
+ color: #94D2FF;
+ font-weight: 600;
+}
+#engineTabBar::tab:pressed {
+ color: #66bcfa;
+}
+
+#engineTopFrame {
+ background-color:#1E252F;
+}
+
+/************** Gem Repo **************/
+
+#gemRepoHeaderLabel {
+ font-size: 12px;
+}
+
+#gemRepoHeaderRefreshButton {
+ background-color: transparent;
+ qproperty-flat: true;
+ qproperty-iconSize: 14px;
+}
+
+#gemRepoHeaderAddButton {
+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #888888, stop: 1.0 #555555);
+ qproperty-flat: true;
+ margin-right:30px;
+ min-width:120px;
+ max-width:120px;
+ min-height:24px;
+ max-height:24px;
+ border-radius: 3px;
+ text-align:center;
+ font-size:12px;
+ font-weight:600;
+}
+#gemRepoHeaderAddButton:hover {
+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #999999, stop: 1.0 #666666);
+}
+#gemRepoHeaderAddButton:pressed {
+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 #555555, stop: 1.0 #777777);
+}
+
+#gemRepoHeaderTable {
+ background-color: transparent;
+ max-height: 30px;
+}
+
+#gemRepoListHeader {
+ background-color: transparent;
+}
+
+#gemRepoInspector {
+ background: #444444;
+}
diff --git a/Code/Tools/ProjectManager/Resources/Refresh.svg b/Code/Tools/ProjectManager/Resources/Refresh.svg
new file mode 100644
index 0000000000..80cc892c68
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/Refresh.svg
@@ -0,0 +1,4 @@
+
diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp
new file mode 100644
index 0000000000..c78a9426db
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ EngineScreenCtrl::EngineScreenCtrl(QWidget* parent)
+ : ScreenWidget(parent)
+ {
+ QVBoxLayout* vLayout = new QVBoxLayout();
+ vLayout->setContentsMargins(0, 0, 0, 0);
+
+ QFrame* topBarFrameWidget = new QFrame(this);
+ topBarFrameWidget->setObjectName("engineTopFrame");
+ QHBoxLayout* topBarHLayout = new QHBoxLayout();
+ topBarHLayout->setContentsMargins(0, 0, 0, 0);
+
+ topBarFrameWidget->setLayout(topBarHLayout);
+
+ QTabWidget* tabWidget = new QTabWidget();
+ tabWidget->setObjectName("engineTab");
+ tabWidget->tabBar()->setObjectName("engineTabBar");
+ tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
+
+ m_engineSettingsScreen = new EngineSettingsScreen();
+ m_gemRepoScreen = new GemRepoScreen();
+
+ tabWidget->addTab(m_engineSettingsScreen, tr("General"));
+ tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
+ topBarHLayout->addWidget(tabWidget);
+
+ vLayout->addWidget(topBarFrameWidget);
+
+ setLayout(vLayout);
+ }
+
+ ProjectManagerScreen EngineScreenCtrl::GetScreenEnum()
+ {
+ return ProjectManagerScreen::UpdateProject;
+ }
+
+ QString EngineScreenCtrl::GetTabText()
+ {
+ return tr("Engine");
+ }
+
+ bool EngineScreenCtrl::IsTab()
+ {
+ return true;
+ }
+
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h
new file mode 100644
index 0000000000..9e799f13e7
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h
@@ -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
+ *
+ */
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+namespace O3DE::ProjectManager
+{
+ QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
+ QT_FORWARD_DECLARE_CLASS(GemRepoScreen)
+
+ class EngineScreenCtrl
+ : public ScreenWidget
+ {
+ public:
+ explicit EngineScreenCtrl(QWidget* parent = nullptr);
+ ~EngineScreenCtrl() = default;
+ ProjectManagerScreen GetScreenEnum() override;
+
+ QString GetTabText() override;
+ bool IsTab() override;
+
+ EngineSettingsScreen* m_engineSettingsScreen = nullptr;
+ GemRepoScreen* m_gemRepoScreen = nullptr;
+ };
+
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
index 0c24aac9f1..dec1c9a257 100644
--- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
@@ -7,15 +7,16 @@
*/
#include
-#include
-#include
-#include
-#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
+
namespace O3DE::ProjectManager
{
EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
@@ -78,16 +79,6 @@ namespace O3DE::ProjectManager
return ProjectManagerScreen::EngineSettings;
}
- QString EngineSettingsScreen::GetTabText()
- {
- return tr("Engine");
- }
-
- bool EngineSettingsScreen::IsTab()
- {
- return true;
- }
-
void EngineSettingsScreen::OnTextChanged()
{
// save engine settings
diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
index 9d212c44b1..2f16400405 100644
--- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
+++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
@@ -24,8 +24,6 @@ namespace O3DE::ProjectManager
~EngineSettingsScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
- QString GetTabText() override;
- bool IsTab() override;
protected slots:
void OnTextChanged();
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.cpp
new file mode 100644
index 0000000000..3e524d8ec8
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+
+namespace O3DE::ProjectManager
+{
+ GemRepoInfo::GemRepoInfo(
+ const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled = true)
+ : m_name(name)
+ , m_creator(creator)
+ , m_summary(summary)
+ , m_lastUpdated(lastUpdated)
+ , m_isEnabled(isEnabled)
+ {
+ }
+
+ bool GemRepoInfo::IsValid() const
+ {
+ return !m_name.isEmpty();
+ }
+
+ bool GemRepoInfo::operator<(const GemRepoInfo& gemRepoInfo) const
+ {
+ return (m_lastUpdated < gemRepoInfo.m_lastUpdated);
+ }
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h
new file mode 100644
index 0000000000..6f4f828951
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+#include
+#endif
+
+namespace O3DE::ProjectManager
+{
+ class GemRepoInfo
+ {
+ public:
+ GemRepoInfo() = default;
+ GemRepoInfo(const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled);
+
+ bool IsValid() const;
+
+ bool operator<(const GemRepoInfo& gemRepoInfo) const;
+
+ QString m_path;
+ QString m_name = "Unknown Gem Repo Name";
+ QString m_creator = "Unknown Creator";
+ bool m_isEnabled = false; //! Is the repo currently enabled for this engine?
+ QString m_summary = "No summary provided.";
+ QString m_directoryLink;
+ QString m_repoLink;
+ QDateTime m_lastUpdated;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp
new file mode 100644
index 0000000000..88ccee2636
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent)
+ : QStyledItemDelegate(parent)
+ , m_model(model)
+ {
+ m_refreshIcon = QIcon(":/Refresh.svg").pixmap(s_refreshIconSize, s_refreshIconSize);
+ m_editIcon = QIcon(":/Edit.svg").pixmap(s_iconSize, s_iconSize);
+ m_deleteIcon = QIcon(":/Delete.svg").pixmap(s_iconSize, s_iconSize);
+ }
+
+ void GemRepoItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
+ {
+ if (!modelIndex.isValid())
+ {
+ return;
+ }
+
+ QStyleOptionViewItem options(option);
+ initStyleOption(&options, modelIndex);
+
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ QRect fullRect, itemRect, contentRect;
+ CalcRects(options, fullRect, itemRect, contentRect);
+ QRect buttonRect = CalcButtonRect(contentRect);
+
+ QFont standardFont(options.font);
+ standardFont.setPixelSize(static_cast(s_fontSize));
+ QFontMetrics standardFontMetrics(standardFont);
+
+ painter->save();
+ painter->setClipping(true);
+ painter->setClipRect(fullRect);
+ painter->setFont(standardFont);
+ painter->setPen(m_textColor);
+
+ // Draw background
+ painter->fillRect(fullRect, m_backgroundColor);
+
+ // Draw item background
+ const QColor itemBackgroundColor = options.state & QStyle::State_MouseOver ? m_itemBackgroundColor.lighter(120) : m_itemBackgroundColor;
+ painter->fillRect(itemRect, itemBackgroundColor);
+
+ // Draw border
+ if (options.state & QStyle::State_Selected)
+ {
+ painter->save();
+ QPen borderPen(m_borderColor);
+ borderPen.setWidth(s_borderWidth);
+ painter->setPen(borderPen);
+ painter->drawRect(itemRect);
+
+ painter->restore();
+ }
+
+ // Repo enabled
+ DrawButton(painter, buttonRect, modelIndex);
+
+ // Repo name
+ QString repoName = GemRepoModel::GetName(modelIndex);
+ repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth);
+
+ QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize);
+ int currentHorizontalOffset = buttonRect.left() + s_buttonWidth + s_buttonSpacing;
+ repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2);
+ repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName);
+
+ painter->drawText(repoNameRect, Qt::TextSingleLine, repoName);
+
+ // Rem repo creator
+ QString repoCreator = GemRepoModel::GetCreator(modelIndex);
+ repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, s_creatorMaxWidth);
+
+ QRect repoCreatorRect = GetTextRect(standardFont, repoCreator, s_fontSize);
+ currentHorizontalOffset += s_nameMaxWidth + s_contentSpacing;
+ repoCreatorRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoCreatorRect.height() / 2);
+ repoCreatorRect = painter->boundingRect(repoCreatorRect, Qt::TextSingleLine, repoCreator);
+
+ painter->drawText(repoCreatorRect, Qt::TextSingleLine, repoCreator);
+
+ // Repo update
+ QString repoUpdatedDate = GemRepoModel::GetLastUpdated(modelIndex).toString("dd/MM/yyyy hh:mmap");
+ repoUpdatedDate = standardFontMetrics.elidedText(repoUpdatedDate, Qt::TextElideMode::ElideRight, s_updatedMaxWidth);
+
+ QRect repoUpdatedDateRect = GetTextRect(standardFont, repoUpdatedDate, s_fontSize);
+ currentHorizontalOffset += s_creatorMaxWidth + s_contentSpacing;
+ repoUpdatedDateRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoUpdatedDateRect.height() / 2);
+ repoUpdatedDateRect = painter->boundingRect(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
+
+ painter->drawText(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
+
+ // Draw refresh button
+ painter->drawPixmap(
+ repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
+ contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
+ m_refreshIcon);
+
+ if (options.state & QStyle::State_MouseOver)
+ {
+ DrawEditButtons(painter, contentRect);
+ }
+
+ painter->restore();
+ }
+
+ QSize GemRepoItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
+ {
+ QStyleOptionViewItem options(option);
+ initStyleOption(&options, modelIndex);
+
+ int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
+ return QSize(marginsHorizontal + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height);
+ }
+
+ bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
+ {
+ if (!modelIndex.isValid())
+ {
+ return false;
+ }
+
+ if (event->type() == QEvent::KeyPress)
+ {
+ auto keyEvent = static_cast(event);
+ if (keyEvent->key() == Qt::Key_Space)
+ {
+ const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
+ GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
+ return true;
+ }
+ }
+
+ if (event->type() == QEvent::MouseButtonPress)
+ {
+ QMouseEvent* mouseEvent = static_cast(event);
+
+ QRect fullRect, itemRect, contentRect;
+ CalcRects(option, fullRect, itemRect, contentRect);
+ const QRect buttonRect = CalcButtonRect(contentRect);
+
+ if (buttonRect.contains(mouseEvent->pos()))
+ {
+ const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
+ GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
+ return true;
+ }
+ }
+
+ return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
+ }
+
+ void GemRepoItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const
+ {
+ outFullRect = QRect(option.rect);
+ outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom()));
+ outContentRect = QRect(outItemRect.adjusted(s_contentMargins.left(), s_contentMargins.top(), -s_contentMargins.right(), -s_contentMargins.bottom()));
+ }
+
+ QRect GemRepoItemDelegate::GetTextRect(QFont& font, const QString& text, qreal fontSize) const
+ {
+ font.setPixelSize(static_cast(fontSize));
+ return QFontMetrics(font).boundingRect(text);
+ }
+
+ QRect GemRepoItemDelegate::CalcButtonRect(const QRect& contentRect) const
+ {
+ const QPoint topLeft = QPoint(contentRect.left(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2);
+ const QSize size = QSize(s_buttonWidth, s_buttonHeight);
+ return QRect(topLeft, size);
+ }
+
+ void GemRepoItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
+ {
+ painter->save();
+ QPoint circleCenter;
+
+ const bool isEnabled = GemRepoModel::IsEnabled(modelIndex);
+ if (isEnabled)
+ {
+ painter->setBrush(m_buttonEnabledColor);
+ painter->setPen(m_buttonEnabledColor);
+
+ circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
+ }
+ else
+ {
+ circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1);
+ }
+
+ // Rounded rect
+ painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
+
+ // Circle
+ painter->setBrush(m_textColor);
+ painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);
+
+ painter->restore();
+ }
+
+ void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
+ {
+ painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon);
+ painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
+ }
+
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h
new file mode 100644
index 0000000000..08d1fdffae
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h
@@ -0,0 +1,82 @@
+/*
+ * 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
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+QT_FORWARD_DECLARE_CLASS(QEvent)
+
+namespace O3DE::ProjectManager
+{
+ class GemRepoItemDelegate
+ : public QStyledItemDelegate
+ {
+ Q_OBJECT // AUTOMOC
+
+ public:
+ explicit GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
+ ~GemRepoItemDelegate() = default;
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
+ bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
+
+ // Colors
+ const QColor m_textColor = QColor("#FFFFFF");
+ const QColor m_backgroundColor = QColor("#333333"); // Outside of the actual repo item
+ const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the repo item
+ const QColor m_borderColor = QColor("#1E70EB");
+ const QColor m_buttonEnabledColor = QColor("#1E70EB");
+
+ // Item
+ inline constexpr static int s_height = 72; // Repo item total height
+ inline constexpr static qreal s_fontSize = 12.0;
+
+ // Margin and borders
+ inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/60, /*bottom=*/8); // Item border distances
+ inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/20, /*right=*/20, /*bottom=*/20); // Distances of the elements within an item to the item borders
+ inline constexpr static int s_borderWidth = 4;
+
+ // Content
+ inline constexpr static int s_contentSpacing = 5;
+ inline constexpr static int s_nameMaxWidth = 145;
+ inline constexpr static int s_creatorMaxWidth = 115;
+ inline constexpr static int s_updatedMaxWidth = 125;
+
+ // Button
+ inline constexpr static int s_buttonWidth = 32;
+ inline constexpr static int s_buttonHeight = 16;
+ inline constexpr static int s_buttonBorderRadius = 8;
+ inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2;
+ inline constexpr static int s_buttonSpacing = 20;
+
+ // Icon
+ inline constexpr static int s_iconSize = 24;
+ inline constexpr static int s_iconSpacing = 16;
+ inline constexpr static int s_refreshIconSize = 14;
+ inline constexpr static int s_refreshIconSpacing = 10;
+
+ protected:
+ void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
+ QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
+ QRect CalcButtonRect(const QRect& contentRect) const;
+ void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
+ void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
+
+ QAbstractItemModel* m_model = nullptr;
+
+ QPixmap m_refreshIcon;
+ QPixmap m_editIcon;
+ QPixmap m_deleteIcon;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp
new file mode 100644
index 0000000000..519d52cb35
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ GemRepoListView::GemRepoListView(QAbstractItemModel* model, QWidget* parent)
+ : QListView(parent)
+ {
+ setObjectName("gemRepoListView");
+ setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+
+ setModel(model);
+ setItemDelegate(new GemRepoItemDelegate(model, this));
+ }
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h
new file mode 100644
index 0000000000..0fd5d5c180
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
+
+namespace O3DE::ProjectManager
+{
+ class GemRepoListView
+ : public QListView
+ {
+ Q_OBJECT // AUTOMOC
+
+ public:
+ explicit GemRepoListView(QAbstractItemModel* model, QWidget* parent = nullptr);
+ ~GemRepoListView() = default;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp
new file mode 100644
index 0000000000..7a42c135e9
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+
+#include
+
+namespace O3DE::ProjectManager
+{
+ GemRepoModel::GemRepoModel(QObject* parent)
+ : QStandardItemModel(parent)
+ {
+ m_selectionModel = new QItemSelectionModel(this, parent);
+ }
+
+ QItemSelectionModel* GemRepoModel::GetSelectionModel() const
+ {
+ return m_selectionModel;
+ }
+
+ void GemRepoModel::AddGemRepo(const GemRepoInfo& gemRepoInfo)
+ {
+ QStandardItem* item = new QStandardItem();
+
+ item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+ item->setData(gemRepoInfo.m_name, RoleName);
+ item->setData(gemRepoInfo.m_creator, RoleCreator);
+ item->setData(gemRepoInfo.m_summary, RoleSummary);
+ item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled);
+ item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink);
+ item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
+ item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
+ item->setData(gemRepoInfo.m_path, RolePath);
+
+ appendRow(item);
+ }
+
+ void GemRepoModel::Clear()
+ {
+ clear();
+ }
+
+ QString GemRepoModel::GetName(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleName).toString();
+ }
+
+ QString GemRepoModel::GetCreator(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleCreator).toString();
+ }
+
+ QString GemRepoModel::GetSummary(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleSummary).toString();
+ }
+
+ QString GemRepoModel::GetDirectoryLink(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleDirectoryLink).toString();
+ }
+
+ QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleRepoLink).toString();
+ }
+
+ QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleLastUpdated).toDateTime();
+ }
+
+ QString GemRepoModel::GetPath(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RolePath).toString();
+ }
+
+ bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleIsEnabled).toBool();
+ }
+
+ void GemRepoModel::SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled)
+ {
+ model.setData(modelIndex, isEnabled, RoleIsEnabled);
+ }
+
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h
new file mode 100644
index 0000000000..2f1537d339
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h
@@ -0,0 +1,58 @@
+/*
+ * 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
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
+
+namespace O3DE::ProjectManager
+{
+ class GemRepoModel
+ : public QStandardItemModel
+ {
+ Q_OBJECT // AUTOMOC
+
+ public:
+ explicit GemRepoModel(QObject* parent = nullptr);
+ QItemSelectionModel* GetSelectionModel() const;
+
+ void AddGemRepo(const GemRepoInfo& gemInfo);
+ void Clear();
+
+ static QString GetName(const QModelIndex& modelIndex);
+ static QString GetCreator(const QModelIndex& modelIndex);
+ static QString GetSummary(const QModelIndex& modelIndex);
+ static QString GetDirectoryLink(const QModelIndex& modelIndex);
+ static QString GetRepoLink(const QModelIndex& modelIndex);
+ static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
+ static QString GetPath(const QModelIndex& modelIndex);
+
+ static bool IsEnabled(const QModelIndex& modelIndex);
+ static void SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled);
+
+ private:
+ enum UserRole
+ {
+ RoleName = Qt::UserRole,
+ RoleCreator,
+ RoleSummary,
+ RoleIsEnabled,
+ RoleDirectoryLink,
+ RoleRepoLink,
+ RoleLastUpdated,
+ RolePath
+ };
+
+ QItemSelectionModel* m_selectionModel = nullptr;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp
new file mode 100644
index 0000000000..82de53a0d0
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ GemRepoScreen::GemRepoScreen(QWidget* parent)
+ : ScreenWidget(parent)
+ {
+ m_gemRepoModel = new GemRepoModel(this);
+
+ QVBoxLayout* vLayout = new QVBoxLayout();
+ vLayout->setMargin(0);
+ vLayout->setSpacing(0);
+ setLayout(vLayout);
+
+ QHBoxLayout* hLayout = new QHBoxLayout();
+ hLayout->setMargin(0);
+ hLayout->setSpacing(0);
+ vLayout->addLayout(hLayout);
+
+ hLayout->addSpacing(60);
+
+ m_gemRepoInspector = new QFrame(this);
+ m_gemRepoInspector->setObjectName(tr("gemRepoInspector"));
+ m_gemRepoInspector->setFixedWidth(240);
+
+ QVBoxLayout* middleVLayout = new QVBoxLayout();
+ middleVLayout->setMargin(0);
+ middleVLayout->setSpacing(0);
+
+ middleVLayout->addSpacing(30);
+
+ QHBoxLayout* topMiddleHLayout = new QHBoxLayout();
+ topMiddleHLayout->setMargin(0);
+ topMiddleHLayout->setSpacing(0);
+
+ m_lastAllUpdateLabel = new QLabel(tr("Last Updated: Never"), this);
+ m_lastAllUpdateLabel->setObjectName("gemRepoHeaderLabel");
+ topMiddleHLayout->addWidget(m_lastAllUpdateLabel);
+
+ topMiddleHLayout->addSpacing(20);
+
+ m_AllUpdateButton = new QPushButton(QIcon(":/Refresh.svg"), tr("Update All"), this);
+ m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
+ topMiddleHLayout->addWidget(m_AllUpdateButton);
+
+ topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
+
+ m_AddRepoButton = new QPushButton(tr("Add Repository"), this);
+ m_AddRepoButton->setObjectName("gemRepoHeaderAddButton");
+ topMiddleHLayout->addWidget(m_AddRepoButton);
+
+ middleVLayout->addLayout(topMiddleHLayout);
+
+ middleVLayout->addSpacing(30);
+
+ // Create a QTableWidget just for its header
+ // Using a seperate model allows the setup of a header exactly as needed
+ m_gemRepoHeaderTable = new QTableWidget(this);
+ m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable");
+ m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader();
+ m_gemRepoListHeader->setObjectName("gemRepoListHeader");
+ m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed);
+
+ // Insert columns so the header labels will show up
+ m_gemRepoHeaderTable->insertColumn(0);
+ m_gemRepoHeaderTable->insertColumn(1);
+ m_gemRepoHeaderTable->insertColumn(2);
+ m_gemRepoHeaderTable->insertColumn(3);
+ m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Enabled"), tr("Repository Name"), tr("Creator"), tr("Updated") });
+
+ const int headerExtraMargin = 10;
+ m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_buttonWidth + GemRepoItemDelegate::s_buttonSpacing - 3);
+ m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+ m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+ m_gemRepoListHeader->resizeSection(3, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
+
+ // Required to set stylesheet in code as it will not be respected if set in qss
+ m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }");
+ middleVLayout->addWidget(m_gemRepoHeaderTable);
+
+ m_gemRepoListView = new GemRepoListView(m_gemRepoModel, this);
+ middleVLayout->addWidget(m_gemRepoListView);
+
+ hLayout->addLayout(middleVLayout);
+ hLayout->addWidget(m_gemRepoInspector);
+
+ Reinit();
+ }
+
+ void GemRepoScreen::Reinit()
+ {
+ m_gemRepoModel->clear();
+ FillModel();
+
+ // Select the first entry after everything got correctly sized
+ QTimer::singleShot(200, [=]{
+ QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
+ m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
+ });
+ }
+
+ void GemRepoScreen::FillModel()
+ {
+ AZ::Outcome, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
+ if (allGemRepoInfosResult.IsSuccess())
+ {
+ // Add all available repos to the model
+ const QVector allGemRepoInfos = allGemRepoInfosResult.GetValue();
+ for (const GemRepoInfo& gemRepoInfo : allGemRepoInfos)
+ {
+ m_gemRepoModel->AddGemRepo(gemRepoInfo);
+ }
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Operation failed"), QString("Cannot retrieve gem repos for engine.\n\nError:\n%2").arg(allGemRepoInfosResult.GetError().c_str()));
+ }
+ }
+
+ ProjectManagerScreen GemRepoScreen::GetScreenEnum()
+ {
+ return ProjectManagerScreen::GemRepos;
+ }
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h
new file mode 100644
index 0000000000..b5316db84f
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+QT_FORWARD_DECLARE_CLASS(QHeaderView)
+QT_FORWARD_DECLARE_CLASS(QTableWidget)
+
+namespace O3DE::ProjectManager
+{
+ QT_FORWARD_DECLARE_CLASS(GemRepoListView)
+ QT_FORWARD_DECLARE_CLASS(GemRepoModel)
+
+ class GemRepoScreen
+ : public ScreenWidget
+ {
+ public:
+ explicit GemRepoScreen(QWidget* parent = nullptr);
+ ~GemRepoScreen() = default;
+ ProjectManagerScreen GetScreenEnum() override;
+
+ void Reinit();
+
+ GemRepoModel* GetGemRepoModel() const { return m_gemRepoModel; }
+
+ private:
+ void FillModel();
+
+ QTableWidget* m_gemRepoHeaderTable = nullptr;
+ QHeaderView* m_gemRepoListHeader = nullptr;
+ GemRepoListView* m_gemRepoListView = nullptr;
+ QFrame* m_gemRepoInspector = nullptr;
+ GemRepoModel* m_gemRepoModel = nullptr;
+
+ QLabel* m_lastAllUpdateLabel;
+ QPushButton* m_AllUpdateButton;
+ QPushButton* m_AddRepoButton;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
index a723ccb917..d2f2d969f1 100644
--- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
+++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
QVector screenEnums =
{
ProjectManagerScreen::Projects,
- ProjectManagerScreen::EngineSettings,
+ ProjectManagerScreen::Engine,
ProjectManagerScreen::CreateProject,
ProjectManagerScreen::UpdateProject
};
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index 18901946f3..6f8ff9abce 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -912,4 +912,45 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(templates));
}
}
+
+ GemRepoInfo PythonBindings::GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath)
+ {
+ /* Placeholder Logic */
+ (void)path;
+ (void)pyEnginePath;
+
+ return GemRepoInfo();
+ }
+
+//#define MOCK_GEM_REPO_INFO true
+
+ AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoInfos()
+ {
+ QVector gemRepos;
+
+#ifndef MOCK_GEM_REPO_INFO
+ auto result = ExecuteWithLockErrorHandling(
+ [&]
+ {
+ /* Placeholder Logic, o3de scripts need method added
+ *
+ for (auto path : m_manifest.attr("get_gem_repos")())
+ {
+ gemRepos.push_back(GemRepoInfoFromPath(path, pybind11::none()));
+ }
+ *
+ */
+ });
+ if (!result.IsSuccess())
+ {
+ return AZ::Failure(result.GetError().c_str());
+ }
+#else
+ gemRepos.push_back(GemRepoInfo("JohnCreates", "John Smith", "", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true));
+ gemRepos.push_back(GemRepoInfo("JanesGems", "Jane Doe", "", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false));
+#endif // MOCK_GEM_REPO_INFO
+
+ std::sort(gemRepos.begin(), gemRepos.end());
+ return AZ::Success(AZStd::move(gemRepos));
+ }
}
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h
index 8482ac56e7..3b766c3797 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.h
@@ -56,12 +56,16 @@ namespace O3DE::ProjectManager
// ProjectTemplate
AZ::Outcome> GetProjectTemplates(const QString& projectPath = {}) override;
+ // Gem Repos
+ AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override;
+
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
AZ::Outcome ExecuteWithLockErrorHandling(AZStd::function executionCallback);
bool ExecuteWithLock(AZStd::function executionCallback);
GemInfo GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
+ GemRepoInfo GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
bool RegisterThisEngine();
diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
index ca14a54630..9fd3002f93 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
namespace O3DE::ProjectManager
{
@@ -56,14 +57,14 @@ namespace O3DE::ProjectManager
/**
* Get info about a Gem
- * @param path the absolute path to the Gem
+ * @param projectPath the absolute path to the Gem
* @return an outcome with GemInfo on success
*/
virtual AZ::Outcome GetGemInfo(const QString& path, const QString& projectPath = {}) = 0;
/**
* Get all available gem infos. This concatenates gems registered by the engine and the project.
- * @param path The absolute path to the project.
+ * @param projectPath The absolute path to the project.
* @return A list of gem infos.
*/
virtual AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
@@ -155,6 +156,14 @@ namespace O3DE::ProjectManager
* @return an outcome with ProjectTemplateInfos on success
*/
virtual AZ::Outcome> GetProjectTemplates(const QString& projectPath = {}) = 0;
+
+ // Gem Repos
+
+ /**
+ * Get all available gem repo infos. Gathers all repos registered with the engine.
+ * @return A list of gem repo infos.
+ */
+ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0;
};
using PythonBindingsInterface = AZ::Interface;
diff --git a/Code/Tools/ProjectManager/Source/ScreenDefs.h b/Code/Tools/ProjectManager/Source/ScreenDefs.h
index 97ebe19751..2ced8c08c9 100644
--- a/Code/Tools/ProjectManager/Source/ScreenDefs.h
+++ b/Code/Tools/ProjectManager/Source/ScreenDefs.h
@@ -23,7 +23,9 @@ namespace O3DE::ProjectManager
Projects,
UpdateProject,
UpdateProjectSettings,
- EngineSettings
+ Engine,
+ EngineSettings,
+ GemRepos
};
static QHash s_ProjectManagerStringNames = {
@@ -34,7 +36,9 @@ namespace O3DE::ProjectManager
{ "Projects", ProjectManagerScreen::Projects},
{ "UpdateProject", ProjectManagerScreen::UpdateProject},
{ "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings},
- { "EngineSettings", ProjectManagerScreen::EngineSettings}
+ { "Engine", ProjectManagerScreen::Engine},
+ { "EngineSettings", ProjectManagerScreen::EngineSettings},
+ { "GemRepos", ProjectManagerScreen::GemRepos}
};
// need to define qHash for ProjectManagerScreen when using scoped enums
diff --git a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp
index f3bddfdd27..44aa713e6a 100644
--- a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp
+++ b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp
@@ -13,7 +13,9 @@
#include
#include
#include
+#include
#include
+#include
namespace O3DE::ProjectManager
{
@@ -41,9 +43,15 @@ namespace O3DE::ProjectManager
case (ProjectManagerScreen::UpdateProjectSettings):
newScreen = new UpdateProjectSettingsScreen(parent);
break;
+ case (ProjectManagerScreen::Engine):
+ newScreen = new EngineScreenCtrl(parent);
+ break;
case (ProjectManagerScreen::EngineSettings):
newScreen = new EngineSettingsScreen(parent);
break;
+ case (ProjectManagerScreen::GemRepos):
+ newScreen = new GemRepoScreen(parent);
+ break;
case (ProjectManagerScreen::Empty):
default:
newScreen = new ScreenWidget(parent);
diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake
index 6d0f392e52..7a336972e0 100644
--- a/Code/Tools/ProjectManager/project_manager_files.cmake
+++ b/Code/Tools/ProjectManager/project_manager_files.cmake
@@ -56,6 +56,8 @@ set(FILES
Source/ProjectsScreen.cpp
Source/ProjectSettingsScreen.h
Source/ProjectSettingsScreen.cpp
+ Source/EngineScreenCtrl.h
+ Source/EngineScreenCtrl.cpp
Source/EngineSettingsScreen.h
Source/EngineSettingsScreen.cpp
Source/ProjectButtonWidget.h
@@ -98,4 +100,14 @@ set(FILES
Source/GemCatalog/GemRequirementListView.cpp
Source/GemCatalog/GemSortFilterProxyModel.h
Source/GemCatalog/GemSortFilterProxyModel.cpp
+ Source/GemRepo/GemRepoScreen.h
+ Source/GemRepo/GemRepoScreen.cpp
+ Source/GemRepo/GemRepoInfo.h
+ Source/GemRepo/GemRepoInfo.cpp
+ Source/GemRepo/GemRepoItemDelegate.h
+ Source/GemRepo/GemRepoItemDelegate.cpp
+ Source/GemRepo/GemRepoListView.h
+ Source/GemRepo/GemRepoListView.cpp
+ Source/GemRepo/GemRepoModel.h
+ Source/GemRepo/GemRepoModel.cpp
)
diff --git a/Gems/Atom/Feature/Common/Code/CMakeLists.txt b/Gems/Atom/Feature/Common/Code/CMakeLists.txt
index b8c414dcc6..9c4ffe5b5e 100644
--- a/Gems/Atom/Feature/Common/Code/CMakeLists.txt
+++ b/Gems/Atom/Feature/Common/Code/CMakeLists.txt
@@ -40,6 +40,8 @@ ly_add_target(
Gem::Atom_Feature_Common.Public
Gem::ImGui.imguilib
#3rdParty::lux_core # AZ_TRAIT_LUXCORE_SUPPORTED is disabled in every platform, Issue #3915 will remove
+ RUNTIME_DEPENDENCIES
+ Gem::ImGui.imguilib
)
ly_add_target(
diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp
index 214643505a..2057c596e5 100644
--- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomDrawQueue.cpp
@@ -565,7 +565,7 @@ namespace AZ
AuxGeomBufferData* AuxGeomDrawQueue::Commit()
{
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomDrawQueue: Commit");
+ AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: Commit");
// get a mutually exclusive lock and then switch to the next buffer, returning a pointer to the current buffer (before the switch)
// grab the lock
@@ -585,7 +585,7 @@ namespace AZ
void AuxGeomDrawQueue::ClearCurrentBufferData()
{
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomDrawQueue: ClearCurrentBufferData");
+ AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: ClearCurrentBufferData");
// no need for mutex here, this function is only called from a function holding a lock
AuxGeomBufferData& data = m_buffers[m_currentBufferIndex];
@@ -649,7 +649,7 @@ namespace AZ
AZ::u8 width,
int32_t viewProjOverrideIndex)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: DrawPrimitiveWithSharedVerticesCommon");
// grab a mutex lock for the rest of this function so that a commit cannot happen during it and
// other threads can't add geometry during it
@@ -720,8 +720,7 @@ namespace AZ
AZ::u8 width,
int32_t viewProjOverrideIndex)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomDrawQueue: DrawPrimitiveWithSharedVerticesCommon");
+ AZ_PROFILE_SCOPE(AzRender, "AuxGeomDrawQueue: DrawPrimitiveWithSharedVerticesCommon");
AZ_Assert(indexCount >= verticesPerPrimitiveType && (indexCount % verticesPerPrimitiveType == 0),
"Index count must be at least %d and must be a multiple of %d",
diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomFeatureProcessor.cpp
index feeb1dedcc..ee6d3ba4a3 100644
--- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/AuxGeomFeatureProcessor.cpp
@@ -80,7 +80,7 @@ namespace AZ
void AuxGeomFeatureProcessor::Render(const FeatureProcessor::RenderPacket& fpPacket)
{
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "AuxGeomFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(AzRender, "AuxGeomFeatureProcessor: Render");
// Get the scene data and switch buffers so that other threads can continue to queue requests
AuxGeomBufferData* bufferData = static_cast(m_sceneDrawQueue.get())->Commit();
diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/DynamicPrimitiveProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/DynamicPrimitiveProcessor.cpp
index f35809f148..9af6493413 100644
--- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/DynamicPrimitiveProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/DynamicPrimitiveProcessor.cpp
@@ -70,7 +70,7 @@ namespace AZ
void DynamicPrimitiveProcessor::PrepareFrame()
{
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "DynamicPrimitiveProcessor: PrepareFrame");
+ AZ_PROFILE_SCOPE(AzRender, "DynamicPrimitiveProcessor: PrepareFrame");
m_drawPackets.clear();
m_processSrgs.clear();
@@ -88,7 +88,7 @@ namespace AZ
void DynamicPrimitiveProcessor::ProcessDynamicPrimitives(const AuxGeomBufferData* bufferData, const RPI::FeatureProcessor::RenderPacket& fpPacket)
{
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "DynamicPrimitiveProcessor: ProcessDynamicPrimitives");
+ AZ_PROFILE_SCOPE(AzRender, "DynamicPrimitiveProcessor: ProcessDynamicPrimitives");
RHI::DrawPacketBuilder drawPacketBuilder;
const DynamicPrimitiveData& srcPrimitives = bufferData->m_primitiveData;
diff --git a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp
index 0045311bef..8b5e439ca0 100644
--- a/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/AuxGeom/FixedShapeProcessor.cpp
@@ -108,8 +108,8 @@ namespace AZ
}
void FixedShapeProcessor::PrepareFrame()
- {
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "FixedShapeProcessor: PrepareFrame");
+ {
+ AZ_PROFILE_SCOPE(AzRender, "FixedShapeProcessor: PrepareFrame");
m_processSrgs.clear();
m_drawPackets.clear();
@@ -127,8 +127,7 @@ namespace AZ
void FixedShapeProcessor::ProcessObjects(const AuxGeomBufferData* bufferData, const RPI::FeatureProcessor::RenderPacket& fpPacket)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("AuxGeom", "FixedShapeProcessor: ProcessObjects");
+ AZ_PROFILE_SCOPE(AzRender, "FixedShapeProcessor: ProcessObjects");
RHI::DrawPacketBuilder drawPacketBuilder;
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CapsuleLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CapsuleLightFeatureProcessor.cpp
index 683295cf5b..849c930afd 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CapsuleLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CapsuleLightFeatureProcessor.cpp
@@ -102,7 +102,7 @@ namespace AZ
void CapsuleLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "CapsuleLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "CapsuleLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -114,7 +114,7 @@ namespace AZ
void CapsuleLightFeatureProcessor::Render(const CapsuleLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "CapsuleLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "CapsuleLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp
index c235f78595..702818dcd9 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp
@@ -196,7 +196,7 @@ namespace AZ
void DirectionalLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket&)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DirectionalLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "DirectionalLightFeatureProcessor: Simulate");
if (m_shadowingLightHandle.IsValid())
{
@@ -293,7 +293,7 @@ namespace AZ
void DirectionalLightFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DirectionalLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "DirectionalLightFeatureProcessor: Render");
if (m_shadowingLightHandle.IsValid())
{
@@ -1232,7 +1232,7 @@ namespace AZ
void DirectionalLightFeatureProcessor::SetFilterParameterToPass(LightHandle handle, const RPI::View* cameraView)
{
- AZ_ATOM_PROFILE_FUNCTION("DirectionalLightFeatureProcessor", "DirectionalLightFeatureProcessor::SetFilterParameterToPass");
+ AZ_PROFILE_SCOPE(RPI, "DirectionalLightFeatureProcessor::SetFilterParameterToPass");
if (handle != m_shadowingLightHandle)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp
index dfbeea0ffe..ca2c038e46 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DiskLightFeatureProcessor.cpp
@@ -123,7 +123,7 @@ namespace AZ
void DiskLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DiskLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "DiskLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -135,7 +135,7 @@ namespace AZ
void DiskLightFeatureProcessor::Render(const DiskLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DiskLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "DiskLightFeatureProcessor: Simulate");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp
index af440e5040..bfb0b1252b 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PointLightFeatureProcessor.cpp
@@ -119,7 +119,7 @@ namespace AZ
void PointLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PointLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "PointLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -131,7 +131,7 @@ namespace AZ
void PointLightFeatureProcessor::Render(const PointLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PointLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "PointLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PolygonLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PolygonLightFeatureProcessor.cpp
index 089adf4621..9dbf01ae26 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/PolygonLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/PolygonLightFeatureProcessor.cpp
@@ -132,7 +132,7 @@ namespace AZ::Render
void PolygonLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PolygonLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "PolygonLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -153,7 +153,7 @@ namespace AZ::Render
void PolygonLightFeatureProcessor::Render(const PolygonLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PolygonLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "PolygonLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/QuadLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/QuadLightFeatureProcessor.cpp
index e22174225d..aadc8c2020 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/QuadLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/QuadLightFeatureProcessor.cpp
@@ -107,7 +107,7 @@ namespace AZ
void QuadLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "QuadLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "QuadLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -119,7 +119,7 @@ namespace AZ
void QuadLightFeatureProcessor::Render(const QuadLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "QuadLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "QuadLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimplePointLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimplePointLightFeatureProcessor.cpp
index b8bbb03335..f49a5e94fc 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimplePointLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimplePointLightFeatureProcessor.cpp
@@ -102,7 +102,7 @@ namespace AZ
void SimplePointLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SimplePointLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "SimplePointLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -114,7 +114,7 @@ namespace AZ
void SimplePointLightFeatureProcessor::Render(const SimplePointLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SimplePointLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "SimplePointLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimpleSpotLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimpleSpotLightFeatureProcessor.cpp
index d4776faec6..277b5026d7 100644
--- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimpleSpotLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/SimpleSpotLightFeatureProcessor.cpp
@@ -102,7 +102,7 @@ namespace AZ
void SimpleSpotLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SimpleSpotLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "SimpleSpotLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -114,7 +114,7 @@ namespace AZ
void SimpleSpotLightFeatureProcessor::Render(const SimpleSpotLightFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SimpleSpotLightFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "SimpleSpotLightFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp
index 4954ffc01c..4ff718b5ad 100644
--- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp
@@ -107,7 +107,7 @@ namespace AZ
void DecalFeatureProcessor::Simulate(const RPI::FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DecalFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "DecalFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -131,7 +131,7 @@ namespace AZ
void DecalFeatureProcessor::Render(const RPI::FeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "DecalFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "DecalFeatureProcessor: Render");
AZStd::array_view> baseMaps = GetImagesFromDecalData<1>();
AZStd::array_view> opacityMaps = GetImagesFromDecalData<2>();
diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp
index 4ecfd7fc09..c0b8f3315a 100644
--- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp
@@ -145,7 +145,7 @@ namespace AZ
void DecalTextureArrayFeatureProcessor::Simulate(const RPI::FeatureProcessor::SimulatePacket& packet)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "DecalTextureArrayFeatureProcessor: Simulate");
AZ_UNUSED(packet);
if (m_deviceBufferNeedsUpdate)
@@ -158,7 +158,7 @@ namespace AZ
void DecalTextureArrayFeatureProcessor::Render(const RPI::FeatureProcessor::RenderPacket& packet)
{
// Note that decals are rendered as part of the forward shading pipeline. We only need to bind the decal buffers/textures in here.
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "DecalTextureArrayFeatureProcessor: Render");
for (const RPI::ViewPtr& view : packet.m_views)
{
@@ -294,7 +294,7 @@ namespace AZ
void DecalTextureArrayFeatureProcessor::SetDecalMaterial(const DecalHandle handle, const AZ::Data::AssetId material)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "DecalTextureArrayFeatureProcessor: SetDecalMaterial");
if (handle.IsNull())
{
AZ_Warning("DecalTextureArrayFeatureProcessor", false, "Invalid handle passed to DecalTextureArrayFeatureProcessor::SetDecalMaterial().");
@@ -364,7 +364,7 @@ namespace AZ
void DecalTextureArrayFeatureProcessor::OnAssetReady(const Data::Asset asset)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "DecalTextureArrayFeatureProcessor: OnAssetReady");
const Data::AssetId& assetId = asset->GetId();
const RPI::MaterialAsset* materialAsset = asset.GetAs();
diff --git a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp
index 543851da0f..d79240d4f1 100644
--- a/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.cpp
@@ -111,7 +111,7 @@ namespace AZ
void DiffuseProbeGridFeatureProcessor::Simulate([[maybe_unused]] const FeatureProcessor::SimulatePacket& packet)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "DiffuseProbeGridFeatureProcessor: Simulate");
// update pipeline states
if (m_needUpdatePipelineStates)
diff --git a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiPass.cpp b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiPass.cpp
index b6ca1191dd..1a68317532 100644
--- a/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiPass.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/ImGui/ImGuiPass.cpp
@@ -577,8 +577,7 @@ namespace AZ
void ImGuiPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("Pass", "ImGuiPass: Execute");
+ AZ_PROFILE_SCOPE(AzRender, "ImGuiPass: BuildCommandListInternal");
context.GetCommandList()->SetViewport(m_viewportState);
@@ -607,8 +606,7 @@ namespace AZ
uint32_t ImGuiPass::UpdateImGuiResources()
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("Pass", "ImGuiPass: UpdateImGuiResources");
+ AZ_PROFILE_SCOPE(AzRender, "ImGuiPass: UpdateImGuiResources");
auto imguiContextScope = ImguiContextScope(m_imguiContext);
diff --git a/Gems/Atom/Feature/Common/Code/Source/ImageBasedLights/ImageBasedLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ImageBasedLights/ImageBasedLightFeatureProcessor.cpp
index 7c41379a14..9cdad34f17 100644
--- a/Gems/Atom/Feature/Common/Code/Source/ImageBasedLights/ImageBasedLightFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/ImageBasedLights/ImageBasedLightFeatureProcessor.cpp
@@ -50,7 +50,7 @@ namespace AZ
void ImageBasedLightFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "ImageBasedLightFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "ImageBasedLightFeatureProcessor: Simulate");
AZ_UNUSED(packet);
m_sceneSrg->SetImage(m_specularEnvMapIndex, m_specular);
diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp
index 8ab324cdf7..52019f8d2f 100644
--- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp
@@ -75,8 +75,7 @@ namespace AZ
void MeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("RPI", "MeshFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "MeshFeatureProcessor: Simulate");
AZ_UNUSED(packet);
AZStd::concurrency_check_scope scopeCheck(m_meshDataChecker);
@@ -149,7 +148,7 @@ namespace AZ
const MeshHandleDescriptor& descriptor,
const MaterialAssignmentMap& materials)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "MeshFeatureProcessor: AcquireMesh");
// don't need to check the concurrency during emplace() because the StableDynamicArray won't move the other elements during insertion
MeshHandle meshDataHandle = m_meshData.emplace();
@@ -984,7 +983,7 @@ namespace AZ
void MeshDataInstance::UpdateDrawPackets(bool forceUpdate /*= false*/)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "MeshDataInstance:: UpdateDrawPackets");
for (auto& drawPacketList : m_drawPacketListsByLod)
{
for (auto& drawPacket : drawPacketList)
@@ -999,7 +998,7 @@ namespace AZ
void MeshDataInstance::BuildCullable()
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "MeshDataInstance: BuildCullable");
AZ_Assert(m_cullableNeedsRebuild, "This function only needs to be called if the cullable to be rebuilt");
AZ_Assert(m_model, "The model has not finished loading yet");
@@ -1076,7 +1075,7 @@ namespace AZ
void MeshDataInstance::UpdateCullBounds(const TransformServiceFeatureProcessor* transformService)
{
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "MeshDataInstance: UpdateCullBounds");
AZ_Assert(m_cullBoundsNeedsUpdate, "This function only needs to be called if the culling bounds need to be rebuilt");
AZ_Assert(m_model, "The model has not finished loading yet");
diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcess/PostProcessFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcess/PostProcessFeatureProcessor.cpp
index 35294d7403..d5b3ebd63b 100644
--- a/Gems/Atom/Feature/Common/Code/Source/PostProcess/PostProcessFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/PostProcess/PostProcessFeatureProcessor.cpp
@@ -49,7 +49,7 @@ namespace AZ
void PostProcessFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PostProcessFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "PostProcessFeatureProcessor: Simulate");
AZ_UNUSED(packet);
UpdateTime();
diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp
index 56d413e84f..a795d88212 100644
--- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/SMAAFeatureProcessor.cpp
@@ -161,7 +161,7 @@ namespace AZ
void SMAAFeatureProcessor::Render([[maybe_unused]] const SMAAFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SMAAFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "SMAAFeatureProcessor: Render");
UpdateConvertToPerceptualPass();
UpdateEdgeDetectionPass();
diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp
index 341d1a0274..91c72013dd 100644
--- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp
@@ -154,8 +154,7 @@ namespace AZ
void ReflectionProbeFeatureProcessor::Simulate([[maybe_unused]] const FeatureProcessor::SimulatePacket& packet)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("ReflectionProbe", "ReflectionProbeFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(AzRender, "ReflectionProbeFeatureProcessor: Simulate");
// update pipeline states
if (m_needUpdatePipelineStates)
@@ -194,7 +193,6 @@ namespace AZ
if (m_probeSortRequired)
{
AZ_PROFILE_SCOPE(AzRender, "Sort reflection probes");
- AZ_ATOM_PROFILE_FUNCTION("ReflectionProbe", "ReflectionProbeFeatureProcessor: Sort reflection probes");
// sort the probes by descending inner volume size, so the smallest volumes are rendered last
auto sortFn = [](AZStd::shared_ptr const& probe1, AZStd::shared_ptr const& probe2) -> bool
diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp
index 68c31bd859..8fb971f8f5 100644
--- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp
@@ -486,7 +486,7 @@ namespace AZ::Render
void ProjectedShadowFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& /*packet*/)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "ProjectedShadowFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "ProjectedShadowFeatureProcessor: Simulate");
if (m_shadowmapPassNeedsUpdate)
{
@@ -581,7 +581,7 @@ namespace AZ::Render
void ProjectedShadowFeatureProcessor::Render(const ProjectedShadowFeatureProcessor::RenderPacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "ProjectedShadowFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(RPI, "ProjectedShadowFeatureProcessor: Render");
if (!m_projectedShadowmapsPasses.empty())
{
diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp
index 0aa72bf2ca..500eadf110 100644
--- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp
@@ -69,8 +69,7 @@ namespace AZ
void SkinnedMeshFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet)
{
- AZ_PROFILE_FUNCTION(AzRender);
- AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Render");
+ AZ_PROFILE_SCOPE(AzRender, "SkinnedMeshFeatureProcessor: Render");
#if 0 //[GFX_TODO][ATOM-13564] Temporarily disable skinning culling until we figure out how to hook up visibility & lod selection with skinning:
//Setup the culling workgroup (it will be re-used for each view)
diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp
index b84d4347a7..4146a669fe 100644
--- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp
@@ -534,7 +534,7 @@ namespace AZ
// lod0 Positions[^ ^] lod0Normals[^ ^] lod1Positions[^ ^] lod1Normals[^ ^]
// lod0 subMesh0+1 Positions[^ ^^ ^] lod0 subMesh0+1 Normals[^ ^^ ^] lod1 sm0+1 pos[^ ^^ ^] lod1 sm0+1 norm[^ ^^ ^]
- AZ_PROFILE_FUNCTION(AzRender);
+ AZ_PROFILE_SCOPE(AzRender, "SkinnedMeshInputBuffers: CreateSkinnedMeshInstance");
AZStd::intrusive_ptr instance = aznew SkinnedMeshInstance;
// Each model gets a unique, random ID, so if the same source model is used for multiple instances, multiple target models will be created.
diff --git a/Gems/Atom/Feature/Common/Code/Source/SkyBox/SkyBoxFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkyBox/SkyBoxFeatureProcessor.cpp
index d0150fc0c2..d4fa3f52be 100644
--- a/Gems/Atom/Feature/Common/Code/Source/SkyBox/SkyBoxFeatureProcessor.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/SkyBox/SkyBoxFeatureProcessor.cpp
@@ -105,7 +105,7 @@ namespace AZ
void SkyBoxFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "SkyBoxFeatureProcessor: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "SkyBoxFeatureProcessor: Simulate");
AZ_UNUSED(packet);
m_sceneSrg->SetConstant(m_skyboxEnableIndex, m_enable);
diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h
index 3fedc99566..70c5771b57 100644
--- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h
+++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h
@@ -9,6 +9,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -21,15 +22,15 @@ namespace AZ
//! Structure that is used to cache a timed region into the thread's local storage.
struct CachedTimeRegion
{
- //! Structure that the profiling macro utilizes to create statically initialized instance to create string
- //! literals in static memory
+ //! Structure used internally for caching assumed global string pointers (ideally literals) to the marker group/region
+ //! NOTE: When used in a separate shared library, the library mustn't be unloaded before the CpuProfiler is shutdown.
struct GroupRegionName
{
GroupRegionName() = delete;
GroupRegionName(const char* const group, const char* const region);
-
- const char* const m_groupName = nullptr;
- const char* const m_regionName = nullptr;
+
+ const char* m_groupName = nullptr;
+ const char* m_regionName = nullptr;
struct Hash
{
@@ -39,31 +40,16 @@ namespace AZ
};
CachedTimeRegion() = default;
- CachedTimeRegion(const GroupRegionName* groupRegionName);
- CachedTimeRegion(const GroupRegionName* groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick);
+ CachedTimeRegion(const GroupRegionName& groupRegionName);
+ CachedTimeRegion(const GroupRegionName& groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick);
- //! Pointer to the GroupRegionName static instance.
- //! NOTE: When used in a separate shared library, the library mustn't be unloaded before
- //! the CpuProfiler is shutdown.
- const GroupRegionName* m_groupRegionName = nullptr;
+ GroupRegionName m_groupRegionName{nullptr, nullptr};
uint16_t m_stackDepth = 0u;
AZStd::sys_time_t m_startTick = 0;
AZStd::sys_time_t m_endTick = 0;
};
- //! Helper class used as a RAII-style mechanism for the macros to begin and end a region.
- class TimeRegion : public CachedTimeRegion
- {
- public:
- TimeRegion() = delete;
- TimeRegion(const GroupRegionName* groupRegionName);
- ~TimeRegion();
-
- //! End region
- void EndRegion();
- };
-
//! Interface class of the CpuProfiler
class CpuProfiler
{
@@ -80,12 +66,6 @@ namespace AZ
static CpuProfiler* Get();
- //! Add a new time region
- virtual void BeginTimeRegion(TimeRegion& timeRegion) = 0;
-
- //! Ends a time region
- virtual void EndTimeRegion() = 0;
-
//! Get the last frame's TimeRegionMap
virtual const TimeRegionMap& GetTimeRegionMap() const = 0;
@@ -101,38 +81,7 @@ namespace AZ
virtual void SetProfilerEnabled(bool enabled) = 0;
virtual bool IsProfilerEnabled() const = 0 ;
-
- //! Used by AZ_ATOM_PROFILE_DYNAMIC to create GroupRegionNames with known lifetimes.
- virtual const CachedTimeRegion::GroupRegionName& InsertDynamicName(const char* groupName, const AZStd::string& regionName) = 0;
};
} // namespace RPI
} // namespace AZ
-
-//! Utility functions for timing a section of code and writing the timing (in cycles) to a new, named time region inside the
-//! provided statistics data.
-
-//! Supply a group and region to the time region
-#define AZ_ATOM_PROFILE_TIME_GROUP_REGION(groupName, regionName) \
- static const AZ::RHI::CachedTimeRegion::GroupRegionName AZ_JOIN(groupRegionName, __LINE__)(groupName, regionName); \
- AZ::RHI::TimeRegion AZ_JOIN(timeRegion, __LINE__)(&AZ_JOIN(groupRegionName, __LINE__));
-
-//! Supply a region to the time region; "Default" will be used for the group
-#define AZ_ATOM_PROFILE_TIME_REGION(regionName) \
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("Default", regionName)
-
-//! Used to create a time region; "Default" will be used for the group, and __FUNCTION__ macro for the region
-#define AZ_ATOM_PROFILE_TIME_FUNCTION() \
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("Default", AZ_FUNCTION_SIGNATURE)
-
-//! Macro that combines the AZ_TRACE_METHOD with time profiling macro
-#define AZ_ATOM_PROFILE_FUNCTION(groupName, regionName) \
- AZ_TRACE_METHOD(); \
- AZ_ATOM_PROFILE_TIME_GROUP_REGION(groupName, regionName) \
-
-//! Macro that allows for region names to be submitted at runtime. Use sparingly - this acquires a lock and allocates new objects within a map.
-#define AZ_ATOM_PROFILE_DYNAMIC(groupName, regionName) \
- static_assert(AZStd::is_convertible_v, "Runtime group names are not allowed, use a static string literal instead."); \
- const AZ::RHI::CachedTimeRegion::GroupRegionName& AZ_JOIN(groupRegionName, __LINE__) = \
- AZ::RHI::CpuProfiler::Get()->InsertDynamicName(groupName, regionName); \
- AZ::RHI::TimeRegion AZ_JOIN(timeRegion, __LINE__)(&AZ_JOIN(groupRegionName, __LINE__));
diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h
index 92b56880b7..29886625ea 100644
--- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h
+++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h
@@ -43,13 +43,13 @@ namespace AZ
static constexpr uint32_t TimeRegionStackSize = 2048u;
// Adds a region to the stack, gets called each time a region begins
- void RegionStackPushBack(TimeRegion& timeRegion);
+ void RegionStackPushBack(CachedTimeRegion& timeRegion);
// Pops a region from the stack, gets called each time a region ends
void RegionStackPopBack();
// Add a new cached time region. If the stack is empty, flush all entries to the cached map
- void AddCachedRegion(CachedTimeRegion&& timeRegionCached);
+ void AddCachedRegion(const CachedTimeRegion& timeRegionCached);
// Tries to flush the map to the passed parameter, only if the thread's mutex is unlocked
void TryFlushCachedMap(CpuProfiler::ThreadTimeRegionMap& cachedRegionMap);
@@ -63,7 +63,7 @@ namespace AZ
// Use fixed vectors to avoid re-allocating new elements
// Keeps track of the regions that added and removed using the macro
- AZStd::fixed_vector m_timeRegionStack;
+ AZStd::fixed_vector m_timeRegionStack;
// Keeps track of regions that completed (i.e regions that was pushed and popped from the stack)
// Intermediate storage point for the CachedTimeRegions, when the stack is empty, all entries will be
@@ -85,7 +85,8 @@ namespace AZ
//! forwards the request to profile a region to the appropriate thread. The user is able to request all
//! cached regions, which are stored on a per thread frequency.
class CpuProfilerImpl final
- : public CpuProfiler
+ : public AZ::Debug::Profiler
+ , public CpuProfiler
, public SystemTickBus::Handler
{
friend class CpuTimingLocalStorage;
@@ -107,16 +108,17 @@ namespace AZ
// m_timeRegionMap so that the next frame has up-to-date profiling data.
void OnSystemTick() final override;
+ //! AZ::Debug::Profiler overrides...
+ void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName) final override;
+ void EndRegion(const AZ::Debug::Budget* budget) final override;
+
//! CpuProfiler overrides...
- void BeginTimeRegion(TimeRegion& timeRegion) final override;
- void EndTimeRegion() final override;
const TimeRegionMap& GetTimeRegionMap() const final override;
bool BeginContinuousCapture() final override;
bool EndContinuousCapture(AZStd::ring_buffer& flushTarget) final override;
bool IsContinuousCaptureInProgress() const final override;
void SetProfilerEnabled(bool enabled) final override;
bool IsProfilerEnabled() const final override;
- const CachedTimeRegion::GroupRegionName& InsertDynamicName(const char* groupName, const AZStd::string& regionName) final override;
private:
static constexpr AZStd::size_t MaxFramesToSave = 2 * 60 * 120; // 2 minutes of 120fps
@@ -133,15 +135,6 @@ namespace AZ
AZStd::vector, AZ::OSStdAllocator> m_registeredThreads;
AZStd::mutex m_threadRegisterMutex;
- // Pool for GroupRegionNames that are generated at runtime through AZ_ATOM_PROFILE_DYNAMIC. Each unique
- // combination of group name and region name submitted will be stored in this pool to emulate static lifetime.
- AZStd::unordered_set m_dynamicGroupRegionNamePool;
-
- // String pool for storing region names submitted at runtime. Each call to AZ_ATOM_PROFILE_DYNAMIC will either construct
- // a string in this pool or use an already-existing entry.
- AZStd::unordered_set m_regionNameStringPool;
- AZStd::mutex m_dynamicNameMutex;
-
// Thread local storage, gets lazily allocated when a thread is created
static thread_local CpuTimingLocalStorage* ms_threadLocalStorage;
diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/MemorySubAllocator.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/MemorySubAllocator.h
index a10afd880f..bdeb252174 100644
--- a/Gems/Atom/RHI/Code/Include/Atom/RHI/MemorySubAllocator.h
+++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/MemorySubAllocator.h
@@ -157,7 +157,7 @@ namespace AZ
template
void MemorySubAllocator::GarbageCollect()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "MemorySubAllocator: GarbageCollect");
+ AZ_PROFILE_SCOPE(RHI, "MemorySubAllocator: GarbageCollect");
for (PageContext& pageContext : m_pageContexts)
{
pageContext.m_allocator.GarbageCollect();
diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/ObjectCollector.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/ObjectCollector.h
index cd8a85a385..988416326e 100644
--- a/Gems/Atom/RHI/Code/Include/Atom/RHI/ObjectCollector.h
+++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/ObjectCollector.h
@@ -173,7 +173,7 @@ namespace AZ
template
void ObjectCollector::Collect(bool forceFlush)
{
- AZ_ATOM_PROFILE_FUNCTION("DX12", "ObjectCollector: Collect");
+ AZ_PROFILE_SCOPE(RHI, "ObjectCollector: Collect");
m_mutex.lock();
if (m_pendingObjects.size())
{
diff --git a/Gems/Atom/RHI/Code/Source/RHI/AsyncWorkQueue.cpp b/Gems/Atom/RHI/Code/Source/RHI/AsyncWorkQueue.cpp
index 52021fa736..a076bf3e58 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/AsyncWorkQueue.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/AsyncWorkQueue.cpp
@@ -128,7 +128,7 @@ namespace AZ
return;
}
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncWorkQueue: WaitToFinish");
AZStd::unique_lock lock(m_waitWorkItemMutex);
m_waitWorkItemCondition.wait(lock, [&]() {return HasFinishedWork(workHandle); });
diff --git a/Gems/Atom/RHI/Code/Source/RHI/BufferPool.cpp b/Gems/Atom/RHI/Code/Source/RHI/BufferPool.cpp
index 9849db254e..5d06d02668 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/BufferPool.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/BufferPool.cpp
@@ -163,7 +163,7 @@ namespace AZ
return ResultCode::InvalidArgument;
}
- AZ_ATOM_PROFILE_FUNCTION("RHI", "BufferPool::OrphanBuffer");
+ AZ_PROFILE_SCOPE(RHI, "BufferPool::OrphanBuffer");
return OrphanBufferInternal(buffer);
}
diff --git a/Gems/Atom/RHI/Code/Source/RHI/CommandQueue.cpp b/Gems/Atom/RHI/Code/Source/RHI/CommandQueue.cpp
index 15621c7f2b..0983d4db22 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/CommandQueue.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/CommandQueue.cpp
@@ -22,7 +22,7 @@ namespace AZ
ResultCode CommandQueue::Init(Device& device, const CommandQueueDescriptor& descriptor)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueue: Init");
#if defined (AZ_RHI_ENABLE_VALIDATION)
if (IsInitialized())
@@ -83,7 +83,7 @@ namespace AZ
void CommandQueue::FlushCommands()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "CommandQueue: FlushCommands");
+ AZ_PROFILE_SCOPE(RHI, "CommandQueue: FlushCommands");
while (!m_isWorkQueueEmpty && !m_isQuitting)
{
AZStd::this_thread::yield();
diff --git a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp
index e16b89b5cd..1bc17adb22 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp
@@ -27,38 +27,14 @@ namespace AZ
return Interface::Get();
}
- // --- TimeRegion ---
-
- TimeRegion::TimeRegion(const GroupRegionName* groupRegionName) :
- CachedTimeRegion(groupRegionName)
- {
- if (CpuProfiler::Get())
- {
- CpuProfiler::Get()->BeginTimeRegion(*this);
- }
- }
-
- TimeRegion::~TimeRegion()
- {
- EndRegion();
- }
-
- void TimeRegion::EndRegion()
- {
- if (CpuProfiler::Get())
- {
- CpuProfiler::Get()->EndTimeRegion();
- }
- }
-
// --- CachedTimeRegion ---
- CachedTimeRegion::CachedTimeRegion(const GroupRegionName* groupRegionName)
+ CachedTimeRegion::CachedTimeRegion(const GroupRegionName& groupRegionName)
{
m_groupRegionName = groupRegionName;
}
- CachedTimeRegion::CachedTimeRegion(const GroupRegionName* groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick)
+ CachedTimeRegion::CachedTimeRegion(const GroupRegionName& groupRegionName, uint16_t stackDepth, uint64_t startTick, uint64_t endTick)
{
m_groupRegionName = groupRegionName;
m_stackDepth = stackDepth;
@@ -92,6 +68,7 @@ namespace AZ
void CpuProfilerImpl::Init()
{
+ Interface::Register(this);
Interface::Register(this);
m_initialized = true;
SystemTickBus::Handler::BusConnect();
@@ -106,6 +83,7 @@ namespace AZ
}
// When this call is made, no more thread profiling calls can be performed anymore
Interface::Unregister(this);
+ Interface::Unregister(this);
// Wait for the remaining threads that might still be processing its profiling calls
AZStd::unique_lock shutdownLock(m_shutdownMutex);
@@ -121,7 +99,7 @@ namespace AZ
SystemTickBus::Handler::BusDisconnect();
}
- void CpuProfilerImpl::BeginTimeRegion(TimeRegion& timeRegion)
+ void CpuProfilerImpl::BeginRegion(const AZ::Debug::Budget* budget, const char* eventName)
{
// Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
if (m_shutdownMutex.try_lock_shared())
@@ -132,6 +110,7 @@ namespace AZ
RegisterThreadStorage();
// Push it to the stack
+ CachedTimeRegion timeRegion({budget->Name(), eventName});
ms_threadLocalStorage->RegionStackPushBack(timeRegion);
}
@@ -139,11 +118,12 @@ namespace AZ
}
}
- void CpuProfilerImpl::EndTimeRegion()
+ void CpuProfilerImpl::EndRegion([[maybe_unused]] const AZ::Debug::Budget* budget)
{
// Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
if (m_shutdownMutex.try_lock_shared())
{
+ // guard against enabling mid-marker
if (m_enabled && ms_threadLocalStorage != nullptr)
{
ms_threadLocalStorage->RegionStackPopBack();
@@ -232,19 +212,6 @@ namespace AZ
return m_enabled;
}
- const CachedTimeRegion::GroupRegionName& CpuProfilerImpl::InsertDynamicName(const char* groupName, const AZStd::string& regionName)
- {
- AZStd::scoped_lock lock(m_dynamicNameMutex);
- AZ_Warning("CpuProfiler", m_regionNameStringPool.size() < MaxRegionStringPoolSize,
- "Stored dynamic region names are accumulating. Consider removing a AZ_ATOM_PROFILE_DYNAMIC invocation.");
- auto [regionNameItr, wasRegionInserted] = m_regionNameStringPool.insert(regionName);
-
- CachedTimeRegion::GroupRegionName newGroupRegionName(groupName, regionNameItr->c_str());
- auto [groupRegionNameItr, wasGroupRegionInserted] = m_dynamicGroupRegionNamePool.insert(newGroupRegionName);
-
- return *groupRegionNameItr;
- }
-
void CpuProfilerImpl::OnSystemTick()
{
if (!m_enabled)
@@ -307,7 +274,7 @@ namespace AZ
m_deleteFlag = true;
}
- void CpuTimingLocalStorage::RegionStackPushBack(TimeRegion& timeRegion)
+ void CpuTimingLocalStorage::RegionStackPushBack(CachedTimeRegion& timeRegion)
{
// If it was (re)enabled, clear the lists first
if (m_clearContainers)
@@ -323,13 +290,13 @@ namespace AZ
timeRegion.m_stackDepth = static_cast(m_stackLevel);
AZ_Assert(m_timeRegionStack.size() < TimeRegionStackSize, "Adding too many time regions to the stack. Increase the size of TimeRegionStackSize.");
- m_timeRegionStack.push_back(&timeRegion);
+ m_timeRegionStack.push_back(timeRegion);
// Increment the stack
m_stackLevel++;
// Set the starting time at the end, to avoid recording the minor overhead
- timeRegion.m_startTick = AZStd::GetTimeNowTicks();
+ m_timeRegionStack.back().m_startTick = AZStd::GetTimeNowTicks();
}
void CpuTimingLocalStorage::RegionStackPopBack()
@@ -344,23 +311,23 @@ namespace AZ
const AZStd::sys_time_t endRegionTime = AZStd::GetTimeNowTicks();
AZ_Assert(!m_timeRegionStack.empty(), "Trying to pop an element in the stack, but it's empty.");
- TimeRegion* back = m_timeRegionStack.back();
+ CachedTimeRegion back = m_timeRegionStack.back();
m_timeRegionStack.pop_back();
// Set the ending time
- back->m_endTick = endRegionTime;
+ back.m_endTick = endRegionTime;
// Decrement the stack
m_stackLevel--;
// Add an entry to the cached region
- AddCachedRegion(CachedTimeRegion(back->m_groupRegionName, back->m_stackDepth, back->m_startTick, back->m_endTick));
+ AddCachedRegion(back);
}
// Gets called when region ends and all data is set
- void CpuTimingLocalStorage::AddCachedRegion(CachedTimeRegion&& timeRegionCached)
+ void CpuTimingLocalStorage::AddCachedRegion(const CachedTimeRegion& timeRegionCached)
{
- if (m_hitSizeLimitMap[timeRegionCached.m_groupRegionName->m_regionName])
+ if (m_hitSizeLimitMap[timeRegionCached.m_groupRegionName.m_regionName])
{
return;
}
@@ -379,12 +346,12 @@ namespace AZ
// Add the cached regions to the map
for (auto& cachedTimeRegion : m_cachedTimeRegions)
{
- const AZStd::string regionName = cachedTimeRegion.m_groupRegionName->m_regionName;
+ const AZStd::string regionName = cachedTimeRegion.m_groupRegionName.m_regionName;
AZStd::vector& regionVec = m_cachedTimeRegionMap[regionName];
regionVec.push_back(cachedTimeRegion);
if (regionVec.size() >= TimeRegionStackSize)
{
- m_hitSizeLimitMap[cachedTimeRegion.m_groupRegionName->m_regionName] = true;
+ m_hitSizeLimitMap.insert_or_assign(AZStd::move(regionName), true);
}
}
@@ -448,8 +415,8 @@ namespace AZ
CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry::CpuProfilingStatisticsSerializerEntry(
const RHI::CachedTimeRegion& cachedTimeRegion, AZStd::thread_id threadId)
{
- m_groupName = cachedTimeRegion.m_groupRegionName->m_groupName;
- m_regionName = cachedTimeRegion.m_groupRegionName->m_regionName;
+ m_groupName = cachedTimeRegion.m_groupRegionName.m_groupName;
+ m_regionName = cachedTimeRegion.m_groupRegionName.m_regionName;
m_stackDepth = cachedTimeRegion.m_stackDepth;
m_startTick = cachedTimeRegion.m_startTick;
m_endTick = cachedTimeRegion.m_endTick;
diff --git a/Gems/Atom/RHI/Code/Source/RHI/Device.cpp b/Gems/Atom/RHI/Code/Source/RHI/Device.cpp
index 3af09717df..9453ff79ee 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/Device.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/Device.cpp
@@ -128,7 +128,7 @@ namespace AZ
{
if (ValidateIsInitialized() && ValidateIsInFrame())
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "Device: EndFrame");
+ AZ_PROFILE_SCOPE(RHI, "Device: EndFrame");
EndFrameInternal();
m_isInFrame = false;
return ResultCode::Success;
@@ -150,7 +150,7 @@ namespace AZ
{
if (ValidateIsInitialized() && ValidateIsNotInFrame())
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "Device: CompileMemoryStatistics");
+ AZ_PROFILE_SCOPE(RHI, "Device: CompileMemoryStatistics");
MemoryStatisticsBuilder builder;
builder.Begin(memoryStatistics, reportFlags);
CompileMemoryStatisticsInternal(builder);
diff --git a/Gems/Atom/RHI/Code/Source/RHI/Fence.cpp b/Gems/Atom/RHI/Code/Source/RHI/Fence.cpp
index b35260caca..ca0493a52c 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/Fence.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/Fence.cpp
@@ -81,7 +81,7 @@ namespace AZ
return ResultCode::InvalidOperation;
}
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "Fence: WaitOnCpu");
WaitOnCpuInternal();
return ResultCode::Success;
}
diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp
index ff6ecb4df5..b85fa88f34 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp
@@ -73,7 +73,7 @@ namespace AZ
void FrameGraph::Clear()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraph: Clear");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraph: Clear");
for (Scope* scope : m_scopes)
{
scope->Deactivate();
@@ -126,7 +126,7 @@ namespace AZ
ResultCode FrameGraph::End()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraph: End");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraph: End");
ResultCode resultCode = ValidateEnd();
if (resultCode != ResultCode::Success)
{
diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphCompiler.cpp
index c4204a4a90..5c5691d975 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphCompiler.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphCompiler.cpp
@@ -121,7 +121,7 @@ namespace AZ
*/
MessageOutcome FrameGraphCompiler::Compile(const FrameGraphCompileRequest& request)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: Compile");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: Compile");
MessageOutcome outcome = ValidateCompileRequest(request);
if (!outcome)
@@ -146,7 +146,7 @@ namespace AZ
/// [Phase 4] Compile platform-specific scope data after all attachments and views have been compiled.
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: Scope Compile");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: Scope Compile");
for (Scope* scope : frameGraph.GetScopes())
{
@@ -162,7 +162,7 @@ namespace AZ
FrameGraph& frameGraph,
FrameSchedulerCompileFlags compileFlags)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileQueueCentricScopeGraph");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileQueueCentricScopeGraph");
const bool disableAsyncQueues = CheckBitsAll(compileFlags, FrameSchedulerCompileFlags::DisableAsyncQueues);
if (disableAsyncQueues)
@@ -480,7 +480,7 @@ namespace AZ
return;
}
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileTransientAttachments");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileTransientAttachments");
ExtendTransientAttachmentAsyncQueueLifetimes(frameGraph, compileFlags);
@@ -769,7 +769,7 @@ namespace AZ
void FrameGraphCompiler::CompileResourceViews(const FrameGraphAttachmentDatabase& attachmentDatabase)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileResourceViews");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileResourceViews");
for (ImageFrameAttachment* imageAttachment : attachmentDatabase.GetImageAttachments())
{
diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp
index 342e537993..429081cd2c 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp
@@ -71,14 +71,13 @@ namespace AZ
void FrameGraphExecuter::Begin(const FrameGraph& frameGraph)
{
- AZ_TRACE_METHOD();
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphExecuter: Begin");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphExecuter: Begin");
BeginInternal(frameGraph);
}
void FrameGraphExecuter::End()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphExecuter: End");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphExecuter: End");
AZ_Assert(m_pendingGroups.empty(), "Pending contexts in queue.");
m_groups.clear();
EndInternal();
diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp
index fe69f56856..90218709fa 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp
@@ -137,7 +137,7 @@ namespace AZ
ResultCode FrameScheduler::ImportScopeProducer(ScopeProducer& scopeProducer)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: ImportScopeProducer");
if (!ValidateIsProcessing())
{
@@ -171,14 +171,14 @@ namespace AZ
MessageOutcome FrameScheduler::Compile(const FrameSchedulerCompileRequest& compileRequest)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: Compile");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: Compile");
PrepareProducers();
m_compileRequest = compileRequest;
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "FrameScheduler: Compile: OnFrameCompile");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: Compile: OnFrameCompile");
FrameEventBus::Broadcast(&FrameEventBus::Events::OnFrameCompile);
}
@@ -193,7 +193,7 @@ namespace AZ
if (outcome.IsSuccess())
{
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "FrameScheduler: Compile: OnFrameCompileEnd");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: Compile: OnFrameCompileEnd");
FrameEventBus::Broadcast(&FrameEventBus::Events::OnFrameCompileEnd, *m_frameGraph);
}
@@ -216,8 +216,7 @@ namespace AZ
void FrameScheduler::PrepareProducers()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: PrepareProducers");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: PrepareProducers");
for (ScopeProducer* scopeProducer : m_scopeProducers)
{
@@ -237,8 +236,7 @@ namespace AZ
void FrameScheduler::CompileProducers()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: CompileProducers");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: CompileProducers");
for (ScopeProducer* scopeProducer : m_scopeProducers)
{
@@ -249,8 +247,7 @@ namespace AZ
void FrameScheduler::CompileShaderResourceGroups()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: CompileShaderResourceGroups");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: CompileShaderResourceGroups");
// Execute all queued resource invalidations, which will mark SRG's for compilation.
{
@@ -286,7 +283,7 @@ namespace AZ
const auto compileGroupsForIntervalLambda = [srgPool, interval]()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler : compileGroupsForIntervalLambda");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler : compileGroupsForIntervalLambda");
srgPool->CompileGroupsForInterval(interval);
};
@@ -322,8 +319,7 @@ namespace AZ
void FrameScheduler::BuildRayTracingShaderTables()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: BuildRayTracingShaderTables");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: BuildRayTracingShaderTables");
for (auto rayTracingShaderTable : m_rayTracingShaderTablesToBuild)
{
@@ -341,8 +337,7 @@ namespace AZ
ResultCode FrameScheduler::BeginFrame()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: BeginFrame");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: BeginFrame");
if (!ValidateIsInitialized())
{
@@ -376,8 +371,7 @@ namespace AZ
ResultCode FrameScheduler::EndFrame()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: EndFrame");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: EndFrame");
if (Validation::IsEnabled())
{
@@ -404,7 +398,7 @@ namespace AZ
m_scopeProducerLookup.clear();
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "FrameScheduler: EndFrame: OnFrameEnd");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: EndFrame: OnFrameEnd");
FrameEventBus::Event(m_device, &FrameEventBus::Events::OnFrameEnd);
}
@@ -431,8 +425,7 @@ namespace AZ
void FrameScheduler::ExecuteGroupInternal(AZ::Job* parentJob, uint32_t groupIndex)
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: ExecuteGroupInternal");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: ExecuteGroupInternal");
FrameGraphExecuteGroup* executeGroup = m_frameGraphExecuter->BeginGroup(groupIndex);
const uint32_t contextCount = executeGroup->GetContextCount();
@@ -474,8 +467,7 @@ namespace AZ
void FrameScheduler::Execute(JobPolicy overrideJobPolicy)
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameScheduler: Execute");
+ AZ_PROFILE_SCOPE(RHI, "FrameScheduler: Execute");
const uint32_t groupCount = m_frameGraphExecuter->GetGroupCount();
const JobPolicy platformJobPolicy = m_frameGraphExecuter->GetJobPolicy();
diff --git a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp
index 6e98456933..868580f0c7 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp
@@ -212,7 +212,7 @@ namespace AZ
void PipelineStateCache::Compact()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "PipelineStateCache: Compact");
+ AZ_PROFILE_SCOPE(RHI, "PipelineStateCache: Compact");
AZStd::unique_lock lock(m_mutex);
// Merge the pending cache into the read-only cache.
diff --git a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp
index a381209d2b..69f7002435 100644
--- a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp
+++ b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp
@@ -187,8 +187,7 @@ namespace AZ
void RHISystem::FrameUpdate(FrameGraphCallback frameGraphCallback)
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("RHI", "RHISystem: FrameUpdate");
+ AZ_PROFILE_SCOPE(RHI, "RHISystem: FrameUpdate");
{
AZ_PROFILE_SCOPE(RHI, "main per-frame work");
@@ -201,7 +200,7 @@ namespace AZ
* own RHI scopes to the frame scheduler. This happens prior to the RPI pass graph registration.
*/
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "RHISystem: FrameUpdate: OnFramePrepare");
+ AZ_PROFILE_SCOPE(RHI, "RHISystem: FrameUpdate: OnFramePrepare");
RHISystemNotificationBus::Broadcast(&RHISystemNotificationBus::Events::OnFramePrepare, m_frameScheduler);
}
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp
index 29b210e17e..cb9dac3864 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp
@@ -196,7 +196,7 @@ namespace AZ
AsyncUploadQueue::FramePacket* AsyncUploadQueue::BeginFramePacket()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: BeginFramePacket");
AZ_Assert(!m_recordingFrame, "The previous frame packet isn't ended");
FramePacket* framePacket = &m_framePackets[m_frameIndex];
@@ -212,7 +212,7 @@ namespace AZ
void AsyncUploadQueue::EndFramePacket(ID3D12CommandQueue* commandQueue)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: EndFramePacket");
AZ_Assert(m_recordingFrame, "The frame packet wasn't started. You need to call StartFramePacket first.");
AssertSuccess(m_commandList->Close());
@@ -229,7 +229,7 @@ namespace AZ
// [GFX TODO][ATOM-4205] Stage/Upload 3D streaming images more efficiently.
uint64_t AsyncUploadQueue::QueueUpload(const RHI::StreamingImageExpandRequest& request, uint32_t residentMip)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: QueueUpload");
uint64_t fenceValue = m_uploadFence.Increment();
@@ -475,7 +475,7 @@ namespace AZ
void AsyncUploadQueue::WaitForUpload(uint64_t fenceValue)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: WaitForUpload");
if (!IsUploadFinished(fenceValue))
{
@@ -489,7 +489,7 @@ namespace AZ
void AsyncUploadQueue::ProcessCallbacks(uint64_t fenceValue)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: ProcessCallbacks");
AZStd::lock_guard lock(m_callbackMutex);
while (m_callbacks.size() > 0 && m_callbacks.front().second <= fenceValue)
{
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp
index c5c71a9f43..efa9518ee1 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp
@@ -33,7 +33,7 @@ namespace AZ
void CommandListBase::Reset(ID3D12CommandAllocator* commandAllocator)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandListBase: Reset");
AZ_Assert(m_queuedBarriers.empty(), "Unflushed barriers in command list.");
m_commandList->Reset(commandAllocator, nullptr);
@@ -95,7 +95,7 @@ namespace AZ
{
if (m_queuedBarriers.size())
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandListBase: FlushBarriers");
m_commandList->ResourceBarrier((UINT)m_queuedBarriers.size(), m_queuedBarriers.data());
m_queuedBarriers.clear();
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListPool.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListPool.cpp
index c31fe8ada8..94262064da 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListPool.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListPool.cpp
@@ -175,7 +175,7 @@ namespace AZ
void CommandListAllocator::Collect()
{
- AZ_ATOM_PROFILE_FUNCTION("DX12", "CommandListAllocator: Collect");
+ AZ_PROFILE_SCOPE(RHI, "CommandListAllocator: Collect(DX12)");
for (uint32_t queueIdx = 0; queueIdx < RHI::HardwareQueueClassCount; ++queueIdx)
{
m_commandListSubAllocators[queueIdx].ForEach([](Internal::CommandListSubAllocator& commandListSubAllocator)
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueue.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueue.cpp
index 9d58217f01..0cde6aeb09 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueue.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueue.cpp
@@ -195,7 +195,7 @@ namespace AZ
void CommandQueue::UpdateTileMappings(CommandList& commandList)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueue: UpdateTileMappings");
for (const CommandList::TileMapRequest& request : commandList.GetTileMapRequests())
{
const uint32_t tileCount = request.m_sourceRegionSize.NumTiles;
@@ -229,7 +229,7 @@ namespace AZ
void CommandQueue::WaitForIdle()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueue: WaitForIdle");
Fence fence;
fence.Init(m_device.get(), RHI::FenceState::Reset);
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp
index 96d621df59..eee137026d 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp
@@ -101,7 +101,7 @@ namespace AZ
void CommandQueueContext::WaitForIdle()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: WaitForIdle");
for (uint32_t hardwareQueueIdx = 0; hardwareQueueIdx < RHI::HardwareQueueClassCount; ++hardwareQueueIdx)
{
if (m_commandQueues[hardwareQueueIdx])
@@ -113,7 +113,7 @@ namespace AZ
void CommandQueueContext::Begin()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: Begin");
{
AZ_PROFILE_SCOPE(RHI, "Clearing Command Queue Timers");
@@ -131,8 +131,7 @@ namespace AZ
void CommandQueueContext::End()
{
- AZ_PROFILE_FUNCTION(RHI);
- AZ_ATOM_PROFILE_FUNCTION("DX12", "CommandQueueContext: End");
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: End");
QueueGpuSignals(m_frameFences[m_currentFrameIndex]);
@@ -146,7 +145,6 @@ namespace AZ
{
AZ_PROFILE_SCOPE(RHI, "Wait and Reset Fence");
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("DX12", "CommandQueueContext: Wait on Fences");
FenceEvent event("FrameFence");
m_frameFences[m_currentFrameIndex].Wait(event);
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp
index 942b514b97..816b904e16 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp
@@ -341,7 +341,7 @@ namespace AZ
void DescriptorContext::GarbageCollect()
{
- AZ_ATOM_PROFILE_FUNCTION("DX12", "DescriptorContext: GarbageCollect");
+ AZ_PROFILE_SCOPE(RHI, "DescriptorContext: GarbageCollect(DX12)");
for (const auto& itr : m_platformLimitsDescriptor->m_descriptorHeapLimits)
{
for (uint32_t shaderVisibleIdx = 0; shaderVisibleIdx < PlatformLimitsDescriptor::NumHeapFlags; ++shaderVisibleIdx)
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp
index 7eaafc3705..722d535cb1 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp
@@ -204,7 +204,7 @@ namespace AZ
RHI::MessageOutcome FrameGraphCompiler::CompileInternal(const RHI::FrameGraphCompileRequest& request)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileInternal(DX12)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileInternal(DX12)");
RHI::FrameGraph& frameGraph = *request.m_frameGraph;
@@ -373,7 +373,7 @@ namespace AZ
void FrameGraphCompiler::CompileResourceBarriers(Scope* rootScope, const RHI::FrameGraphAttachmentDatabase& attachmentDatabase)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileResourceBarriers(DX12)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileResourceBarriers(DX12)");
for (RHI::BufferFrameAttachment* bufferFrameAttachment : attachmentDatabase.GetBufferAttachments())
{
@@ -394,7 +394,7 @@ namespace AZ
ResourceTransitionLoggerNull logger(bufferFrameAttachment.GetId());
#endif
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileBufferBarriers(DX12)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileBufferBarriers(DX12)");
Buffer& buffer = static_cast(*bufferFrameAttachment.GetBuffer());
RHI::BufferScopeAttachment* scopeAttachment = bufferFrameAttachment.GetFirstScopeAttachment();
@@ -469,7 +469,7 @@ namespace AZ
ResourceTransitionLoggerNull logger(imageFrameAttachment.GetId());
#endif
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileImageBarriers (DX12)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileImageBarriers (DX12)");
Image& image = static_cast(*imageFrameAttachment.GetImage());
RHI::ImageScopeAttachment* scopeAttachment = imageFrameAttachment.GetFirstScopeAttachment();
diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/StagingMemoryAllocator.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/StagingMemoryAllocator.cpp
index 1a32f06fa2..337b25a793 100644
--- a/Gems/Atom/RHI/DX12/Code/Source/RHI/StagingMemoryAllocator.cpp
+++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/StagingMemoryAllocator.cpp
@@ -55,7 +55,7 @@ namespace AZ
void StagingMemoryAllocator::GarbageCollect()
{
- AZ_ATOM_PROFILE_FUNCTION("DX12", "StagingMemoryAllocator: GarbageCollect");
+ AZ_PROFILE_SCOPE(RHI, "StagingMemoryAllocator: GarbageCollect(DX12)");
m_mediumBlockAllocators.ForEach([](MemoryLinearSubAllocator& subAllocator)
{
subAllocator.GarbageCollect();
diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandQueueContext.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandQueueContext.cpp
index 7f0c535383..fb11700e04 100644
--- a/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandQueueContext.cpp
+++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/CommandQueueContext.cpp
@@ -79,8 +79,8 @@ namespace AZ
void CommandQueueContext::End()
{
- AZ_PROFILE_FUNCTION(RHI);
-
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: End");
+
QueueGpuSignals(m_frameFences[m_currentFrameIndex]);
for (uint32_t hardwareQueueIdx = 0; hardwareQueueIdx < RHI::HardwareQueueClassCount; ++hardwareQueueIdx)
{
@@ -92,7 +92,6 @@ namespace AZ
{
AZ_PROFILE_SCOPE(RHI, "Wait and Reset Fence");
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "CommandQueueContext: Wait on Fences");
//Synchronize the CPU with the GPU by waiting on the fence until signalled by the GPU. CPU can only go upto
//RHI::Limits::Device::FrameCountMax frames ahead of the GPU
diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/FrameGraphCompiler.cpp
index 520e417f6b..a347ed9b12 100644
--- a/Gems/Atom/RHI/Metal/Code/Source/RHI/FrameGraphCompiler.cpp
+++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/FrameGraphCompiler.cpp
@@ -33,7 +33,7 @@ namespace AZ
RHI::MessageOutcome FrameGraphCompiler::CompileInternal(const RHI::FrameGraphCompileRequest& request)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileInternal(Metal)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileInternal(Metal)");
RHI::FrameGraph& frameGraph = *request.m_frameGraph;
if (!RHI::CheckBitsAny(request.m_compileFlags, RHI::FrameSchedulerCompileFlags::DisableAsyncQueues))
{
diff --git a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp
index 90ba04c3db..567f89b6e0 100644
--- a/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp
+++ b/Gems/Atom/RHI/Metal/Code/Source/RHI/SwapChain.cpp
@@ -192,7 +192,7 @@ namespace AZ
id SwapChain::RequestDrawable(bool isFrameCaptureEnabled)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "SwapChain::RequestDrawable");
+ AZ_PROFILE_SCOPE(RHI, "SwapChain::RequestDrawable");
m_metalView.metalLayer.framebufferOnly = !isFrameCaptureEnabled;
const uint32_t currentImageIndex = GetCurrentImageIndex();
if(m_drawables[currentImageIndex])
diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/AsyncUploadQueue.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/AsyncUploadQueue.cpp
index 7a8499ab9d..2c3958cb65 100644
--- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/AsyncUploadQueue.cpp
+++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/AsyncUploadQueue.cpp
@@ -451,7 +451,7 @@ namespace AZ
AsyncUploadQueue::FramePacket* AsyncUploadQueue::BeginFramePacket(Queue* queue)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: BeginFramePacket");
AZ_Assert(!m_recordingFrame, "The previous frame packet isn't ended.");
auto& device = static_cast(GetDevice());
@@ -471,7 +471,7 @@ namespace AZ
void AsyncUploadQueue::EndFramePacket(Queue* queue, Semaphore* semaphoreToSignal /*=nullptr*/)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: EndFramePacket");
AZ_Assert(m_recordingFrame, "The frame packet wasn't started. You need to call StartFramePacket first.");
m_commandList->EndCommandBuffer();
@@ -636,7 +636,7 @@ namespace AZ
void AsyncUploadQueue::ProcessCallback(const RHI::AsyncWorkHandle& handle)
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "AsyncUploadQueue: ProcessCallback");
AZStd::unique_lock lock(m_callbackListMutex);
auto findIter = m_callbackList.find(handle);
if (findIter != m_callbackList.end())
diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueueContext.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueueContext.cpp
index 39f0ac9d58..c0d34e2bf9 100644
--- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueueContext.cpp
+++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueueContext.cpp
@@ -42,7 +42,7 @@ namespace AZ
void CommandQueueContext::End()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: End");
for (auto& commandQueue : m_commandQueues)
{
@@ -55,7 +55,6 @@ namespace AZ
{
AZ_PROFILE_SCOPE(RHI, "Wait on Fences");
- AZ_ATOM_PROFILE_FUNCTION("RHI", "CommandQueueContext: Wait on Fences");
FencesPerQueue& nextFences = m_frameFences[m_currentFrameIndex];
for (auto& fence : nextFences)
@@ -79,7 +78,7 @@ namespace AZ
void CommandQueueContext::WaitForIdle()
{
- AZ_PROFILE_FUNCTION(RHI);
+ AZ_PROFILE_SCOPE(RHI, "CommandQueueContext: WaitForIdle");
for (auto& commandQueue : m_commandQueues)
{
commandQueue->WaitForIdle();
diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/FrameGraphCompiler.cpp
index 372ebc273d..2a30e36e04 100644
--- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/FrameGraphCompiler.cpp
+++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/FrameGraphCompiler.cpp
@@ -45,7 +45,7 @@ namespace AZ
RHI::MessageOutcome FrameGraphCompiler::CompileInternal(const RHI::FrameGraphCompileRequest& request)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileInternal(Vulkan)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileInternal(Vulkan)");
AZ_Assert(request.m_frameGraph, "FrameGraph is null.");
RHI::FrameGraph& frameGraph = *request.m_frameGraph;
@@ -89,7 +89,7 @@ namespace AZ
void FrameGraphCompiler::CompileResourceBarriers(const RHI::FrameGraphAttachmentDatabase& attachmentDatabase)
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileResourceBarriers(Vulkan)");
+ AZ_PROFILE_SCOPE(RHI, "FrameGraphCompiler: CompileResourceBarriers(Vulkan)");
for (RHI::BufferFrameAttachment* bufferFrameAttachment : attachmentDatabase.GetBufferAttachments())
{
diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h
index 52af56ba0e..6ca3bca652 100644
--- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h
+++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h
@@ -19,6 +19,11 @@
#include
+namespace UnitTest
+{
+ class MaterialTests;
+}
+
namespace AZ
{
class ReflectContext;
@@ -40,6 +45,7 @@ namespace AZ
friend class MaterialAssetCreator;
friend class MaterialAssetHandler;
friend class MaterialAssetCreatorCommon;
+ friend class UnitTest::MaterialTests;
public:
AZ_RTTI(MaterialAsset, "{522C7BE0-501D-463E-92C6-15184A2B7AD8}", AZ::Data::AssetData);
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp
index bedf4fe989..f67343faa1 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp
@@ -299,7 +299,7 @@ namespace AZ
//work function
void Process() override
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
const View::UsageFlags viewFlags = m_jobData->m_view->GetUsageFlags();
const RHI::DrawListMask drawListMask = m_jobData->m_view->GetDrawListMask();
@@ -645,7 +645,7 @@ namespace AZ
uint32_t AddLodDataToView(const Vector3& pos, const Cullable::LodData& lodData, RPI::View& view)
{
#ifdef AZ_CULL_PROFILE_DETAILED
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "AddLodDataToView");
#endif
const Matrix4x4& viewToClip = view.GetViewToClipMatrix();
@@ -725,7 +725,7 @@ namespace AZ
void CullingScene::BeginCulling(const AZStd::vector& views)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "CullingScene: BeginCulling");
+ AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
m_debugCtx.ResetCullStats();
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp
index 257bb689ee..8077038ea7 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp
@@ -75,7 +75,7 @@ namespace AZ
void GpuQuerySystem::Update()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "GpuQuerySystem: Update");
+ AZ_PROFILE_SCOPE(RPI, "GpuQuerySystem: Update");
for (auto& queryPool : m_queryPoolArray)
{
if (queryPool)
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Image/ImageSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Image/ImageSystem.cpp
index d4a55d1c29..71e2eaa2f5 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Image/ImageSystem.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Image/ImageSystem.cpp
@@ -34,6 +34,8 @@
#include
#include
+AZ_DECLARE_BUDGET(RPI);
+
namespace AZ
{
namespace RPI
@@ -171,7 +173,7 @@ namespace AZ
void ImageSystem::Update()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "ImageSystem: Update");
+ AZ_PROFILE_SCOPE(RPI, "ImageSystem: Update");
AZStd::lock_guard lock(m_activeStreamingPoolMutex);
for (StreamingImagePool* imagePool : m_activeStreamingPools)
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp
index 2dcaa70deb..d44ed40c7b 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp
@@ -100,9 +100,15 @@ namespace AZ
ShaderReloadNotificationBus::MultiHandler::BusConnect(shaderItem.GetShaderAsset().GetId());
}
+ // If this Init() is actually a re-initialize, we need to re-apply any overridden property values
+ // after loading the property values from the asset, so we save that data here.
MaterialPropertyFlags prevOverrideFlags = m_propertyOverrideFlags;
AZStd::vector prevPropertyValues = m_propertyValues;
+ // The property values are cleared to their default state to ensure that SetPropertyValue() does not early-return
+ // when called below. This is important when Init() is actually a re-initialize.
+ m_propertyValues.clear();
+
// Initialize the shader runtime data like shader constant buffers and shader variants by applying the
// material's property values. This will feed through the normal runtime material value-change data flow, which may
// include custom property change handlers provided by the material type.
@@ -504,7 +510,7 @@ namespace AZ
MaterialPropertyValue& savedPropertyValue = m_propertyValues[index.GetIndex()];
- // If the property value didn't actually change, don't waste time running functors and compiling the changes
+ // If the property value didn't actually change, don't waste time running functors and compiling the changes.
if (savedPropertyValue == value)
{
return false;
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp
index 6fd313e27f..50da470ec4 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp
@@ -42,7 +42,7 @@ namespace AZ
Data::Instance Model::CreateInternal(const Data::Asset& modelAsset)
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "Model: CreateInternal");
Data::Instance model = aznew Model();
const RHI::ResultCode resultCode = model->Init(modelAsset);
@@ -56,7 +56,7 @@ namespace AZ
RHI::ResultCode Model::Init(const Data::Asset& modelAsset)
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "Model: Init");
m_lods.resize(modelAsset->GetLodAssets().size());
@@ -128,7 +128,7 @@ namespace AZ
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "Model: LocalRayIntersection");
if (!GetModelAsset())
{
@@ -171,7 +171,7 @@ namespace AZ
float& distanceNormalized,
AZ::Vector3& normal) const
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "Model: RayIntersection");
const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
const AZ::Transform inverseTM = modelTransform.GetInverse();
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp
index ef9521d23c..a1651defbc 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLodUtils.cpp
@@ -27,7 +27,7 @@ namespace AZ
ModelLodIndex SelectLod(const View* view, const Vector3& position, const Model& model, ModelLodIndex lodOverride)
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "ModelLodUtils: SelectLod");
ModelLodIndex lodIndex;
if (model.GetLodCount() == 1)
{
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp
index a8feeb1047..34d72338dc 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp
@@ -169,7 +169,7 @@ namespace AZ
void PassSystem::RemovePasses()
{
m_state = PassSystemState::RemovingPasses;
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: RemovePasses");
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: RemovePasses");
if (!m_removePassList.empty())
{
@@ -189,8 +189,7 @@ namespace AZ
void PassSystem::BuildPasses()
{
m_state = PassSystemState::BuildingPasses;
- AZ_PROFILE_FUNCTION(RPI);
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: BuildPassAttachments");
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: BuildPasses");
m_passHierarchyChanged = m_passHierarchyChanged || !m_buildPassList.empty();
@@ -239,8 +238,7 @@ namespace AZ
void PassSystem::InitializePasses()
{
m_state = PassSystemState::InitializingPasses;
- AZ_PROFILE_FUNCTION(RPI);
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: BuildPassAttachments");
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: InitializePasses");
m_passHierarchyChanged = m_passHierarchyChanged || !m_initializePassList.empty();
@@ -277,7 +275,6 @@ namespace AZ
void PassSystem::Validate()
{
m_state = PassSystemState::ValidatingPasses;
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: Validate");
if (PassValidation::IsEnabled())
{
@@ -286,7 +283,7 @@ namespace AZ
return;
}
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: Validate");
PassValidationResults validationResults;
m_rootPass->Validate(validationResults);
@@ -298,7 +295,7 @@ namespace AZ
void PassSystem::ProcessQueuedChanges()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: ProcessQueuedChanges");
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: ProcessQueuedChanges");
RemovePasses();
BuildPasses();
InitializePasses();
@@ -307,8 +304,7 @@ namespace AZ
void PassSystem::FrameUpdate(RHI::FrameGraphBuilder& frameGraphBuilder)
{
- AZ_PROFILE_FUNCTION(RPI);
- AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: FrameUpdate");
+ AZ_PROFILE_SCOPE(RPI, "PassSystem: FrameUpdate");
ResetFrameStatistics();
ProcessQueuedChanges();
@@ -317,14 +313,14 @@ namespace AZ
Pass::FramePrepareParams params{ &frameGraphBuilder };
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Pass: FrameBegin");
+ AZ_PROFILE_SCOPE(RPI, "Pass: FrameBegin");
m_rootPass->FrameBegin(params);
}
}
void PassSystem::FrameEnd()
{
- AZ_ATOM_PROFILE_FUNCTION("RHI", "PassSystem: FrameEnd");
+ AZ_PROFILE_SCOPE(RHI, "PassSystem: FrameEnd");
m_state = PassSystemState::FrameEnd;
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RasterPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RasterPass.cpp
index 4c923d4a1a..d9f98c11d3 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RasterPass.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RasterPass.cpp
@@ -216,7 +216,7 @@ namespace AZ
void RasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "RasterPass: CompileResources");
if (m_shaderResourceGroup == nullptr)
{
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp
index 500bf21628..5df1c655d6 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp
@@ -233,7 +233,7 @@ namespace AZ
void RPISystem::OnSystemTick()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: OnSystemTick");
+ AZ_PROFILE_SCOPE(RPI, "RPISystem: OnSystemTick");
// Image system update is using system tick but not game tick so it can stream images in background even game is pausing
m_imageSystem.Update();
@@ -245,7 +245,7 @@ namespace AZ
{
return;
}
- AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: SimulationTick");
+ AZ_PROFILE_SCOPE(RPI, "RPISystem: SimulationTick");
AssetInitBus::Broadcast(&AssetInitBus::Events::PostLoadInit);
@@ -273,8 +273,7 @@ namespace AZ
return;
}
- AZ_PROFILE_FUNCTION(RPI);
- AZ_ATOM_PROFILE_FUNCTION("RPI", "RPISystem: RenderTick");
+ AZ_PROFILE_SCOPE(RPI, "RPISystem: RenderTick");
// Query system update is to increment the frame count
m_querySystem.Update();
@@ -301,7 +300,7 @@ namespace AZ
});
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "RPISystem: FrameEnd");
+ AZ_PROFILE_SCOPE(RPI, "RPISystem: FrameEnd");
m_dynamicDraw.FrameEnd();
m_passSystem.FrameEnd();
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp
index 7f0ab9c8aa..409a084e4f 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp
@@ -397,7 +397,7 @@ namespace AZ
void RenderPipeline::OnStartFrame()
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "RenderPipeline: OnStartFrame");
m_lastRenderStartTime = m_lastRenderRequestTime;
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp
index 9fa49f7f2a..778548f14c 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp
@@ -350,7 +350,7 @@ namespace AZ
void Scene::Simulate([[maybe_unused]] const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: Simulate");
+ AZ_PROFILE_SCOPE(RPI, "Scene: Simulate");
m_simulationTime = tickInfo.m_currentGameTime;
@@ -389,7 +389,7 @@ namespace AZ
{
if (completionJob)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: WaitAndCleanCompletionJob");
+ AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob");
//[GFX TODO]: the completion job should start earlier and wait for completion here
completionJob->StartAndWaitForCompletion();
delete completionJob;
@@ -422,11 +422,10 @@ namespace AZ
void Scene::PrepareRender([[maybe_unused]]const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy)
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: PrepareRender");
+ AZ_PROFILE_SCOPE(RPI, "Scene: PrepareRender");
{
AZ_PROFILE_SCOPE(RPI, "WaitForSimulationCompletion");
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "WaitForSimulationCompletion");
WaitAndCleanCompletionJob(m_simulationCompletion);
}
@@ -435,7 +434,7 @@ namespace AZ
// Get active pipelines which need to be rendered and notify them of an impending frame.
AZStd::vector activePipelines;
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnPrepareFrame");
+ AZ_PROFILE_SCOPE(RPI, "Scene: OnPrepareFrame");
for (auto& pipeline : m_pipelines)
{
pipeline->OnPrepareFrame();
@@ -449,7 +448,7 @@ namespace AZ
// Get active pipelines which need to be rendered and notify them frame started
for (const auto& pipeline : activePipelines)
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame");
+ AZ_PROFILE_SCOPE(RPI, "Scene: OnStartFrame");
pipeline->OnStartFrame();
}
@@ -468,7 +467,7 @@ namespace AZ
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Setup Views");
+ AZ_PROFILE_SCOPE(RPI, "Setup Views");
// Collect persistent views from all pipelines to be rendered
AZStd::map persistentViews;
@@ -506,8 +505,7 @@ namespace AZ
}
{
- AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "CollectDrawPackets");
+ AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion();
// Launch FeatureProcessor::Render() jobs
@@ -550,14 +548,13 @@ namespace AZ
// Add dynamic draw data for all the views
if (m_dynamicDrawSystem)
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "DynamicDraw SubmitDrawData");
+ AZ_PROFILE_SCOPE(RPI, "DynamicDraw SubmitDrawData");
m_dynamicDrawSystem->SubmitDrawData(this, m_renderPacket.m_views);
}
}
{
AZ_PROFILE_BEGIN(RPI, "FinalizeDrawLists");
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "FinalizeDrawLists");
if (jobPolicy == RHI::JobPolicy::Serial)
{
for (auto& view : m_renderPacket.m_views)
@@ -586,14 +583,14 @@ namespace AZ
}
{
- AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene OnEndPrepareRender");
+ AZ_PROFILE_SCOPE(RPI, "Scene OnEndPrepareRender");
SceneNotificationBus::Event(GetId(), &SceneNotification::OnEndPrepareRender);
}
}
void Scene::OnFrameEnd()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: OnFrameEnd");
+ AZ_PROFILE_SCOPE(RPI, "Scene: OnFrameEnd");
bool didRender = false;
for (auto& pipeline : m_pipelines)
{
@@ -730,7 +727,7 @@ namespace AZ
void Scene::RebuildPipelineStatesLookup()
{
- AZ_ATOM_PROFILE_FUNCTION("RPI", "Scene: RebuildPipelineStatesLookup");
+ AZ_PROFILE_SCOPE(RPI, "Scene: RebuildPipelineStatesLookup");
m_pipelineStatesLookup.clear();
AZStd::queue parents;
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Metrics/ShaderMetricsSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Metrics/ShaderMetricsSystem.cpp
index 9e633077e7..528d32e217 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Metrics/ShaderMetricsSystem.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Metrics/ShaderMetricsSystem.cpp
@@ -113,7 +113,7 @@ namespace AZ
return;
}
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "ShaderMetricsSystem: RequestShaderVariant");
AZStd::lock_guard lock(m_metricsMutex);
diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp
index bdb3ea8899..c2356cea45 100644
--- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp
+++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp
@@ -239,7 +239,7 @@ namespace AZ
void View::FinalizeDrawLists()
{
- AZ_PROFILE_FUNCTION(RPI);
+ AZ_PROFILE_SCOPE(RPI, "View: FinalizeDrawLists");
m_drawListContext.FinalizeLists();
if (m_passesByDrawList)
{
diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp
index d82a17e1f3..f202747bf6 100644
--- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp
+++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTests.cpp
@@ -182,6 +182,13 @@ namespace UnitTest
EXPECT_EQ(srgData.GetImageView(srgData.FindShaderInputImageIndex(Name{ "m_image" }), 0), m_testImage->GetImageView());
EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_enum" })), 2u);
}
+
+ //! Provides write access to private material asset property values, primarily for simulating
+ //! MaterialAsset hot reload.
+ MaterialPropertyValue& AccessMaterialAssetPropertyValue(Data::Asset materialAsset, Name propertyName)
+ {
+ return materialAsset->m_propertyValues[materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyName).GetIndex()];
+ }
};
TEST_F(MaterialTests, TestCreateVsFindOrCreate)
@@ -313,6 +320,30 @@ namespace UnitTest
EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_uint" })), 42u);
}
+ TEST_F(MaterialTests, TestSetPropertyValueWhenValueIsUnchanged)
+ {
+ Data::Instance material = Material::FindOrCreate(m_testMaterialAsset);
+
+ EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 2.5f));
+
+ ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName());
+ EXPECT_TRUE(material->Compile());
+
+ // Taint the SRG so we can check whether it was set by the SetPropertyValue() calls below.
+ const RHI::ShaderResourceGroup* srg = material->GetRHIShaderResourceGroup();
+ const RHI::ShaderResourceGroupData& srgData = srg->GetData();
+ const_cast(&srgData)->SetConstant(m_testMaterialSrgLayout->FindShaderInputConstantIndex(Name{"m_float"}), 0.0f);
+
+ // Set the properties to the same values as before
+ EXPECT_FALSE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 2.5f));
+
+ ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName());
+ EXPECT_FALSE(material->Compile());
+
+ // Make sure the SRG is still tainted, because the SetPropertyValue() functions weren't processed
+ EXPECT_EQ(srgData.GetConstant(srgData.FindShaderInputConstantIndex(Name{ "m_float" })), 0.0f);
+ }
+
TEST_F(MaterialTests, TestImageNotProvided)
{
Data::Asset materialAssetWithEmptyImage;
@@ -785,4 +816,45 @@ namespace UnitTest
EXPECT_EQ((float)inputColor.GetElement(i), (float)colorFromMaterial.GetElement(i));
}
}
+
+ TEST_F(MaterialTests, TestReinitializeForHotReload)
+ {
+ Data::Instance material = Material::FindOrCreate(m_testMaterialAsset);
+ const RHI::ShaderResourceGroupData* srgData = &material->GetRHIShaderResourceGroup()->GetData();
+ ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName());
+
+ // Check the default property value
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 1.5f);
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 1.5f);
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -2);
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -2);
+
+ // Override a property value
+ EXPECT_TRUE(material->SetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" }), 5.5f));
+
+ // Apply the changes
+ EXPECT_TRUE(material->Compile());
+ ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName());
+
+ // Check the updated values with one overridden
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 5.5f);
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 5.5f);
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -2);
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -2);
+
+ // Pretend there was a hot-reload with new default values
+ AccessMaterialAssetPropertyValue(m_testMaterialAsset, Name{"MyFloat"}) = 0.5f;
+ AccessMaterialAssetPropertyValue(m_testMaterialAsset, Name{"MyInt"}) = -7;
+ AZ::Data::AssetBus::Event(m_testMaterialAsset.GetId(), &AZ::Data::AssetBus::Handler::OnAssetReloaded, m_testMaterialAsset);
+ srgData = &material->GetRHIShaderResourceGroup()->GetData();
+ ProcessQueuedSrgCompilations(m_testMaterialShaderAsset, m_testMaterialSrgLayout->GetName());
+
+ // Make sure the override values are still there
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_float" })), 5.5f);
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyFloat" })), 5.5f);
+
+ // Make sure the new default value is applied where it was not overridden
+ EXPECT_EQ(material->GetPropertyValue(material->FindPropertyIndex(Name{ "MyInt" })), -7);
+ EXPECT_EQ(srgData->GetConstant(srgData->FindShaderInputConstantIndex(Name{ "m_int" })), -7);
+ }
}
diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h
index ea4dd20250..a649fcf0f7 100644
--- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h
+++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h
@@ -174,7 +174,7 @@ namespace AZ
AZStd::unordered_map> m_savedData;
// Region color cache
- AZStd::unordered_map m_regionColorMap;
+ AZStd::unordered_map m_regionColorMap;
// Tracks the frame boundaries
AZStd::vector m_frameEndTicks = { INT64_MIN };
diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl
index 61f68197b6..a573e3019a 100644
--- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl
+++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl
@@ -124,7 +124,7 @@ namespace AZ
m_cpuTimingStatisticsWhenPause = currentCpuTimingStatistics;
CollectFrameData();
- CullFrameData(currentCpuTimingStatistics);
+ CullFrameData(currentCpuTimingStatistics);
// Only listen to system ticks when the profiler is active
if (!SystemTickBus::Handler::BusIsConnected())
@@ -148,7 +148,7 @@ namespace AZ
}
}
ImGui::End();
-
+
if (m_captureToFile)
{
AZStd::sys_time_t timeNow = AZStd::GetTimeNowSecond();
@@ -325,7 +325,7 @@ namespace AZ
{
const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
-
+
switch (columnToSort)
{
case (0): // Sort by group name
@@ -343,7 +343,7 @@ namespace AZ
case (4): // Sort by invocations
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_invocationsLastFrame, ascending));
break;
- case (5): // Sort by total time
+ case (5): // Sort by total time
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_lastFrameTotalTicks, ascending));
break;
}
@@ -401,7 +401,7 @@ namespace AZ
}
DrawTable();
- }
+ }
}
inline void ImGuiCpuProfiler::DrawFilePicker()
@@ -460,17 +460,17 @@ namespace AZ
const auto [groupRegionNameItr, wasGroupRegionNameInserted] =
m_deserializedGroupRegionNamePool.emplace(groupNameItr->c_str(), regionNameItr->c_str());
- const RHI::CachedTimeRegion newRegion(&(*groupRegionNameItr), entry.m_stackDepth, entry.m_startTick, entry.m_endTick);
+ const RHI::CachedTimeRegion newRegion(*groupRegionNameItr, entry.m_stackDepth, entry.m_startTick, entry.m_endTick);
m_savedData[entry.m_threadId].push_back(newRegion);
- // Since we don't serialize the frame boundaries, we need to use the RPI's OnSystemTick event as a heuristic.
+ // Since we don't serialize the frame boundaries, we need to use the RPI's OnSystemTick event as a heuristic.
const static Name frameBoundaryName = Name("RPISystem: OnSystemTick");
if (entry.m_regionName == frameBoundaryName)
{
m_frameEndTicks.push_back(entry.m_endTick);
- }
+ }
- // Update running statistics
+ // Update running statistics
if (!m_groupRegionMap[*groupNameItr].contains(*regionNameItr))
{
m_groupRegionMap[*groupNameItr][*regionNameItr].m_groupName = *groupNameItr;
@@ -487,7 +487,7 @@ namespace AZ
// Invariant: each vector in m_savedData must be sorted so that we can efficiently cull region data.
for (auto& [threadId, singleThreadData] : m_savedData)
{
- AZStd::sort(singleThreadData.begin(), singleThreadData.end(),
+ AZStd::sort(singleThreadData.begin(), singleThreadData.end(),
[](const TimeRegion& lhs, const TimeRegion& rhs)
{
return lhs.m_startTick < rhs.m_startTick;
@@ -669,7 +669,7 @@ namespace AZ
// Iterate through the entire TimeRegionMap and copy the data since it will get deleted on the next frame
for (const auto& [threadId, singleThreadRegionMap] : timeRegionMap)
{
- const size_t threadIdHashed = AZStd::hash{}(threadId);
+ const size_t threadIdHashed = AZStd::hash{}(threadId);
// The profiler can sometime return threads without any profiling events when dropping threads, FIXME(ATOM-15949)
if (singleThreadRegionMap.size() == 0)
{
@@ -686,7 +686,7 @@ namespace AZ
newVisualizerData.push_back(region); // Copies
// Also update the statistical view's data
- const AZStd::string& groupName = region.m_groupRegionName->m_groupName;
+ const AZStd::string& groupName = region.m_groupRegionName.m_groupName;
if (!m_groupRegionMap[groupName].contains(regionName))
{
@@ -765,7 +765,7 @@ namespace AZ
inline void ImGuiCpuProfiler::DrawBlock(const TimeRegion& block, u64 targetRow)
{
// Don't draw anything if the user is searching for regions and this block doesn't pass the filter
- if (!m_visualizerHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName))
+ if (!m_visualizerHighlightFilter.PassFilter(block.m_groupRegionName.m_regionName))
{
return;
}
@@ -798,7 +798,7 @@ namespace AZ
if (regionPixelWidth > maxCharWidth) // We can draw at least one character
{
const AZStd::string label =
- AZStd::string::format("%s/ %s", block.m_groupRegionName->m_groupName, block.m_groupRegionName->m_regionName);
+ AZStd::string::format("%s/ %s", block.m_groupRegionName.m_groupName, block.m_groupRegionName.m_regionName);
const float textWidth = ImGui::CalcTextSize(label.c_str()).x;
if (regionPixelWidth < textWidth) // Not enough space in the block to draw the whole name, draw clipped text.
@@ -809,7 +809,7 @@ namespace AZ
// so we must adjust for the scale manually.
const float scaleFactor = ImGui::GetIO().FontGlobalScale;
const float fontSize = ImGui::GetFont()->FontSize * scaleFactor;
-
+
ImGui::GetFont()->RenderText(drawList, fontSize, startPoint, IM_COL32_WHITE, clipRect, label.c_str(), 0);
}
else // We have enough space to draw the entire label, draw and center text.
@@ -828,7 +828,7 @@ namespace AZ
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
m_enableVisualizer = false;
- const auto newFilter = AZStd::string(block.m_groupRegionName->m_regionName);
+ const auto newFilter = AZStd::string(block.m_groupRegionName.m_regionName);
m_timedRegionFilter = ImGuiTextFilter(newFilter.c_str());
m_timedRegionFilter.Build();
}
@@ -836,7 +836,7 @@ namespace AZ
drawList->AddRect(startPoint, endPoint, ImGui::GetColorU32({ 1, 1, 1, 1 }), 0.0, 0, 1.5);
ImGui::BeginTooltip();
- ImGui::Text("%s::%s", block.m_groupRegionName->m_groupName, block.m_groupRegionName->m_regionName);
+ ImGui::Text("%s::%s", block.m_groupRegionName.m_groupName, block.m_groupRegionName.m_regionName);
ImGui::Text("Execution time: %.3f ms", CpuProfilerImGuiHelper::TicksToMs(block.m_endTick - block.m_startTick));
ImGui::Text("Ticks %lld => %lld", block.m_startTick, block.m_endTick);
ImGui::EndTooltip();
@@ -846,10 +846,10 @@ namespace AZ
inline ImU32 ImGuiCpuProfiler::GetBlockColor(const TimeRegion& block)
{
// Use the GroupRegionName pointer a key into the cache, equal regions will have equal pointers
- const GroupRegionName* key = block.m_groupRegionName;
- if (m_regionColorMap.contains(key)) // Cache hit
+ const GroupRegionName& key = block.m_groupRegionName;
+ if (auto iter = m_regionColorMap.find(key); iter != m_regionColorMap.end()) // Cache hit
{
- return ImGui::GetColorU32(m_regionColorMap[key]);
+ return ImGui::GetColorU32(iter->second);
}
// Cache miss, generate a new random color
diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt
index bf28ed3f14..e68681315e 100644
--- a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt
+++ b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt
@@ -109,6 +109,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
RUNTIME_DEPENDENCIES
Gem::Atom_RPI.Editor
Gem::Atom_Feature_Common.Editor
+ Legacy::EditorCommon
)
# The AtomLyIntegration_CommonFeatures.Editor module is used for Builders and Tools
diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp
index cbb30e6cd8..d8cae4564d 100644
--- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp
+++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp
@@ -231,7 +231,7 @@ namespace SurfaceData
void SurfaceDataMeshComponent::UpdateMeshData()
{
- AZ_PROFILE_FUNCTION(Entity);
+ AZ_PROFILE_SCOPE(Entity, "SurfaceDataMeshComponent: UpdateMeshData");
bool meshValidBeforeUpdate = false;
bool meshValidAfterUpdate = false;
diff --git a/Gems/Camera/Code/CMakeLists.txt b/Gems/Camera/Code/CMakeLists.txt
index 3fbeec0908..138f0fda85 100644
--- a/Gems/Camera/Code/CMakeLists.txt
+++ b/Gems/Camera/Code/CMakeLists.txt
@@ -61,6 +61,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
Legacy::EditorCommon
AZ::AzToolsFramework
Gem::Camera.Static
+ RUNTIME_DEPENDENCIES
+ Legacy::EditorCommon
)
# tools and builders use the above module.
diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphCommands.cpp
index dbc25d7a60..bd5a14f772 100644
--- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphCommands.cpp
+++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphCommands.cpp
@@ -715,12 +715,15 @@ namespace CommandSystem
// restore the workspace dirty flag
GetCommandManager()->SetWorkspaceDirtyFlag(m_oldWorkspaceDirtyFlag);
- AZStd::string resultString;
- GetCommandManager()->ExecuteCommandInsideCommand("Unselect -animGraphIndex SELECT_ALL", resultString);
+ MCore::CommandGroup commandGroup;
+ commandGroup.AddCommandString("RecorderClear");
+ commandGroup.AddCommandString("Unselect -animGraphIndex SELECT_ALL");
if (animGraph)
{
- GetCommandManager()->ExecuteCommandInsideCommand(AZStd::string::format("Select -animGraphID %d", animGraph->GetID()), resultString);
+ commandGroup.AddCommandString(AZStd::string::format("Select -animGraphID %d", animGraph->GetID()));
}
+ AZStd::string resultString;
+ GetCommandManager()->ExecuteCommandGroupInsideCommand(commandGroup, resultString);
return true;
}
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h
index 55ed70088d..d1f9e001b1 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h
@@ -33,7 +33,7 @@ namespace Multiplayer
using EntityDirtiedEvent = AZ::Event<>;
using EntitySyncRewindEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event;
- using EntityPreRenderEvent = AZ::Event;
+ using EntityPreRenderEvent = AZ::Event;
using EntityCorrectionEvent = AZ::Event<>;
//! @class NetBindComponent
@@ -114,7 +114,7 @@ namespace Multiplayer
void NotifyLocalChanges();
void NotifySyncRewindState();
void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId);
- void NotifyPreRender(float deltaTime, float blendFactor);
+ void NotifyPreRender(float deltaTime);
void NotifyCorrection();
void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler);
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h
index 914aeaadd3..a018279a79 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h
@@ -29,24 +29,9 @@ namespace Multiplayer
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
- void OnPreRender(float deltaTime, float blendFactor);
+ void OnPreRender(float deltaTime);
void OnCorrection();
- void OnRotationChangedEvent(const AZ::Quaternion& rotation);
- void OnTranslationChangedEvent(const AZ::Vector3& translation);
- void OnScaleChangedEvent(float scale);
- void OnResetCountChangedEvent();
-
- void UpdateTargetHostFrameId();
-
- AZ::Transform m_previousTransform = AZ::Transform::CreateIdentity();
- AZ::Transform m_targetTransform = AZ::Transform::CreateIdentity();
-
- AZ::Event::Handler m_rotationEventHandler;
- AZ::Event::Handler m_translationEventHandler;
- AZ::Event::Handler m_scaleEventHandler;
- AZ::Event::Handler m_resetCountEventHandler;
-
EntityPreRenderEvent::Handler m_entityPreRenderEventHandler;
EntityCorrectionEvent::Handler m_entityCorrectionEventHandler;
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h
index 69d2eb4071..11f5ac7e47 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h
@@ -203,15 +203,13 @@ namespace Multiplayer
m_previousHostFrameId = time->GetHostFrameId();
m_previousHostTimeMs = time->GetHostTimeMs();
m_previousRewindConnectionId = time->GetRewindingConnectionId();
- time->AlterTime(frameId, timeMs, connectionId);
m_previousBlendFactor = time->GetHostBlendFactor();
- time->AlterBlendFactor(blendFactor);
+ time->AlterTime(frameId, timeMs, blendFactor, connectionId);
}
inline ~ScopedAlterTime()
{
INetworkTime* time = GetNetworkTime();
- time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousRewindConnectionId);
- time->AlterBlendFactor(m_previousBlendFactor);
+ time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousBlendFactor, m_previousRewindConnectionId);
}
private:
HostFrameId m_previousHostFrameId = InvalidHostFrameId;
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h
index ae47aa8373..c12cbb660b 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h
@@ -52,12 +52,6 @@ namespace Multiplayer
//! @return the ConnectionId of the connection requesting the rewind operation
virtual AzNetworking::ConnectionId GetRewindingConnectionId() const = 0;
- //! Get the controlling connection that may be currently altering global game time.
- //! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics
- //! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered hostFrameId
- //! @return the HostFrameId taking into account the provided rewinding connectionId
- virtual HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
-
//! Forcibly sets the current network time to the provided frameId and game time in milliseconds.
//! @param frameId the new HostFrameId to use
//! @param timeMs the new HostTimeMs to use
@@ -66,12 +60,9 @@ namespace Multiplayer
//! Alters the current HostFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new HostFrameId to use
//! @param timeMs the new HostTimeMs to use
+ //! @param blendFactor the factor used to blend between values at the current and previous HostFrameId
//! @param rewindConnectionId the rewinding ConnectionId
- virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) = 0;
-
- //! Alters the current Host blend factor. Used to drive interpolation in rewound states.
- //! @param blendFactor the blend factor to use
- virtual void AlterBlendFactor(float blendFactor) = 0;
+ virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) = 0;
//! Syncs all entities contained within a volume to the current rewind state.
//! @param rewindVolume the volume to rewind entities within (needed for physics entities)
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h
index a8af564c15..152f6f47a7 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h
@@ -60,7 +60,7 @@ namespace Multiplayer
//! @return value in const base type form
const BASE_TYPE& Get() const;
- //! Const base type retriever for one host frame behind Get(). Only intended for use in SyncRewind contexts.
+ //! Const base type retriever for one host frame behind Get() when contextually appropriate, otherwise identical to Get().
//! @return value in const base type form
const BASE_TYPE& GetPrevious() const;
@@ -86,9 +86,13 @@ namespace Multiplayer
private:
//! Returns what the appropriate current time is for this rewindable property.
- //! @return the appropriate current time is for this rewindable property
+ //! @return the appropriate current time for this rewindable property
HostFrameId GetCurrentTimeForProperty() const;
+ //! Returns what the appropriate previous time is for this rewindable property.
+ //! @return the appropriate previous time for this rewindable property
+ HostFrameId GetPreviousTimeForProperty() const;
+
//! Updates the latest value for this object instance, if frameTime represents a current or future time.
//! Any attempts to set old values on the object will fail
//! @param value the new value to set in the object history
diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl
index b0c9bc0c46..9183a1e9da 100644
--- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl
+++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl
@@ -69,7 +69,7 @@ namespace Multiplayer
template
inline const BASE_TYPE& RewindableObject::GetPrevious() const
{
- return GetValueForTime(GetCurrentTimeForProperty() - HostFrameId(1));
+ return GetValueForTime(GetPreviousTimeForProperty());
}
template
@@ -118,7 +118,22 @@ namespace Multiplayer
inline HostFrameId RewindableObject::GetCurrentTimeForProperty() const
{
INetworkTime* networkTime = Multiplayer::GetNetworkTime();
- return networkTime->GetHostFrameIdForRewindingConnection(m_owningConnectionId);
+ if (networkTime->IsTimeRewound() && (m_owningConnectionId == networkTime->GetRewindingConnectionId()))
+ {
+ return networkTime->GetUnalteredHostFrameId();
+ }
+ return networkTime->GetHostFrameId();
+ }
+
+ template
+ inline HostFrameId RewindableObject::GetPreviousTimeForProperty() const
+ {
+ INetworkTime* networkTime = Multiplayer::GetNetworkTime();
+ if (networkTime->IsTimeRewound() && (m_owningConnectionId == networkTime->GetRewindingConnectionId()))
+ {
+ return networkTime->GetUnalteredHostFrameId();
+ }
+ return networkTime->GetHostFrameId() - HostFrameId(1);
}
template
diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja
index 0f62da11f3..41f0fa1b02 100644
--- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja
+++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja
@@ -7,21 +7,21 @@
{% macro DeclareNetworkPropertyGetter(Property) %}
{% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{% if Property.attrib['Container'] == 'Array' %}
-{% if Property.attrib['IsRewindable']|booleanTrue %}
+{% if Property.attrib['IsRewindable']|booleanTrue %}
const RewindableArray<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Array() const;
-{% else %}
+{% else %}
const AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Array() const;
-{% endif %}
+{% endif %}
const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}(int32_t index) const;
{% if Property.attrib['GenerateEventBindings']|booleanTrue %}
void {{ PropertyName }}AddEvent(AZ::Event::Handler& handler);
{% endif %}
{% elif Property.attrib['Container'] == 'Vector' %}
-{% if Property.attrib['IsRewindable']|booleanTrue %}
+{% if Property.attrib['IsRewindable']|booleanTrue %}
const RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Vector() const;
-{% else %}
+{% else %}
const AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Vector() const;
-{% endif %}
+{% endif %}
const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}(int32_t index) const;
const {{ Property.attrib['Type'] }}& {{ PropertyName }}GetBack() const;
uint32_t {{ PropertyName }}GetSize() const;
@@ -31,6 +31,9 @@ void {{ PropertyName }}SizeChangedAddEvent(AZ::Event::Handler& handler
{% endif %}
{% else %}
const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const;
+{% if Property.attrib['IsRewindable']|booleanTrue %}
+const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}Previous() const;
+{% endif %}
{% if Property.attrib['GenerateEventBindings']|booleanTrue %}
void {{ PropertyName }}AddEvent(AZ::Event<{{ Property.attrib['Type'] }}>::Handler& handler);
{% endif %}
diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja
index e57e8dad2f..eb4b81ea96 100644
--- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja
+++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja
@@ -3,11 +3,11 @@
{% macro LowerFirst(text) %}{{ text[0] | lower}}{{ text[1:] }}{% endmacro %}
{% macro DefineNetworkPropertyGet(ClassName, Property, Prefix = '') %}
{% if Property.attrib['Container'] == 'Array' %}
-{% if Property.attrib['IsRewindable']|booleanTrue %}
+{% if Property.attrib['IsRewindable']|booleanTrue %}
const RewindableArray<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Array() const
-{% else %}
+{% else %}
const AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Array() const
-{% endif %}
+{% endif %}
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }};
}
@@ -25,11 +25,11 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Even
{% endif %}
{% elif Property.attrib['Container'] == 'Vector' %}
-{% if Property.attrib['IsRewindable']|booleanTrue %}
+{% if Property.attrib['IsRewindable']|booleanTrue %}
const RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Vector() const
-{% else %}
+{% else %}
const AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Vector() const
-{% endif %}
+{% endif %}
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }};
}
@@ -68,7 +68,12 @@ const {{ Property.attrib['Type'] }}& {{ ClassName }}::Get{{ UpperFirst(Property.
{
return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }};
}
-
+{% if Property.attrib['IsRewindable']|booleanTrue %}
+const {{ Property.attrib['Type'] }}& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Previous() const
+{
+ return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetPrevious();
+}
+{% endif %}
{% if Property.attrib['GenerateEventBindings']|booleanTrue %}
void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Event<{{ Property.attrib['Type'] }}>::Handler& handler)
{
diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
index 203750d761..c553bc5351 100644
--- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
+++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml
@@ -32,11 +32,11 @@
-
+
-
+
diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
index cec005cc26..8ab2e61e5e 100644
--- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
+++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
@@ -12,9 +12,9 @@
-
+
-
+
diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp
index 41d7d7cb1c..747f53d37b 100644
--- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp
@@ -185,12 +185,9 @@ namespace Multiplayer
// Discard move input events, client may be speed hacking
if (m_clientBankedTime < sv_MaxBankTimeWindowSec)
{
- // Client blends from previous frame to target so here we subtract blend factor to get to that state
- const float blendFactor = AZStd::min(AZStd::max(0.f, input.GetHostBlendFactor()), 1.0f);
- const AZ::TimeMs blendMs = AZ::TimeMs(static_cast(static_cast(cl_InputRateMs)) * (1.0f - blendFactor));
m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary
{
- ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs() - blendMs, input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
+ ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, static_cast(clientInputRateSec));
}
@@ -436,10 +433,13 @@ namespace Multiplayer
NetworkInputArray inputArray(GetEntityHandle());
NetworkInput& input = inputArray[0];
+ const float blendFactor = AZStd::min(AZStd::max(0.f, multiplayer->GetCurrentBlendFactor()), 1.0f);
+ const AZ::TimeMs blendMs = AZ::TimeMs(static_cast(static_cast(cl_InputRateMs)) * (1.0f - blendFactor));
input.SetClientInputId(m_clientInputId);
input.SetHostFrameId(networkTime->GetHostFrameId());
- input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs());
+ // Account for the client blending from previous frame to current
+ input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs() - blendMs);
input.SetHostBlendFactor(multiplayer->GetCurrentBlendFactor());
// Allow components to form the input for this frame
diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp
index a581b0c4a2..7d630ebe1d 100644
--- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp
@@ -395,9 +395,9 @@ namespace Multiplayer
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
}
- void NetBindComponent::NotifyPreRender(float deltaTime, float blendFactor)
+ void NetBindComponent::NotifyPreRender(float deltaTime)
{
- m_entityPreRenderEvent.Signal(deltaTime, blendFactor);
+ m_entityPreRenderEvent.Signal(deltaTime);
}
void NetBindComponent::NotifyCorrection()
diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp
index fa08794c4d..1d9fee16e0 100644
--- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp
@@ -26,11 +26,7 @@ namespace Multiplayer
}
NetworkTransformComponent::NetworkTransformComponent()
- : m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); })
- , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); })
- , m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); })
- , m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); })
- , m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); })
+ : m_entityPreRenderEventHandler([this](float deltaTime) { OnPreRender(deltaTime); })
, m_entityCorrectionEventHandler([this]() { OnCorrection(); })
{
;
@@ -43,15 +39,8 @@ namespace Multiplayer
void NetworkTransformComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
- RotationAddEvent(m_rotationEventHandler);
- TranslationAddEvent(m_translationEventHandler);
- ScaleAddEvent(m_scaleEventHandler);
- ResetCountAddEvent(m_resetCountEventHandler);
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
-
- // When coming into relevance, reset all blending factors so we don't interpolate to our start position
- OnResetCountChangedEvent();
}
void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -59,59 +48,31 @@ namespace Multiplayer
;
}
- void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation)
- {
- m_previousTransform.SetRotation(m_targetTransform.GetRotation());
- m_targetTransform.SetRotation(rotation);
- UpdateTargetHostFrameId();
- }
-
- void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation)
- {
- m_previousTransform.SetTranslation(m_targetTransform.GetTranslation());
- m_targetTransform.SetTranslation(translation);
- UpdateTargetHostFrameId();
- }
-
- void NetworkTransformComponent::OnScaleChangedEvent(float scale)
- {
- m_previousTransform.SetUniformScale(m_targetTransform.GetUniformScale());
- m_targetTransform.SetUniformScale(scale);
- UpdateTargetHostFrameId();
- }
-
- void NetworkTransformComponent::OnResetCountChangedEvent()
- {
- m_targetTransform.SetRotation(GetRotation());
- m_targetTransform.SetTranslation(GetTranslation());
- m_targetTransform.SetUniformScale(GetScale());
- m_previousTransform = m_targetTransform;
- }
-
- void NetworkTransformComponent::UpdateTargetHostFrameId()
- {
- HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId();
- if (currentHostFrameId > m_targetHostFrameId)
- {
- m_targetHostFrameId = currentHostFrameId;
- }
- }
-
- void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime, float blendFactor)
+ void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime)
{
if (!HasController())
{
AZ::Transform blendTransform;
- if (Multiplayer::GetNetworkTime() && Multiplayer::GetNetworkTime()->GetHostFrameId() > m_targetHostFrameId)
+ blendTransform.SetRotation(GetRotation());
+ blendTransform.SetTranslation(GetTranslation());
+ blendTransform.SetUniformScale(GetScale());
+
+ const float blendFactor = GetMultiplayer()->GetCurrentBlendFactor();
+ if (!AZ::IsClose(blendFactor, 1.0f))
{
- m_previousTransform = m_targetTransform;
- blendTransform = m_targetTransform;
- }
- else
- {
- blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor));
- blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor));
- blendTransform.SetUniformScale(AZ::Lerp(m_previousTransform.GetUniformScale(), m_targetTransform.GetUniformScale(), blendFactor));
+ AZ::Transform blendTransformPrevious;
+ blendTransformPrevious.SetRotation(GetRotationPrevious());
+ blendTransformPrevious.SetTranslation(GetTranslationPrevious());
+ blendTransformPrevious.SetUniformScale(GetScalePrevious());
+
+ if (!blendTransform.IsClose(blendTransformPrevious))
+ {
+ blendTransform.SetRotation(blendTransformPrevious.GetRotation().Slerp(blendTransform.GetRotation(), blendFactor));
+ blendTransform.SetTranslation(
+ blendTransformPrevious.GetTranslation().Lerp(blendTransform.GetTranslation(), blendFactor));
+ blendTransform.SetUniformScale(
+ AZ::Lerp(blendTransformPrevious.GetUniformScale(), blendTransform.GetUniformScale(), blendFactor));
+ }
}
if (!GetTransformComponent()->GetWorldTM().IsClose(blendTransform))
@@ -124,12 +85,15 @@ namespace Multiplayer
void NetworkTransformComponent::OnCorrection()
{
// Snap to latest
- OnResetCountChangedEvent();
+ AZ::Transform targetTransform;
+ targetTransform.SetRotation(GetRotation());
+ targetTransform.SetTranslation(GetTranslation());
+ targetTransform.SetUniformScale(GetScale());
// Hard set the entities transform
- if (!GetTransformComponent()->GetWorldTM().IsClose(m_targetTransform))
+ if (!GetTransformComponent()->GetWorldTM().IsClose(targetTransform))
{
- GetTransformComponent()->SetWorldTM(m_targetTransform);
+ GetTransformComponent()->SetWorldTM(targetTransform);
}
}
diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
index 543107a3ed..c28fb23cc4 100644
--- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
+++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp
@@ -934,7 +934,7 @@ namespace Multiplayer
for (NetBindComponent* netBindComponent : gatheredEntities)
{
- netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor);
+ netBindComponent->NotifyPreRender(deltaTime);
}
}
else
@@ -946,7 +946,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = entity->FindComponent();
if (netBindComponent != nullptr)
{
- netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor);
+ netBindComponent->NotifyPreRender(deltaTime);
}
}
}
diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp
index cd9c1737d6..b37f485ff4 100644
--- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp
+++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp
@@ -65,11 +65,6 @@ namespace Multiplayer
return m_rewindingConnectionId;
}
- HostFrameId NetworkTime::GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const
- {
- return (IsTimeRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_hostFrameId;
- }
-
void NetworkTime::ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs)
{
AZ_Assert(!IsTimeRewound(), "Forcibly setting network time is unsupported under a rewound time scope");
@@ -79,16 +74,12 @@ namespace Multiplayer
m_rewindingConnectionId = AzNetworking::InvalidConnectionId;
}
- void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId)
+ void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId)
{
m_hostFrameId = frameId;
m_hostTimeMs = timeMs;
- m_rewindingConnectionId = rewindConnectionId;
- }
-
- void NetworkTime::AlterBlendFactor(float blendFactor)
- {
m_hostBlendFactor = blendFactor;
+ m_rewindingConnectionId = rewindConnectionId;
}
void NetworkTime::SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume)
@@ -121,8 +112,15 @@ namespace Multiplayer
if (networkTransform != nullptr)
{
- // We're not presently factoring in interpolated position here
- const AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); // Get the rewound position
+ // Get the rewound position for target host frame ID plus the one preceding it for potential lerp
+ AZ::Vector3 rewindCenter = networkTransform->GetTranslation();
+ const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious();
+ const float blendFactor = GetNetworkTime()->GetHostBlendFactor();
+ if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious))
+ {
+ // If we have a blend factor, lerp the translation for accuracy
+ rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor);
+ }
const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions
const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb
diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h
index 0278ddd2b0..2bcf019623 100644
--- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h
+++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h
@@ -32,10 +32,8 @@ namespace Multiplayer
AZ::TimeMs GetHostTimeMs() const override;
float GetHostBlendFactor() const override;
AzNetworking::ConnectionId GetRewindingConnectionId() const override;
- HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override;
void ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs) override;
- void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override;
- void AlterBlendFactor(float blendFactor) override;
+ void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) override;
void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) override;
void ClearRewoundEntities() override;
//! @}
diff --git a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp
index b8d60a94e8..04de971f0d 100644
--- a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp
+++ b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp
@@ -57,6 +57,35 @@ namespace UnitTest
}
}
+ TEST_F(RewindableObjectTests, CurrentPreviousTests)
+ {
+ Multiplayer::RewindableObject test(0);
+
+ for (uint32_t i = 0; i < RewindableBufferFrames; ++i)
+ {
+ test = i;
+ EXPECT_EQ(i, test);
+ Multiplayer::GetNetworkTime()->IncrementHostFrameId();
+ }
+
+ {
+ // Test that Get/GetPrevious return different value when not on the owning connection
+ Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId);
+ EXPECT_EQ(RewindableBufferFrames - 1, test.Get());
+ EXPECT_EQ(RewindableBufferFrames - 2, test.GetPrevious());
+ }
+
+ // Test that Get/GetPrevious return the unaltered frame on the owning conection
+ Multiplayer::GetNetworkTime()->AlterTime(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::ConnectionId(0));
+ {
+ Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::ConnectionId(0));
+ test.SetOwningConnectionId(AzNetworking::ConnectionId(0));
+ EXPECT_EQ(RewindableBufferFrames - 1, test.Get());
+ EXPECT_EQ(RewindableBufferFrames - 1, test.GetPrevious());
+ }
+ Multiplayer::GetNetworkTime()->AlterTime(static_cast(RewindableBufferFrames), AZ::TimeMs(0), 1.f, AzNetworking::InvalidConnectionId);
+ }
+
TEST_F(RewindableObjectTests, OverflowTests)
{
Multiplayer::RewindableObject test(0);
diff --git a/cmake/3rdParty.cmake b/cmake/3rdParty.cmake
index f70ed1aa02..4b7f929279 100644
--- a/cmake/3rdParty.cmake
+++ b/cmake/3rdParty.cmake
@@ -6,6 +6,17 @@
#
#
+define_property(TARGET PROPERTY LY_SYSTEM_LIBRARY
+ BRIEF_DOCS "Defines a 3rdParty library as a system library"
+ FULL_DOCS [[
+ Property which is set on third party targets that should be considered
+ as provided by the system. Such targets are excluded from the runtime
+ dependencies considerations, and are not distributed as part of the
+ O3DE SDK package. Instead, users of the SDK are expected to install
+ such a third party library themselves.
+ ]]
+)
+
# Do not overcomplicate searching for the 3rdParty path, if it is not easy to find,
# the user should define it.
@@ -79,9 +90,10 @@ endfunction()
# "fileA\nMy/Output/Subfolder/lib"
# "fileB\nMy/Output/Subfolder/bin"
#
+# \arg:SYSTEM If specified, the library is considered a system library, and is not copied to the build output directory
function(ly_add_external_target)
- set(options)
+ set(options SYSTEM)
set(oneValueArgs NAME VERSION 3RDPARTY_DIRECTORY PACKAGE 3RDPARTY_ROOT_DIRECTORY OUTPUT_SUBDIRECTORY)
set(multiValueArgs HEADER_CHECK COMPILE_DEFINITIONS INCLUDE_DIRECTORIES BUILD_DEPENDENCIES RUNTIME_DEPENDENCIES)
@@ -300,6 +312,10 @@ function(ly_add_external_target)
)
endif()
+ if(ly_add_external_target_SYSTEM)
+ set_target_properties(3rdParty::${NAME_WITH_NAMESPACE} PROPERTIES LY_SYSTEM_LIBRARY TRUE)
+ endif()
+
endif()
endfunction()
@@ -327,4 +343,4 @@ if(NOT INSTALLED_ENGINE)
ly_include_cmake_file_list(cmake/3rdParty/cmake_files.cmake)
ly_get_absolute_pal_filename(pal_3rdparty_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME})
ly_include_cmake_file_list(${pal_3rdparty_dir}/cmake_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake)
-endif()
\ No newline at end of file
+endif()
diff --git a/cmake/Install.cmake b/cmake/Install.cmake
index 6f50c78fd7..adcd28fa37 100644
--- a/cmake/Install.cmake
+++ b/cmake/Install.cmake
@@ -152,3 +152,20 @@ function(ly_install_run_code CODE)
)
endfunction()
+
+#! ly_install_run_script: specifies path to script to be added to the install process (will run at install time)
+#
+# \notes:
+# - refer to cmake's install(SCRIPT documentation for more information
+#
+function(ly_install_run_script SCRIPT)
+
+ if(NOT LY_INSTALL_ENABLED)
+ return()
+ endif()
+
+ install(SCRIPT ${SCRIPT}
+ COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being
+ )
+
+endfunction()
\ No newline at end of file
diff --git a/cmake/LYPython.cmake b/cmake/LYPython.cmake
index eeb9f97a55..a8095fbc95 100644
--- a/cmake/LYPython.cmake
+++ b/cmake/LYPython.cmake
@@ -21,14 +21,17 @@ include(cmake/LySet.cmake)
# CMAKE_HOST_SYSTEM_NAME is "Windows", "Darwin", or "Linux" in our cases..
if (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Linux" )
ly_set(LY_PYTHON_VERSION 3.7.10)
+ ly_set(LY_PYTHON_VERSION_MAJOR_MINOR 3.7)
ly_set(LY_PYTHON_PACKAGE_NAME python-3.7.10-rev2-linux)
ly_set(LY_PYTHON_PACKAGE_HASH 6b9cf455e6190ec38836194f4454bb9db6bfc6890b4baff185cc5520aa822f05)
elseif (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Darwin" )
ly_set(LY_PYTHON_VERSION 3.7.10)
+ ly_set(LY_PYTHON_VERSION_MAJOR_MINOR 3.7)
ly_set(LY_PYTHON_PACKAGE_NAME python-3.7.10-rev1-darwin)
ly_set(LY_PYTHON_PACKAGE_HASH 3f65801894e4e44b5faa84dd85ef80ecd772dcf728cdd2d668a6e75978a32695)
elseif (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows" )
ly_set(LY_PYTHON_VERSION 3.7.10)
+ ly_set(LY_PYTHON_VERSION_MAJOR_MINOR 3.7)
ly_set(LY_PYTHON_PACKAGE_NAME python-3.7.10-rev2-windows)
ly_set(LY_PYTHON_PACKAGE_HASH 06d97488a2dbabe832ecfa832a42d3e8a7163ba95e975f032727331b0f49d280)
endif()
diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake
index fdd3256d78..8fb2effe29 100644
--- a/cmake/Platform/Common/Install_common.cmake
+++ b/cmake/Platform/Common/Install_common.cmake
@@ -21,15 +21,21 @@ define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET
ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core)
-cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory)
-cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE library_output_directory)
-
if(LY_MONOLITHIC_GAME)
set(LY_BUILD_PERMUTATION Monolithic)
else()
set(LY_BUILD_PERMUTATION Default)
endif()
+cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory)
+cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE library_output_directory)
+# Get the output folders, archive is always the same, but runtime/library can be in subfolders defined per target
+cmake_path(RELATIVE_PATH CMAKE_ARCHIVE_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE archive_output_directory)
+
+cmake_path(APPEND archive_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
+cmake_path(APPEND library_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
+cmake_path(APPEND runtime_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
+
#! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target
function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_target_source_dir)
# De-alias target name
@@ -78,9 +84,6 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
endforeach()
endif()
- # Get the output folders, archive is always the same, but runtime/library can be in subfolders defined per target
- cmake_path(RELATIVE_PATH CMAKE_ARCHIVE_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE archive_output_directory)
-
get_target_property(target_runtime_output_directory ${TARGET_NAME} RUNTIME_OUTPUT_DIRECTORY)
if(target_runtime_output_directory)
cmake_path(RELATIVE_PATH target_runtime_output_directory BASE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} OUTPUT_VARIABLE target_runtime_output_subdirectory)
@@ -91,10 +94,6 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar
cmake_path(RELATIVE_PATH target_library_output_directory BASE_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} OUTPUT_VARIABLE target_library_output_subdirectory)
endif()
- cmake_path(APPEND archive_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
- cmake_path(APPEND library_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
- cmake_path(APPEND runtime_output_directory "${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
-
if(COMMAND ly_install_target_override)
# Mac needs special handling because of a cmake issue
ly_install_target_override(TARGET ${TARGET_NAME}
@@ -372,6 +371,10 @@ function(ly_setup_o3de_install)
COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}
)
+ if(COMMAND ly_post_install_steps)
+ ly_post_install_steps()
+ endif()
+
endfunction()
#! ly_setup_cmake_install: install the "cmake" folder
@@ -528,7 +531,7 @@ endfunction()"
# of baking the path. This is needed so `cmake --install --prefix ` works regardless of the CMAKE_INSTALL_PREFIX
# used to generate the solution.
# CMAKE_INSTALL_PREFIX is still used when building the INSTALL target
- set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${LY_BUILD_PERMUTATION}")
+ set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}")
set(target_file_dir "${install_output_folder}/${target_runtime_output_subdirectory}")
ly_get_runtime_dependencies(runtime_dependencies ${target})
foreach(runtime_dependency ${runtime_dependencies})
diff --git a/cmake/Platform/Common/RuntimeDependencies_common.cmake b/cmake/Platform/Common/RuntimeDependencies_common.cmake
index 95d91da7f7..60e55453f8 100644
--- a/cmake/Platform/Common/RuntimeDependencies_common.cmake
+++ b/cmake/Platform/Common/RuntimeDependencies_common.cmake
@@ -70,12 +70,23 @@ function(ly_get_runtime_dependencies ly_RUNTIME_DEPENDENCIES ly_TARGET)
# link dependencies are not runtime dependencies (we dont have anything to copy) however, we need to traverse
# them since them or some dependency downstream could have something to copy over
- foreach(link_dependency ${link_dependencies})
- if(NOT ${link_dependency} MATCHES "^::@") # Skip wraping produced when targets are not created in the same directory (https://cmake.org/cmake/help/latest/prop_tgt/LINK_LIBRARIES.html)
- unset(dependencies)
- ly_get_runtime_dependencies(dependencies ${link_dependency})
- list(APPEND all_runtime_dependencies ${dependencies})
+ foreach(link_dependency IN LISTS link_dependencies)
+ if(${link_dependency} MATCHES "^::@")
+ # Skip wraping produced when targets are not created in the same directory
+ # (https://cmake.org/cmake/help/latest/prop_tgt/LINK_LIBRARIES.html)
+ continue()
endif()
+
+ if(TARGET ${link_dependency} AND link_dependency MATCHES "^3rdParty::")
+ get_target_property(is_system_library ${link_dependency} LY_SYSTEM_LIBRARY)
+ if(is_system_library)
+ continue()
+ endif()
+ endif()
+
+ unset(dependencies)
+ ly_get_runtime_dependencies(dependencies ${link_dependency})
+ list(APPEND all_runtime_dependencies ${dependencies})
endforeach()
# For manual dependencies, we want to copy over the dependency and traverse them
diff --git a/cmake/Platform/Mac/Configurations_mac.cmake b/cmake/Platform/Mac/Configurations_mac.cmake
index 33a58dbbda..b1c50b751d 100644
--- a/cmake/Platform/Mac/Configurations_mac.cmake
+++ b/cmake/Platform/Mac/Configurations_mac.cmake
@@ -29,9 +29,7 @@ else()
endif()
# Signing
-# The "-o linker-signed" flag is required as a work-around for the following CMake issue:
-# https://gitlab.kitware.com/cmake/cmake/-/issues/21854
-ly_set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep -o linker-signed")
+ly_set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep")
# Generate scheme files for Xcode
ly_set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
diff --git a/cmake/Platform/Mac/InstallUtils_mac.cmake.in b/cmake/Platform/Mac/InstallUtils_mac.cmake.in
new file mode 100644
index 0000000000..de6d9ddf65
--- /dev/null
+++ b/cmake/Platform/Mac/InstallUtils_mac.cmake.in
@@ -0,0 +1,168 @@
+#
+# 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
+#
+#
+
+function(fixup_qt_framework lib_name framework_path)
+
+ file(REMOVE_RECURSE
+ ${framework_path}/Headers
+ ${framework_path}/Resources
+ ${framework_path}/${lib_name}
+ ${framework_path}/Versions/Current
+ ${framework_path}/Versions/5/Headers
+ )
+
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink 5 Current
+ WORKING_DIRECTORY ${framework_path}/Versions
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink Versions/Current/${lib_name} ${lib_name}
+ WORKING_DIRECTORY ${framework_path}
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink Versions/Current/Resources Resources
+ WORKING_DIRECTORY ${framework_path}
+ )
+
+endfunction()
+
+function(fixup_python_framework framework_path)
+
+ file(REMOVE_RECURSE
+ ${framework_path}/Versions/Current
+ ${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/Headers
+ ${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/lib/Python
+ ${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/lib/python@LY_PYTHON_VERSION_MAJOR_MINOR@/test
+ ${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/lib/python@LY_PYTHON_VERSION_MAJOR_MINOR@/site-packages/scipy/io/tests
+ ${framework_path}/Python
+ ${framework_path}/Resources
+ ${framework_path}/Headers
+ )
+
+ file(GLOB_RECURSE exe_file_list "${framework_path}/**/*.exe")
+ if(exe_file_list)
+ file(REMOVE_RECURSE ${exe_file_list})
+ endif()
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink include/python@LY_PYTHON_VERSION_MAJOR_MINOR@m Headers
+ WORKING_DIRECTORY ${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink @LY_PYTHON_VERSION_MAJOR_MINOR@ Current
+ WORKING_DIRECTORY ${framework_path}/Versions/
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink Versions/Current/Python Python
+ WORKING_DIRECTORY ${framework_path}
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink Versions/Current/Headers Headers
+ WORKING_DIRECTORY ${framework_path}
+ )
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink Versions/Current/Resources Resources
+ WORKING_DIRECTORY ${framework_path}
+ )
+ file(CHMOD ${framework_path}/Versions/Current/Python
+ PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+ )
+
+endfunction()
+
+function(codesign_file file entitlement_file)
+
+ if (NOT @LY_ENABLE_HARDENED_RUNTIME@)
+ return()
+ endif()
+
+ if(EXISTS ${entitlement_file})
+
+ execute_process(COMMAND "/usr/bin/codesign" "--force" "--sign" "@LY_CODE_SIGN_IDENTITY@" "--deep" "-o" "runtime" "--timestamp" "--entitlements" "${entitlement_file}" "${file}"
+ TIMEOUT 300
+ OUTPUT_VARIABLE codesign_out
+ RESULT_VARIABLE codesign_ret
+ )
+ else()
+ execute_process(COMMAND "/usr/bin/codesign" "--force" "--sign" "@LY_CODE_SIGN_IDENTITY@" "--deep" "-o" "runtime" "--timestamp" "${file}"
+ TIMEOUT 300
+ OUTPUT_VARIABLE codesign_out
+ RESULT_VARIABLE codesign_ret
+ )
+ endif()
+
+ if(NOT ${codesign_ret} EQUAL "0")
+ message(FATAL_ERROR "Codesign operation for ${file_path} returned ${codesign_ret} with message ${codesign_out}")
+ endif()
+
+endfunction()
+
+function(codesign_python_framework_binaries framework_path)
+
+ if (NOT @LY_ENABLE_HARDENED_RUNTIME@)
+ return()
+ endif()
+
+ # The codesign "--deep" flag will only codesign binaries in folders with specific names.
+ # We need to codesign all the binaries that the "--deep" flag will miss.
+ file(GLOB_RECURSE files
+ LIST_DIRECTORIES false
+ "${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/bin/**"
+ "${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/lib/**"
+ "${framework_path}/Versions/@LY_PYTHON_VERSION_MAJOR_MINOR@/Resources/**")
+
+ foreach(file ${files})
+ if(NOT EXISTS ${file})
+ file(REMOVE ${file})
+ continue()
+ endif()
+ cmake_path(SET path_var "${file}")
+ cmake_path(GET path_var EXTENSION LAST_ONLY extension)
+ set(should_codesign FALSE)
+ set(extension_skip_list ".dylib" ".so" ".7m")
+ if (NOT extension)
+ set(should_codesign TRUE)
+ elseif(extension IN_LIST extension_skip_list)
+ set(should_codesign TRUE)
+ endif()
+ if(${should_codesign})
+ codesign_file("${file}" "@LY_ROOT_FOLDER@/python/Platform/Mac/PythonEntitlements.plist")
+ endif()
+ endforeach()
+
+endfunction()
+
+function(ly_copy source_file target_directory)
+
+ if("${source_file}" MATCHES "\\.[Ff]ramework[^\\.]")
+
+ # fixup origin to copy the whole Framework folder
+ string(REGEX REPLACE "(.*\\.[Ff]ramework).*" "\\1" source_file "${source_file}")
+
+ endif()
+ get_filename_component(target_filename "${source_file}" NAME)
+ file(COPY "${source_file}" DESTINATION "${target_directory}" FILE_PERMISSIONS @LY_COPY_PERMISSIONS@ FOLLOW_SYMLINK_CHAIN)
+
+ # Our Qt and Python frameworks aren't in the correct bundle format to be codesigned.
+ if("${target_filename}" MATCHES "(Qt[^.]+)\\.[Ff]ramework")
+ fixup_qt_framework(${CMAKE_MATCH_1} "${target_directory}/${target_filename}")
+ # For some Qt frameworks(QtCore), signing the bundle doesn't work because of bundle
+ # format issues(despite the fixes above). But once we've patched the framework above, there's
+ # only one executable that we need to sign so we can do it directly.
+ set(target_filename "${target_filename}/Versions/5/${CMAKE_MATCH_1}")
+ elseif("${target_filename}" MATCHES "Python.framework")
+ fixup_python_framework("${target_directory}/${target_filename}")
+ codesign_python_framework_binaries("${target_directory}/${target_filename}")
+ endif()
+ codesign_file("${target_directory}/${target_filename}" "none")
+
+endfunction()
+
+function(ly_download_and_codesign_sdk_python)
+ execute_process(COMMAND ${CMAKE_COMMAND} -DPAL_PLATFORM_NAME=Mac -DLY_3RDPARTY_PATH=${CMAKE_INSTALL_PREFIX}/python -P ${CMAKE_INSTALL_PREFIX}/python/get_python.cmake)
+ fixup_python_framework(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework)
+ codesign_python_framework_binaries(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework)
+ codesign_file(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework @LY_ROOT_FOLDER@/python/Platform/Mac/PythonEntitlements.plist)
+endfunction()
+
+function(ly_codesign_sdk)
+ codesign_file(${LY_INSTALL_PATH_ORIGINAL}/O3DE_SDK.app "none")
+endfunction()
+
+
diff --git a/cmake/Platform/Mac/Install_mac.cmake b/cmake/Platform/Mac/Install_mac.cmake
index 8f07b1bfde..bdc2300131 100644
--- a/cmake/Platform/Mac/Install_mac.cmake
+++ b/cmake/Platform/Mac/Install_mac.cmake
@@ -6,6 +6,8 @@
#
#
+include(cmake/Platform/Common/Install_common.cmake)
+
# This is used to generate a setreg file which will be placed inside the bundle
# for targets that request it(eg. AssetProcessor/Editor). This is the relative path
# to the bundle from the installed engine's root. This will be used to compute the
@@ -16,7 +18,7 @@ set(installed_binaries_path_template [[
"AzCore": {
"Runtime": {
"FilePaths": {
- "InstalledBinariesFolder": "bin/Mac/$"
+ "InstalledBinariesFolder": "@runtime_output_directory@"
}
}
}
@@ -24,15 +26,20 @@ set(installed_binaries_path_template [[
}]]
)
-unset(target_conf_dir)
-foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES)
- string(TOUPPER ${conf} UCONF)
- string(APPEND target_conf_dir $<$:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${UCONF}}>)
-endforeach()
+# This setreg file will be used by all of our installed app bundles to locate installed
+# runtime dependencies. It contains the path to binary install directory relative to
+# the installed engine root.
+string(CONFIGURE "${installed_binaries_path_template}" configured_setreg_file)
+file(GENERATE
+ OUTPUT ${CMAKE_BINARY_DIR}/runtime_install/$/BinariesInstallPath.setreg
+ CONTENT "${configured_setreg_file}"
+)
-set(installed_binaries_setreg_path ${target_conf_dir}/Registry/installed_binaries_path.setreg)
-
-file(GENERATE OUTPUT ${installed_binaries_setreg_path} CONTENT ${installed_binaries_path_template})
+# ly_install_run_script isn't defined yet so we use install(SCRIPT) directly.
+# This needs to be done here because it needs to update the install prefix
+# before cmake does anything else in the install process.
+configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake @ONLY)
+install(SCRIPT ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME})
#! ly_install_target_override: Mac specific target installation
function(ly_install_target_override)
@@ -70,33 +77,62 @@ function(ly_install_target_override)
COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}
)
+ set(install_relative_binaries_path "${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR}")
+
if (${is_bundle})
set_property(TARGET ${ly_platform_install_target_TARGET} PROPERTY RESOURCE ${cached_resources_dir})
+ set(runtime_output_filename "$.app")
+ else()
+ set(runtime_output_filename "$")
+ endif()
+
+ get_target_property(target_type ${ly_platform_install_target_TARGET} TYPE)
+ if(target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS)
+ get_target_property(entitlement_file ${ly_platform_install_target_TARGET} ENTITLEMENT_FILE_PATH)
+ if (NOT entitlement_file)
+ set(entitlement_file "none")
+ endif()
+
+ ly_file_read(${LY_ROOT_FOLDER}/cmake/Platform/Mac/runtime_install_mac.cmake.in template_file)
+ string(CONFIGURE "${template_file}" configured_template_file @ONLY)
+ file(GENERATE
+ OUTPUT ${CMAKE_BINARY_DIR}/runtime_install/$/${ly_platform_install_target_TARGET}.cmake
+ CONTENT "${configured_template_file}"
+ )
endif()
-endfunction()
-
-#! ly_install_add_install_path_setreg: Adds the install path setreg file as a dependency
-function(ly_install_add_install_path_setreg NAME)
- set_property(TARGET ${NAME} APPEND PROPERTY INTERFACE_LY_TARGET_FILES "${installed_binaries_setreg_path}\nRegistry")
endfunction()
#! ly_install_code_function_override: Mac specific copy function to handle frameworks
function(ly_install_code_function_override)
- install(CODE
-"function(ly_copy source_file target_directory)
- if(\"\${source_file}\" MATCHES \"\\\\.[Ff]ramework[^\\\\.]\")
-
- # fixup origin to copy the whole Framework folder
- string(REGEX REPLACE \"(.*\\\\.[Ff]ramework).*\" \"\\\\1\" source_file \"\${source_file}\")
- get_filename_component(target_filename \"\${source_file}\" NAME)
-
- endif()
- file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS})
-endfunction()"
- COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}
- )
+ configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/InstallUtils_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake @ONLY)
+ ly_install_run_script(${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake)
+
+endfunction()
+
+#! ly_post_install_steps: Any additional platform specific post install steps
+function(ly_post_install_steps)
+
+ # On Mac, after CMake is done installing, the code signatures on all our built binaries will be invalid.
+ # We need to now codesign each dynamic library, executable, and app bundle. It's specific to each target
+ # because there could potentially be different entitlements for different targets.
+ get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS)
+ foreach(alias_target IN LISTS all_targets)
+ ly_de_alias_target(${alias_target} target)
+ # Exclude targets that dont produce runtime outputs
+ get_target_property(target_type ${target} TYPE)
+ if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS)
+ continue()
+ endif()
+
+ ly_install_run_script(${CMAKE_BINARY_DIR}/runtime_install/$/${target}.cmake)
+ endforeach()
+
+ ly_install_run_code("
+ ly_download_and_codesign_sdk_python()
+ ly_codesign_sdk()
+ set(CMAKE_INSTALL_PREFIX ${LY_INSTALL_PATH_ORIGINAL})
+ ")
endfunction()
-include(cmake/Platform/Common/Install_common.cmake)
diff --git a/cmake/Platform/Mac/LYWrappers_mac.cmake b/cmake/Platform/Mac/LYWrappers_mac.cmake
index 578f6fe041..245d3bb7c1 100644
--- a/cmake/Platform/Mac/LYWrappers_mac.cmake
+++ b/cmake/Platform/Mac/LYWrappers_mac.cmake
@@ -6,6 +6,16 @@
#
#
+set(LY_ENABLE_HARDENED_RUNTIME OFF CACHE BOOL "Enable hardened runtime capability for Mac builds. This should be ON when building the engine for notarization/distribution.")
+
+define_property(TARGET PROPERTY ENTITLEMENT_FILE_PATH
+ BRIEF_DOCS "Path to the entitlement file"
+ FULL_DOCS [[
+ On MacOS, entitlements are used to grant certain privileges
+ to applications at runtime. Use this propery to specify the
+ path to a .plist file containing entitlements.
+ ]]
+)
function(ly_apply_platform_properties target)
@@ -14,6 +24,18 @@ function(ly_apply_platform_properties target)
INSTALL_RPATH "@executable_path/;@executable_path/../Frameworks"
)
+ get_property(is_imported TARGET ${target} PROPERTY IMPORTED)
+ if((NOT is_imported) AND (LY_ENABLE_HARDENED_RUNTIME))
+ get_property(target_type TARGET ${target} PROPERTY TYPE)
+ set(runtime_types_list "MODULE_LIBRARY" "SHARED_LIBRARY" "EXECUTABLE")
+ if (target_type IN_LIST runtime_types_list)
+ set_target_properties(${target} PROPERTIES
+ XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
+ XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS NO
+ )
+ endif()
+ endif()
+
endfunction()
diff --git a/cmake/Platform/Mac/PAL_mac.cmake b/cmake/Platform/Mac/PAL_mac.cmake
index b415daf44a..065b35d69c 100644
--- a/cmake/Platform/Mac/PAL_mac.cmake
+++ b/cmake/Platform/Mac/PAL_mac.cmake
@@ -39,3 +39,6 @@ set(LY_ASSET_DEPLOY_ASSET_TYPE "mac" CACHE STRING "Set the asset type for deploy
# Set the python cmd tool
ly_set(LY_PYTHON_CMD ${CMAKE_CURRENT_SOURCE_DIR}/python/python.sh)
+
+# Only x86_64 is currently supported on Mac
+ly_set(CMAKE_OSX_ARCHITECTURES "x86_64")
diff --git a/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in b/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in
new file mode 100644
index 0000000000..5033aadd6e
--- /dev/null
+++ b/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in
@@ -0,0 +1,39 @@
+#
+# 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
+#
+#
+
+cmake_minimum_required(VERSION 3.20)
+
+# The O3DE SDK will be shipped as an app bundle. So we create an O3DE_SDK.app directory
+# and install SDK into the app's Contents/Engine directory.
+set(LY_INSTALL_PATH_ORIGINAL ${CMAKE_INSTALL_PREFIX})
+
+file(INSTALL @LY_ROOT_FOLDER@/Code/Tools/BundleLauncher/info.plist
+ DESTINATION ${CMAKE_INSTALL_PREFIX}/O3DE_SDK.app/Contents
+)
+
+# This SDK launcher will install python site-packages and then launch the ProjectManager
+# when a user double clicks on the SDK from Finder. We're only going to need one version
+# of the SDK launcher regardless of what configs of the engine are installed.
+if (EXISTS @CMAKE_BINARY_DIR@/bin/profile/O3DE_SDK)
+ set(sdk_launcher_config profile)
+elseif (EXISTS @CMAKE_BINARY_DIR@/bin/debug/O3DE_SDK)
+ set(sdk_launcher_config debug)
+elseif (EXISTS @CMAKE_BINARY_DIR@/bin/release/O3DE_SDK)
+ set(sdk_launcher_config release)
+endif()
+file(INSTALL @CMAKE_BINARY_DIR@/bin/${sdk_launcher_config}/O3DE_SDK
+ DESTINATION ${CMAKE_INSTALL_PREFIX}/O3DE_SDK.app/Contents/MacOS
+ USE_SOURCE_PERMISSIONS
+)
+file(INSTALL @CMAKE_BINARY_DIR@/runtime_install/${sdk_launcher_config}/BinariesInstallPath.setreg
+ DESTINATION ${CMAKE_INSTALL_PREFIX}/O3DE_SDK.app/Contents/MacOS/Registry
+)
+
+# We need to update the CMAKE_INSTALL_PREFIX so that the engine is installed inside the app bundle.
+file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/O3DE_SDK.app/Contents/Engine)
+set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/O3DE_SDK.app/Contents/Engine)
\ No newline at end of file
diff --git a/cmake/Platform/Mac/runtime_dependencies_mac.cmake.in b/cmake/Platform/Mac/runtime_dependencies_mac.cmake.in
index d65578f82a..11551f608f 100644
--- a/cmake/Platform/Mac/runtime_dependencies_mac.cmake.in
+++ b/cmake/Platform/Mac/runtime_dependencies_mac.cmake.in
@@ -73,8 +73,8 @@ function(ly_copy source_file target_directory)
return()
endif()
- # fixup the destination so it ends up in Contents/Plugins
- string(REGEX REPLACE "(.*\\.app/Contents)/MacOS" "\\1/plugins" target_directory "${target_directory}")
+ # fixup the destination so it ends up in Contents/PlugIns
+ string(REGEX REPLACE "(.*\\.app/Contents)/MacOS" "\\1/PlugIns" target_directory "${target_directory}")
set(local_plugin_dirs ${plugin_dirs})
list(APPEND local_plugin_dirs "${target_directory}")
@@ -212,7 +212,6 @@ if(@target_file_dir@ MATCHES ".app/Contents/MacOS")
file(REMOVE_RECURSE ${remove_file_list})
endif()
-
endif()
else() # Non-bundle case
diff --git a/cmake/Platform/Mac/runtime_install_mac.cmake.in b/cmake/Platform/Mac/runtime_install_mac.cmake.in
new file mode 100644
index 0000000000..65b1ede77b
--- /dev/null
+++ b/cmake/Platform/Mac/runtime_install_mac.cmake.in
@@ -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
+#
+#
+
+cmake_path(SET file_path "${CMAKE_INSTALL_PREFIX}/@install_relative_binaries_path@/@runtime_output_filename@")
+cmake_path(GET file_path EXTENSION LAST_ONLY file_ext)
+
+if(file_ext STREQUAL .app)
+
+ file(INSTALL @CMAKE_BINARY_DIR@/runtime_install/$/BinariesInstallPath.setreg
+ DESTINATION ${file_path}/Contents/MacOS/Registry
+ )
+
+ if(EXISTS "${file_path}/Contents/Frameworks/Python.framework")
+ codesign_python_framework_binaries("${file_path}/Contents/Frameworks/Python.framework")
+ endif()
+
+else()
+
+ find_program(LY_INSTALL_NAME_TOOL install_name_tool)
+ if (NOT LY_INSTALL_NAME_TOOL)
+ message(FATAL_ERROR "Unable to locate 'install_name_tool'")
+ endif()
+
+ execute_process(COMMAND
+ ${LY_INSTALL_NAME_TOOL} -add_rpath @loader_path ${file_path})
+
+endif()
+
+codesign_file("${file_path}" "@entitlement_file@")
diff --git a/python/Platform/Mac/PythonEntitlements.plist b/python/Platform/Mac/PythonEntitlements.plist
new file mode 100644
index 0000000000..ed4892befa
--- /dev/null
+++ b/python/Platform/Mac/PythonEntitlements.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+
+
diff --git a/python/get_python.sh b/python/get_python.sh
index 248a0790fc..e8d35a311e 100755
--- a/python/get_python.sh
+++ b/python/get_python.sh
@@ -30,7 +30,8 @@ cd $DIR
python_exitcode=$?
if [ $python_exitcode == 0 ]; then
echo get_python.sh: Python is already downloaded: $(./python.sh --version)
- $DIR/pip.sh install -r $DIR/requirements.txt --quiet --disable-pip-version-check
+ $DIR/pip.sh install -r $DIR/requirements.txt --disable-pip-version-check --no-warn-script-location
+ $DIR/pip.sh install -e $DIR/../scripts/o3de --no-deps --disable-pip-version-check --no-warn-script-location
exit 0
fi
if [[ "$OSTYPE" = *"darwin"* ]];
@@ -73,5 +74,6 @@ if [ $retVal -ne 0 ]; then
fi
echo installing via pip...
-$DIR/pip.sh install -r $DIR/requirements.txt --disable-pip-version-check
+$DIR/pip.sh install -r $DIR/requirements.txt --disable-pip-version-check --no-warn-script-location
+$DIR/pip.sh install -e $DIR/../scripts/o3de --no-deps --disable-pip-version-check --no-warn-script-location
exit $?
diff --git a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt
index 21c4755a2e..2cbbe58b38 100644
--- a/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt
+++ b/scripts/build/build_node/Platform/Linux/package-list.ubuntu-focal.txt
@@ -13,6 +13,8 @@ libxcb-xinput0 # For Qt plugins at runtime
libfontconfig1-dev # For Qt plugins at runtime
libcurl4-openssl-dev # For HttpRequestor
libsdl2-dev # for WWise/Audio
-libxkbcommon-dev
+libxcb-xkb-dev # For xcb keyboard input
+libxkbcommon-x11-dev # For xcb keyboard input
+libxkbcommon-dev # For xcb keyboard input
zlib1g-dev
mesa-common-dev