Terrain_SupportsPhysics.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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 Primitive 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. from consts.physics import PHYSX_PRIMITIVE_COLLIDER
  45. import editor_python_test_tools.hydra_editor_utils as hydra
  46. import azlmbr.math as azmath
  47. import azlmbr.legacy.general as general
  48. import azlmbr.bus as bus
  49. import azlmbr.editor as editor
  50. import math
  51. SET_BOX_X_SIZE = 1024.0
  52. SET_BOX_Y_SIZE = 1024.0
  53. SET_BOX_Z_SIZE = 100.0
  54. # 1) Load the level
  55. hydra.open_base_level()
  56. #1a) Load the level components
  57. hydra.add_level_component("Terrain World")
  58. hydra.add_level_component("Terrain World Renderer")
  59. # 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
  60. entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"]
  61. entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"]
  62. ball_components_to_add = ["Sphere Shape", PHYSX_PRIMITIVE_COLLIDER, "PhysX Dynamic Rigid Body"]
  63. terrain_spawner_entity = hydra.Entity("TestEntity1")
  64. terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)
  65. Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid())
  66. height_provider_entity = hydra.Entity("TestEntity2")
  67. height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id)
  68. Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid())
  69. # 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,
  70. # so that it will stay in place. This specific location is chosen because the ball should intersect the terrain.
  71. ball = hydra.Entity("Ball")
  72. ball.create_entity(azmath.Vector3(600.0, 600.0, 46.0), ball_components_to_add)
  73. Report.result(Tests.create_test_ball, ball.id.IsValid())
  74. # Give everything a chance to finish initializing.
  75. general.idle_wait_frames(1)
  76. # 3) Start the Tracer to catch any errors and warnings
  77. with Tracer() as section_tracer:
  78. # 4) Change the Axis Aligned Box Shape dimensions
  79. box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE)
  80. terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
  81. box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions")
  82. Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions)
  83. # 5) Set the Reference Shape to TestEntity1
  84. height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id)
  85. entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id")
  86. Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id)
  87. # 6) Set the FastNoise Gradient frequency to 0.01
  88. Frequency = 0.01
  89. height_provider_entity.get_set_test(2, "Configuration|Frequency", Frequency)
  90. FrequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency")
  91. Report.result(Tests.frequency_changed, math.isclose(Frequency, FrequencyVal, abs_tol = 0.00001))
  92. # 7) Set the Gradient List to TestEntity2
  93. propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2])
  94. propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id)
  95. checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0)
  96. Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id)
  97. # 7a) Disable and Enable the Terrain Height Gradient List so that the change to the container is recognized
  98. editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]])
  99. PrefabWaiter.wait_for_propagation()
  100. # 8) Set the PhysX Primitive Collider to Sphere mode
  101. shape = 0
  102. hydra.get_set_test(ball, 1, "Shape Configuration|Shape", shape)
  103. setShape = hydra.get_component_property_value(ball.components[1], "Shape Configuration|Shape")
  104. Report.result(Tests.shape_set, shape == setShape)
  105. # 9) Set the PhysX Rigid Body to not use gravity
  106. hydra.get_set_test(ball, 2, "Configuration|Gravity enabled", False)
  107. general.enter_game_mode()
  108. general.idle_wait_frames(1)
  109. # 10) Enter game mode and test if the ball detects the heightfield collision within 5 seconds
  110. TIMEOUT = 5.0
  111. class Collider:
  112. id = general.find_game_entity("Ball")
  113. touched_ground = False
  114. terrain_id = general.find_game_entity("TestEntity1")
  115. def on_collision_persist(args):
  116. other_id = args[0]
  117. if other_id.Equal(terrain_id):
  118. Report.info("Ball intersected with heightfield")
  119. Collider.touched_ground = True
  120. handler = azlmbr.physics.CollisionNotificationBusHandler()
  121. handler.connect(Collider.id)
  122. handler.add_callback("OnCollisionPersist", on_collision_persist)
  123. helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
  124. Report.result(Tests.test_collision, Collider.touched_ground)
  125. general.exit_game_mode()
  126. # 11) Verify there are no errors and warnings in the logs
  127. helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0)
  128. for error_info in section_tracer.errors:
  129. Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
  130. for assert_info in section_tracer.asserts:
  131. Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
  132. if __name__ == "__main__":
  133. from editor_python_test_tools.utils import Report
  134. Report.start_test(Terrain_SupportsPhysics)