You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/AutomatedTesting/Gem/PythonTests/physics/C4976202_RigidBody_StopsWhe...

334 lines
17 KiB
Python

"""
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
"""
"""
Test case ID : C4976202
Test Case Title : Verify that if the object is moving with Kinetic energy less than
the sleep threshold value, then physX will put it to stop after 0.4 secs (once the
wake counter goes to zero) if the KE is still below the threshold
"""
# fmt: off
class Tests:
enter_game_mode_1 = ("Entered game mode_1", "Failed to enter game mode_1" )
Trigger1_exists_1 = ("Trigger1 entity was found_1", "Trigger1 entity was not found_1" )
Trigger2_exists_1 = ("Trigger2 entity was found_1", "Trigger2 entity was not found_1" )
Trigger3_exists_1 = ("Trigger3 entity was found_1", "Trigger3 entity was not found_1" )
Pushing_Cube_exists_1 = ("Pushing_Cube entity was found_1", "Pushing_Cube entity was not found_1" )
Target_Ball_exists_1 = ("Target_Ball entity was found_1", "Target_Ball entity was not found_1" )
threshold_setup_1 = ("The sleep threshold was setup properly_1", "The sleep threshold was not setup properly_1" )
cube_collided_with_ball_1 = ("The cube hit the ball the_1", "The cube did not hit the ball the_1" )
ball_stopped_moving_1 = ("The ball has stopped moving before timeout_1", "The ball did not stop moving before timeout was reached_1" )
trigger_patterns_match_1 = ("The actual trigger pattern matches expected_1", "The actual trigger pattern did not match expected_1" )
check_y_z_movement_1 = ("The ball did not move too far in Y or Z_1", "The ball moved farther than expected in Y or Z after collision_1")
exit_game_mode_1 = ("Exited game mode_1", "Couldn't exit game mode_1" )
enter_game_mode_2 = ("Entered game mode_2", "Failed to enter game mode_2" )
Trigger1_exists_2 = ("Trigger1 entity was found_2", "Trigger1 entity was not found_2" )
Trigger2_exists_2 = ("Trigger2 entity was found_2", "Trigger2 entity was not found_2" )
Trigger3_exists_2 = ("Trigger3 entity was found_2", "Trigger3 entity was not found_2" )
Pushing_Cube_exists_2 = ("Pushing_Cube entity was found_2", "Pushing_Cube entity was not found_2" )
Target_Ball_exists_2 = ("Target_Ball entity was found_2", "Target_Ball entity was not found_2" )
threshold_setup_2 = ("The sleep threshold was setup properly_2", "The sleep threshold was not setup properly_2" )
cube_collided_with_ball_2 = ("The cube hit the ball the_2", "The cube did not hit the ball the_2" )
ball_stopped_moving_2 = ("The ball has stopped moving before timeout_2", "The ball did not stop moving before timeout was reached_2" )
trigger_patterns_match_2 = ("The actual trigger pattern matches expected_2", "The actual trigger pattern did not match expected_2" )
check_y_z_movement_2 = ("The ball did not move too far in Y or Z_2", "The ball moved farther than expected in Y or Z after collision_2")
exit_game_mode_2 = ("Exited game mode_2", "Couldn't exit game mode_2" )
enter_game_mode_3 = ("Entered game mode_3", "Failed to enter game mode_3" )
Trigger1_exists_3 = ("Trigger1 entity was found_3", "Trigger1 entity was not found_3" )
Trigger2_exists_3 = ("Trigger2 entity was found_3", "Trigger2 entity was not found_3" )
Trigger3_exists_3 = ("Trigger3 entity was found_3", "Trigger3 entity was not found_3" )
Pushing_Cube_exists_3 = ("Pushing_Cube entity was found_3", "Pushing_Cube entity was not found_3" )
Target_Ball_exists_3 = ("Target_Ball entity was found_3", "Target_Ball entity was not found_3" )
threshold_setup_3 = ("The sleep threshold was setup properly_3", "The sleep threshold was not setup properly_3" )
cube_collided_with_ball_3 = ("The cube hit the ball the_3", "The cube did not hit the ball the_3" )
ball_stopped_moving_3 = ("The ball has stopped moving before timeout_3", "The ball did not stop moving before timeout was reached_3" )
trigger_patterns_match_3 = ("The actual trigger pattern matches expected_3", "The actual trigger pattern did not match expected_3" )
check_y_z_movement_3 = ("The ball did not move too far in Y or Z_3", "The ball moved farther than expected in Y or Z after collision_3")
exit_game_mode_3 = ("Exited game mode_3", "Couldn't exit game mode_3" )
stop_locations_comparison = ("The stop locations are correctly ordered", "The stop locations are not ordered correctly" )
# fmt: on
def C4976202_RigidBody_StopsWhenBelowKineticThreshold():
"""
Summary:
A Pushing Cube and a Sphere are suspended over the PhysX Terrain.
The cube entity has an initial velocity and on start will move toward the ball
and collide with it. When the kinetic energy of the ball is below a certain threshold,
it should stop moving.
Level Description:
Terrain (entity):
PhysX Terrain component: default settings
PushingCube (entity): Start Inactive
Mesh component: Box shape
PhysX Collider component: Box shape; default settings
PhysX Rigid Body component: Initial linear velocity: 5m/s in positive X direction;
gravity disabled; mass 1.0kg;
TargetBall (entity):
Mesh component: Sphere shape
PhysX Collider component: Sphere shape; default settings
PhysX Rigid Body component: Linear damping: 0.5; mass 10kg; gravity disabled; sleep threshold: 1.0;
Trigger1 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Trigger2 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Trigger3 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Expected Behavior:
The cube entity should push the ball entity into motion.
When the Kinetic Energy of the ball drops below the sleep threshold, it should stop moving.
The test steps will run for each sleep threshold value (1.0, 5.0, and 10.0). The ball should stop
before touching a specific Trigger (1.0 & Trigger3, 5.0 & Trigger 2, 10.0 & Trigger 1)
Test Steps:
1) Open the level
# Steps 2-9 are repeated for each threshold value
2) Enter game mode
3) Find entities and setup handlers or values
4) Activate the PushingCube entity to start movement
5) Wait for the ball to be hit by the pushing block
6) Wait for the ball to come to a stop in X direction
7) Check that the triggers match the expected trigger results
8) Check that the ball didn't move too far in Y or Z directions
9) Exit game mode
10) Run test_steps_per_threshold for remaining threshold values
11) Compare the stop locations to each other
12) Close the editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import os
import sys
import ImportPathHelper as imports
imports.init()
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TRIGGER_TIMEOUT = 5.0
TIMEOUT_SECONDS = 2.0
VELOCITY_TOLERANCE = 0.05
Y_Z_BUFFER = 0.01
class TestData:
"""
This is a placeholder to store the values persisting the duration of the test
"""
threshold_count = 0
stop_locations = []
class Entity(object):
"""
Base class for Entities to add basic common functionality such as fetch_id, validate_existence
"""
def __init__(self, name, test_steps_run):
self.name = name
self.test_steps_run = test_steps_run
self.id = None
self.fetch_id()
self.validate_exist()
def fetch_id(self):
self.id = general.find_game_entity(self.name)
def validate_exist(self):
Report.critical_result(Tests.__dict__[self.name + "_exists_" + str(self.test_steps_run)], self.id.IsValid())
def get_location(self):
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Trigger(Entity):
"""
Trigger entities used to tell if the Target_Ball has moved far enough/too far compared to expected distances
"""
def __init__(self, test_steps_run, name):
super(Trigger, self).__init__(name, test_steps_run)
self.handler = None
self.triggered = False
self.triggered_by = None
self.attach_handler()
def on_trigger_enter(self, args):
other_id = args[0]
self.triggered = True
self.triggered_by = azlmbr.entity.GameEntityContextRequestBus(
azlmbr.bus.Broadcast, "GetEntityName", other_id
)
Report.info("{} was triggered by {}.".format(self.name, self.triggered_by))
def attach_handler(self):
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
class PushingCube(Entity):
def __init__(self, test_steps_run, name="Pushing_Cube"):
super(PushingCube, self).__init__(name, test_steps_run)
def activate(self):
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.id)
class TargetBall(Entity):
def __init__(self, test_steps_run, sleep_threshold, name="Target_Ball"):
super(TargetBall, self).__init__(name, test_steps_run)
self.expected_sleep_threshold = sleep_threshold
self.handler = None
self.init_velocity = None
self.grabbed_velocity = None
self.collided_with_box = False
self.stopped_moving_x = False
self.trigger_timed_out = True
self.pushing_cube_id = None
self.setup_sleep_threshold()
self.attach_collision_handler()
def setup_sleep_threshold(self):
azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "SetSleepThreshold", self.id, self.expected_sleep_threshold
)
grabbed_value = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetSleepThreshold", self.id)
Report.info("Expected sleep threshold value: {}".format(self.expected_sleep_threshold))
Report.info("Actual value: {}".format(grabbed_value))
Report.critical_result(
Tests.__dict__["threshold_setup_" + str(TestData.threshold_count)],
grabbed_value == self.expected_sleep_threshold,
)
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.pushing_cube_id):
Report.info("Push_Box collided with ball.")
self.init_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
Report.info_vector3(self.init_velocity, "Initial Velocity of {}".format(self.name))
self.collided_with_box = True
def attach_collision_handler(self):
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
def has_stopped_moving_in_x(self):
"""
This method is used as a condition for helper.wait_for_condition()
"""
velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
if abs(velocity.x) < VELOCITY_TOLERANCE:
Report.info("{} has stopped moving in the X direction.".format(self.name))
self.stopped_moving_x = True
self.trigger_timed_out = False
return True
return False
def check_y_z_delta(self):
"""
Used to check that the entity has not moved too far in either the Y or Z direction
"""
def is_within_tolerance(velocity_one_direction):
return abs(velocity_one_direction) < Y_Z_BUFFER
Report.info_vector3(self.init_velocity, "Initial Velocity: ")
Report.result(
Tests.__dict__["check_y_z_movement_" + str(TestData.threshold_count)],
is_within_tolerance(self.init_velocity.y) and is_within_tolerance(self.init_velocity.z),
)
# 1) Open the level
helper.open_level("Physics", "C4976202_RigidBody_StopsWhenBelowKineticThreshold")
def test_steps(sleep_threshold_value, trigger_pattern):
TestData.threshold_count += 1
# 2) Enter game mode
helper.enter_game_mode(Tests.__dict__["enter_game_mode_" + str(TestData.threshold_count)])
# 3) Find entities and setup handlers or values
target_ball = TargetBall(TestData.threshold_count, sleep_threshold_value)
pushing_cube = PushingCube(TestData.threshold_count)
target_ball.pushing_cube_id = pushing_cube.id
triggers = []
for i in range(1, 4):
triggers.append(Trigger(TestData.threshold_count, "Trigger" + str(i)))
# 4) Activate the Pushing_Box entity to start movement
pushing_cube.activate()
# 5) Wait for the ball to be hit by the pushing block
helper.wait_for_condition(lambda: target_ball.collided_with_box, TIMEOUT_SECONDS)
Report.result(
Tests.__dict__["cube_collided_with_ball_" + str(TestData.threshold_count)], target_ball.collided_with_box
)
# 6) Wait for the ball to come to a stop in X direction
helper.wait_for_condition(target_ball.has_stopped_moving_in_x, TRIGGER_TIMEOUT)
Report.result(
Tests.__dict__["ball_stopped_moving_" + str(TestData.threshold_count)], target_ball.stopped_moving_x
)
# record the location of the stopping point
TestData.stop_locations.append(target_ball.get_location())
# 7) Check that the triggers match the expected trigger results
trigger_result = (triggers[0].triggered, triggers[1].triggered, triggers[2].triggered)
triggers_matched = trigger_result == trigger_pattern
Report.result(Tests.__dict__["trigger_patterns_match_" + str(TestData.threshold_count)], triggers_matched)
# 8) Check that the ball didn't move too far in Y or Z directions
target_ball.check_y_z_delta()
# 9) Exit game mode
helper.exit_game_mode(Tests.__dict__["exit_game_mode_" + str(TestData.threshold_count)])
# 10) Run test_steps_per_threshold for each threshold value
test_steps(1.0, (True, True, False))
test_steps(5.0, (True, False, False))
test_steps(10.0, (False, False, False))
# 11) Compare the stop locations to each other
order = TestData.stop_locations[0].x > TestData.stop_locations[1].x > TestData.stop_locations[2].x
Report.result(Tests.stop_locations_comparison, order)
if __name__ == "__main__":
import ImportPathHelper as imports
imports.init()
from editor_python_test_tools.utils import Report
Report.start_test(C4976202_RigidBody_StopsWhenBelowKineticThreshold)