Terrain_SupportsPhysics.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. #fmt: off
  7. class Tests:
  8. create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity")
  9. create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity")
  10. create_test_ball = ("Ball created successfully", "Failed to create Ball")
  11. box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions")
  12. shape_changed = ("Shape changed successfully", "Failed Shape change")
  13. entity_added = ("Entity added successfully", "Failed Entity add")
  14. frequency_changed = ("Frequency changed successfully", "Failed Frequency change")
  15. shape_set = ("Shape set to Sphere successfully", "Failed to set Sphere shape")
  16. test_collision = ("Ball collided with terrain", "Ball failed to collide with terrain")
  17. no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
  18. #fmt: on
  19. def Terrain_SupportsPhysics():
  20. """
  21. Summary:
  22. General validation that terrain physics heightfields work within the context of the PhysX integration.
  23. Expected Behavior:
  24. The terrain system is initialized, gets a hilly physics heightfield generated, and detects a collision between a sphere and a hill
  25. in a timely fashion without errors or warnings.
  26. Test Steps:
  27. 1) Load the base level
  28. 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
  29. 2a) Create a ball at 600.0, 600.0, 46.0 - This position intersects the terrain when it has hills generated correctly.
  30. 3) Start the Tracer to catch any errors and warnings
  31. 4) Change the Axis Aligned Box Shape dimensions
  32. 5) Set the Reference Shape to TestEntity1
  33. 6) Set the FastNoise gradient frequency to 0.01
  34. 7) Set the Gradient List to TestEntity2
  35. 8) Set the PhysX Collider to Sphere mode
  36. 9) Set the Rigid Body to start with no gravity enabled, so that it sits in place, intersecting the expected terrain
  37. 10) Enter game mode and test if the ball detects the heightfield intersection within 3 seconds
  38. 11) Verify there are no errors and warnings in the logs
  39. :return: None
  40. """
  41. from editor_python_test_tools.wait_utils import PrefabWaiter
  42. from editor_python_test_tools.utils import TestHelper as helper
  43. from editor_python_test_tools.utils import Report, Tracer
  44. import editor_python_test_tools.hydra_editor_utils as hydra
  45. import azlmbr.math as azmath
  46. import azlmbr.legacy.general as general
  47. import azlmbr.bus as bus
  48. import azlmbr.editor as editor
  49. import math
  50. SET_BOX_X_SIZE = 1024.0
  51. SET_BOX_Y_SIZE = 1024.0
  52. SET_BOX_Z_SIZE = 100.0
  53. # 1) Load the level
  54. hydra.open_base_level()
  55. #1a) Load the level components
  56. hydra.add_level_component("Terrain World")
  57. hydra.add_level_component("Terrain World Renderer")
  58. # 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
  59. entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"]
  60. entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"]
  61. ball_components_to_add = ["Sphere Shape", "PhysX Collider", "PhysX Dynamic Rigid Body"]
  62. terrain_spawner_entity = hydra.Entity("TestEntity1")
  63. terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)
  64. Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid())
  65. height_provider_entity = hydra.Entity("TestEntity2")
  66. height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id)
  67. Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid())
  68. # 2a) Create a ball at 600.0, 600.0, 46.0 - The ball is created as a collider with a Rigid Body, but at rest and without gravity,
  69. # so that it will stay in place. This specific location is chosen because the ball should intersect the terrain.
  70. ball = hydra.Entity("Ball")
  71. ball.create_entity(azmath.Vector3(600.0, 600.0, 46.0), ball_components_to_add)
  72. Report.result(Tests.create_test_ball, ball.id.IsValid())
  73. # Give everything a chance to finish initializing.
  74. general.idle_wait_frames(1)
  75. # 3) Start the Tracer to catch any errors and warnings
  76. with Tracer() as section_tracer:
  77. # 4) Change the Axis Aligned Box Shape dimensions
  78. box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE)
  79. terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
  80. box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions")
  81. Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions)
  82. # 5) Set the Reference Shape to TestEntity1
  83. height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id)
  84. entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id")
  85. Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id)
  86. # 6) Set the FastNoise Gradient frequency to 0.01
  87. Frequency = 0.01
  88. height_provider_entity.get_set_test(2, "Configuration|Frequency", Frequency)
  89. FrequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency")
  90. Report.result(Tests.frequency_changed, math.isclose(Frequency, FrequencyVal, abs_tol = 0.00001))
  91. # 7) Set the Gradient List to TestEntity2
  92. propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2])
  93. propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id)
  94. checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0)
  95. Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id)
  96. # 7a) Disable and Enable the Terrain Height Gradient List so that the change to the container is recognized
  97. editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]])
  98. PrefabWaiter.wait_for_propagation()
  99. # 8) Set the PhysX Collider to Sphere mode
  100. shape = 0
  101. hydra.get_set_test(ball, 1, "Shape Configuration|Shape", shape)
  102. setShape = hydra.get_component_property_value(ball.components[1], "Shape Configuration|Shape")
  103. Report.result(Tests.shape_set, shape == setShape)
  104. # 9) Set the PhysX Rigid Body to not use gravity
  105. hydra.get_set_test(ball, 2, "Configuration|Gravity enabled", False)
  106. general.enter_game_mode()
  107. general.idle_wait_frames(1)
  108. # 10) Enter game mode and test if the ball detects the heightfield collision within 5 seconds
  109. TIMEOUT = 5.0
  110. class Collider:
  111. id = general.find_game_entity("Ball")
  112. touched_ground = False
  113. terrain_id = general.find_game_entity("TestEntity1")
  114. def on_collision_persist(args):
  115. other_id = args[0]
  116. if other_id.Equal(terrain_id):
  117. Report.info("Ball intersected with heightfield")
  118. Collider.touched_ground = True
  119. handler = azlmbr.physics.CollisionNotificationBusHandler()
  120. handler.connect(Collider.id)
  121. handler.add_callback("OnCollisionPersist", on_collision_persist)
  122. helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
  123. Report.result(Tests.test_collision, Collider.touched_ground)
  124. general.exit_game_mode()
  125. # 11) Verify there are no errors and warnings in the logs
  126. helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0)
  127. for error_info in section_tracer.errors:
  128. Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
  129. for assert_info in section_tracer.asserts:
  130. Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
  131. if __name__ == "__main__":
  132. from editor_python_test_tools.utils import Report
  133. Report.start_test(Terrain_SupportsPhysics)