Material_RestitutionCombine.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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 : C4044457
  7. # Test Case Title : Verify that when two objects with different materials collide, the restitution combine works
  8. # fmt: off
  9. class Tests():
  10. enter_game_mode = ("Entered game mode", "Failed to enter game mode")
  11. find_ramp = ("Ramp entity found", "Ramp entity not found")
  12. find_box_minimum = ("Box entity 'minimum' found", "Box entity 'minimum' not found")
  13. find_box_multiply = ("Box entity 'multiply' found", "Box entity 'multiply' not found")
  14. find_box_average = ("Box entity 'average' found", "Box entity 'average' not found")
  15. find_box_maximum = ("Box entity 'maximum' found", "Box entity 'maximum' not found")
  16. box_fell_minimum = ("Box 'minimum' fell", "Box 'minimum' did not fall")
  17. box_fell_multiply = ("Box 'multiply' fell", "Box 'multiply' did not fall")
  18. box_fell_average = ("Box 'average' fell", "Box 'average' did not fall")
  19. box_fell_maximum = ("Box 'maximum' fell", "Box 'maximum' did not fall")
  20. box_hit_ramp_minimum = ("Box 'minimum' hit the ramp", "Box 'minimum' did not hit the ramp before timeout")
  21. box_hit_ramp_multiply = ("Box 'multiply' hit the ramp", "Box 'multiply' did not hit the ramp before timeout")
  22. box_hit_ramp_average = ("Box 'average' hit the ramp", "Box 'average' did not hit the ramp before timeout")
  23. box_hit_ramp_maximum = ("Box 'maximum' hit the ramp", "Box 'maximum' did not hit the ramp before timeout")
  24. box_peaked_minimum = ("Box 'minimum' reached its max height", "Box 'minimum' did not reach its' max height before timeout")
  25. box_peaked_multiply = ("Box 'multiply' reached its max height", "Box 'multiply' did not reach its' max height before timeout")
  26. box_peaked_average = ("Box 'average' reached its max height", "Box 'average' did not reach its' max height before timeout")
  27. box_peaked_maximum = ("Box 'maximum' reached its max height", "Box 'maximum' did not reach its' max height before timeout")
  28. minimum_equals_multiply = ("Box 'minimum' and 'multiply' bounced equal heights", "Box 'minimum' and 'multiply' did not bounce equal heights")
  29. distance_ordered = ("Boxes with greater restitution values bounced higher", "Boxes with greater restitution values did not bounce higher")
  30. exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
  31. # fmt: on
  32. def Material_RestitutionCombine():
  33. """
  34. Summary:
  35. Level Description:
  36. Four boxes sit above a horizontal 'ramp'. Gravity on each rigidbody component is set to disabled.
  37. The boxes are identical, save for their physX material.
  38. A new material library was created with 4 materials, minimum, multiply, average, and maximum.
  39. Each material has its 'restitution combine' mode assigned as named; as well as the following properties:
  40. dynamic friction: 0.1
  41. static friction: 0.1
  42. restitution: 0.1
  43. An additional material was created for the ramp entity. It has the following properties:
  44. dynamic friction: 1.0
  45. static friction: 1.0
  46. restitution: 1.0
  47. friction combine: Average
  48. Each box is assigned its corresponding material
  49. Each box also has a PhysX box collider with default settings
  50. Expected Behavior:
  51. For each box, this script will enable gravity, then wait for the box to collide with the ramp.
  52. It then measures the height of the bounce relative to when it first came in contact with the ramp.
  53. When the z component of the box's velocity reaches zero (or below zero), the box latches its bounce height.
  54. The box is then frozen in place and the steps run for the next box in the list.
  55. Boxes with greater restitution combine mode retain more energy between collisions, therefore bouncing higher.
  56. minimum: 0.1 vs 1 -> 0.1
  57. multiply: 0.1 * 1 -> 0.1
  58. average: (0.1 + 1) / 2 -> 0.55
  59. maximum: 0.1 vs 1 -> 1
  60. Test Steps:
  61. 1) Open level
  62. 2) Enter game mode
  63. 3) Find the ramp
  64. For each box:
  65. 4) Find the box
  66. 5) Drop the box
  67. 6) Ensure the box collides with the ramp
  68. 7) Ensure the box reaches its peak height
  69. 8) Special case: assert that minimum and multiply bounce the same height
  70. 9) Assert that greater restitution combine modes bounce higher
  71. 10) Exit game mode
  72. 11) Close the editor
  73. Note:
  74. - This test file must be called from the Open 3D Engine Editor command terminal
  75. - Any passed and failed tests are written to the Editor.log file.
  76. Parsing the file or running a log_monitor are required to observe the test results.
  77. :return: None
  78. """
  79. import os
  80. import sys
  81. from editor_python_test_tools.utils import Report
  82. from editor_python_test_tools.utils import TestHelper as helper
  83. import azlmbr
  84. import azlmbr.legacy.general as general
  85. import azlmbr.bus as bus
  86. import azlmbr.math as lymath
  87. DISTANCE_TOLERANCE = 0.005
  88. TIMEOUT = 5
  89. class Box:
  90. def __init__(self, name, valid_test, fell_test, hit_ramp_test, peaked_test):
  91. self.name = name
  92. self.id = general.find_game_entity(name)
  93. self.start_position = self.get_position()
  94. self.hit_ramp = False
  95. self.hit_ramp_position = None
  96. self.bounce_height = 0.0
  97. self.valid_test = valid_test
  98. self.fell_test = fell_test
  99. self.hit_ramp_test = hit_ramp_test
  100. self.peaked_test = peaked_test
  101. self.set_gravity_enabled(False)
  102. def get_position(self):
  103. return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
  104. def get_velocity(self):
  105. return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
  106. def set_velocity(self, value):
  107. return azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", self.id, value)
  108. def set_gravity_enabled(self, value):
  109. azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetGravityEnabled", self.id, value)
  110. def on_collision_begin(args):
  111. other_id = args[0]
  112. for box in all_boxes:
  113. if box.id.Equal(other_id):
  114. box.hit_ramp_position = box.get_position()
  115. box.hit_ramp = True
  116. def reached_max_height(box):
  117. current_position = box.get_position()
  118. current_height = current_position.z - box.hit_ramp_position.z
  119. current_linear_velocity = box.get_velocity()
  120. if current_linear_velocity.z > 0.0:
  121. box.bounce_height = current_height
  122. return False
  123. else:
  124. Report.info("Box {} reached {:.3f}M high".format(box.name, box.bounce_height))
  125. return True
  126. def is_falling(box):
  127. return box.get_velocity().z < 0.0
  128. def float_is_close(value, target, tolerance):
  129. return abs(value - target) <= tolerance
  130. helper.init_idle()
  131. # 1) Open level
  132. helper.open_level("Physics", "Material_RestitutionCombine")
  133. # 2) Enter game mode
  134. helper.enter_game_mode(Tests.enter_game_mode)
  135. # fmt: off
  136. # Set up our boxes
  137. box_minimum = Box(
  138. name = "Minimum",
  139. valid_test = Tests.find_box_minimum,
  140. fell_test = Tests.box_fell_minimum,
  141. hit_ramp_test = Tests.box_hit_ramp_minimum,
  142. peaked_test = Tests.box_peaked_minimum,
  143. )
  144. box_multiply = Box(
  145. name = "Multiply",
  146. valid_test = Tests.find_box_multiply,
  147. fell_test = Tests.box_fell_multiply,
  148. hit_ramp_test = Tests.box_hit_ramp_multiply,
  149. peaked_test = Tests.box_peaked_multiply,
  150. )
  151. box_average = Box(
  152. name = "Average",
  153. valid_test = Tests.find_box_average,
  154. fell_test = Tests.box_fell_average,
  155. hit_ramp_test = Tests.box_hit_ramp_average,
  156. peaked_test = Tests.box_peaked_average,
  157. )
  158. box_maximum = Box(
  159. name = "Maximum",
  160. valid_test = Tests.find_box_maximum,
  161. fell_test = Tests.box_fell_maximum,
  162. hit_ramp_test = Tests.box_hit_ramp_maximum,
  163. peaked_test = Tests.box_peaked_maximum,
  164. )
  165. all_boxes = (box_minimum, box_multiply, box_average, box_maximum)
  166. # fmt: on
  167. # 3) Find the ramp
  168. ramp_id = general.find_game_entity("Ramp")
  169. Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
  170. handler = azlmbr.physics.CollisionNotificationBusHandler()
  171. handler.connect(ramp_id)
  172. handler.add_callback("OnCollisionBegin", on_collision_begin)
  173. for box in all_boxes:
  174. Report.info("********Dropping Box {}********".format(box.name))
  175. # 4) Find the box
  176. Report.critical_result(box.valid_test, box.id.IsValid())
  177. # 5) Drop the box
  178. box.set_gravity_enabled(True)
  179. Report.critical_result(box.fell_test, helper.wait_for_condition(lambda: is_falling(box), TIMEOUT))
  180. # 6) Wait for the box to hit the ground
  181. Report.result(box.hit_ramp_test, helper.wait_for_condition(lambda: box.hit_ramp, TIMEOUT))
  182. # 7) Measure the bounce height
  183. Report.result(box.peaked_test, helper.wait_for_condition(lambda: reached_max_height(box), TIMEOUT))
  184. # Freeze the box so it does not interfere with the other boxes
  185. box.set_velocity(lymath.Vector3(0.0, 0.0, 0.0))
  186. box.set_gravity_enabled(False)
  187. # 8) Special case: assert that minimum and multiply bounce the same height
  188. boxes_are_close = float_is_close(box_minimum.bounce_height, box_multiply.bounce_height, DISTANCE_TOLERANCE)
  189. Report.result(Tests.minimum_equals_multiply, boxes_are_close)
  190. # 9) Assert that greater coefficients result in higher bounces
  191. distance_ordered = (
  192. boxes_are_close and box_minimum.bounce_height < box_average.bounce_height < box_maximum.bounce_height
  193. )
  194. Report.result(Tests.distance_ordered, distance_ordered)
  195. # 10) Exit game mode
  196. helper.exit_game_mode(Tests.exit_game_mode)
  197. if __name__ == "__main__":
  198. from editor_python_test_tools.utils import Report
  199. Report.start_test(Material_RestitutionCombine)