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/rigid_body/C4976201_RigidBody_MassIsAs...

373 lines
19 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 : C4976201
# Test Case Title : Verify that the value assigned to the Mass of the object, gets actually assigned to the object
# fmt: off
class Tests:
# test iteration 1
enter_game_mode_1 = ("Entered game mode first time", "Failed to enter game mode first time")
ProjectileSphere_exists_1 = ("ProjectileSphere entity found first time", "ProjectileSphere entity not found first time")
TargetSphere_exists_1 = ("TargetSphere entity found first time", "TargetSphere entity not found first time")
Trigger1_exists_1 = ("Trigger1 entity found first time", "Trigger1 entity not found first time")
Trigger2_exists_1 = ("Trigger2 entity found first time", "Trigger2 entity not found first time")
Trigger3_exists_1 = ("Trigger3 entity found first time", "Trigger3 entity not found first time")
TargetSphere_mass_1 = ("Mass of TargetSphere was set to 1.0", "Mass of TargetSphere was not set to 1.0")
spheres_collided_1 = ("ProjectileSphere and TargetSphere collided first time", "Timed out before ProjectileSphere & 2 collided first time")
stopped_correctly_1 = ("TargetSphere hit Trigger1 & Trigger2 but not Trigger_3", "TargetSphere did not stop correctly")
check_y_1 = ("sphere did not move far from expected in Y direction _1", "TargetSphere moved an unexpected distance in Y direction _1")
check_z_1 = ("sphere did not move far from expected in Z direction _1", "TargetSphere moved an unexpected distance in Z direction _1")
exit_game_mode_1 = ("Exited game mode first time", "Couldn't exit game mode first time")
# test iteration 2
enter_game_mode_2 = ("Entered game mode second time", "Failed to enter game mode second time")
ProjectileSphere_exists_2 = ("ProjectileSphere entity found second time", "ProjectileSphere entity not found second time")
TargetSphere_exists_2 = ("TargetSphere entity found second time", "TargetSphere entity not found second time")
Trigger1_exists_2 = ("Trigger1 entity found second time", "Trigger1 entity not found second time")
Trigger2_exists_2 = ("Trigger2 entity found second time", "Trigger2 entity not found second time")
Trigger3_exists_2 = ("Trigger3 entity found second time", "Trigger3 entity not found second time")
TargetSphere_mass_2 = ("Mass of TargetSphere was set to 10.0", "Mass of TargetSphere was not set to 10.0")
spheres_collided_2 = ("ProjectileSphere and TargetSphere collided second time", "Timed out before ProjectileSphere & 2 collided second time")
stopped_correctly_2 = ("TargetSphere hit Trigger1 but not Trigger2 or Trigger3", "TargetSphere did not stop correctly")
check_y_2 = ("sphere did not move far from expected in Y direction _2", "TargetSphere moved an unexpected distance in Y direction _2")
check_z_2 = ("sphere did not move far from expected in Z direction _2", "TargetSphere moved an unexpected distance in Z direction _2")
exit_game_mode_2 = ("Exited game mode second time", "Couldn't exit game mode second time")
# test iteration 3
enter_game_mode_3 = ("Entered game mode third time", "Failed to enter game mode third time")
ProjectileSphere_exists_3 = ("ProjectileSphere entity found third time", "ProjectileSphere entity not found third time")
TargetSphere_exists_3 = ("TargetSphere entity found third time", "TargetSphere entity not found third time")
Trigger1_exists_3 = ("Trigger1 entity found third time", "Trigger1 entity not found third time")
Trigger2_exists_3 = ("Trigger2 entity found third time", "Trigger2 entity not found third time")
Trigger3_exists_3 = ("Trigger3 entity found third time", "Trigger3 entity not found third time")
TargetSphere_mass_3 = ("Mass of TargetSphere was set to 100.0", "Mass of TargetSphere was not set to 100.0")
spheres_collided_3 = ("ProjectileSphere and TargetSphere collided third time", "Timed out before ProjectileSphere & 2 collided third time")
stopped_correctly_3 = ("TargetSphere did not hit Trigger1, Trigger2, or Trigger3", "TargetSphere hit one or more triggers before stopping")
check_y_3 = ("sphere did not move far from expected in Y direction _3", "TargetSphere moved an unexpected distance in Y direction _3")
check_z_3 = ("sphere did not move far from expected in Z direction _3", "TargetSphere moved an unexpected distance in Z direction _3")
exit_game_mode_3 = ("Exited game mode third time", "Couldn't exit game mode third time")
# general
velocity_sizing = ("The velocities are in the correct order of magnitude", "The velocities are not correctly ordered in magnitude")
# fmt: on
def C4976201_RigidBody_MassIsAssigned():
"""
Summary:
Checking that the mass set to the object is actually applied via colliding entities
Level Description:
ProjectileSphere (entity) - Sphere shaped Mesh; Sphere shaped PhysX Collider;
PhysX Rigid Body: initial linear velocity in X direction is 5m/s, initial mass 1kg,
gravity disabled, linear damping default (0.05)
TargetSphere (entity) - Sphere shaped Mesh; Sphere shaped PhysX Collider;
PhysX Rigid Body: no initial velocity, initial mass 1kg, gravity disabled, linear damping 1.0
Expected Behavior:
The ProjectileSphere entity will float towards TargetSphere entity and then collide with it.
Because they are the same mass initially, the second sphere will move after collision.
TargetSphere's mass will be increased and scenario will run again,
but TargetSphere will have a smaller velocity after collision.
TargetSphere will then increase mass again and should barely move after the final collision.
Test Steps:
1) Open level
2) Repeat steps 3-9
3) Enter game mode
4) Find and setup entities
5) Set mass of the TargetSphere
6) Check for collision
7) Wait for TargetSphere x velocity = 0
8) Check the triggers
9) Exit game mode
10) Verify the velocity of TargetSphere decreased after collision as mass increased
11) 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 azlmbr.legacy.general as general
import azlmbr.bus
import azlmbr
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
MOVEMENT_TIMEOUT = 7.0
COLLISION_TIMEOUT = 2.0
VELOCITY_ZERO = 0.01
Y_Z_BUFFER = 0.01
TARGET_SPHERE_NAME = "TargetSphere"
PROJECTILE_SPHERE_NAME = "ProjectileSphere"
TRIGGER_1_NAME = "Trigger1"
TRIGGER_2_NAME = "Trigger2"
TRIGGER_3_NAME = "Trigger3"
class ProjectileSphere:
def __init__(self, test_iteration):
self.name = PROJECTILE_SPHERE_NAME
self.test_iteration = test_iteration
self.timeout_reached = True
self.id = general.find_game_entity(self.name)
Report.critical_result(
Tests.__dict__["ProjectileSphere_exists_" + str(self.test_iteration)], self.id.IsValid()
)
def destroy_me(self):
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DestroyGameEntity", self.id)
class TargetSphere:
def __init__(self, mass_to_assign, stop_before_trigger_name, expected_trigger_pattern, test_iteration):
self.id = None
self.name = TARGET_SPHERE_NAME
self.start_mass = None
self.mass_to_assign = mass_to_assign
self.collision_begin = False
self.after_collision_velocity = None
self.x_movement_timeout = True
self.stop_before_trigger_name = stop_before_trigger_name
self.expected_trigger_pattern = expected_trigger_pattern
self.collision_ended = False
self.test_iteration = test_iteration
self.test_set_mass = self.get_test("TargetSphere_mass_")
self.test_enter_game_mode = self.get_test("enter_game_mode_")
self.test_ProjectileSphere_exist = self.get_test("ProjectileSphere_exists_")
self.test_TargetSphere_exist = self.get_test("TargetSphere_exists_")
self.test_spheres_collided = self.get_test("spheres_collided_")
self.test_stop_properly = self.get_test("stopped_correctly_")
self.test_check_y = self.get_test("check_y_")
self.test_check_z = self.get_test("check_z_")
self.test_exit_game_mode = self.get_test("exit_game_mode_")
def get_test(self, test_prefix):
return Tests.__dict__[test_prefix + str(self.test_iteration)]
def find(self):
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__["TargetSphere_exists_" + str(self.test_iteration)], self.id.IsValid())
def setup_mass(self):
self.start_mass = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", self.id)
Report.info("{} starting mass: {}".format(self.name, self.start_mass))
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetMass", self.id, self.mass_to_assign)
general.idle_wait_frames(1) # wait for mass to apply
mass_after_set = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", self.id)
Report.info("{} mass after setting: {}".format(self.name, mass_after_set))
Report.result(self.test_set_mass, self.mass_to_assign == mass_after_set)
def current_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.id):
Report.info("spheres collision begin")
self.collision_begin = True
def on_collision_end(self, args):
other_id = args[0]
if other_id.Equal(self.id):
Report.info("spheres collision end")
self.after_collision_velocity = self.current_velocity()
self.collision_ended = True
def add_collision_handlers(self, projectile_sphere_id):
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(projectile_sphere_id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
self.handler.add_callback("OnCollisionEnd", self.on_collision_end)
def x_velocity_zero(self):
if abs(self.current_velocity().x) < VELOCITY_ZERO:
Report.info("TargetSphere has stopped moving.")
self.x_movement_timeout = False
return True
return False
def collision_complete(self):
return self.collision_begin and self.collision_ended
def check_y_z_movement_from_collision(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.after_collision_velocity, "Initial Velocity: ")
Report.result(self.test_check_y, is_within_tolerance(self.after_collision_velocity.y))
Report.result(self.test_check_z, is_within_tolerance(self.after_collision_velocity.z))
class Trigger:
"""
Used in the level to tell if the TargetSphere entity has moved a certain distance.
There are three triggers set up in the level.
"""
def __init__(self, name, test_iteration):
self.name = name
self.handler = None
self.triggered = False
self.test_iteration = test_iteration
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name + "_exists_" + str(self.test_iteration)], self.id.IsValid())
self.setup_handler()
def on_trigger_enter(self, args):
"""
This is passed into this object's handler.add_callback().
"""
other_id = args[0]
self.triggered = True
triggered_by_name = azlmbr.entity.GameEntityContextRequestBus(
azlmbr.bus.Broadcast, "GetEntityName", other_id
)
Report.info("{} was triggered by {}.".format(self.name, triggered_by_name))
def setup_handler(self):
"""
This is called to setup the handler for this trigger object
"""
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
class TriggerResultPattern:
"""
Used to store and determine which triggers were activated and compare to expected
"""
def __init__(self, trigger1_activated, trigger2_activated, trigger3_activated):
self.trigger1_activated = trigger1_activated
self.trigger2_activated = trigger2_activated
self.trigger3_activated = trigger3_activated
def __eq__(self, other_pattern):
"""
Used to determine if two patterns equal/match each other (i.e. Expected VS Actual)
"""
if isinstance(other_pattern, self.__class__):
return (
self.trigger1_activated == other_pattern.trigger1_activated
and self.trigger2_activated == other_pattern.trigger2_activated
and self.trigger3_activated == other_pattern.trigger3_activated
)
else:
return False
def report(self, expect_actual):
Report.info(
"""TargetSphere {} Triggers:
Trigger_1: {}
Trigger_2: {}
Trigger_3: {}
""".format(
expect_actual, self.trigger1_activated, self.trigger2_activated, self.trigger3_activated
)
)
target_sphere_1kg = TargetSphere(
mass_to_assign=1.0,
stop_before_trigger_name=TRIGGER_3_NAME,
expected_trigger_pattern=TriggerResultPattern(True, True, False),
test_iteration=1,
)
target_sphere_10kg = TargetSphere(
mass_to_assign=10.0,
stop_before_trigger_name=TRIGGER_2_NAME,
expected_trigger_pattern=TriggerResultPattern(True, False, False),
test_iteration=2,
)
target_sphere_100kg = TargetSphere(
mass_to_assign=100.0,
stop_before_trigger_name=TRIGGER_1_NAME,
expected_trigger_pattern=TriggerResultPattern(False, False, False),
test_iteration=3,
)
target_spheres = [target_sphere_1kg, target_sphere_10kg, target_sphere_100kg]
target_sphere_velocities = {}
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "C4976201_RigidBody_MassIsAssigned")
# 2) Repeat steps 3-9
for target_sphere in target_spheres:
Report.info("***************** Begin Test Iteration {} ******************".format(target_sphere.test_iteration))
# 3) Enter game mode
helper.enter_game_mode(target_sphere.test_enter_game_mode)
# 4) Find and setup entities
projectile_sphere = ProjectileSphere(target_sphere.test_iteration)
target_sphere.find()
target_sphere.add_collision_handlers(projectile_sphere.id)
trigger_1 = Trigger(TRIGGER_1_NAME, target_sphere.test_iteration)
trigger_2 = Trigger(TRIGGER_2_NAME, target_sphere.test_iteration)
trigger_3 = Trigger(TRIGGER_3_NAME, target_sphere.test_iteration)
# 5) Set mass of the TargetSphere
target_sphere.setup_mass()
# 6) Check for collision
helper.wait_for_condition(target_sphere.collision_complete, COLLISION_TIMEOUT)
Report.critical_result(target_sphere.test_spheres_collided, target_sphere.collision_complete())
projectile_sphere.destroy_me()
Report.info_vector3(
target_sphere.after_collision_velocity, "Velocity of {} after the collision: ".format(target_sphere.name)
)
Report.info("The sphere should stop before touching {}".format(target_sphere.stop_before_trigger_name))
# 7) Wait for TargetSphere x velocity = 0
helper.wait_for_condition(target_sphere.x_velocity_zero, MOVEMENT_TIMEOUT)
if target_sphere.x_movement_timeout is True:
Report.info("TargetSphere failed to stop moving in the x direction before timeout was reached.")
# 8) Check the triggers
actual_trigger_pattern = TriggerResultPattern(trigger_1.triggered, trigger_2.triggered, trigger_3.triggered)
patterns_match = actual_trigger_pattern == target_sphere.expected_trigger_pattern
target_sphere.expected_trigger_pattern.report("Expected")
actual_trigger_pattern.report("Actual")
Report.result(target_sphere.test_stop_properly, patterns_match)
target_sphere.check_y_z_movement_from_collision()
target_sphere_velocities.update({target_sphere.test_iteration: target_sphere.after_collision_velocity.x})
# 9) Exit game mode
helper.exit_game_mode(target_sphere.test_exit_game_mode)
Report.info("~~~~~~~~~~~~~~ Test Iteration {} End ~~~~~~~~~~~~~~~~~~".format(target_sphere.test_iteration))
# 10) Verify the velocity of TargetSphere decreased after collision as mass increased
outcome = target_sphere_velocities[1] > target_sphere_velocities[2] > target_sphere_velocities[3]
Report.result(Tests.velocity_sizing, outcome)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(C4976201_RigidBody_MassIsAssigned)