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.
334 lines
17 KiB
Python
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)
|