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.
413 lines
22 KiB
Python
413 lines
22 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 : C5959808
|
|
# Test Case Title : Verify Force Region Position Offset
|
|
|
|
|
|
|
|
# fmt:off
|
|
class Tests:
|
|
# General tests
|
|
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
|
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
|
test_completed = ("The test successfully completed", "The test timed out")
|
|
# ***** Entities found *****
|
|
# Force Regions
|
|
force_region_x_found = ("Force Region for X axis test was found", "Force Region for X axis test was NOT found")
|
|
force_region_y_found = ("Force Region for Y axis test was found", "Force Region for Y axis test was NOT found")
|
|
force_region_z_found = ("Force Region for Z axis test was found", "Force Region for Z axis test was NOT found")
|
|
# Force Region Pass Boxes
|
|
force_region_pass_box_x_found = ("Force Region Pass Box for X axis test was found", "Force Region Pass Box for X axis test was NOT found")
|
|
force_region_pass_box_y_found = ("Force Region Pass Box for Y axis test was found", "Force Region Pass Box for Y axis test was NOT found")
|
|
force_region_pass_box_z_found = ("Force Region Pass Box for Z axis test was found", "Force Region Pass Box for Z axis test was NOT found")
|
|
# External Pass Boxes
|
|
external_pass_box_x_found = ("External Pass Box for X axis test was found", "External Pass Box for X axis test was NOT found")
|
|
external_pass_box_y_found = ("External Pass Box for Y axis test was found", "External Pass Box for Y axis test was NOT found")
|
|
external_pass_box_z_found = ("External Pass Box for Z axis test was found", "External Pass Box for Z axis test was NOT found")
|
|
# Force Region Fail Boxes
|
|
force_region_fail_box_x_found = ("Force Region Fail Box for X axis test was found", "Force Region Fail Box for X axis test was NOT found")
|
|
force_region_fail_box_y_found = ("Force Region Fail Box for Y axis test was found", "Force Region Fail Box for Y axis test was NOT found")
|
|
force_region_fail_box_z_found = ("Force Region Fail Box for Z axis test was found", "Force Region Fail Box for Z axis test was NOT found")
|
|
# External Fail Boxes
|
|
external_fail_box_x_found = ("External Fail Box for X axis test was found", "External Fail Box for X axis test was NOT found")
|
|
external_fail_box_y_found = ("External Fail Box for Y axis test was found", "External Fail Box for Y axis test was NOT found")
|
|
external_fail_box_z_found = ("External Fail Box for Z axis test was found", "External Fail Box for Z axis test was NOT found")
|
|
# Pass spheres
|
|
sphere_pass_x_found = ("Pass Sphere for X axis test was found", "Pass Sphere for X axis test was NOT found")
|
|
sphere_pass_y_found = ("Pass Sphere for Y axis test was found", "Pass Sphere for Y axis test was NOT found")
|
|
sphere_pass_z_found = ("Pass Sphere for Z axis test was found", "Pass Sphere for Z axis test was NOT found")
|
|
# Bounce Spheres
|
|
sphere_bounce_x_found = ("Bounce Sphere for X axis test was found", "Bounce Sphere for X axis test was NOT found")
|
|
sphere_bounce_y_found = ("Bounce Sphere for Y axis test was found", "Bounce Sphere for Y axis test was NOT found")
|
|
sphere_bounce_z_found = ("Bounce Sphere for Z axis test was found", "Bounce Sphere for Z axis test was NOT found")
|
|
|
|
# ****** Entities' results ******
|
|
# Force Regions
|
|
force_region_x_mag_result = ("Force Region for X axis magnitude exerted was as expected", "Force Region for X axis magnitude exerted was NOT as expected")
|
|
force_region_y_mag_result = ("Force Region for Y axis magnitude exerted was as expected", "Force Region for Y axis magnitude exerted was NOT as expected")
|
|
force_region_z_mag_result = ("Force Region for Z axis magnitude exerted was as expected", "Force Region for Z axis magnitude exerted was NOT as expected")
|
|
force_region_x_norm_result = ("Force Region for X axis normal exerted was as expected", "Force Region for X axis normal exerted was NOT as expected")
|
|
force_region_y_norm_result = ("Force Region for Y axis normal exerted was as expected", "Force Region for Y axis normal exerted was NOT as expected")
|
|
force_region_z_norm_result = ("Force Region for Z axis normal exerted was as expected", "Force Region for Z axis normal exerted was NOT as expected")
|
|
# Force Region Pass Boxes
|
|
force_region_pass_box_x_result = ("Force Region Pass Box for X axis collided with exactly one sphere", "Force Region Pass Box for X axis DID NOT collide with exactly one sphere")
|
|
force_region_pass_box_y_result = ("Force Region Pass Box for Y axis collided with exactly one sphere", "Force Region Pass Box for Y axis DID NOT collide with exactly one sphere")
|
|
force_region_pass_box_z_result = ("Force Region Pass Box for Z axis collided with exactly one sphere", "Force Region Pass Box for Z axis DID NOT collide with exactly one sphere")
|
|
# External Pass Boxes
|
|
external_pass_box_x_result = ("External Pass Box for X axis collided with exactly one sphere", "External Pass Box for X axis DID NOT collide with exactly one sphere")
|
|
external_pass_box_y_result = ("External Pass Box for Y axis collided with exactly one sphere", "External Pass Box for Y axis DID NOT collide with exactly one sphere")
|
|
external_pass_box_z_result = ("External Pass Box for Z axis collided with exactly one sphere", "External Pass Box for Z axis DID NOT collide with exactly one sphere")
|
|
# Force Region Fail Boxes
|
|
force_region_fail_box_x_result = ("Force Region Fail Box for X axis collided with no spheres", "Force Region Fail Box for X axis DID collide with a sphere")
|
|
force_region_fail_box_y_result = ("Force Region Fail Box for Y axis collided with no spheres", "Force Region Fail Box for Y axis DID collide with a sphere")
|
|
force_region_fail_box_z_result = ("Force Region Fail Box for Z axis collided with no spheres", "Force Region Fail Box for Z axis DID collide with a sphere")
|
|
# External Fail Boxes
|
|
external_fail_box_x_result = ("External Fail Box for X axis collided with no spheres", "External Fail Box for X axis DID collide with a sphere")
|
|
external_fail_box_y_result = ("External Fail Box for Y axis collided with no spheres", "External Fail Box for Y axis DID collide with a sphere")
|
|
external_fail_box_z_result = ("External Fail Box for Z axis collided with no spheres", "External Fail Box for Z axis DID collide with a sphere")
|
|
# Pass spheres
|
|
sphere_pass_x_result = ("Pass Sphere for X axis collided with expected Box", "Pass Sphere for X axis DID NOT collide with expected Box")
|
|
sphere_pass_y_result = ("Pass Sphere for Y axis collided with expected Box", "Pass Sphere for Y axis DID NOT collide with expected Box")
|
|
sphere_pass_z_result = ("Pass Sphere for Z axis collided with expected Box", "Pass Sphere for Z axis DID NOT collide with expected Box")
|
|
# Bounce Spheres
|
|
sphere_bounce_x_result = ("Bounce Sphere for X axis collided with expected Box", "Bounce Sphere for X axis DID NOT collide with expected Box")
|
|
sphere_bounce_y_result = ("Bounce Sphere for Y axis collided with expected Box", "Bounce Sphere for Y axis DID NOT collide with expected Box")
|
|
sphere_bounce_z_result = ("Bounce Sphere for Z axis collided with expected Box", "Bounce Sphere for Z axis DID NOT collide with expected Box")
|
|
# fmt:on
|
|
|
|
@staticmethod
|
|
# Test tuple accessor via string
|
|
def get_test(test_name):
|
|
if test_name in Tests.__dict__:
|
|
return Tests.__dict__[test_name]
|
|
else:
|
|
return None
|
|
|
|
def C5959808_ForceRegion_PositionOffset():
|
|
"""
|
|
Summary:
|
|
Force Region positional offset is tested for each of the 3 axises (X, Y, and Z). Each axis's test has one
|
|
ForceRegion, two spheres and four boxes. By monitoring which box each sphere collides with we can validate the
|
|
integrity of the ForceRegions positional offset.
|
|
|
|
Level Description:
|
|
Each axis's test has the following entities:
|
|
one force region - set for point force and with it's collider set offset (on the axis in test).
|
|
two spheres - one positioned near the transform of the force region, one positioned near the [offset] collider for
|
|
the force region
|
|
four boxes - One box is positioned inside the force region's transform, one inside the force region's [offset]
|
|
collider. The other two boxes are positioned behind the two spheres (relative to the direction they will be
|
|
initially traveling)
|
|
|
|
Expected Behavior:
|
|
All three axises' tests run in parallel. when the tests begin, the spheres should move toward their expected
|
|
force regions. The spheres positioned to collide with their region's [offset] collider should be forced backwards
|
|
before entering the force region and collide with the box behind it. The spheres positioned by their force region's
|
|
transforms should pass straight into the transform and collide with the box inside the transform.
|
|
The boxes inside the Force Regions' [offset] colliders and the boxes behind the spheres set to move into the Force
|
|
Regions' transforms should not register any collisions.
|
|
|
|
Steps:
|
|
1) Open level and enter game mode
|
|
2) Set up tests and variables
|
|
3) Wait for test results (or time out)
|
|
(Report results)
|
|
4) Exit game mode and close the editor
|
|
|
|
:return: None
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import ImportPathHelper as imports
|
|
|
|
imports.init()
|
|
|
|
from editor_python_test_tools.utils import Report
|
|
from editor_python_test_tools.utils import TestHelper as helper
|
|
|
|
import azlmbr.legacy.general as general
|
|
import azlmbr.math as azmath
|
|
import azlmbr.bus
|
|
import azlmbr
|
|
|
|
# Constants
|
|
CLOSE_ENOUGH = 0.01 # Close enough threshold for comparing floats
|
|
TIME_OUT = 2.0 # Time out (in seconds) until test is aborted
|
|
FORCE_MAGNITUDE = 1000.0 # Point force magnitude for Force Regions
|
|
SPEED = 3.0 # Initial speed (in m/s) of the moving spheres.
|
|
|
|
# Full list for all spheres. Used for EntityId look up in event handlers
|
|
all_spheres = []
|
|
|
|
# Entity base class handles very general entity initialization
|
|
# Should be treated as a "virtual" class and all implementing child
|
|
# classes should implement a "self.result()" function referenced in EntityBase::report(self)
|
|
class EntityBase:
|
|
def __init__(self, name):
|
|
# type: (str) -> None
|
|
self.name = name
|
|
self.print_list = []
|
|
self.id = general.find_game_entity(name)
|
|
found_test = Tests.get_test(name + "_found")
|
|
Report.critical_result(found_test, self.id.IsValid())
|
|
|
|
# Reports this entity's result. Implicitly calls "get" on result.
|
|
# Subclasses implement their own definition of a successful result
|
|
def report(self):
|
|
# type: () -> None
|
|
result_test = Tests.get_test(self.name + "_result")
|
|
Report.result(result_test, self.result())
|
|
|
|
# Prints the print queue (with decorated header) if not empty
|
|
def print_log(self):
|
|
# type: () -> None
|
|
if self.print_list:
|
|
Report.info("*********** {} **********".format(self))
|
|
for line in self.print_list:
|
|
Report.info(line)
|
|
Report.info("")
|
|
|
|
# Quick string cast, returns entity name
|
|
def __str__(self):
|
|
# type: () -> str
|
|
return self.name
|
|
|
|
# ForceRegion handles all the data and behavior associated with a ForceRegion (for this test)
|
|
# They simply wait for a Sphere to collide with them. On collision they store the calculated force
|
|
# magnitude for verification.
|
|
class ForceRegion(EntityBase):
|
|
def __init__(self, name, magnitude):
|
|
# type: (str, float) -> None
|
|
EntityBase.__init__(self, name)
|
|
self.expected_magnitude = magnitude
|
|
self.actual_magnitude = None
|
|
self.expected_normal = None
|
|
self.actual_normal = None
|
|
# Set point force Magnitude
|
|
azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "SetMagnitude", self.id, magnitude)
|
|
# Set up handler
|
|
self.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
|
|
self.handler.connect(None)
|
|
self.handler.add_callback("OnCalculateNetForce", self.on_calc_force)
|
|
|
|
# Callback function for OnCalculateNetForce event
|
|
def on_calc_force(self, args):
|
|
# type: ([EntityId, EntityId, azmath.Vector3, float]) -> None
|
|
if self.id.Equal(args[0]) and self.actual_magnitude is None:
|
|
for sphere in all_spheres:
|
|
if sphere.id.Equal(args[1]):
|
|
# Log event in print queue (for me and for the sphere)
|
|
self.print_list.append("Exerting force on {}:".format(sphere))
|
|
sphere.print_list.append("Force exerted by {}".format(self))
|
|
# Save calculated data to be compared later
|
|
self.actual_normal = args[2]
|
|
self.actual_magnitude = args[3]
|
|
self.expected_normal = sphere.initial_velocity.GetNormalizedSafe().Unary()
|
|
# Add expected/actual to print queue
|
|
self.print_list.append("Force Vector: ")
|
|
self.print_list.append(
|
|
" Expected: ({:.2f}, {:.2f}, {:.2f})".format(
|
|
self.expected_normal.x, self.expected_normal.y, self.expected_normal.z
|
|
)
|
|
)
|
|
self.print_list.append(
|
|
" Actual: ({:.2f}, {:.2f}, {:.2f})".format(
|
|
self.actual_normal.x, self.actual_normal.y, self.actual_normal.z
|
|
)
|
|
)
|
|
self.print_list.append("Force Magnitude: ")
|
|
self.print_list.append(" Expected: {}".format(self.expected_magnitude))
|
|
self.print_list.append(" Actual: {:.2f}".format(self.actual_magnitude))
|
|
|
|
# EntityBase::report() overload.
|
|
# Force regions have 2 test tuples to report on
|
|
def report(self):
|
|
magnitude_test = Tests.get_test(self.name + "_mag_result")
|
|
normal_test = Tests.get_test(self.name + "_norm_result")
|
|
Report.result(magnitude_test, self.magnitude_result())
|
|
Report.result(normal_test, self.normal_result())
|
|
|
|
# Test result calculations
|
|
# Used in EntityBase for reporting results
|
|
def result(self):
|
|
# type: () -> bool
|
|
return self.magnitude_result() and self.normal_result()
|
|
|
|
def magnitude_result(self):
|
|
# type: () -> bool
|
|
return (
|
|
self.actual_magnitude is not None
|
|
and abs(self.actual_magnitude - self.expected_magnitude) < CLOSE_ENOUGH
|
|
)
|
|
|
|
def normal_result(self):
|
|
# type: () -> bool
|
|
return (
|
|
self.actual_normal is not None
|
|
and self.expected_normal is not None
|
|
and self.expected_normal.IsClose(self.actual_normal, CLOSE_ENOUGH)
|
|
)
|
|
|
|
# Spheres are the objects that test the force regions. They store an expected collision entity and an
|
|
# actual collision entity
|
|
class Sphere(EntityBase):
|
|
def __init__(self, name, initial_velocity, expected_collision):
|
|
# type: (str, azmath.Vector3, EntityBase, bool) -> None
|
|
EntityBase.__init__(self, name)
|
|
self.initial_velocity = initial_velocity
|
|
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", self.id, initial_velocity)
|
|
self.print_list.append(
|
|
"Initial velocity: ({:.2f}, {:.2f}, {:.2f})".format(
|
|
initial_velocity.x, initial_velocity.y, initial_velocity.z
|
|
)
|
|
)
|
|
self.expected_collision = expected_collision
|
|
self.print_list.append("Expected Collision: {}".format(expected_collision))
|
|
self.actual_collision = None
|
|
self.active = True
|
|
self.force_normal = None
|
|
|
|
# Registers a collision with this sphere. Saves a reference to the colliding entity for processing later.
|
|
# Deactivate self after collision is registered.
|
|
def collide(self, collision_entity):
|
|
# type: (EntityBase) -> None
|
|
# Log the event
|
|
self.print_list.append("Collided with {}".format(collision_entity))
|
|
self.actual_collision = collision_entity
|
|
# Deactivate self
|
|
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", self.id)
|
|
self.active = False
|
|
|
|
# Calculates result
|
|
# Used in EntityBase for reporting results
|
|
def result(self):
|
|
if self.actual_collision is None:
|
|
return False
|
|
else:
|
|
return self.expected_collision.id.Equal(self.actual_collision.id)
|
|
|
|
# Box entities wait for a collision with a sphere as a means of validation the force region's offset
|
|
# worked according to plan.
|
|
class Box(EntityBase):
|
|
def __init__(self, name, expected_sphere_collisions):
|
|
# type: (str, int) -> None
|
|
EntityBase.__init__(self, name)
|
|
self.spheres_collided = 0
|
|
self.expected_sphere_collisions = expected_sphere_collisions
|
|
# Set up handler
|
|
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
|
self.handler.connect(self.id)
|
|
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
|
|
|
# Callback function for OnCollisionBegin event
|
|
def on_collision_begin(self, args):
|
|
for sphere in all_spheres:
|
|
if sphere.id.Equal(args[0]):
|
|
# Log event
|
|
self.print_list.append("Collided with {}".format(sphere))
|
|
# Register collision with sphere
|
|
sphere.collide(self)
|
|
self.spheres_collided += 1 # Count collisions for validation later
|
|
break
|
|
|
|
# Calculates test result
|
|
# Used in EntityBase for reporting results
|
|
def result(self):
|
|
return self.spheres_collided == self.expected_sphere_collisions
|
|
|
|
# Manages the entities required to run the test for one axis (X, Y, or Z)
|
|
class AxisTest:
|
|
def __init__(self, axis, init_velocity):
|
|
# type: (str, azmath.Vector3) -> None
|
|
self.name = axis + " axis test"
|
|
self.force_region = ForceRegion("force_region_" + axis, FORCE_MAGNITUDE)
|
|
self.spheres = [
|
|
Sphere("sphere_pass_" + axis, init_velocity, Box("force_region_pass_box_" + axis, 1)),
|
|
Sphere("sphere_bounce_" + axis, init_velocity, Box("external_pass_box_" + axis, 1)),
|
|
]
|
|
self.boxes = [
|
|
Box("external_fail_box_" + axis, 0),
|
|
Box("force_region_fail_box_" + axis, 0)
|
|
] + [
|
|
# Gets the Boxes passed to spheres on init
|
|
sphere.expected_collision for sphere in self.spheres
|
|
]
|
|
# Full list for all entities this test is responsible for
|
|
self.all_entities = self.boxes + self.spheres + [self.force_region]
|
|
# Add spheres to global "lookup" list
|
|
all_spheres.extend(self.spheres)
|
|
|
|
# Checks for all entities' test passing conditions
|
|
def passed(self):
|
|
return all([e.result() for e in self.all_entities])
|
|
|
|
# Returns true when this test has completed (i.e. when the spheres have collided and are deactivated)
|
|
def completed(self):
|
|
return all([not sphere.active for sphere in self.spheres])
|
|
|
|
# Reports results for all entities in this test
|
|
def report(self):
|
|
Report.info("::::::::::::::::::::::::::::: {} Results :::::::::::::::::::::::::::::".format(self.name))
|
|
for entity in self.all_entities:
|
|
entity.report()
|
|
|
|
# Prints the logs for all entities in this test
|
|
def print_log(self):
|
|
Report.info("::::::::::::::::::::::::::::: {} Log :::::::::::::::::::::::::::::".format(self.name))
|
|
for entity in self.all_entities:
|
|
entity.print_log()
|
|
|
|
# *********** Execution Code ***********
|
|
|
|
# 1) Open level
|
|
helper.init_idle()
|
|
helper.open_level("Physics", "C5959808_ForceRegion_PositionOffset")
|
|
helper.enter_game_mode(Tests.enter_game_mode)
|
|
|
|
# 2) Variable set up
|
|
# Initial velocities for the three different directions spheres will be moving
|
|
x_vel = azmath.Vector3(SPEED, 0.0, 0.0)
|
|
y_vel = azmath.Vector3(0.0, SPEED, 0.0)
|
|
z_vel = azmath.Vector3(0.0, 0.0, SPEED)
|
|
|
|
# The three tests, one for each axis
|
|
axis_tests = [
|
|
AxisTest("x", z_vel), # Spheres move in Z direction when testing X axis offset
|
|
AxisTest("y", x_vel), # Spheres move in X direction when testing Y axis offset
|
|
AxisTest("z", y_vel), # Spheres move in Y direction when testing Z axis offset
|
|
]
|
|
|
|
# 3) Wait for test results or time out
|
|
Report.result(
|
|
Tests.test_completed, helper.wait_for_condition(
|
|
lambda: all([test.completed() for test in axis_tests]), TIME_OUT
|
|
)
|
|
)
|
|
|
|
# Report results
|
|
for test in axis_tests:
|
|
test.report()
|
|
|
|
# Print entity print queues for each failed test
|
|
for test in axis_tests:
|
|
if not test.passed():
|
|
test.print_log()
|
|
|
|
# 4) Exit game mode and close editor
|
|
helper.exit_game_mode(Tests.exit_game_mode)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import ImportPathHelper as imports
|
|
imports.init()
|
|
|
|
from editor_python_test_tools.utils import Report
|
|
Report.start_test(C5959808_ForceRegion_PositionOffset)
|