Material_ComponentsInSyncWithLibrary.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. # Test Case ID : C15308221
  7. # Test Case Title : Verify that material library and slots are always in sync and work consistently through the different places of usage
  8. # fmt: off
  9. class Tests:
  10. enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
  11. find_terrain_0 = ("Test 0) The Terrain was found", "Test 0) The Terrain was not found")
  12. find_terrain_box_0 = ("Test 0) Terrain test box was found", "Test 0) Terrain test box was not found")
  13. find_collider_0 = ("Test 0) Box collider was found", "Test 0) Box collider was not found")
  14. find_ragdoll_0 = ("Test 0) Ragdoll was found", "Test 0) Ragdoll was not found")
  15. find_character_controller_0 = ("Test 0) Character controller was found", "Test 0) Character controller was not found")
  16. find_controller_box_0 = ("Test 0) Character controller test box was found", "Test 0) Character controller test box was not found")
  17. terrain_box_bounced_0 = ("Test 0) Terrain test box bounced", "Test 0) Terrain test box did not bounce")
  18. collider_bounced_0 = ("Test 0) Box collider bounced", "Test 0) Box collider did not bounce")
  19. ragdoll_bounced_0 = ("Test 0) Modified ragdoll bounced", "Test 0) Modified ragdoll did not bounce")
  20. controller_box_bounced_0 = ("Test 0) Character controller test box bounced", "Test 0) Character controller test box did not bounce")
  21. exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
  22. all_bounced_equal_0 = ("Test 0) All entities bounced the same height", "Test 0) All entities did not bounce the same height")
  23. enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
  24. find_terrain_1 = ("Test 1) The Terrain was found", "Test 1) The Terrain was not found")
  25. find_terrain_box_1 = ("Test 1) Terrain test box was found", "Test 1) Terrain test box was not found")
  26. find_collider_1 = ("Test 1) Box collider was found", "Test 1) Box collider was not found")
  27. find_ragdoll_1 = ("Test 1) Ragdoll was found", "Test 1) Ragdoll was not found")
  28. find_character_controller_1 = ("Test 1) Character controller was found", "Test 1) Character controller was not found")
  29. find_controller_box_1 = ("Test 1) Character controller test box was found", "Test 1) Character controller test box was not found")
  30. terrain_box_bounced_1 = ("Test 1) Terrain test box bounced", "Test 1) Terrain test box did not bounce")
  31. collider_bounced_1 = ("Test 1) Box collider bounced", "Test 1) Box collider did not bounce")
  32. ragdoll_bounced_1 = ("Test 1) Modified ragdoll bounced", "Test 1) Modified ragdoll did not bounce")
  33. controller_box_bounced_1 = ("Test 1) Character controller test box bounced", "Test 1) Character controller test box did not bounce")
  34. exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
  35. all_bounced_equal_1 = ("Test 1) All entities bounced the same height", "Test 1) All entities did not bounce the same height")
  36. all_bounced_greater = ("All entities bounced higher on the second test", "All entities did not bounce higher on the second test")
  37. # fmt: on
  38. def Material_ComponentsInSyncWithLibrary():
  39. """
  40. Summary:
  41. Runs an automated test to verify that the material library is always in sync between the different PhysX components
  42. Level Description:
  43. A new material library was created with 1 material, called "Modified":
  44. dynamic friction: 0.5
  45. static friction: 0.5
  46. restitution: 0.25
  47. There are 4 types of components we want to test for:
  48. PhysX Ragdoll:
  49. A ragdoll ("ragdoll") with the "Modified" material applied to all of its colliders. Positioned above the
  50. terrain.
  51. PhysX collider:
  52. A PhysX box collider ("collider") with a the "Modified" material applied. Positioned above the terrain.
  53. PhysX terrain:
  54. A PhysX terrain ("terrain"), and a PhysX box collider ("terrain_box"). "terrain_box" is positioned above
  55. "terrain". A new layer was created with the "Modified" material and painted onto the terrain under
  56. "terrain_box". "terrain_box" has the default material applied.
  57. PhysX character controller:
  58. A character controller ("character_controller"), and a PhysX box collider ("controller_box").
  59. "controller_box" is positioned above "character_controller" and is assigned the default material.
  60. "character_controller" is assigned "Modified"
  61. Expected behavior:
  62. For every iteration this test measures the bounce height of each entity. The entities save their traveled distances
  63. each iteration, to verify different behavior between each setup.
  64. First the test verifies the entities all behave identically, without changing anything. All entities should bounce
  65. the same height.
  66. Next, the test modifies the restitution value for 'Modified' (from 0.25 to 0.75). All entities should again bounce
  67. the same height. Additionally, all entities should bounce higher with the new restitution than they did previously.
  68. Test Steps:
  69. 1) Open level
  70. 2) Collect basis values without modifying anything
  71. 2.1) Enter game mode
  72. 2.2) Find entities
  73. 2.3) Wait for entities to bounce
  74. 2.4) Exit game mode
  75. 3) Verify all entities behave the same as a baseline
  76. 4) Modify the restitution value of 'modified'
  77. 4.1 - 4.4) <same as 2.1 - 2.4>
  78. 5) Verify the entities all still behave the same
  79. 6) Verify that the material change was propagated correctly
  80. 7) Close editor
  81. Note:
  82. - This test file must be called from the Open 3D Engine Editor command terminal
  83. - Any passed and failed tests are written to the Editor.log file.
  84. Parsing the file or running a log_monitor are required to observe the test results.
  85. :return: None
  86. """
  87. import os
  88. import sys
  89. import azlmbr.legacy.general as general
  90. import azlmbr.bus as bus
  91. import azlmbr.components
  92. import azlmbr.physics
  93. import azlmbr.math as lymath
  94. from Physmaterial_Editor import Physmaterial_Editor
  95. from editor_python_test_tools.utils import Report
  96. from editor_python_test_tools.utils import TestHelper as helper
  97. TIMEOUT = 3.0
  98. BOUNCE_TOLERANCE = 0.1
  99. class Entity:
  100. def __init__(self, name, bounce_off_of_name):
  101. self.name = name
  102. self.bounce_off_of_name = bounce_off_of_name
  103. self.bounces = []
  104. def find_and_reset(self):
  105. self.hit_position = None
  106. self.hit_terrain = False
  107. self.max_bounce = 0.0
  108. self.reached_max_bounce = False
  109. self.id = general.find_game_entity(self.name)
  110. self.setup_handler()
  111. return self.id.IsValid()
  112. def on_collision_enter(self, args):
  113. entering = args[0]
  114. if entering.Equal(self.id):
  115. if not self.hit_terrain:
  116. self.hit_terrain_position = self.position
  117. self.hit_terrain = True
  118. def setup_handler(self):
  119. self.bounce_off_of_id = general.find_game_entity(self.bounce_off_of_name)
  120. self.handler = azlmbr.physics.CollisionNotificationBusHandler()
  121. self.handler.connect(self.bounce_off_of_id)
  122. self.handler.add_callback("OnCollisionBegin", self.on_collision_enter)
  123. @property
  124. def position(self):
  125. return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
  126. def get_test(test_name):
  127. return Tests.__dict__[test_name]
  128. def run_test(test_number):
  129. # x.1) Enter game mode
  130. helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
  131. # x.2) Find entities
  132. controller_valid = general.find_game_entity("character_controller").IsValid()
  133. terrain_valid = general.find_game_entity("terrain").IsValid()
  134. Report.critical_result(get_test("find_character_controller_{}".format(test_number)), controller_valid)
  135. Report.critical_result(get_test("find_terrain_{}".format(test_number)), terrain_valid)
  136. collider_valid = collider.find_and_reset()
  137. controller_box_valid = controller_box.find_and_reset()
  138. ragdoll_valid = ragdoll.find_and_reset()
  139. terrain_box_valid = terrain_box.find_and_reset()
  140. Report.critical_result(get_test("find_collider_{}".format(test_number)), collider_valid)
  141. Report.critical_result(get_test("find_controller_box_{}".format(test_number)), controller_box_valid)
  142. Report.critical_result(get_test("find_ragdoll_{}".format(test_number)), ragdoll_valid)
  143. Report.critical_result(get_test("find_terrain_box_{}".format(test_number)), terrain_box_valid)
  144. def wait_for_bounce():
  145. for entity in all_entities:
  146. if entity.hit_terrain:
  147. current_bounce_height = entity.position.z - entity.hit_terrain_position.z
  148. if current_bounce_height >= entity.max_bounce:
  149. entity.max_bounce = current_bounce_height
  150. elif entity.max_bounce > 0.0:
  151. entity.reached_max_bounce = True
  152. return all([entity.reached_max_bounce for entity in all_entities])
  153. # x.3) Wait for entities to bounce
  154. helper.wait_for_condition(wait_for_bounce, TIMEOUT)
  155. Report.result(get_test("collider_bounced_{}".format(test_number)), collider.reached_max_bounce)
  156. Report.result(get_test("controller_box_bounced_{}".format(test_number)), controller_box.reached_max_bounce)
  157. Report.result(get_test("ragdoll_bounced_{}".format(test_number)), ragdoll.reached_max_bounce)
  158. Report.result(get_test("terrain_box_bounced_{}".format(test_number)), terrain_box.reached_max_bounce)
  159. for entity in all_entities:
  160. entity.bounces.append(entity.max_bounce)
  161. # x.4) Exit game mode
  162. helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
  163. # 1) Open level and enter game mode
  164. helper.init_idle()
  165. helper.open_level("Physics", "Material_ComponentsInSyncWithLibrary")
  166. # Setup persisting entities
  167. collider = Entity("collider", "terrain")
  168. controller_box = Entity("controller_box", "character_controller")
  169. ragdoll = Entity("ragdoll", "terrain")
  170. terrain_box = Entity("terrain_box", "terrain")
  171. all_entities = [collider, controller_box, ragdoll, terrain_box]
  172. # 2) Collect basis values without modifying anything
  173. run_test(0)
  174. # 3) Verify all entities behave the same as a baseline
  175. test_0_max_bounce = max([entity.bounces[0] for entity in all_entities])
  176. test_0_min_bounce = min([entity.bounces[0] for entity in all_entities])
  177. Report.result(
  178. Tests.all_bounced_equal_0, lymath.Math_IsClose(test_0_max_bounce, test_0_min_bounce, BOUNCE_TOLERANCE)
  179. )
  180. # 4) Modify the restitution value of 'modified'
  181. material_editor = Physmaterial_Editor("c15308221_material_componentsinsyncwithlibrary.physmaterial")
  182. material_editor.modify_material("Modified", "Restitution", 0.75)
  183. material_editor.save_changes()
  184. run_test(1)
  185. # 5) Verify the entities all still behave the same
  186. test_1_max_bounce = max([entity.bounces[1] for entity in all_entities])
  187. test_1_min_bounce = min([entity.bounces[1] for entity in all_entities])
  188. Report.result(
  189. Tests.all_bounced_equal_1, lymath.Math_IsClose(test_1_max_bounce, test_1_min_bounce, BOUNCE_TOLERANCE)
  190. )
  191. # 6) Verify that the material change was propagated correctly
  192. all_bounced_greater = all([entity.bounces[0] < entity.bounces[1] for entity in all_entities])
  193. Report.result(Tests.all_bounced_greater, all_bounced_greater)
  194. if __name__ == "__main__":
  195. from editor_python_test_tools.utils import Report
  196. Report.start_test(Material_ComponentsInSyncWithLibrary)