From 786a72cf63229dc3d7576bb701bcfeef278175e1 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:46:17 +0000 Subject: [PATCH] LYN-7376 test_TerrainHeightGradientList_AddRemoveGradients Signed-off-by: sphrose <82213493+sphrose@users.noreply.github.com> --- ...ightGradientList_AddRemoveGradientWorks.py | 144 ++++++++++++++++++ .../Gem/PythonTests/Terrain/TestSuite_Main.py | 3 + .../Terrain/TerrainDataRequestBus.cpp | 61 ++++++++ .../Terrain/TerrainDataRequestBus.h | 20 +++ .../TerrainHeightGradientListComponent.cpp | 9 ++ .../TerrainHeightGradientListComponent.h | 5 +- .../TerrainLayerSpawnerComponent.cpp | 11 ++ .../Components/TerrainLayerSpawnerComponent.h | 3 +- 8 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py new file mode 100644 index 0000000000..0be1f1ca10 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py @@ -0,0 +1,144 @@ +""" +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 HeightTests: + single_gradient_height_correct = ( + "Successfully retrieved height for gradient1.", + "Failed to retrieve height for gradient1." + ) + double_gradient_height_correct = ( + "Successfully retrieved height when two gradients exist.", + "Failed to retrieve height when two gradients exist." + ) + triple_gradient_height_correct = ( + "Successfully retrieved height when three gradients exist.", + "Failed to retrieve height when three gradients exist." + ) + terrain_data_changed_call_count_correct = ( + "OnTerrainDataChanged called expected number of times.", + "OnTerrainDataChanged call count incorrect." + ) + +def TerrainHeightGradientList_AddRemoveGradientWorks(): + """ + Summary: + Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree. + :return: None + """ + + import os + import math as sys_math + + import azlmbr.legacy.general as general + import azlmbr.bus as bus + import azlmbr.math as math + import azlmbr.terrain as terrain + import azlmbr.editor as editor + import azlmbr.vegetation as vegetation + import azlmbr.entity as EntityId + + 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 + import editor_python_test_tools.pyside_utils as pyside_utils + from editor_python_test_tools.editor_entity_utils import EditorEntity + + terrain_changed_call_count = 0 + expected_terrain_changed_calls = 0 + + aabb_component_name = "Axis Aligned Box Shape" + gradientlist_component_name = "Terrain Height Gradient List" + layerspawner_component_name = "Terrain Layer Spawner" + + gradient_value_path = "Configuration|Value" + + def create_entity_at(entity_name, components_to_add, x, y, z): + entity = hydra.Entity(entity_name) + entity.create_entity(math.Vector3(x, y, z), components_to_add) + + return entity + + def on_terrain_changed(args): + nonlocal terrain_changed_call_count + + terrain_changed_call_count += 1 + + def set_component_path_val(entity, component, path, value): + entity.get_set_test(component, path, value) + + def set_gradients_check_height(main_entity, gradient_list, expected_height, test_results): + nonlocal expected_terrain_changed_calls + + test_tolerance = 0.01 + gradient_list_path = "Configuration|Gradient Entities" + + set_component_path_val(main_entity, 1, gradient_list_path, gradient_list) + + expected_terrain_changed_calls += 1 + + # Wait until the terrain data has been updated. + helper.wait_for_condition(lambda: terrain_changed_call_count == expected_terrain_changed_calls, 2.0) + + # Get the height at the origin. + height = terrain.TerrainDataRequestBus(bus.Broadcast, "GetHeightFromFloats", 0.0, 0.0, 0) + + Report.result(test_results, sys_math.isclose(height, expected_height, abs_tol=test_tolerance)) + + helper.init_idle() + + # Open a level. + helper.open_level("Physics", "Base") + helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0) + + general.idle_wait_frames(1) + + # Add a terrain world component + world_component = hydra.add_level_component("Terrain World") + + aabb_height = 1024.0 + box_dimensions = math.Vector3(1.0, 1.0, aabb_height); + + # Create a main entity with a LayerSpawner, AAbb and HeightGradientList. + main_entity = create_entity_at("entity2", [layerspawner_component_name, gradientlist_component_name, aabb_component_name], 0.0, 0.0, aabb_height/2.0) + + # Create three gradient entities. + gradient_entity1 = create_entity_at("Constant Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0); + gradient_entity2 = create_entity_at("Constant Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0); + gradient_entity3 = create_entity_at("Constant Gradient3", ["Constant Gradient"], 0.0, 0.0, 0.0); + + # Give everything a chance to finish initializing. + general.idle_wait_frames(1) + + # Set the gradients to different values. + gradient_values = [0.5, 0.8, 0.3] + set_component_path_val(gradient_entity1, 0, gradient_value_path, gradient_values[0]) + set_component_path_val(gradient_entity2, 0, gradient_value_path, gradient_values[1]) + set_component_path_val(gradient_entity3, 0, gradient_value_path, gradient_values[2]) + + # Give the TerrainSystem time to tick. + general.idle_wait_frames(1) + + # Set the dimensions of the Aabb. + set_component_path_val(main_entity, 2, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions) + + # Set up a handler to wait for notifications from the TerrainSystem. + handler = azlmbr.terrain.TerrainDataNotificationBusHandler() + handler.connect() + handler.add_callback("OnTerrainDataChanged", on_terrain_changed) + + # Add a gradient to GradientList, then check the height returned from the TerrainSystem is correct. + set_gradients_check_height(main_entity, [gradient_entity1.id], aabb_height * gradient_values[0], HeightTests.single_gradient_height_correct) + + # Add gradient2 and check height at the origin, this should have changed to match the second gradient value. + set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id], aabb_height * gradient_values[1], HeightTests.double_gradient_height_correct) + + # Add gradient3, the height should still be the second value, as that was the highest. + set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id, gradient_entity3.id], aabb_height * gradient_values[1], HeightTests.triple_gradient_height_correct) + +if __name__ == "__main__": + + from editor_python_test_tools.utils import Report + Report.start_test(TerrainHeightGradientList_AddRemoveGradientWorks) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py index 786651713c..fc9a917f47 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py @@ -27,3 +27,6 @@ class TestAutomation(EditorTestSuite): class test_Terrain_SupportsPhysics(EditorSharedTest): from .EditorScripts import Terrain_SupportsPhysics as test_module + + class test_TerrainHeightGradientList_AddRemoveGradientWorks(EditorSharedTest): + from .EditorScripts import TerrainHeightGradientList_AddRemoveGradientWorks as test_module diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp index 561408db20..033bbfccd6 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp @@ -12,12 +12,57 @@ namespace AzFramework::Terrain { + // Create a handler that can be accessed from Python scripts to receive terrain change notifications. + class TerrainDataNotificationHandler final + : public AzFramework::Terrain::TerrainDataNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER( + TerrainDataNotificationHandler, + "{A83EF103-295A-4653-8279-F30FBF3F9037}", + AZ::SystemAllocator, + OnTerrainDataCreateBegin, + OnTerrainDataCreateEnd, + OnTerrainDataDestroyBegin, + OnTerrainDataDestroyEnd, + OnTerrainDataChanged); + + void OnTerrainDataCreateBegin() override + { + Call(FN_OnTerrainDataCreateBegin); + } + + void OnTerrainDataCreateEnd() override + { + Call(FN_OnTerrainDataCreateEnd); + } + + void OnTerrainDataDestroyBegin() override + { + Call(FN_OnTerrainDataDestroyBegin); + } + + void OnTerrainDataDestroyEnd() override + { + Call(FN_OnTerrainDataDestroyEnd); + } + + void OnTerrainDataChanged( + const AZ::Aabb& dirtyRegion, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask dataChangedMask) override + { + Call(FN_OnTerrainDataChanged, dirtyRegion, dataChangedMask); + } + }; + void TerrainDataRequests::Reflect(AZ::ReflectContext* context) { if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("TerrainDataRequestBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Category, "Terrain") + ->Attribute(AZ::Script::Attributes::Module, "terrain") ->Event("GetHeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetHeight) ->Event("GetNormal", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetNormal) ->Event("GetMaxSurfaceWeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetMaxSurfaceWeight) @@ -33,8 +78,24 @@ namespace AzFramework::Terrain ->Event("GetTerrainAabb", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainAabb) ->Event("GetTerrainHeightQueryResolution", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainHeightQueryResolution) + ->Event("GetHeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetHeightVal) + ->Event("GetHeightFromVector2", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetHeightValFromVector2) + ->Event("GetHeightFromFloats", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetHeightValFromFloats) ; + + behaviorContext->EBus("TerrainDataNotificationBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Terrain") + ->Attribute(AZ::Script::Attributes::Module, "terrain") + ->Event("OnTerrainDataCreateBegin", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataCreateBegin) + ->Event("OnTerrainDataCreateEnd", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataCreateEnd) + ->Event("OnTerrainDataDestroyBegin", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataDestroyBegin) + ->Event("OnTerrainDataDestroyEnd", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataDestroyEnd) + ->Event("OnTerrainDataChanged", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataChanged) + ->Handler() + ; } + //TerrainDataNotificationHandler::Reflect(context); } } // namespace AzFramework::Terrain diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h index 30a2f8e044..c36a27b229 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h @@ -130,6 +130,26 @@ namespace AzFramework SurfaceData::SurfacePoint& outSurfacePoint, Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const = 0; + + private: + // Functions without the optional bool* parameter that can be used from Python tests. + float GetHeightVal(AZ::Vector3 position, Sampler sampler = Sampler::BILINEAR) const + { + bool terrainExists; + return GetHeight(position, sampler, &terrainExists); + } + + float GetHeightValFromVector2(AZ::Vector2 position, Sampler sampler = Sampler::BILINEAR) const + { + bool terrainExists; + return GetHeightFromVector2(position, sampler, &terrainExists); + } + + float GetHeightValFromFloats(float x, float y, Sampler sampler = Sampler::BILINEAR) const + { + bool terrainExists; + return GetHeightFromFloats(x, y, sampler, &terrainExists); + } }; using TerrainDataRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index e9c9874af5..108ae70632 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -48,6 +48,15 @@ namespace Terrain ->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC_CE("GradientService")) ; } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Category, "Terrain") + ->Constructor() + ->Property("gradientEntities", BehaviorValueProperty(&TerrainHeightGradientListConfig::m_gradientEntities)) + ; + } } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h index b5b1a44192..12a51b2ac1 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h @@ -44,6 +44,7 @@ namespace Terrain AZStd::vector m_gradientEntities; }; + static const AZ::Uuid TerrainHeightGradientListComponentTypeId = "{1BB3BA6C-6D4A-4636-B542-F23ECBA8F2AB}"; class TerrainHeightGradientListComponent : public AZ::Component @@ -54,7 +55,7 @@ namespace Terrain public: template friend class LmbrCentral::EditorWrappedComponentBase; - AZ_COMPONENT(TerrainHeightGradientListComponent, "{1BB3BA6C-6D4A-4636-B542-F23ECBA8F2AB}"); + AZ_COMPONENT(TerrainHeightGradientListComponent, TerrainHeightGradientListComponentTypeId); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); @@ -64,6 +65,8 @@ namespace Terrain TerrainHeightGradientListComponent() = default; ~TerrainHeightGradientListComponent() = default; + ////////////////////////////////////////////////////////////////////////// + // TerrainAreaHeightRequestBus void GetHeight(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) override; ////////////////////////////////////////////////////////////////////////// diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp index c3803c25e8..83259415bd 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp @@ -54,6 +54,17 @@ namespace Terrain ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainLayerSpawnerConfig::m_useGroundPlane, "Use Ground Plane", "Determines whether or not to provide a default ground plane") ; } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Category, "Terrain") + ->Constructor() + ->Property("layer", BehaviorValueProperty(&TerrainLayerSpawnerConfig::m_layer)) + ->Property("priority", BehaviorValueProperty(&TerrainLayerSpawnerConfig::m_priority)) + ->Property("useGroundPlane", BehaviorValueProperty(&TerrainLayerSpawnerConfig::m_useGroundPlane)) + ->Method("GetSelectableLayers", &TerrainLayerSpawnerConfig::GetSelectableLayers); + } } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h index 683f9e9f06..3ce73b7a5a 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.h @@ -52,6 +52,7 @@ namespace Terrain bool m_useGroundPlane = true; }; + static const AZ::Uuid TerrainLayerSpawnerComponentTypeId = "{3848605F-A4EA-478C-B710-84AB8DCA9EC5}"; class TerrainLayerSpawnerComponent : public AZ::Component @@ -61,7 +62,7 @@ namespace Terrain public: template friend class LmbrCentral::EditorWrappedComponentBase; - AZ_COMPONENT(TerrainLayerSpawnerComponent, "{3848605F-A4EA-478C-B710-84AB8DCA9EC5}"); + AZ_COMPONENT(TerrainLayerSpawnerComponent, TerrainLayerSpawnerComponentTypeId); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);