text: """
  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 VegetationTests:
      vegetation_on_gradient_1 = (
          "Vegetation detected at correct position on Gradient1", 
          "Vegetation not detected at correct position on Gradient1"
      )
      vegetation_on_gradient_2 = (
          "Vegetation detected at correct position on Gradient2", 
          "Vegetation not detected at correct position on Gradient2"
      )
      unfiltered_vegetation_count_correct = (
          "Unfiltered vegetation spawn count correct", 
          "Unfiltered vegetation spawn count incorrect"
      )
  
      testTag2_excluded_vegetation_count_correct = (
          "TestTag2 filtered vegetation count correct", 
          "TestTag2 filtered vegetation count incorrect"
      )
      testTag2_excluded_vegetation_z_correct = (
          "TestTag2 filtered vegetation spawned in correct position", 
          "TestTag2 filtered vegetation failed to spawn in correct position"
      )
  
      testTag3_excluded_vegetation_count_correct = (
          "TestTag3 filtered vegetation count correct", 
          "TestTag3 filtered vegetation count incorrect"
      )
      testTag3_excluded_vegetation_z_correct = (
          "TestTag3 filtered vegetation spawned in correct position", 
          "TestTag3 filtered vegetation failed to spawn in correct position"
      )
  
      cleared_exclusion_vegetation_count_correct = (
          "Cleared filter vegetation count correct", 
          "Cleared filter vegetation count incorrect"
      )
  
  def TerrainSystem_VegetationSpawnsOnTerrainSurfaces():
      """
      Summary:
      Load an empty level, 
      Create two entities with constant gradient components with different values.
      Create two entities with TerrainLayerSpawners
      Create an entity to spawn vegetation
      Ensure that vegetation spawns at the correct heights
      Add a VegetationSurfaceMaskFilter and ensure it responds correctly to surface changes.
      :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.areasystem as areasystem
      import azlmbr.vegetation as vegetation
      import azlmbr.terrain as terrain
      import azlmbr.surface_data as surface_data
  
      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
  
      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 FindHighestAndLowestZValuesInArea(aabb):
          vegetation_items = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', aabb)
  
          lowest_z = min([item.position.z for item in vegetation_items])
          highest_z = max([item.position.z for item in vegetation_items])
  
          return highest_z, lowest_z
  
      # Open an empty level.
      hydra.open_base_level()
      
      general.idle_wait_frames(1)
  
      box_height = 20.0
      box_y_position = 10.0
      box_dimensions = math.Vector3(20.0, 20.0, box_height)
  
      # Add Terrain Rendering
      hydra.add_level_component("Terrain World")
      hydra.add_level_component("Terrain World Renderer")
  
      # Create two terrain entities at adjoining positions
      terrain_entity_1 = create_entity_at("Terrain1", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 0.0, box_y_position, box_height/2.0)
      terrain_entity_1.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
  
      terrain_entity_2 = create_entity_at("Terrain2", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 20.0, box_y_position, box_height/2.0)
      terrain_entity_2.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
  
      # Create two gradient entities.
      gradient_value_1 = 0.25
      gradient_value_2 = 0.5
  
      gradient_entity_1 = create_entity_at("Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0)
      gradient_entity_1.get_set_test(0, "Configuration|Value", gradient_value_1)
  
      gradient_entity_2 = create_entity_at("Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0)
      gradient_entity_2.get_set_test(0, "Configuration|Value", gradient_value_2)
  
      mapping = terrain.TerrainSurfaceGradientMapping()
      mapping.gradientEntityId = gradient_entity_1.id
      pte = hydra.get_property_tree(terrain_entity_1.components[3])
      pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
  
      mapping = terrain.TerrainSurfaceGradientMapping()
      mapping.gradientEntityId = gradient_entity_2.id
      pte = hydra.get_property_tree(terrain_entity_2.components[3])
      pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
  
      # create a vegetation entity that overlaps both terrain entities.
      vegetation_entity = create_entity_at("Vegetation", ["Vegetation Layer Spawner", "Axis Aligned Box Shape", "Vegetation Asset List", "Vegetation Surface Mask Filter"], 10.0, box_y_position, box_height/2.0)
      vegetation_entity.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
  
      # Set the vegetation area to a PrefabInstanceSpawner with a specific prefab asset selected.
      prefab_spawner = vegetation.PrefabInstanceSpawner()
      prefab_spawner.SetPrefabAssetPath(os.path.join("Prefabs", "PinkFlower.spawnable"))
      descriptor = hydra.get_component_property_value(vegetation_entity.components[2], 'Configuration|Embedded Assets|[0]')
      descriptor.spawner = prefab_spawner
      vegetation_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
  
      # Assign gradients to layer spawners.
      terrain_entity_1.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_1.id])
      terrain_entity_2.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_2.id])
  
      # Move view so that the entities are visible.
      general.set_current_view_position(17.0, -66.0, 41.0)
      general.set_current_view_rotation(-15, 0, 0)
  
      # Expected item counts under conditions to be tested.
      # By default, vegetation spawns at a density of 20 items per 16 meters, 
      # so in a 20m square, there should be around 25 ^ 2 items depending on whether area edges are included.
      # In this case there are 26 ^ 2 items.
      expected_surface_tag_excluded_item_count = 338
      expected_no_exclusions_item_count = 676
  
      # Wait for the vegetation to spawn
      helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
  
      # Check the spawn count is correct.
      item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
      Report.result(VegetationTests.unfiltered_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
  
      test_aabb = math.Aabb_CreateFromMinMax(math.Vector3(-10.0, -10.0, 0.0), math.Vector3(30.0, 10.0, box_height))
  
      # Find the z positions of the items with the lowest and highest x values, this will avoid the overlap area where z values are blended between the surface heights.
      highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
      
      # Check that the z values are as expected.
      Report.result(VegetationTests.vegetation_on_gradient_1, sys_math.isclose(lowest_z, box_height * gradient_value_1, abs_tol=0.01))
      Report.result(VegetationTests.vegetation_on_gradient_2, sys_math.isclose(highest_z, box_height * gradient_value_2, abs_tol=0.01))
  
      # Assign SurfaceTags to the SurfaceGradientLists
      terrain_entity_1.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag2"))
      terrain_entity_2.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag3"))
  
      # Give the VegetationSurfaceFilter an exclusion list, set it to exclude test_tag2 which should remove all the lower items which are in terrain_entity_1.
      vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags", [surface_data.SurfaceTag()])
      vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag2"))
    
      # Wait for the vegetation to respawn and check z values.
      helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
  
      item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
      Report.result(VegetationTests.testTag2_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
  
      highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
  
      Report.result(VegetationTests.testTag2_excluded_vegetation_z_correct, lowest_z > box_height * gradient_value_1)
  
      # Clear the filter and ensure vegetation respawns.
      vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("invalid"))
      helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
  
      item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
      Report.result(VegetationTests.cleared_exclusion_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
  
      # Exclude test_tag3 to exclude the higher items in terrain_entity_2 and recheck.
      vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag3"))
  
      helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
  
      item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
      Report.result(VegetationTests.testTag3_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
  
      highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
  
      Report.result(VegetationTests.testTag3_excluded_vegetation_z_correct, highest_z < box_height * gradient_value_2)
  
  
  if __name__ == "__main__":
  
      from editor_python_test_tools.utils import Report
      Report.start_test(TerrainSystem_VegetationSpawnsOnTerrainSurfaces)
  