Standarization: Python files
Signed-off-by: ANT\aljanru <aljanru@amazon.co.uk>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.components
|
||||
import azlmbr.physics
|
||||
|
||||
class Box:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.distances = []
|
||||
|
||||
def find(self):
|
||||
self.id = general.find_game_entity(self.name)
|
||||
self.start_position = self.position
|
||||
return self.id.IsValid()
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return velocity.IsZero()
|
||||
|
||||
def push(self, impulse):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", self.id, impulse)
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
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 : C4925577
|
||||
# Test Case Title : Verify that material can be assigned to PhysX terrain in Terrain Texture Layers
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
game_mode_enter = ("Game mode was successfully entered", "Game mode could not be entered")
|
||||
find_terrain = ("Terrain was found", "Terrain was not found")
|
||||
find_ball_default = ("Ball_Default was found", "Ball_Default was not found")
|
||||
find_ball_rubber = ("Ball_Rubber was found", "Ball_Rubber was not found")
|
||||
find_ball_concrete = ("Ball_Concrete was found", "Ball_Concrete was not found")
|
||||
all_gravity_disabled = ("All the balls started with gravity disabled", "Not all the balls started with gravity disabled")
|
||||
same_starting_height = ("The 3 balls started at the same height", "The 3 balls were not the same height at start")
|
||||
balls_are_aligned = ("The balls are initially lined up properly", "The balls are not initially lined up properly")
|
||||
terrain_collide_default = ("Ball_Default has collided with terrain", "Ball_Default timed out before colliding with terrain")
|
||||
terrain_collide_rubber = ("Ball_Rubber has collided with terrain", "Ball_Rubber timed out before colliding with terrain")
|
||||
terrain_collide_concrete = ("Ball_Concrete has collided with terrain", "Ball_Concrete timed out before colliding with terrain")
|
||||
peak_reached_default = ("Ball_Default has reached peak height", "Ball_Default timed out before reaching peak")
|
||||
peak_reached_rubber = ("Ball_Rubber has reached peak height", "Ball_Rubber timed out before reaching peak")
|
||||
peak_reached_concrete = ("Ball_Concrete has reached peak height", "Ball_Concrete timed out before reaching peak")
|
||||
bounce_height_order_correct = ("The ball bounce heights are correctly ordered", "The ball bounce heights are not correctly ordered")
|
||||
game_mode_exit = ("Game mode was successfully exited", "Game mode could not exit properly")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_CanBeAssignedToTerrain():
|
||||
"""
|
||||
Summary:
|
||||
Three spheres are suspended above the terrain. Beneath two of the balls,
|
||||
there is a different material painted on the terrain.
|
||||
They should all bounce at different heights per their respective terrains
|
||||
|
||||
Terrain entity: PhysX Terrain component: default settings
|
||||
Ball Entities: Sphere shaped Mesh component
|
||||
Sphere shaped PhysX Collider component: default settings
|
||||
PhysX Rigid Body component: Gravity disabled, default settings
|
||||
|
||||
Concrete Material: Restitution: 0.0; Restitution Combine: Average
|
||||
Rubber Material: Restitution: 1.0; Restitution Combine: Average
|
||||
|
||||
Expected Behavior:
|
||||
The three balls start off at the same height. When game mode is entered they will fall towards the terrain.
|
||||
After the ball collides with the terrain, they will bounce back at different heights respective to their
|
||||
terrain material collisions. Ball_Default is the control and is dropped on default terrain material.
|
||||
Ball_Rubber bounces off the rubber terrain material and should bounce higher than the default.
|
||||
Ball_Concrete strikes the concrete terrain material and should not bounce as high as the default material.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find entities
|
||||
4) Check that gravity is disabled for all the balls initially
|
||||
5) Check that the balls are aligned and all falling from the same height
|
||||
Steps 6-9 run for each ball
|
||||
6) Assign the tests and enable handlers to their respective spheres
|
||||
7) Enable gravity on ball entities
|
||||
8) Check that the balls collide with the PhysX Terrain
|
||||
9) Wait for the ball to reach its peak height; record height and freeze it
|
||||
10) Compare the bounce heights of the balls
|
||||
11) Exit game mode and 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
|
||||
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.bus
|
||||
import azlmbr.physics as phys
|
||||
import azlmbr.math as mathazon
|
||||
|
||||
# fmt: off
|
||||
ZERO_VECTOR = mathazon.Vector3(0.0, 0.0, 0.0)
|
||||
X_POSITION_RUBBER = 60.0 # Point on X axis material was painted rubber during level setup
|
||||
X_POSITION_DEFAULT = 70.0 # Area in between other materials where Default material exists
|
||||
X_POSITION_CONCRETE = 80.0 # Point on X axis material was painted concrete during level setup
|
||||
Y_POSITION_VALUE = 42.0 # Point on Y axis materials were painted during level setup
|
||||
POSITION_BUFFER = 4.0 # Material paint radius is 4.0 m
|
||||
TIMEOUT_IN_SECONDS = 3.0
|
||||
NUM_WAIT_FRAMES_ENTITY_LOAD = 2 # Frames to wait to allow entities to load in level
|
||||
# fmt: on
|
||||
|
||||
class Terrain:
|
||||
id = None
|
||||
name = None
|
||||
handler = None
|
||||
|
||||
class Sphere:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(self.name)
|
||||
self.gravity_enabled = phys.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
|
||||
self.world_location_start = self.get_location()
|
||||
self.handler = None
|
||||
self.hit_ground = False
|
||||
self.bounced = False
|
||||
self.peak_reached = False
|
||||
self.ground_height = 0.0
|
||||
self.peak_height = 0.0
|
||||
|
||||
def assign_tests(self):
|
||||
if self.name == "Ball_Default":
|
||||
self.test_find_ball = Tests.find_ball_default
|
||||
self.test_terrain_collide = Tests.terrain_collide_default
|
||||
self.test_peak_reached = Tests.peak_reached_default
|
||||
|
||||
elif self.name == "Ball_Rubber":
|
||||
self.test_find_ball = Tests.find_ball_rubber
|
||||
self.test_terrain_collide = Tests.terrain_collide_rubber
|
||||
self.test_peak_reached = Tests.peak_reached_rubber
|
||||
|
||||
elif self.name == "Ball_Concrete":
|
||||
self.test_find_ball = Tests.find_ball_concrete
|
||||
self.test_terrain_collide = Tests.terrain_collide_concrete
|
||||
self.test_peak_reached = Tests.peak_reached_concrete
|
||||
|
||||
def get_location(self):
|
||||
# () -> Vector3
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_linear_velocity(self):
|
||||
# () -> Vector3
|
||||
return phys.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
def set_linear_velocity(self, vector):
|
||||
# (Vector3) -> None
|
||||
phys.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", self.id, vector)
|
||||
|
||||
def freeze_self(self):
|
||||
# () -> None
|
||||
self.set_linear_velocity(ZERO_VECTOR)
|
||||
self.enable_gravity(False)
|
||||
|
||||
def check_gravity(self):
|
||||
# () -> bool
|
||||
return phys.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
|
||||
|
||||
def enable_gravity(self, bool_to_set=True):
|
||||
# (bool) -> None
|
||||
phys.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id, bool_to_set)
|
||||
|
||||
def peak_height_reached(self):
|
||||
"""
|
||||
Used for conditional waiting;
|
||||
If peak is reached: sets the value for self.peak_reached to True, saves peak world height,
|
||||
freezes self to keep it from continuing to bounce and possibly interfering with another ball instance
|
||||
"""
|
||||
current_location = self.get_location()
|
||||
if current_location.z < self.peak_height:
|
||||
self.peak_reached = True
|
||||
Report.info("{} has peaked at {:.6} in the world.".format(self.name, self.peak_height))
|
||||
self.freeze_self()
|
||||
return True
|
||||
self.peak_height = current_location.z
|
||||
return False
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
# Ball collides with the ground
|
||||
other_id = args[0]
|
||||
other_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", other_id)
|
||||
if other_name == Terrain.name:
|
||||
self.hit_ground = True
|
||||
Report.info("{} has collided with the terrain.".format(self.name))
|
||||
location = self.get_location()
|
||||
self.ground_height = location.z
|
||||
|
||||
def on_collision_end(self, args):
|
||||
# Ball bounces off the ground
|
||||
other_id = args[0]
|
||||
other_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", other_id)
|
||||
if other_name == Terrain.name:
|
||||
self.bounced = True
|
||||
Report.info("{} has bounced off the terrain.".format(self.name))
|
||||
|
||||
def enable_handler(self):
|
||||
self.handler = phys.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
self.handler.add_callback("OnCollisionEnd", self.on_collision_end)
|
||||
|
||||
def is_close(actual, expected, buffer):
|
||||
return abs(actual - expected) < buffer
|
||||
|
||||
def balls_are_aligned(balls_list):
|
||||
aligned = True
|
||||
|
||||
for ball in balls_list:
|
||||
# check x axis per level setup
|
||||
if ball.name == "Ball_Default":
|
||||
if not is_close(ball.world_location_start.x, X_POSITION_DEFAULT, POSITION_BUFFER):
|
||||
Report.info("Ball_Default is not close enough to expected X position")
|
||||
aligned = False
|
||||
|
||||
elif ball.name == "Ball_Rubber":
|
||||
if not is_close(ball.world_location_start.x, X_POSITION_RUBBER, POSITION_BUFFER):
|
||||
Report.info("Ball_Rubber is not close enough to expected X position")
|
||||
aligned = False
|
||||
|
||||
elif ball.name == "Ball_Concrete":
|
||||
if not is_close(ball.world_location_start.x, X_POSITION_CONCRETE, POSITION_BUFFER):
|
||||
Report.info("Ball_Concrete is not close enough to expected X position")
|
||||
aligned = False
|
||||
|
||||
# check y axis per level setup
|
||||
if not is_close(ball.world_location_start.y, Y_POSITION_VALUE, POSITION_BUFFER):
|
||||
aligned = False
|
||||
Report.info("One or more balls are not close enough to expected Y position")
|
||||
|
||||
return aligned
|
||||
|
||||
def ball_heights_match(balls_list):
|
||||
heights_match = True
|
||||
|
||||
for ball in balls_list:
|
||||
# check ball heights match each other (z axis)
|
||||
if ball.world_location_start.z != balls[0].world_location_start.z:
|
||||
heights_match = False
|
||||
Report.info("The balls are not falling from the same height.")
|
||||
Report.failure(Tests.same_starting_height)
|
||||
|
||||
return heights_match
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_CanBeAssignedToTerrain")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.game_mode_enter)
|
||||
general.idle_wait_frames(NUM_WAIT_FRAMES_ENTITY_LOAD)
|
||||
|
||||
# 3) Find entities
|
||||
Terrain.id = general.find_game_entity("Terrain")
|
||||
Terrain.name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", Terrain.id)
|
||||
|
||||
ball_default = Sphere("Ball_Default")
|
||||
ball_rubber = Sphere("Ball_Rubber")
|
||||
ball_concrete = Sphere("Ball_Concrete")
|
||||
balls = (ball_rubber, ball_default, ball_concrete)
|
||||
|
||||
Report.critical_result(Tests.find_terrain, Terrain.id.IsValid())
|
||||
Report.critical_result(Tests.find_ball_default, ball_default.id.IsValid())
|
||||
Report.critical_result(Tests.find_ball_rubber, ball_rubber.id.IsValid())
|
||||
Report.critical_result(Tests.find_ball_concrete, ball_concrete.id.IsValid())
|
||||
|
||||
# 4) Check that gravity is disabled for all the balls initially
|
||||
gravity_disabled_for_all = True
|
||||
for ball in balls:
|
||||
if ball.gravity_enabled is True:
|
||||
gravity_disabled_for_all = False
|
||||
|
||||
Report.result(Tests.all_gravity_disabled, gravity_disabled_for_all)
|
||||
|
||||
# 5) Check that the balls are aligned and all falling from the same height
|
||||
balls_are_aligned = balls_are_aligned(balls)
|
||||
Report.critical_result(Tests.balls_are_aligned, balls_are_aligned)
|
||||
|
||||
same_starting_height = ball_heights_match(balls)
|
||||
Report.critical_result(Tests.same_starting_height, same_starting_height)
|
||||
|
||||
# Steps 6-9 run for each ball
|
||||
for ball in balls:
|
||||
# 6) Assign the tests and enable handlers to their respective spheres
|
||||
ball.assign_tests()
|
||||
ball.enable_handler()
|
||||
|
||||
# 7) Enable gravity on ball entities
|
||||
ball.enable_gravity()
|
||||
|
||||
# 8) Check that the balls collide with the PhysX Terrain
|
||||
helper.wait_for_condition(lambda: ball.bounced, TIMEOUT_IN_SECONDS)
|
||||
Report.result(ball.test_terrain_collide, ball.hit_ground)
|
||||
|
||||
# 9) Wait for the ball to reach its peak height; record height and freeze it
|
||||
helper.wait_for_condition(ball.peak_height_reached, TIMEOUT_IN_SECONDS)
|
||||
Report.result(ball.test_peak_reached, ball.peak_reached)
|
||||
|
||||
# 10) Compare the bounce heights of the balls
|
||||
# The restitution of rubber is greater than the default; the restitution of concrete is less than the default
|
||||
height_order_correct = ball_rubber.peak_height > ball_default.peak_height > ball_concrete.peak_height
|
||||
Report.result(Tests.bounce_height_order_correct, height_order_correct)
|
||||
|
||||
# 11) Exit game mode and close the editor
|
||||
helper.exit_game_mode(Tests.game_mode_exit)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_CanBeAssignedToTerrain)
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
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 : C15556261
|
||||
# Test Case Title : Check that the material assignment works with Character Controller
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
|
||||
# level
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
|
||||
#balls
|
||||
ball_to_hit_rubber_char_controller_found = ("ball_to_hit_rubber_char_controller found", "ball_to_hit_rubber_char_controller NOT FOUND ")
|
||||
ball_to_hit_rubber_char_controller_gravity = ("ball_to_hit_rubber_char_controller gravity is disabled", "ball_to_hit_rubber_char_controller GRAVITY IS ENABLED ")
|
||||
ball_to_hit_rubber_char_controller_position = ("ball_to_hit_rubber_char_controller valid postion", "ball_to_hit_rubber_char_controller INVALID POSITION ")
|
||||
ball_to_hit_rubber_char_controller_collision = ("ball_to_hit_rubber_char_controller collided with its target", "ball_to_hit_rubber_char_controller DID NOT COLLIDE WITH its target")
|
||||
|
||||
ball_to_hit_glass_char_controller_found = ("ball_to_hit_glass_char_controller found", "ball_to_hit_glass_char_controller NOT FOUND ")
|
||||
ball_to_hit_glass_char_controller_gravity = ("ball_to_hit_glass_char_controller gravity is disabled", "ball_to_hit_glass_char_controller GRAVITY IS ENABLED ")
|
||||
ball_to_hit_glass_char_controller_position = ("ball_to_hit_glass_char_controller valid postion", "ball_to_hit_glass_char_controller INVALID POSITION ")
|
||||
ball_to_hit_glass_char_controller_collision = ("ball_to_hit_glass_char_controller collided with its target", "ball_to_hit_glass_char_controller DID NOT COLLIDE WITH its target")
|
||||
|
||||
ball_to_hit_rock_char_controller_found = ("ball_to_hit_rock_char_controller found", "ball_to_hit_rock_char_controller NOT FOUND ")
|
||||
ball_to_hit_rock_char_controller_gravity = ("ball_to_hit_rock_char_controller gravity is disabled", "ball_to_hit_rock_char_controller GRAVITY IS ENABLED ")
|
||||
ball_to_hit_rock_char_controller_position = ("ball_to_hit_rock_char_controller valid postion", "ball_to_hit_rock_char_controller INVALID POSITION ")
|
||||
ball_to_hit_rock_char_controller_collision = ("ball_to_hit_rock_char_controller collided with its target", "ball_to_hit_rock_char_controller DID NOT COLLIDE WITH its target")
|
||||
|
||||
# targets
|
||||
char_rubber_found = ("character controller rubber found", "character controller rubber NOT FOUND ")
|
||||
char_rock_found = ("character controller rock found", "character controller rock NOT FOUND ")
|
||||
char_glass_found = ("character controller glass found", "character controller glass NOT FOUND ")
|
||||
|
||||
# balls velocity
|
||||
balls_velocity = ("balls velocity : rubber > glass > rock", "unexpected balls velocity")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_CharacterController():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that character controllers with different surface materials behave accordingly.
|
||||
|
||||
Level Description:
|
||||
3 character controllers with capsule shape, surface materials: rubber, rock, glass.
|
||||
3 balls with sphere shape on same X and Z coordinates of each character controller, initial linear velocity
|
||||
of 5 m/s on Y axis. All 3 balls have rock surface material.
|
||||
|
||||
Expected Behavior:
|
||||
The balls should all hit their corresponding character controller.
|
||||
The character controller with rubber should make the ball bounce back with almost the same speed.
|
||||
The one with glass should make the ball bounce but with reduced speed.
|
||||
The ball should not bounce off the character controller with rock material.
|
||||
The balls linear velocity is checked at the end of the test. Expected results for linear velocities are:
|
||||
rubber > glass > rock
|
||||
|
||||
Test Steps:
|
||||
1) Loads the level
|
||||
2) Enters game mode
|
||||
3) Setup balls
|
||||
3.1) Validate ball ID
|
||||
3.2) Validate ball gravity
|
||||
3.3) Connect ball to target
|
||||
3.4) Validate ball position
|
||||
4) Wait for balls to collide
|
||||
5) Get balls velocity
|
||||
6) Validate velocity is as rubber > glass > rock
|
||||
7) Exit game mode
|
||||
8) Close editor
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
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.bus
|
||||
|
||||
# Constants
|
||||
TIME_OUT = 3.0
|
||||
WAIT_TIME_AFTER_COLLISSION = 0.1
|
||||
|
||||
def is_close(value1, value2, tolerance=0.01):
|
||||
return abs(value1 - value2) <= tolerance
|
||||
|
||||
def get_test(entity_name, suffix):
|
||||
return Tests.__dict__[entity_name + suffix]
|
||||
|
||||
class Entity: # Base class for targets and balls
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.id = None
|
||||
self.position = None
|
||||
self.gravity = None
|
||||
|
||||
def validate_ID(self):
|
||||
self.id = general.find_game_entity(self.name)
|
||||
found_tuple = get_test(self.name, "_found")
|
||||
Report.critical_result(found_tuple, self.id.IsValid())
|
||||
|
||||
class CharacterController(Entity):
|
||||
def __init__(self, name):
|
||||
Entity.__init__(self, name)
|
||||
self.material = self.name.rpartition("_")[2]
|
||||
|
||||
class Ball(Entity):
|
||||
def __init__(self, name, target_name):
|
||||
Entity.__init__(self, name)
|
||||
self.target_name = target_name
|
||||
self.entered_times = 0
|
||||
self.collided_with_target = False
|
||||
# 3.1) Validate ball ID
|
||||
self.validate_ID()
|
||||
# 3.2) Validate gravity is disabled
|
||||
self.validate_gravity()
|
||||
# 3.3) Setup collision targets
|
||||
self.setup_target()
|
||||
# 3.4) Validate ball position
|
||||
self.validate_position()
|
||||
|
||||
def validate_position(self):
|
||||
self.position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
self.target.position = azlmbr.components.TransformBus(
|
||||
azlmbr.bus.Event, "GetWorldTranslation", self.target.id
|
||||
)
|
||||
position_tuple = get_test(self.name, "_position")
|
||||
Report.critical_result(
|
||||
position_tuple,
|
||||
(is_close(self.position.x, self.target.position.x))
|
||||
and (is_close(self.position.z, self.target.position.z + 1)),
|
||||
)
|
||||
|
||||
def validate_gravity(self):
|
||||
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
|
||||
gravity_tuple = get_test(self.name, "_gravity")
|
||||
Report.critical_result(gravity_tuple, not gravity_enabled)
|
||||
|
||||
def detect_collision_target(self, args):
|
||||
entering_entity_id = args[0]
|
||||
if entering_entity_id.Equal(self.target.id):
|
||||
Report.info(self.name + " collided with " + self.target.name)
|
||||
self.collided_with_target = True
|
||||
collision_tuple = get_test(self.name, "_collision")
|
||||
Report.critical_result(collision_tuple, self.collided_with_target)
|
||||
|
||||
def setup_target(self):
|
||||
self.target = CharacterController(self.target_name)
|
||||
self.target.validate_ID()
|
||||
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.collision_handler.connect(self.id)
|
||||
self.collision_handler.add_callback("OnCollisionBegin", self.detect_collision_target)
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Load the level
|
||||
helper.open_level("Physics", "Material_CharacterController")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# 3) Setup balls
|
||||
all_balls = [
|
||||
Ball(name="ball_to_hit_rubber_char_controller", target_name="char_rubber"),
|
||||
Ball(name="ball_to_hit_glass_char_controller", target_name="char_glass"),
|
||||
Ball(name="ball_to_hit_rock_char_controller", target_name="char_rock"),
|
||||
]
|
||||
|
||||
# 4) Wait for balls movement
|
||||
helper.wait_for_condition(lambda: all(ball.collided_with_target for ball in all_balls), TIME_OUT)
|
||||
general.idle_wait(WAIT_TIME_AFTER_COLLISSION)
|
||||
|
||||
# 5) Get each ball's linear velocity after collision
|
||||
for ball in all_balls:
|
||||
ball.linear_velocity_magnitude = azlmbr.physics.RigidBodyRequestBus(
|
||||
azlmbr.bus.Event, "GetLinearVelocity", ball.id
|
||||
).GetLength()
|
||||
|
||||
# 6) Check ball's velocity
|
||||
Report.result(
|
||||
Tests.balls_velocity,
|
||||
all_balls[0].linear_velocity_magnitude
|
||||
> all_balls[1].linear_velocity_magnitude
|
||||
> all_balls[2].linear_velocity_magnitude,
|
||||
)
|
||||
|
||||
# 7) Exit Game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_CharacterController)
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
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 : C15308221
|
||||
# Test Case Title : Verify that material library and slots are always in sync and work consistently through the different places of usage
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
|
||||
find_terrain_0 = ("Test 0) The Terrain was found", "Test 0) The Terrain was not found")
|
||||
find_terrain_box_0 = ("Test 0) Terrain test box was found", "Test 0) Terrain test box was not found")
|
||||
find_collider_0 = ("Test 0) Box collider was found", "Test 0) Box collider was not found")
|
||||
find_ragdoll_0 = ("Test 0) Ragdoll was found", "Test 0) Ragdoll was not found")
|
||||
find_character_controller_0 = ("Test 0) Character controller was found", "Test 0) Character controller was not found")
|
||||
find_controller_box_0 = ("Test 0) Character controller test box was found", "Test 0) Character controller test box was not found")
|
||||
|
||||
terrain_box_bounced_0 = ("Test 0) Terrain test box bounced", "Test 0) Terrain test box did not bounce")
|
||||
collider_bounced_0 = ("Test 0) Box collider bounced", "Test 0) Box collider did not bounce")
|
||||
ragdoll_bounced_0 = ("Test 0) Modified ragdoll bounced", "Test 0) Modified ragdoll did not bounce")
|
||||
controller_box_bounced_0 = ("Test 0) Character controller test box bounced", "Test 0) Character controller test box did not bounce")
|
||||
exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
|
||||
all_bounced_equal_0 = ("Test 0) All entities bounced the same height", "Test 0) All entities did not bounce the same height")
|
||||
|
||||
enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
|
||||
find_terrain_1 = ("Test 1) The Terrain was found", "Test 1) The Terrain was not found")
|
||||
find_terrain_box_1 = ("Test 1) Terrain test box was found", "Test 1) Terrain test box was not found")
|
||||
find_collider_1 = ("Test 1) Box collider was found", "Test 1) Box collider was not found")
|
||||
find_ragdoll_1 = ("Test 1) Ragdoll was found", "Test 1) Ragdoll was not found")
|
||||
find_character_controller_1 = ("Test 1) Character controller was found", "Test 1) Character controller was not found")
|
||||
find_controller_box_1 = ("Test 1) Character controller test box was found", "Test 1) Character controller test box was not found")
|
||||
|
||||
terrain_box_bounced_1 = ("Test 1) Terrain test box bounced", "Test 1) Terrain test box did not bounce")
|
||||
collider_bounced_1 = ("Test 1) Box collider bounced", "Test 1) Box collider did not bounce")
|
||||
ragdoll_bounced_1 = ("Test 1) Modified ragdoll bounced", "Test 1) Modified ragdoll did not bounce")
|
||||
controller_box_bounced_1 = ("Test 1) Character controller test box bounced", "Test 1) Character controller test box did not bounce")
|
||||
exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
|
||||
all_bounced_equal_1 = ("Test 1) All entities bounced the same height", "Test 1) All entities did not bounce the same height")
|
||||
|
||||
all_bounced_greater = ("All entities bounced higher on the second test", "All entities did not bounce higher on the second test")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_ComponentsInSyncWithLibrary():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that the material library is always in sync between the different PhysX components
|
||||
|
||||
Level Description:
|
||||
A new material library was created with 1 material, called "Modified":
|
||||
dynamic friction: 0.5
|
||||
static friction: 0.5
|
||||
restitution: 0.25
|
||||
|
||||
There are 4 types of components we want to test for:
|
||||
PhysX Ragdoll:
|
||||
A ragdoll ("ragdoll") with the "Modified" material applied to all of its colliders. Positioned above the
|
||||
terrain.
|
||||
PhysX collider:
|
||||
A PhysX box collider ("collider") with a the "Modified" material applied. Positioned above the terrain.
|
||||
PhysX terrain:
|
||||
A PhysX terrain ("terrain"), and a PhysX box collider ("terrain_box"). "terrain_box" is positioned above
|
||||
"terrain". A new layer was created with the "Modified" material and painted onto the terrain under
|
||||
"terrain_box". "terrain_box" has the default material applied.
|
||||
PhysX character controller:
|
||||
A character controller ("character_controller"), and a PhysX box collider ("controller_box").
|
||||
"controller_box" is positioned above "character_controller" and is assigned the default material.
|
||||
"character_controller" is assigned "Modified"
|
||||
|
||||
Expected behavior:
|
||||
For every iteration this test measures the bounce height of each entity. The entities save their traveled distances
|
||||
each iteration, to verify different behavior between each setup.
|
||||
|
||||
First the test verifies the entities all behave identically, without changing anything. All entities should bounce
|
||||
the same height.
|
||||
|
||||
Next, the test modifies the restitution value for 'Modified' (from 0.25 to 0.75). All entities should again bounce
|
||||
the same height. Additionally, all entities should bounce higher with the new restitution than they did previously.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Collect basis values without modifying anything
|
||||
2.1) Enter game mode
|
||||
2.2) Find entities
|
||||
2.3) Wait for entities to bounce
|
||||
2.4) Exit game mode
|
||||
3) Verify all entities behave the same as a baseline
|
||||
4) Modify the restitution value of 'modified'
|
||||
4.1 - 4.4) <same as 2.1 - 2.4>
|
||||
5) Verify the entities all still behave the same
|
||||
6) Verify that the material change was propagated correctly
|
||||
7) Close 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 as bus
|
||||
import azlmbr.components
|
||||
import azlmbr.physics
|
||||
import azlmbr.math as lymath
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
TIMEOUT = 3.0
|
||||
BOUNCE_TOLERANCE = 0.1
|
||||
|
||||
class Entity:
|
||||
def __init__(self, name, bounce_off_of_name):
|
||||
self.name = name
|
||||
self.bounce_off_of_name = bounce_off_of_name
|
||||
self.bounces = []
|
||||
|
||||
def find_and_reset(self):
|
||||
self.hit_position = None
|
||||
self.hit_terrain = False
|
||||
self.max_bounce = 0.0
|
||||
self.reached_max_bounce = False
|
||||
self.id = general.find_game_entity(self.name)
|
||||
self.setup_handler()
|
||||
return self.id.IsValid()
|
||||
|
||||
def on_collision_enter(self, args):
|
||||
entering = args[0]
|
||||
if entering.Equal(self.id):
|
||||
if not self.hit_terrain:
|
||||
self.hit_terrain_position = self.position
|
||||
self.hit_terrain = True
|
||||
|
||||
def setup_handler(self):
|
||||
self.bounce_off_of_id = general.find_game_entity(self.bounce_off_of_name)
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.bounce_off_of_id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_enter)
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name]
|
||||
|
||||
def run_test(test_number):
|
||||
# x.1) Enter game mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
|
||||
|
||||
# x.2) Find entities
|
||||
controller_valid = general.find_game_entity("character_controller").IsValid()
|
||||
terrain_valid = general.find_game_entity("terrain").IsValid()
|
||||
|
||||
Report.critical_result(get_test("find_character_controller_{}".format(test_number)), controller_valid)
|
||||
Report.critical_result(get_test("find_terrain_{}".format(test_number)), terrain_valid)
|
||||
|
||||
collider_valid = collider.find_and_reset()
|
||||
controller_box_valid = controller_box.find_and_reset()
|
||||
ragdoll_valid = ragdoll.find_and_reset()
|
||||
terrain_box_valid = terrain_box.find_and_reset()
|
||||
|
||||
Report.critical_result(get_test("find_collider_{}".format(test_number)), collider_valid)
|
||||
Report.critical_result(get_test("find_controller_box_{}".format(test_number)), controller_box_valid)
|
||||
Report.critical_result(get_test("find_ragdoll_{}".format(test_number)), ragdoll_valid)
|
||||
Report.critical_result(get_test("find_terrain_box_{}".format(test_number)), terrain_box_valid)
|
||||
|
||||
def wait_for_bounce():
|
||||
for entity in all_entities:
|
||||
if entity.hit_terrain:
|
||||
current_bounce_height = entity.position.z - entity.hit_terrain_position.z
|
||||
if current_bounce_height >= entity.max_bounce:
|
||||
entity.max_bounce = current_bounce_height
|
||||
elif entity.max_bounce > 0.0:
|
||||
entity.reached_max_bounce = True
|
||||
return all([entity.reached_max_bounce for entity in all_entities])
|
||||
|
||||
# x.3) Wait for entities to bounce
|
||||
helper.wait_for_condition(wait_for_bounce, TIMEOUT)
|
||||
|
||||
Report.result(get_test("collider_bounced_{}".format(test_number)), collider.reached_max_bounce)
|
||||
Report.result(get_test("controller_box_bounced_{}".format(test_number)), controller_box.reached_max_bounce)
|
||||
Report.result(get_test("ragdoll_bounced_{}".format(test_number)), ragdoll.reached_max_bounce)
|
||||
Report.result(get_test("terrain_box_bounced_{}".format(test_number)), terrain_box.reached_max_bounce)
|
||||
|
||||
for entity in all_entities:
|
||||
entity.bounces.append(entity.max_bounce)
|
||||
|
||||
# x.4) Exit game mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_ComponentsInSyncWithLibrary")
|
||||
|
||||
# Setup persisting entities
|
||||
collider = Entity("collider", "terrain")
|
||||
controller_box = Entity("controller_box", "character_controller")
|
||||
ragdoll = Entity("ragdoll", "terrain")
|
||||
terrain_box = Entity("terrain_box", "terrain")
|
||||
all_entities = [collider, controller_box, ragdoll, terrain_box]
|
||||
|
||||
# 2) Collect basis values without modifying anything
|
||||
run_test(0)
|
||||
|
||||
# 3) Verify all entities behave the same as a baseline
|
||||
test_0_max_bounce = max([entity.bounces[0] for entity in all_entities])
|
||||
test_0_min_bounce = min([entity.bounces[0] for entity in all_entities])
|
||||
Report.result(
|
||||
Tests.all_bounced_equal_0, lymath.Math_IsClose(test_0_max_bounce, test_0_min_bounce, BOUNCE_TOLERANCE)
|
||||
)
|
||||
|
||||
# 4) Modify the restitution value of 'modified'
|
||||
material_editor = Physmaterial_Editor("c15308221_material_componentsinsyncwithlibrary.physmaterial")
|
||||
material_editor.modify_material("Modified", "Restitution", 0.75)
|
||||
material_editor.save_changes()
|
||||
run_test(1)
|
||||
|
||||
# 5) Verify the entities all still behave the same
|
||||
test_1_max_bounce = max([entity.bounces[1] for entity in all_entities])
|
||||
test_1_min_bounce = min([entity.bounces[1] for entity in all_entities])
|
||||
Report.result(
|
||||
Tests.all_bounced_equal_1, lymath.Math_IsClose(test_1_max_bounce, test_1_min_bounce, BOUNCE_TOLERANCE)
|
||||
)
|
||||
|
||||
# 6) Verify that the material change was propagated correctly
|
||||
all_bounced_greater = all([entity.bounces[0] < entity.bounces[1] for entity in all_entities])
|
||||
Report.result(Tests.all_bounced_greater, all_bounced_greater)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_ComponentsInSyncWithLibrary)
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
"""
|
||||
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 : C15096735
|
||||
# Test Case Title : Verify that default material library works consistently across all systems that use it
|
||||
|
||||
|
||||
|
||||
# fmt:off
|
||||
class Tests:
|
||||
# *** Universal test tuples ***
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
no_time_out = ("No time out detected", "The test timed out")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
|
||||
# *** Terrain test tuples ***
|
||||
terrain_rubber_result_found = ("Terrain's Rubber Result Entity Found", "Terrain's Rubber Result Entity NOT Found")
|
||||
terrain_concrete_result_found = ("Terrain's Concrete Result Entity Found", "Terrain's Concrete Result Entity NOT Found")
|
||||
terrain_rubber_result_stopped = ("Terrain's Rubber Result Entity Stopped", "Terrain's Rubber Result Entity DID NOT Stop")
|
||||
terrain_concrete_result_stopped = ("Terrain's Concrete Result Entity Stopped", "Terrain's Concrete Result Entity DID NOT Stop")
|
||||
terrain_found = ("Terrain Entity Found", "Terrain Entity NOT Found")
|
||||
terrain_expected_collisions = ("Terrain Entity Collisions Were Expected", "Terrain Entity DID NOT Collide With All Expected Entities")
|
||||
terrain_trigger_rubber_high_found = ("Terrain's Rubber High Trigger Found", "Terrain's Rubber High Trigger NOT Found")
|
||||
terrain_trigger_rubber_high_expected_collision = ("Terrain's Rubber High Trigger Collision Was As Expected", "Terrain's Rubber High Trigger Collision Was Not As Expected")
|
||||
terrain_trigger_rubber_low_found = ("Terrain's Rubber Low Trigger Found", "Terrain's Rubber Low Trigger NOT Found")
|
||||
terrain_trigger_rubber_low_expected_collision = ("Terrain's Rubber Low Trigger Collision Was As Expected", "Terrain's Rubber Low Trigger Collision Was Not As Expected")
|
||||
terrain_trigger_concrete_high_found = ("Terrain's Concrete High Trigger Found", "Terrain's Concrete High Trigger NOT Found")
|
||||
terrain_trigger_concrete_high_expected_collision = ("Terrain's Concrete High Trigger Collision Was As Expected", "Terrain's Concrete High Trigger Collision Was Not As Expected")
|
||||
terrain_trigger_concrete_low_found = ("Terrain's Concrete Low Trigger Found", "Terrain's Concrete Low Trigger NOT Found")
|
||||
terrain_trigger_concrete_low_expected_collision = ("Terrain's Concrete Low Trigger Collision Was As Expected", "Terrain's Concrete Low Trigger Collision Was Not As Expected")
|
||||
|
||||
# *** Platform test tuples ***
|
||||
platform_rubber_result_found = ("Platform's Rubber Result Entity Found", "Platform's Rubber Result Entity NOT Found")
|
||||
platform_concrete_result_found = ("Platform's Concrete Result Entity Found", "Platform's Concrete Result Entity NOT Found")
|
||||
platform_rubber_result_stopped = ("Platform's Rubber Result Entity Stopped", "Platform's Rubber Result Entity DID NOT Stop")
|
||||
platform_concrete_result_stopped = ("Platform's Concrete Result Entity Stopped", "Platform's Concrete Result Entity DID NOT Stop")
|
||||
platform_rubber_found = ("Platform Rubber Test Entity Found", "Platform Rubber Test Entity NOT Found")
|
||||
platform_rubber_expected_collisions = ("Platform Rubber Test Entity Collisions Were Expected", "Platform Rubber Test Entity DID NOT Collide With All Expected Entities")
|
||||
platform_concrete_found = ("Platform Concrete Test Entity Found", "Platform Concrete Test Entity NOT Found")
|
||||
platform_concrete_expected_collisions = ("Platform Concrete Test Entity Collisions Were Expected", "Platform Concrete Test Entity DID NOT Collide With All Expected Entities")
|
||||
platform_trigger_rubber_high_found = ("Platform's Rubber High Trigger Found", "Platform's Rubber High Trigger NOT Found")
|
||||
platform_trigger_rubber_high_expected_collision = ("Platform's Rubber High Trigger Collision Was As Expected", "Platform's Rubber High Trigger Collision Was Not As Expected")
|
||||
platform_trigger_rubber_low_found = ("Platform's Rubber Low Trigger Found", "Platform's Rubber Low Trigger NOT Found")
|
||||
platform_trigger_rubber_low_expected_collision = ("Platform's Rubber Low Trigger Collision Was As Expected", "Platform's Rubber Low Trigger Collision Was Not As Expected")
|
||||
platform_trigger_concrete_high_found = ("Platform's Concrete High Trigger Found", "Platform's Concrete High Trigger NOT Found")
|
||||
platform_trigger_concrete_high_expected_collision = ("Platform's Concrete High Trigger Collision Was As Expected", "Platform's Concrete High Trigger Collision Was Not As Expected")
|
||||
platform_trigger_concrete_low_found = ("Platform's Concrete Low Trigger Found", "Platform's Concrete Low Trigger NOT Found")
|
||||
platform_trigger_concrete_low_expected_collision = ("Platform's Concrete Low Trigger Collision Was As Expected", "Platform's Concrete Low Trigger Collision Was Not As Expected")
|
||||
|
||||
# *** Controller test tuples ***
|
||||
controller_rubber_result_found = ("Controller's Rubber Result Entity Found", "Controller's Rubber Result Entity NOT Found")
|
||||
controller_concrete_result_found = ("Controller's Concrete Result Entity Found", "Controller's Concrete Result Entity NOT Found")
|
||||
controller_rubber_result_stopped = ("Controller's Rubber Result Entity Stopped", "Controller's Rubber Result Entity DID NOT Stop")
|
||||
controller_concrete_result_stopped = ("Controller's Concrete Result Entity Stopped", "Controller's Concrete Result Entity DID NOT Stop")
|
||||
controller_rubber_found = ("Controller Rubber Test Entity Found", "Controller Rubber Test Entity NOT Found")
|
||||
controller_rubber_expected_collisions = ("Controller Rubber Test Entity Collisions Were Expected", "Controller Rubber Test Entity DID NOT Collide With All Expected Entities")
|
||||
controller_concrete_found = ("Controller Concrete Test Entity Found", "Controller Concrete Test Entity NOT Found")
|
||||
controller_concrete_expected_collisions = ("Controller Concrete Test Entity Collisions Were Expected", "Controller Concrete Test Entity DID NOT Collide With All Expected Entities")
|
||||
controller_trigger_rubber_high_found = ("Controller's Rubber High Trigger Found", "Controller's Rubber High Trigger NOT Found")
|
||||
controller_trigger_rubber_high_expected_collision = ("Controller's Rubber High Trigger Collision Was As Expected", "Controller's Rubber High Trigger Collision Was Not As Expected")
|
||||
controller_trigger_rubber_low_found = ("Controller's Rubber Low Trigger Found", "Controller's Rubber Low Trigger NOT Found")
|
||||
controller_trigger_rubber_low_expected_collision = ("Controller's Rubber Low Trigger Collision Was As Expected", "Controller's Rubber Low Trigger Collision Was Not As Expected")
|
||||
controller_trigger_concrete_high_found = ("Controller's Concrete High Trigger Found", "Controller's Concrete High Trigger NOT Found")
|
||||
controller_trigger_concrete_high_expected_collision = ("Controller's Concrete High Trigger Collision Was As Expected", "Controller's Concrete High Trigger Collision Was Not As Expected")
|
||||
controller_trigger_concrete_low_found = ("Controller's Concrete Low Trigger Found", "Controller's Concrete Low Trigger NOT Found")
|
||||
controller_trigger_concrete_low_expected_collision = ("Controller's Concrete Low Trigger Collision Was As Expected", "Controller's Concrete Low Trigger Collision Was Not As Expected")
|
||||
|
||||
# *** Ragdoll test tuples ***
|
||||
ragdoll_rubber_result_found = ("Ragdoll's Rubber Result Entity Found", "Ragdoll's Rubber Result Entity NOT Found")
|
||||
ragdoll_concrete_result_found = ("Ragdoll's Concrete Result Entity Found", "Ragdoll's Concrete Result Entity NOT Found")
|
||||
ragdoll_rubber_result_stopped = ("Ragdoll's Rubber Result Entity Stopped", "Ragdoll's Rubber Result Entity DID NOT Stop")
|
||||
ragdoll_concrete_result_stopped = ("Ragdoll's Concrete Result Entity Stopped", "Ragdoll's Concrete Result Entity DID NOT Stop")
|
||||
ragdoll_rubber_found = ("Ragdoll Rubber Test Entity Found", "Ragdoll Rubber Test Entity NOT Found")
|
||||
ragdoll_rubber_expected_collisions = ("Ragdoll Rubber Test Entity Collisions Were Expected", "Ragdoll Rubber Test Entity DID NOT Collide With All Expected Entities")
|
||||
ragdoll_concrete_found = ("Ragdoll Concrete Test Entity Found", "Ragdoll Concrete Test Entity NOT Found")
|
||||
ragdoll_concrete_expected_collisions = ("Ragdoll Concrete Test Entity Collisions Were Expected", "Ragdoll Concrete Test Entity DID NOT Collide With All Expected Entities")
|
||||
ragdoll_trigger_rubber_high_found = ("Ragdoll's Rubber High Trigger Found", "Ragdoll's Rubber High Trigger NOT Found")
|
||||
ragdoll_trigger_rubber_high_expected_collision = ("Ragdoll's Rubber High Trigger Collision Was As Expected", "Ragdoll's Rubber High Trigger Collision Was Not As Expected")
|
||||
ragdoll_trigger_rubber_low_found = ("Ragdoll's Rubber Low Trigger Found", "Ragdoll's Rubber Low Trigger NOT Found")
|
||||
ragdoll_trigger_rubber_low_expected_collision = ("Ragdoll's Rubber Low Trigger Collision Was As Expected", "Ragdoll's Rubber Low Trigger Collision Was Not As Expected")
|
||||
ragdoll_trigger_concrete_high_found = ("Ragdoll's Concrete High Trigger Found", "Ragdoll's Concrete High Trigger NOT Found")
|
||||
ragdoll_trigger_concrete_high_expected_collision = ("Ragdoll's Concrete High Trigger Collision Was As Expected", "Ragdoll's Concrete High Trigger Collision Was Not As Expected")
|
||||
ragdoll_trigger_concrete_low_found = ("Ragdoll's Concrete Low Trigger Found", "Ragdoll's Concrete Low Trigger NOT Found")
|
||||
ragdoll_trigger_concrete_low_expected_collision = ("Ragdoll's Concrete Low Trigger Collision Was As Expected", "Ragdoll's Concrete Low Trigger Collision Was Not As Expected")
|
||||
|
||||
@staticmethod
|
||||
# Accesses the Tests dictionary to retrieve test tuples
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name.lower()]
|
||||
# fmt:on
|
||||
|
||||
|
||||
def Material_DefaultLibraryConsistentOnAllFeatures():
|
||||
"""
|
||||
Summary:
|
||||
This script tests the behavior of the default PhysXMaterial library. Two separate materials are applied to a variety
|
||||
of game entity types. The two materials have opposite restitution values (0.0 and 1.0). For each entity type and
|
||||
material another entity is made to "bounce" off it. The distance of the bounce is measured and validated via
|
||||
Triggers.
|
||||
|
||||
Level Description:
|
||||
Four tests are step up, each with 1 or 2 TestEntities. Each of these sub-tests have two ResultEntities (either
|
||||
spheres or boxes) each set to collide with either a rubber or concrete material. Each of these ResultEntities have
|
||||
two TriggerEntities associated with them (High and Low). These Triggers are set up so the bouncing ResultEntities
|
||||
should trigger the Low TriggerEntity, but not the High.
|
||||
The four TestEntities whose material properties are validated are:
|
||||
Terrain - Using the Terrain Texture Tools
|
||||
Platforms - Basic box entities with RigidBodies and Colliders
|
||||
Character Controllers - PhysXCharacterController entities
|
||||
Ragdolls - Entities with Actor, AnimGraph and PhysXRagdoll components
|
||||
|
||||
Expected Behavior:
|
||||
The four entity tests should run in series. Each test should have two spheres (or cubes) bounce off of their
|
||||
assigned test entity. Upon collision, Triggers should appear, and the spheres (or cubes) should only intersect
|
||||
with the lower trigger. When the spheres (or cubes) reach the highest point of their bounce they should disappear.
|
||||
At this time the Triggers should disappear and the next test should activate.
|
||||
|
||||
Test Steps:
|
||||
1) Load level and enter game mode
|
||||
2) Find entities and initialize test states
|
||||
For each test
|
||||
3) Activate ResultEntities
|
||||
4) Wait for expected collision(s)
|
||||
5) Activate Triggers
|
||||
6) Wait for ResultEntities to stop / test to conclude
|
||||
7) Deactivate Triggers
|
||||
4) Exit game mode / Close 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
|
||||
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.bus
|
||||
import azlmbr
|
||||
import azlmbr.math as azmath
|
||||
|
||||
# Constants
|
||||
TIME_OUT = 2.5
|
||||
INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0)
|
||||
|
||||
# Entity Base class to be inherited by specific Entity classes
|
||||
# Handles as much "general entity" logic as possible to reduce code copying
|
||||
# Should be considered "virtual" and should not be directly instantiated
|
||||
class EntityBase:
|
||||
|
||||
# Initializes the core features for an Entity and reports the critical result for being located successfully
|
||||
def __init__(self, name):
|
||||
# type: (str) -> None
|
||||
self.name = name
|
||||
self.active = True
|
||||
self.id = general.find_game_entity(self.name)
|
||||
# Report result
|
||||
found_test_tuple = Tests.get_test(self.name + "_Found")
|
||||
Report.critical_result(found_test_tuple, self.id.IsValid())
|
||||
|
||||
# Sets whether the Entity is activated or deactivated. Logs event
|
||||
def set_active(self, active):
|
||||
# type: (bool) -> None
|
||||
if active and not self.active:
|
||||
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.id)
|
||||
elif not active and self.active:
|
||||
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", self.id)
|
||||
self.active = active
|
||||
|
||||
# String cast, returns Entity name
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
# They are the default objects to be "bounced" off of TestEntities.
|
||||
# ResultEntities collect data about how far they bounce and deactivate themselves when done
|
||||
class ResultEntity(EntityBase):
|
||||
|
||||
# Instantiates a ResultEntity: calls EntityBase.__init__
|
||||
def __init__(self, name):
|
||||
# type: (str) -> None
|
||||
EntityBase.__init__(self, name)
|
||||
self.collision_entity = None
|
||||
self.bounce_peak_pos = None
|
||||
self.result_tuple = Tests.get_test(self.name + "_Stopped")
|
||||
self.velocity = None
|
||||
self.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
self.initial_pos = self.current_pos
|
||||
# Double check that gravity is enabled
|
||||
if not azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id):
|
||||
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id, True)
|
||||
self.set_active(False)
|
||||
|
||||
# Refreshes current velocity and checks if this Entity has stopped (or started "falling")
|
||||
# after expected collision, then deactivates itself
|
||||
def refresh(self):
|
||||
# type: () -> None
|
||||
if self.active:
|
||||
# 4) Wait for expected collision
|
||||
self.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
self.velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
if self.collision_entity is not None:
|
||||
# After collision takes place, track the highest bounce position
|
||||
if self.velocity.z <= 0.0:
|
||||
self.bounce_peak_pos = self.current_pos
|
||||
self.set_active(False)
|
||||
|
||||
# Overload of EntityBase::set_active
|
||||
# When activated, sets the linear velocity to the calibrated LINEAR_VELOCITY
|
||||
def set_active(self, active):
|
||||
# type: (bool) -> None
|
||||
EntityBase.set_active(self, active)
|
||||
if active:
|
||||
self.velocity = INITIAL_VELOCITY
|
||||
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", self.id, self.velocity)
|
||||
|
||||
# Reports test result.
|
||||
# Successful if we collided with something and then came to a stop
|
||||
def report_result(self):
|
||||
# type: () -> None
|
||||
Report.result(self.result_tuple, self.collision_entity is not None and self.bounce_peak_pos is not None)
|
||||
|
||||
# Returns true if the entity is done with it's test
|
||||
def is_done(self):
|
||||
# type: () -> bool
|
||||
return self.bounce_peak_pos is not None
|
||||
|
||||
# TestEntities are the surfaces that have their physics material set.
|
||||
# When a ResultEntity collides with a TestEntity, relevant Triggers are Activated
|
||||
class TestEntity(EntityBase):
|
||||
|
||||
# Initializes a TestEntity: calls EntityBase.__init__
|
||||
def __init__(self, name, expected_entity, triggers):
|
||||
# type: (str, ResultEntity, [TriggerEntity,]) -> None
|
||||
EntityBase.__init__(self, name)
|
||||
self.expected_entity = expected_entity
|
||||
self.triggers = triggers
|
||||
self.result_tuple = Tests.get_test(self.name + "_Expected_Collisions")
|
||||
self.collision = False
|
||||
# Assign event handler
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
# Event handler for when a collision begins
|
||||
def on_collision_begin(self, args):
|
||||
# type: ([EntityId]) -> None
|
||||
if self.expected_entity.id.Equal(args[0]):
|
||||
if not self.collision:
|
||||
self.collision = True
|
||||
self.expected_entity.collision_entity = self # Assign myself as their collision_entity
|
||||
# 5) Activate triggers associated with the colliding Entity's test
|
||||
for trigger in self.triggers:
|
||||
trigger.set_active(True)
|
||||
|
||||
# Reports result:
|
||||
# Successful if expected collision occurred
|
||||
def report_result(self):
|
||||
# type: () -> None
|
||||
Report.result(self.result_tuple, self.collision)
|
||||
|
||||
# TriggerEntities are quantitative test metrics. They are used to either look for
|
||||
# expected collisions (Low Triggers) or to look for unexpected collisions (High Triggers)
|
||||
class TriggerEntity(EntityBase):
|
||||
|
||||
def __init__(self, name, expected_entity):
|
||||
# type: (str, ResultEntity or None) -> None
|
||||
EntityBase.__init__(self, name)
|
||||
self.expected_entity = expected_entity # Expected Entity to hit trigger (or None)
|
||||
self.result_entity = None # Actual Entity to hit trigger (or None)
|
||||
self.triggered = False
|
||||
self.handler = None
|
||||
self.result_tuple = Tests.get_test(self.name + "_Expected_Collision")
|
||||
self.set_active(False) # Triggers Deactivate after initialization and are activated by TestEntities
|
||||
|
||||
# Override for EntityBase::set_active(bool) -> None
|
||||
# Sets event handler and calls EntityBase.set_active(bool)
|
||||
def set_active(self, active):
|
||||
# type: (bool) -> None
|
||||
if not self.active and active:
|
||||
# Activating: register event handler
|
||||
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
elif self.active and not active:
|
||||
# Deactivating: disconnect event handler and set to None
|
||||
if self.handler is not None:
|
||||
self.handler.disconnect()
|
||||
self.handler = None
|
||||
EntityBase.set_active(self, active)
|
||||
|
||||
# Event handler for when an entity enters trigger
|
||||
def on_trigger_enter(self, args):
|
||||
# type: ([EntityId]) -> None
|
||||
if not self.triggered:
|
||||
self.triggered = True
|
||||
self.result_entity = args[0]
|
||||
|
||||
# Reports result:
|
||||
# Successful if the expected_entity and the result_entity are the same
|
||||
# (Both None or both referencing the same Game Entity)
|
||||
def report_result(self):
|
||||
# type: () -> None
|
||||
if self.expected_entity is None:
|
||||
result = self.result_entity is None
|
||||
elif self.result_entity is None:
|
||||
result = False
|
||||
else:
|
||||
result = self.expected_entity.id.Equal(self.result_entity)
|
||||
Report.result(self.result_tuple, result)
|
||||
|
||||
# Tests manage all the Entities required for a specific Material Assignment Test.
|
||||
class Test:
|
||||
|
||||
# Initializes the test by setting up the required entities and lists for managing them.
|
||||
def __init__(self, base_str):
|
||||
# type: (str) -> None
|
||||
self.name = base_str
|
||||
|
||||
rubber_result = ResultEntity(base_str + "_Rubber_Result")
|
||||
concrete_result = ResultEntity(base_str + "_Concrete_Result")
|
||||
rubber_triggers = [
|
||||
# Trigger Entities associated with rubber Result Entity
|
||||
TriggerEntity(base_str + "_Trigger_Rubber_High", None),
|
||||
TriggerEntity(base_str + "_Trigger_Rubber_Low", rubber_result),
|
||||
]
|
||||
concrete_triggers = [
|
||||
# Trigger Entities associated with concrete Result Entity
|
||||
TriggerEntity(base_str + "_Trigger_Concrete_High", None),
|
||||
TriggerEntity(base_str + "_Trigger_Concrete_Low", concrete_result),
|
||||
]
|
||||
|
||||
# If base_str is "Terrain" both test entities should reference the same Terrain
|
||||
rubber_test_entity_name = base_str if base_str == "Terrain" else base_str + "_Rubber"
|
||||
concrete_test_entity_name = base_str if base_str == "Terrain" else base_str + "_Concrete"
|
||||
|
||||
# Test Entities
|
||||
rubber_test_entity = TestEntity(rubber_test_entity_name, rubber_result, rubber_triggers)
|
||||
concrete_test_entity = TestEntity(concrete_test_entity_name, concrete_result, concrete_triggers)
|
||||
|
||||
# Add entities to my lists
|
||||
self.results = [rubber_result, concrete_result]
|
||||
self.triggers = concrete_triggers + rubber_triggers
|
||||
self.test_objects = self.triggers + self.results + [rubber_test_entity, concrete_test_entity]
|
||||
|
||||
# Calls refresh on result entities.
|
||||
def refresh(self):
|
||||
# type: () -> None
|
||||
for result in self.results:
|
||||
result.refresh()
|
||||
|
||||
# Silently calls update, then returns True if all results are collected
|
||||
def is_done(self):
|
||||
# type: () -> bool
|
||||
self.refresh()
|
||||
if all(result.is_done() for result in self.results):
|
||||
# 7) Deactivate Triggers
|
||||
for trigger in self.triggers:
|
||||
trigger.set_active(False)
|
||||
return True
|
||||
return False
|
||||
|
||||
# Activates the result entity to start the test
|
||||
def start(self):
|
||||
# type: () -> None
|
||||
# 3 Activate ResultEntities
|
||||
for result in self.results:
|
||||
result.set_active(True)
|
||||
|
||||
# Reports results for all test objects
|
||||
def report_result(self):
|
||||
# type: () -> None
|
||||
for obj in self.test_objects:
|
||||
obj.report_result()
|
||||
|
||||
# *********** Execution Code ************
|
||||
|
||||
# 1) Open level and start game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_DefaultLibraryConsistentOnAllFeatures")
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# Create and start Terrain Test
|
||||
tests = [
|
||||
# 2) Find entities and initialize test states
|
||||
Test("Terrain"),
|
||||
Test("Platform"),
|
||||
Test("Controller"),
|
||||
Test("Ragdoll")
|
||||
]
|
||||
|
||||
# 3) Run tests
|
||||
for test in tests:
|
||||
test.start()
|
||||
|
||||
# 6) Wait for ResultEntities to stop / test to conclude
|
||||
Report.result(Tests.no_time_out, helper.wait_for_condition(test.is_done, TIME_OUT))
|
||||
|
||||
test.report_result()
|
||||
|
||||
# 4) Exit game mode and close editor
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_DefaultLibraryConsistentOnAllFeatures)
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
"""
|
||||
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 : C15096732
|
||||
# Test Case Title : Verify Default material library works across different levels
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
# Game Mode 2
|
||||
enter_game_mode_2 = ("Entered game mode 2", "Failed to enter game mode 2")
|
||||
sphere_found_2 = ("Test 2: Sphere was found", "Test 2: Sphere was not found")
|
||||
terrain_found_2 = ("Test 2: Terrrain Entity found", "Test 2: Terrain Entity was not found")
|
||||
trigger_found_2 = ("Test 2: trigger found", "Test 2: trigger not found")
|
||||
sphere_initial_position_2 = ("Test 2: Sphere initial position valid", "Test 2: Sphere initial position not valid")
|
||||
sphere_initial_velocity_2 = ("Test 2: Sphere initial velocity valid", "Test 2: Sphere initial velocity not valid")
|
||||
sphere_collision_2 = ("Test 2: Sphere collided with Terrain", "Test 2: Sphere did not collide")
|
||||
exit_game_mode_2 = ("Exited game mode 2", "Couldn't exit game mode 2")
|
||||
# Game Mode 3
|
||||
enter_game_mode_3 = ("Entered game mode 3", "Failed to enter game mode 3")
|
||||
sphere_found_3 = ("Test 3: Sphere was found", "Test 3: Sphere was not found")
|
||||
terrain_found_3 = ("Test 3: Terrrain Entity found", "Test 3: Terrain Entity was not found")
|
||||
trigger_found_3 = ("Test 3: trigger found", "Test 3: trigger not found")
|
||||
sphere_initial_position_3 = ("Test 3: Sphere initial position valid", "Test 3: Sphere initial position not valid")
|
||||
sphere_initial_velocity_3 = ("Test 3: Sphere initial velocity valid", "Test 3: Sphere initial velocity not valid")
|
||||
sphere_collision_3 = ("Test 3: Sphere collided with Terrain", "Test 3: Sphere did not collide")
|
||||
exit_game_mode_3 = ("Exited game mode 3", "Couldn't exit game mode 3")
|
||||
|
||||
# Test Verification
|
||||
levels_start_equal = ("Both levels are the same", "Both levels are not the same")
|
||||
material_library_switch = ("Library switch updated the sphere", "Library switch didn't update sphere")
|
||||
levels_stay_equal = ("Both levels are still the same", "Both levels are not the same post_change")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_DefaultLibraryUpdatedAcrossLevels_after():
|
||||
"""
|
||||
Summary: Verify Default material library works across different levels, this is the second stage to the test.
|
||||
The reload was required for the editor to pick up changes in default material library in the
|
||||
default.physxconfiguration file. After the tests are run this script will load the data from the previous
|
||||
script and compare it to the two new tests to see if changing the default material library progpogated
|
||||
correctly. C15096732_Material_DefaultLibraryUpdatedAcrossLevels_b.physmaterial is the default material
|
||||
file for these two tests.
|
||||
|
||||
Level Description: There are two levels indexed 1 and 2 each are completely identical to the other.
|
||||
sphere - Placed between trigger and terrain with velocity in the negative z direction; has physx rigid body,
|
||||
physx collider with sphere shape and useful_material_0 assigned (before change in default material library)
|
||||
and sphere shape
|
||||
trigger - A trigger sitting above the sphere and terrain; has physx rigid body, physx collider with box
|
||||
shape, and box shape
|
||||
terrain - Terrain placeholder with transform inline with default terrain height; has physx terrain
|
||||
component with default characteristics
|
||||
|
||||
physxconfiguration files: The change in default material library is achieved by launching the editor twice and
|
||||
overriding the default.physxconfiguration file with files that are nearly identical other than having
|
||||
different default material libraries
|
||||
|
||||
Materials: no_bounce_0 and no_bounce_1 are set so that they do not bounce from the terrain. global
|
||||
Default and bounce_0 and bounce_1 are set so that a bounce does occur. During the test the sphere first has
|
||||
a no_bounce material applied after the change in default material library to one with the bounce material the
|
||||
sphere has the global Default material applied. The bounce materials exist to avoid any current or future
|
||||
issues with an empty material library.
|
||||
|
||||
Test run explanation:
|
||||
Test 0: Collect baseline for default material library in the first level
|
||||
Test 1: Collect baseline for default material library in the second level
|
||||
Test 2: Collect resulting data for changed material library in the first level
|
||||
Test 3: Collect resulting data for changed material library in the second level
|
||||
|
||||
Expected Behavior: For the two test run by this script the ball will bounce from the terrain and hit the trigger
|
||||
as the material for spheres is now the global Default material.
|
||||
|
||||
Iterated Game Mode steps:
|
||||
1) Open the correct level is open
|
||||
2) Enter Game Mode
|
||||
3) Create and Verify Entities
|
||||
4) Wait for Sphere collision with Terrain Entity
|
||||
5) Allow time to hit trigger
|
||||
6) Log Final Values
|
||||
7) Exit Game Mode
|
||||
|
||||
Test Steps:
|
||||
1) Create Test Objects
|
||||
2) Run Game Mode steps once for each test
|
||||
3) Read results from local tmp file
|
||||
4) Validate test wide results
|
||||
5) Close 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
|
||||
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.bus
|
||||
import azlmbr.math as math
|
||||
|
||||
# Constants
|
||||
FLOAT_THRESHOLD = 0.0001
|
||||
TIMEOUT = 2.0
|
||||
INITIAL_VELOCITY = math.Vector3(0.0, 0.0, -10.0)
|
||||
|
||||
# Helper Functions
|
||||
class Entity:
|
||||
def __init__(self, name, index):
|
||||
self.id = general.find_game_entity(name)
|
||||
self.name = name
|
||||
self.index = index
|
||||
# ID validation
|
||||
self.found = Tests.__dict__["{}_found_{}".format(self.name, index)]
|
||||
Report.critical_result(self.found, self.id.IsValid())
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
class Material_Test:
|
||||
def __init__(self, test_index, level):
|
||||
self.test_index = test_index
|
||||
self.level = level
|
||||
self.entity_list = None
|
||||
# Setting Flags
|
||||
self.terrain_collision = False
|
||||
self.trigger_triggered = False
|
||||
|
||||
def set_handlers(self):
|
||||
trigger = self.entity_list[2]
|
||||
# Set handler for collision
|
||||
self.handler_0 = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler_0.connect(self.entity_list[0].id)
|
||||
self.handler_0.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
# Set handler for trigger
|
||||
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.handler.connect(trigger.id)
|
||||
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
if args[0].equal(self.entity_list[1].id):
|
||||
self.terrain_collision = True
|
||||
|
||||
def on_trigger_enter(self, args):
|
||||
if args[0].equal(self.entity_list[0].id):
|
||||
self.trigger_triggered = True
|
||||
|
||||
def check_sphere_initial_position(self, position_valid):
|
||||
initial_position = Tests.__dict__["sphere_initial_position_{}".format(self.test_index)]
|
||||
Report.critical_result(initial_position, position_valid)
|
||||
|
||||
def check_sphere_initial_velocity(self):
|
||||
initial_velocity_string = Tests.__dict__["sphere_initial_velocity_{}".format(self.test_index)]
|
||||
Report.critical_result(initial_velocity_string, self.entity_list[0].velocity.IsClose(INITIAL_VELOCITY, 0.1))
|
||||
|
||||
def check_sphere_collision(self):
|
||||
collision = Tests.__dict__["sphere_collision_{}".format(self.test_index)]
|
||||
Report.result(collision, self.terrain_collision)
|
||||
|
||||
def default_material_library_changed_as_expected(velocity_list, hit_trigger_list):
|
||||
hit_trigger_change = not hit_trigger_list[1] and hit_trigger_list[2]
|
||||
velocity_change_valid = (
|
||||
abs(velocity_list[1].x - velocity_list[2].x) < FLOAT_THRESHOLD
|
||||
and abs(velocity_list[1].y - velocity_list[2].y) < FLOAT_THRESHOLD
|
||||
and velocity_list[1].z <= velocity_list[2].z
|
||||
)
|
||||
|
||||
return velocity_change_valid and hit_trigger_change
|
||||
|
||||
def compare_level_baseline(velocity_list, hit_trigger_list):
|
||||
velocities_valid = (
|
||||
abs(velocity_list[0].z - velocity_list[1].z) < FLOAT_THRESHOLD
|
||||
and abs(velocity_list[0].y - velocity_list[1].y) < FLOAT_THRESHOLD
|
||||
and abs(velocity_list[0].x - velocity_list[1].x) < FLOAT_THRESHOLD
|
||||
)
|
||||
hit_trigger_correct = hit_trigger_list[0] == hit_trigger_list[1]
|
||||
|
||||
return velocities_valid and hit_trigger_correct
|
||||
|
||||
def levels_coinsistent_after_modification(velocity_list, hit_trigger_list):
|
||||
velocities_valid = (
|
||||
abs(velocity_list[2].z - velocity_list[3].z) < 0.01
|
||||
and abs(velocity_list[2].y - velocity_list[3].y) < FLOAT_THRESHOLD
|
||||
and abs(velocity_list[2].x - velocity_list[3].x) < FLOAT_THRESHOLD
|
||||
)
|
||||
hit_trigger_correct = hit_trigger_list[2] == hit_trigger_list[3]
|
||||
|
||||
return velocities_valid and hit_trigger_correct
|
||||
|
||||
def get_data_from_previous_tests():
|
||||
from ast import literal_eval
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
os.getcwd(), "AutomatedTesting", "Levels", "Physics", "Material_DefaultLibraryUpdatedAcrossLevels", "_last_run_before_change_data.txt"
|
||||
)) as data_file:
|
||||
lines = data_file.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
if i < 2:
|
||||
line = literal_eval(line)
|
||||
lines[i] = math.Vector3(float(line[0]), float(line[1]), float(line[2]))
|
||||
else:
|
||||
lines[i] = line == "True"
|
||||
except Exception as e:
|
||||
Report.info(e)
|
||||
helper.fail_fast("Could not save data of first two tests.")
|
||||
return lines[:2], lines[2:4]
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Create Test Objects
|
||||
test_2 = Material_Test(test_index=2, level=0)
|
||||
test_3 = Material_Test(test_index=3, level=1)
|
||||
test_list = [test_2, test_3]
|
||||
|
||||
# 2) Run Game Mode steps once for each test
|
||||
for test in test_list:
|
||||
# 1) Open the correct level is open
|
||||
helper.open_level(
|
||||
"physics",
|
||||
f"Material_DefaultLibraryUpdatedAcrossLevels\\{test.level}"
|
||||
)
|
||||
|
||||
# 2) Enter Game Mode
|
||||
helper.enter_game_mode(Tests.__dict__["enter_game_mode_{}".format(test.test_index)])
|
||||
|
||||
# 3) Create and Verify Entities
|
||||
sphere = Entity("sphere", test.test_index)
|
||||
terrain = Entity("terrain", test.test_index)
|
||||
trigger = Entity("trigger", test.test_index)
|
||||
test.entity_list = [sphere, terrain, trigger]
|
||||
|
||||
position_valid = terrain.position.z < sphere.position.z < trigger.position.z
|
||||
test.check_sphere_initial_position(position_valid)
|
||||
test.check_sphere_initial_velocity()
|
||||
|
||||
# 4) Wait for Sphere collision with Terrain Entity
|
||||
test.set_handlers()
|
||||
helper.wait_for_condition(lambda: test.terrain_collision, TIMEOUT)
|
||||
test.check_sphere_collision()
|
||||
|
||||
# 5) Allow time for Sphere to hit trigger
|
||||
helper.wait_for_condition(lambda: test.trigger_triggered, TIMEOUT)
|
||||
|
||||
# 6) Log Final Values
|
||||
test.final_velocity = sphere.velocity
|
||||
|
||||
# 7) Exit Game Mode
|
||||
helper.exit_game_mode(Tests.__dict__["exit_game_mode_{}".format(test.test_index)])
|
||||
|
||||
# 3) Verify that logged attributes show both levels are the same before and after the change in default material library
|
||||
# and show that there was a change before and after the change in default material library
|
||||
sphere_final_velocities_0, hit_trigger_list_0 = get_data_from_previous_tests()
|
||||
|
||||
sphere_final_velocities = sphere_final_velocities_0 + [test.final_velocity for test in test_list]
|
||||
hit_trigger_list = hit_trigger_list_0 + [test.trigger_triggered for test in test_list]
|
||||
Report.result(Tests.levels_start_equal, compare_level_baseline(sphere_final_velocities, hit_trigger_list))
|
||||
Report.result(
|
||||
Tests.material_library_switch,
|
||||
default_material_library_changed_as_expected(sphere_final_velocities, hit_trigger_list),
|
||||
)
|
||||
Report.result(
|
||||
Tests.levels_stay_equal, levels_coinsistent_after_modification(sphere_final_velocities, hit_trigger_list)
|
||||
)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_DefaultLibraryUpdatedAcrossLevels_after)
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
"""
|
||||
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 : C15096732
|
||||
# Test Case Title : Verify Default material library works across different levels
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
# Game Mode 0
|
||||
enter_game_mode_0 = ("Entered game mode 0", "Failed to enter game mode 0")
|
||||
sphere_found_0 = ("Test 0: Sphere was found", "Test 0: Sphere was not found")
|
||||
terrain_found_0 = ("Test 0: Terrrain Entity found", "Test 0: Terrain Entity was not found")
|
||||
trigger_found_0 = ("Test 0: trigger found", "Test 0: trigger not found")
|
||||
sphere_initial_position_0 = ("Test 0: Sphere initial position valid", "Test 0: Sphere initial position not valid")
|
||||
sphere_initial_velocity_0 = ("Test 0: Sphere initial velocity valid", "Test 0: Sphere initial velocity not valid")
|
||||
sphere_collision_0 = ("Test 0: Sphere collided with Terrain", "Test 0: Sphere did not collide")
|
||||
exit_game_mode_0 = ("Exited game mode 0", "Couldn't exit game mode 0")
|
||||
# Game Mode 1
|
||||
enter_game_mode_1 = ("Entered game mode 1", "Failed to enter game mode 1")
|
||||
sphere_found_1 = ("Test 1: Sphere was found", "Test 1: Sphere was not found")
|
||||
terrain_found_1 = ("Test 1: Terrrain Entity found", "Test 1: Terrain Entity was not found")
|
||||
trigger_found_1 = ("Test 1: trigger found", "Test 1: trigger not found")
|
||||
sphere_initial_position_1 = ("Test 1: Sphere initial position valid", "Test 1: Sphere initial position not valid")
|
||||
sphere_initial_velocity_1 = ("Test 1: Sphere initial velocity valid", "Test 1: Sphere initial velocity not valid")
|
||||
sphere_collision_1 = ("Test 1: Sphere collided with Terrain", "Test 1: Sphere did not collide")
|
||||
exit_game_mode_1 = ("Exited game mode 1", "Couldn't exit game mode 1")
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_DefaultLibraryUpdatedAcrossLevels_before():
|
||||
"""
|
||||
Summary: Verify Default material library works across different levels, this is the first stage to the test.
|
||||
After the tests are run this script will save data into a text file and the editor closed.
|
||||
C15096732_Material_DefaultLibraryUpdatedAcrossLevels_a.physmaterial is the default material file for
|
||||
these two tests.
|
||||
|
||||
Level Description: There are two levels indexed 1 and 2 each are completely identical to the other.
|
||||
sphere - Placed between trigger and terrain with velocity in the negative z direction; has physx rigid body,
|
||||
physx collider with sphere shape and useful_material_0 assigned (before change in default material library)
|
||||
and sphere shape
|
||||
trigger - A trigger sitting above the sphere and terrain; has physx rigid body, physx collider with box
|
||||
shape, and box shape
|
||||
terrain - Terrain placeholder with transform inline with default terrain height; has physx terrain
|
||||
component with default characteristics
|
||||
|
||||
physxconfiguration files: The change in default material library is achieved by launching the editor twice and
|
||||
overriding the default.physxconfiguration file with files that are nearly identical other than having
|
||||
different default material libraries
|
||||
|
||||
Materials: no_bounce_0 and no_bounce_1 are set so that they do not bounce from the terrain. global
|
||||
Default and bounce_0 and bounce_1 are set so that a bounce does occur. During the test the sphere first has
|
||||
a no_bounce material applied after the change in default material library to one with the bounce material the
|
||||
sphere has the global Default material applied. The bounce materials exist to avoid any current or future
|
||||
issues with an empty material library.
|
||||
|
||||
Test run explanation:
|
||||
Test 0: Collect baseline for default material library in the first level
|
||||
Test 1: Collect baseline for default material library in the second level
|
||||
Test 2: Collect resulting data for changed material library in the first level
|
||||
Test 3: Collect resulting data for changed material library in the second level
|
||||
|
||||
Expected Behavior: For the two test run by this script the ball will not bounce from the terrain and will
|
||||
not hit the trigger
|
||||
|
||||
Iterated Game Mode steps:
|
||||
1) Open the correct level is open
|
||||
2) Enter Game Mode
|
||||
3) Create and Verify Entities
|
||||
4) Wait for Sphere collision with Terrain Entity
|
||||
5) Allow time to hit trigger
|
||||
6) Log Final Values
|
||||
7) Exit Game Mode
|
||||
|
||||
Test Steps:
|
||||
1) Create Test Objects
|
||||
2) Run Game Mode steps once for each test
|
||||
3) Log results of two steps to a local tmp file
|
||||
4) Close 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
|
||||
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.bus
|
||||
import azlmbr.math as math
|
||||
|
||||
# Constants
|
||||
TIMEOUT = 2.0
|
||||
INITIAL_VELOCITY = math.Vector3(0.0, 0.0, -10.0)
|
||||
INITIAL_VELOCITY_THRESHOLD = 0.1
|
||||
|
||||
# Helper Functions
|
||||
class Entity:
|
||||
def __init__(self, name, index):
|
||||
self.id = general.find_game_entity(name)
|
||||
self.name = name
|
||||
self.index = index
|
||||
# ID validation
|
||||
self.found = Tests.__dict__["{}_found_{}".format(self.name, index)]
|
||||
Report.critical_result(self.found, self.id.IsValid())
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
class Material_Test:
|
||||
def __init__(self, test_index, level):
|
||||
self.test_index = test_index
|
||||
self.level = level
|
||||
self.entity_list = None
|
||||
# Setting Flags
|
||||
self.terrain_collision = False
|
||||
self.trigger_triggered = False
|
||||
|
||||
def set_handlers(self):
|
||||
trigger = self.entity_list[2]
|
||||
# Set handler for collision
|
||||
self.handler_0 = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler_0.connect(self.entity_list[0].id)
|
||||
self.handler_0.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
# Set handler for trigger
|
||||
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.handler.connect(trigger.id)
|
||||
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
if args[0].equal(self.entity_list[1].id):
|
||||
self.terrain_collision = True
|
||||
|
||||
def on_trigger_enter(self, args):
|
||||
if args[0].Equal(self.entity_list[0].id):
|
||||
self.trigger_triggered = True
|
||||
|
||||
def check_sphere_initial_position(self, position_valid):
|
||||
initial_position = Tests.__dict__["sphere_initial_position_{}".format(self.test_index)]
|
||||
Report.critical_result(initial_position, position_valid)
|
||||
|
||||
def check_sphere_initial_velocity(self):
|
||||
initial_velocity_string = Tests.__dict__["sphere_initial_velocity_{}".format(self.test_index)]
|
||||
Report.critical_result(
|
||||
initial_velocity_string,
|
||||
self.entity_list[0].velocity.IsClose(INITIAL_VELOCITY, INITIAL_VELOCITY_THRESHOLD),
|
||||
)
|
||||
|
||||
def check_sphere_collision(self):
|
||||
collision = Tests.__dict__["sphere_collision_{}".format(self.test_index)]
|
||||
Report.result(collision, self.terrain_collision)
|
||||
|
||||
def save_test_data(data):
|
||||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
os.getcwd(), "AutomatedTesting", "Levels", "Physics", "Material_DefaultLibraryUpdatedAcrossLevels", "_last_run_before_change_data.txt"
|
||||
),"w") as data_file:
|
||||
for data_point in data:
|
||||
data_file.write(str(data_point))
|
||||
data_file.write("\n")
|
||||
except Exception as e:
|
||||
Report.info(e)
|
||||
helper.fail_fast("Could not save data of first two tests.")
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Create Test Objects
|
||||
test_0 = Material_Test(test_index=0, level=0)
|
||||
test_1 = Material_Test(test_index=1, level=1)
|
||||
test_list = [test_0, test_1]
|
||||
|
||||
# 2) Run Game Mode steps once for each test
|
||||
for test in test_list:
|
||||
# 1) Open the correct level is open
|
||||
helper.open_level(
|
||||
"Physics",
|
||||
f"Material_DefaultLibraryUpdatedAcrossLevels\\{test.level}"
|
||||
)
|
||||
|
||||
# 2) Enter Game Mode
|
||||
helper.enter_game_mode(Tests.__dict__["enter_game_mode_{}".format(test.test_index)])
|
||||
|
||||
# 3) Create and Verify Entities
|
||||
sphere = Entity("sphere", test.test_index)
|
||||
terrain = Entity("terrain", test.test_index)
|
||||
trigger = Entity("trigger", test.test_index)
|
||||
test.entity_list = [sphere, terrain, trigger]
|
||||
|
||||
position_valid = terrain.position.z < sphere.position.z < trigger.position.z
|
||||
test.check_sphere_initial_position(position_valid)
|
||||
test.check_sphere_initial_velocity()
|
||||
|
||||
# 4) Wait for Sphere collision with Terrain Entity
|
||||
test.set_handlers()
|
||||
helper.wait_for_condition(lambda: test.terrain_collision, TIMEOUT)
|
||||
test.check_sphere_collision()
|
||||
|
||||
# 5) Allow time for Sphere to hit trigger
|
||||
helper.wait_for_condition(lambda: test.trigger_triggered, TIMEOUT)
|
||||
|
||||
# 6) Log Final Values
|
||||
test.final_velocity = sphere.velocity
|
||||
|
||||
# 7) Exit Game Mode
|
||||
helper.exit_game_mode(Tests.__dict__["exit_game_mode_{}".format(test.test_index)])
|
||||
|
||||
# 3) Log results of two steps to a local tmp file
|
||||
sphere_final_velocities = [
|
||||
[test.final_velocity.x, test.final_velocity.y, test.final_velocity.z] for test in test_list
|
||||
]
|
||||
hit_trigger_list = [test.trigger_triggered for test in test_list]
|
||||
|
||||
save_test_data(sphere_final_velocities + hit_trigger_list)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_DefaultLibraryUpdatedAcrossLevels_before)
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
"""
|
||||
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 : C15096737
|
||||
# Test Case Title : Verify that a change in the default material library material information
|
||||
# affects all the materials that reference it, even non-defaulted
|
||||
# exactly like if the library was selected
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
# level
|
||||
enter_game_mode_0 = ("Entered game mode 0", "Failed to enter game mode 0")
|
||||
exit_game_mode_0 = ("Exited game mode 0", "Couldn't exit game mode 0")
|
||||
enter_game_mode_1 = ("Entered game mode 1", "Failed to enter game mode 1")
|
||||
exit_game_mode_1 = ("Exited game mode 1", "Couldn't exit game mode 1")
|
||||
|
||||
# targets
|
||||
terrain_found = ("Terrain found in each test", "TERRAIN NOT FOUND in a test")
|
||||
target_character_rubber_found = ("target_character_rubber found in each test", "target_character_rubber NOT FOUND in a test")
|
||||
target_character_concrete_found = ("target_character_concrete found in each test", "target_character_concrete NOT FOUND in a test")
|
||||
|
||||
# collider activity
|
||||
rubber_sphere_found = ("rubber_sphere found in each test", "rubber_sphere NOT FOUND in a test in a test")
|
||||
rubber_sphere_trigger_found = ("rubber_sphere_trigger found in each test", "rubber_sphere_trigger NOT FOUND in a test")
|
||||
rubber_sphere_collided = ("rubber_sphere collided in each test", "rubber_sphere DIDN'T COLLIDE in a test")
|
||||
|
||||
concrete_sphere_found = ("concrete_sphere found in each test", "concrete_sphere NOT FOUND in a test")
|
||||
concrete_sphere_trigger_found = ("concrete_sphere_trigger found in each test", "concrete_sphere_trigger NOT FOUND in a test")
|
||||
concrete_sphere_collided = ("concrete_sphere collided in each test", "concrete_sphere DIDN'T COLLIDE in a test")
|
||||
|
||||
character_rubber_found = ("character_rubber found in each test", "character_rubber NOT FOUND in a test")
|
||||
character_rubber_trigger_found = ("character_rubber_trigger found in each test", "character_rubber_trigger NOT FOUND in a test")
|
||||
character_rubber_collided = ("character_rubber collided in each test", "character_rubber DIDN'T COLLIDE in a test")
|
||||
|
||||
character_concrete_found = ("character_concrete found in each test", "character_concrete NOT FOUND in a test")
|
||||
character_concrete_trigger_found = ("character_concrete_trigger found in each test", "character_concrete_trigger NOT FOUND in a test")
|
||||
character_concrete_collided = ("character_concrete collided in each test", "character_concrete DIDN'T COLLIDE in a test")
|
||||
|
||||
terrain_rubber_found = ("terrain_rubber found in each test", "terrain_rubber NOT FOUND in a test")
|
||||
terrain_rubber_trigger_found = ("terrain_rubber_trigger found in each test", "terrain_rubber_trigger NOT FOUND in a test")
|
||||
terrain_rubber_collided = ("terrain_rubber collided in each test", "terrain_rubber DIDN'T COLLIDE in a test")
|
||||
|
||||
terrain_concrete_found = ("terrain_concrete found in each test", "terrain_concrete NOT FOUND in a test")
|
||||
terrain_concrete_trigger_found = ("terrain_concrete_trigger found in each test", "terrain_concrete_trigger NOT FOUND in a test")
|
||||
terrain_concrete_collided = ("terrain_concrete collided in each test", "terrain_concrete DIDN'T COLLIDE in a test")
|
||||
|
||||
ragdoll_rubber_found = ("ragdoll_rubber found in each test", "ragdoll_rubber NOT FOUND in a test")
|
||||
ragdoll_rubber_trigger_found = ("ragdoll_rubber_trigger found in each test", "ragdoll_rubber_trigger NOT FOUND in a test")
|
||||
ragdoll_rubber_collided = ("ragdoll_rubber collided in each test", "ragdoll_rubber DIDN'T COLLIDE in a test")
|
||||
|
||||
ragdoll_concrete_found = ("ragdoll_concrete found in each test", "ragdoll_concrete NOT FOUND in a test")
|
||||
ragdoll_concrete_trigger_found = ("ragdoll_concrete_trigger found in each test", "ragdoll_concrete_trigger NOT FOUND in a test")
|
||||
ragdoll_concrete_collided = ("ragdoll_concrete collided in each test", "ragdoll_concrete DIDN'T COLLIDE in a test")
|
||||
|
||||
# Verification
|
||||
material_library_updated = ("Default material library updated", "Default material library not updated")
|
||||
rubber_material_changed = ("Rubber material changed correctly", "Rubber didn't react correctly")
|
||||
concrete_material_changed = ("Concrete material changed correctly", "Concrete didn't react correctly")
|
||||
# fmt: on
|
||||
|
||||
def Material_DefaultMaterialLibraryChangesWork():
|
||||
"""
|
||||
Summary: Runs an automated test to verify that material selected in the default material library is applied to PhysX
|
||||
colliders, character controller, terrain texture layers and ragdolls and that material can respond to change.
|
||||
|
||||
PhysX Config Description:
|
||||
A PhysX material library called all_ones is set as the default material library in PhysX Config File.
|
||||
The library has two materials surfaces: rubber with Restitution = 1.0, Restitution Combine = Maximum
|
||||
and concrete with Restitution = 0.0, Restitution combine = Multiply.
|
||||
The custom config file is loaded before editor is launched.
|
||||
|
||||
Level Description:
|
||||
Consists of 4 sets of entities.
|
||||
Each entity has either rubber or concrete material assigned to it. Each entity has a corresponding trigger placed
|
||||
between the entity and its collision target entity (terrain or character controller).
|
||||
The entities, their triggers and their target are colored blue if they have rubber material, or red for concrete.
|
||||
|
||||
Expected Behavior:
|
||||
The entities start their movement once the level is loaded. They should touch their corresponding triggers first,
|
||||
then collide with their target entity. The ones with rubber material are supposed to bounce back and touch the
|
||||
triggers. The ones with concrete material are supposed to stick to the target and stop moving, therefore not
|
||||
touching the triggers anymore. After the edits to material library the affect will be swapped.
|
||||
|
||||
Main Script Steps:
|
||||
1) Loads the level
|
||||
2) Setup targets and colliders
|
||||
3) Run Test 0
|
||||
4) Edit Material Library
|
||||
5) Run Test 1
|
||||
6) Validate Results
|
||||
7) Close editor
|
||||
|
||||
Test Steps:
|
||||
1) Enter Game Mode
|
||||
2) Validate target Id's
|
||||
3) Validate all Colliders and setup targets
|
||||
4) Wait for Collision, Report Results
|
||||
5) Allow Time to Hit trigger
|
||||
6) Exit Game Mode
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
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.bus
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
|
||||
# Constants
|
||||
TIME_OUT = 2.0
|
||||
PROPAGATION_FRAMES = 180
|
||||
|
||||
def get_test(entity_name, suffix):
|
||||
return Tests.__dict__[entity_name + suffix]
|
||||
|
||||
# Base class for triggers, targets and colliders
|
||||
class Entity(object):
|
||||
# Global Holding Variable for test index
|
||||
current_test = None
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.found_in_before_test = False
|
||||
|
||||
# Validates entity ids reports if the ids are valid for both test cases
|
||||
# Fast fails if any id is invalid
|
||||
def validate_id(self):
|
||||
self.id = general.find_game_entity(self.name)
|
||||
if Entity.current_test == 0 and self.id.IsValid():
|
||||
self.found_in_before_test = True
|
||||
elif Entity.current_test == 1:
|
||||
Report.critical_result(get_test(self.name, "_found"), self.id.IsValid() and self.found_in_before_test)
|
||||
else:
|
||||
helper.fail_fast("{} was not found in test {}".format(self.name, Entity.current_test))
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
class Collider(Entity):
|
||||
def __init__(self, name, target):
|
||||
Entity.__init__(self, name)
|
||||
self.target = target
|
||||
# Data holding variables
|
||||
self.collided_with_target_0 = False
|
||||
self.collided_with_target_1 = False
|
||||
self.hit_trigger_0 = False
|
||||
self.hit_trigger_1 = False
|
||||
|
||||
# Initialized target collisions
|
||||
def setup_target(self):
|
||||
self.target.validate_id
|
||||
# Watch target for collision with collider
|
||||
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.collision_handler.connect(self.id)
|
||||
self.collision_handler.add_callback("OnCollisionBegin", self.detect_collision_target)
|
||||
|
||||
def activate_trigger(self):
|
||||
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.trigger.id)
|
||||
Report.info("{} activated".format(self.trigger.name))
|
||||
|
||||
# Sets up trigger and activates it post-collision with target
|
||||
def setup_trigger(self):
|
||||
if Entity.current_test == 0:
|
||||
self.trigger = Entity(self.name + "_trigger")
|
||||
self.trigger.validate_id()
|
||||
self.activate_trigger()
|
||||
# Watch for collider entrance
|
||||
self.trigger.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.trigger.handler.connect(self.trigger.id)
|
||||
self.trigger.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
|
||||
def on_trigger_enter(self, args):
|
||||
if self.id.equal(args[0]) and not getattr(self, "hit_trigger_{}".format(Entity.current_test)):
|
||||
Report.info("{} entered {} in test {}".format(self.name, self.trigger.name, Entity.current_test))
|
||||
setattr(self, "hit_trigger_{}".format(Entity.current_test), True)
|
||||
|
||||
def detect_collision_target(self, args):
|
||||
print("Collision_going_on")
|
||||
if self.target.id.equal(args[0]) and not getattr(self, "collided_with_target_{}".format(Entity.current_test)):
|
||||
Report.info("{} collided with {}".format(self.name, self.target.name))
|
||||
setattr(self, "collided_with_target_{}".format(Entity.current_test), True)
|
||||
self.setup_trigger()
|
||||
|
||||
def edit_material_library():
|
||||
# Flips the Restitution values of rubber and concrete
|
||||
material_library = Physmaterial_Editor("all_ones_1.physmaterial")
|
||||
rubber_restitution = material_library.modify_material("rubber", "Restitution", 0)
|
||||
rubber_restitution_combine = material_library.modify_material("rubber", "RestitutionCombine", "Multiply")
|
||||
concrete_restitution = material_library.modify_material("concrete", "Restitution", 1)
|
||||
concrete_restitution_combine = material_library.modify_material("concrete", "RestitutionCombine", "Average")
|
||||
|
||||
material_library.save_changes()
|
||||
return rubber_restitution and rubber_restitution_combine and concrete_restitution and concrete_restitution_combine
|
||||
|
||||
def check_rubber_material_updated(rubber_colliders):
|
||||
# Checks that all rubber colliders hit the trigger on test 0 and not on test 1
|
||||
before_test_passed = all([collider.hit_trigger_0 for collider in rubber_colliders])
|
||||
after_test_passed = all([not collider.hit_trigger_1 for collider in rubber_colliders])
|
||||
|
||||
return before_test_passed and after_test_passed
|
||||
|
||||
def check_concrete_material_updated(concrete_colliders):
|
||||
# Checks that all concrete colliders didn't hit the trigger on test 0 and did on test 1
|
||||
before_test_passed = all([not collider.hit_trigger_0 for collider in concrete_colliders])
|
||||
after_test_passed = all([collider.hit_trigger_1 for collider in concrete_colliders])
|
||||
|
||||
return before_test_passed and after_test_passed
|
||||
|
||||
def test_run(index, all_colliders):
|
||||
Entity.current_test = index
|
||||
# 1) Enter Game Mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_", str(index)))
|
||||
|
||||
# 2) Validate target Ids
|
||||
terrain.validate_id()
|
||||
target_character_concrete.validate_id()
|
||||
target_character_rubber.validate_id()
|
||||
|
||||
# 3) Validate all Colliders and setup targets
|
||||
for collider in all_colliders:
|
||||
collider.validate_id()
|
||||
collider.setup_target()
|
||||
|
||||
# 4) Wait for Collision, Report Results
|
||||
if not helper.wait_for_condition(lambda: all([getattr(collider, "collided_with_target_{}".format(index)) for collider in all_colliders]), TIME_OUT):
|
||||
failed_colliders = ", ".join([collider.name for collider in all_colliders if not getattr(collider, "collided_with_target_{}".format(index))])
|
||||
helper.fail_fast("A collision with target did not occur for these colliders: {}".format(failed_colliders))
|
||||
elif index == 1:
|
||||
for collider in all_colliders:
|
||||
Report.result(get_test(collider.name, "_collided"), collider.collided_with_target_0 and collider.collided_with_target_1)
|
||||
|
||||
# 5) Allow time to hit trigger
|
||||
general.idle_wait_frames(PROPAGATION_FRAMES)
|
||||
|
||||
# 6) Exit Game Mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_", str(index)))
|
||||
|
||||
# Main Script
|
||||
helper.init_idle()
|
||||
# 1) Load the level
|
||||
helper.open_level("Physics", "Material_DefaultMaterialLibraryChangesWork")
|
||||
|
||||
# 2) Setup targets and colliders
|
||||
terrain = Entity("terrain")
|
||||
target_character_rubber = Entity("target_character_rubber")
|
||||
target_character_concrete = Entity("target_character_concrete")
|
||||
|
||||
rubber_sphere = Collider(name="rubber_sphere", target=terrain)
|
||||
concrete_sphere = Collider(name="concrete_sphere", target=terrain)
|
||||
character_rubber = Collider(name="character_rubber", target=target_character_rubber)
|
||||
character_concrete = Collider(name="character_concrete", target=target_character_concrete)
|
||||
terrain_rubber = Collider(name="terrain_rubber", target=terrain)
|
||||
terrain_concrete = Collider(name="terrain_concrete", target=terrain)
|
||||
ragdoll_rubber = Collider(name="ragdoll_rubber", target=terrain)
|
||||
ragdoll_concrete = Collider(name="ragdoll_concrete", target=terrain)
|
||||
|
||||
rubber_test_entities = [rubber_sphere, character_rubber, terrain_rubber, ragdoll_rubber]
|
||||
concrete_test_entities = [concrete_sphere, character_concrete, terrain_concrete, ragdoll_concrete]
|
||||
test_entities = rubber_test_entities + concrete_test_entities
|
||||
|
||||
# 3) Run test 0
|
||||
test_run(index=0, all_colliders=test_entities)
|
||||
|
||||
# 4) Edit Material Library
|
||||
Report.critical_result(Tests.material_library_updated, edit_material_library())
|
||||
|
||||
# Wait for material library changes to propagate
|
||||
general.idle_wait_frames(PROPAGATION_FRAMES)
|
||||
|
||||
# 5) Run test 1
|
||||
test_run(index=1, all_colliders=test_entities)
|
||||
|
||||
# 6) Validate Results
|
||||
Report.result(Tests.concrete_material_changed, check_concrete_material_updated(concrete_test_entities))
|
||||
Report.result(Tests.rubber_material_changed, check_rubber_material_updated(rubber_test_entities))
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_DefaultMaterialLibraryChangesWork)
|
||||
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
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 : C4044459
|
||||
# Test Case Title : Verify the functionality of dynamic friction
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_ramp = ("Ramp entity found", "Ramp entity not found")
|
||||
find_box_zero = ("Box entity 'zero' found", "Box entity 'zero' not found")
|
||||
find_box_low = ("Box entity 'low' found", "Box entity 'low' not found")
|
||||
find_box_mid = ("Box entity 'mid' found", "Box entity 'mid' not found")
|
||||
find_box_high = ("Box entity 'high' found", "Box entity 'high' not found")
|
||||
box_at_rest_start_zero = ("Box 'zero' began test motionless", "Box 'zero' did not begin test motionless")
|
||||
box_at_rest_start_low = ("Box 'low' began test motionless", "Box 'low' did not begin test motionless")
|
||||
box_at_rest_start_mid = ("Box 'mid' began test motionless", "Box 'mid' did not begin test motionless")
|
||||
box_at_rest_start_high = ("Box 'high' began test motionless", "Box 'high' did not begin test motionless")
|
||||
box_was_pushed_zero = ("Box 'zero' moved", "Box 'zero' did not move before timeout")
|
||||
box_was_pushed_low = ("Box 'low' moved", "Box 'low' did not move before timeout")
|
||||
box_was_pushed_mid = ("Box 'mid' moved", "Box 'mid' did not move before timeout")
|
||||
box_was_pushed_high = ("Box 'high' moved", "Box 'high' did not move before timeout")
|
||||
box_at_rest_end_zero = ("Box 'zero' came to rest", "Box 'zero' did not come to rest before timeout")
|
||||
box_at_rest_end_low = ("Box 'low' came to rest", "Box 'low' did not come to rest before timeout")
|
||||
box_at_rest_end_mid = ("Box 'mid' came to rest", "Box 'mid' did not come to rest before timeout")
|
||||
box_at_rest_end_high = ("Box 'high' came to rest", "Box 'high' did not come to rest before timeout")
|
||||
distance_ordered = ("Boxes with greater dynamic friction traveled shorter", "Boxes with greater dynamic friction traveled further")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_DynamicFriction():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to ensure that greater dynamic friction coefficient settings on a physX material results in
|
||||
rigidbody entities (with that material) that require a greater force in order to remain in motion
|
||||
|
||||
Level Description:
|
||||
Four boxes sit on a horizontal 'ramp'. The boxes are identical, save for their physX material:
|
||||
|
||||
A new material library was created with 4 materials and their dynamic friction coefficient:
|
||||
zero_dynamic_friction: 0.00
|
||||
low_dynamic_friction: 0.50
|
||||
mid_dynamic_friction: 1.00
|
||||
high_dynamic_friction: 1.50
|
||||
Each material is identical otherwise.
|
||||
|
||||
Each box is assigned its corresponding friction material
|
||||
Each box also has a PhysX box collider with default settings
|
||||
The COM of the boxes is placed on the plane (0, 0, -0.5), so as to remove any torque moments and resulting rotations
|
||||
|
||||
Expected Behavior:
|
||||
For each box, this script will apply a force impulse in the world X direction
|
||||
Boxes with greater dynamic friction coefficients should travel a shorter distance along the ramp.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find the ramp
|
||||
|
||||
For each box:
|
||||
4) Find the box
|
||||
5) Ensure the box is stationary
|
||||
6) Push the box and wait for it to come to rest
|
||||
|
||||
7) Assert that greater coefficients result in a shorter distance travelled
|
||||
8) Exit game mode
|
||||
9) 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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(10.0, 0.0, 0.0)
|
||||
TIMEOUT = 5
|
||||
|
||||
class Box:
|
||||
def __init__(self, name, valid_test, stationary_start_test, moved_test, stationary_end_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.distance = 0.0
|
||||
self.valid_test = valid_test
|
||||
self.stationary_start_test = stationary_start_test
|
||||
self.moved_test = moved_test
|
||||
self.stationary_end_test = stationary_end_test
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return vector_is_close_to_zero(velocity)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def vector_is_close_to_zero(vector):
|
||||
return abs(vector.x) <= 0.001 and abs(vector.y) <= 0.001 and abs(vector.z) <= 0.001
|
||||
|
||||
def push(box):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", box.id, FORCE_IMPULSE)
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_DynamicFriction")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# fmt: off
|
||||
# Set up our boxes
|
||||
box_zero = Box(
|
||||
name = "Zero",
|
||||
valid_test = Tests.find_box_zero,
|
||||
stationary_start_test = Tests.box_at_rest_start_zero,
|
||||
moved_test = Tests.box_was_pushed_zero,
|
||||
stationary_end_test = Tests.box_at_rest_end_zero
|
||||
)
|
||||
box_low = Box(
|
||||
name = "Low",
|
||||
valid_test = Tests.find_box_low,
|
||||
stationary_start_test = Tests.box_at_rest_start_low,
|
||||
moved_test = Tests.box_was_pushed_low,
|
||||
stationary_end_test = Tests.box_at_rest_end_low
|
||||
)
|
||||
box_mid = Box(
|
||||
name = "Mid",
|
||||
valid_test = Tests.find_box_mid,
|
||||
stationary_start_test = Tests.box_at_rest_start_mid,
|
||||
moved_test = Tests.box_was_pushed_mid,
|
||||
stationary_end_test = Tests.box_at_rest_end_mid
|
||||
)
|
||||
box_high = Box(
|
||||
name = "High",
|
||||
valid_test = Tests.find_box_high,
|
||||
stationary_start_test = Tests.box_at_rest_start_high,
|
||||
moved_test = Tests.box_was_pushed_high,
|
||||
stationary_end_test = Tests.box_at_rest_end_high
|
||||
)
|
||||
all_boxes = (box_zero, box_low, box_mid, box_high)
|
||||
# fmt: on
|
||||
|
||||
# 3) Find the ramp
|
||||
ramp_id = general.find_game_entity("Ramp")
|
||||
Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
|
||||
|
||||
for box in all_boxes:
|
||||
Report.info("********Pushing Box {}********".format(box.name))
|
||||
# 4) Find the box
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
# 5) Ensure the box is stationary
|
||||
Report.result(box.stationary_start_test, box.is_stationary())
|
||||
# 6) Push the box
|
||||
push(box)
|
||||
Report.result(box.moved_test, helper.wait_for_condition(lambda: not box.is_stationary(), TIMEOUT))
|
||||
Report.result(box.stationary_end_test, helper.wait_for_condition(lambda: box.is_stationary(), TIMEOUT))
|
||||
|
||||
end_position = box.get_position()
|
||||
box.distance = end_position.GetDistance(box.start_position)
|
||||
Report.info("Box {} travelled {:.3f} meters".format(box.name, box.distance))
|
||||
|
||||
# 7) Assert that greater coefficients result in shorter travelled distance
|
||||
distance_ordered = box_high.distance < box_mid.distance < box_low.distance < box_zero.distance
|
||||
Report.result(Tests.distance_ordered, distance_ordered)
|
||||
|
||||
# 8) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_DynamicFriction)
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
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 : C4044694
|
||||
# Test Case Title : Verify that if we add an empty Material library in Collider Component, the object continues to use Default material values
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_terrain = ("The Terrain was found", "The Terrain was not found")
|
||||
find_default_box = ("'default_box' was found", "'default_box' was not found")
|
||||
find_empty_box = ("'empty_box' was found", "'empty_box' was not found")
|
||||
find_default_sphere = ("'default_sphere' was found", "'default_sphere' was not found")
|
||||
find_empty_sphere = ("'empty_sphere' was found", "'empty_sphere' was not found")
|
||||
boxes_moved = ("All boxes moved", "Boxes failed to move")
|
||||
boxes_at_rest = ("All boxes came to rest", "Boxes failed to come to rest")
|
||||
default_sphere_bounced = ("'default_sphere' bounced", "'default_sphere' did not bounce")
|
||||
empty_sphere_bounced = ("'empty_sphere' bounced", "'empty_sphere' did not bounce")
|
||||
default_box_equals_empty = ("'default_box' and 'empty_box' traveled the same distance", "'default_box' and 'empty_box' did not travel the same distance")
|
||||
default_sphere_equals_empty = ("'default_sphere' and 'empty_sphere' bounce heights were equal", "'default_sphere' and 'empty_sphere' bounce heights were not equal")
|
||||
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_EmptyLibraryUsesDefault():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that an object with an empty Material library in a Collider Component continues to
|
||||
use the default material values
|
||||
|
||||
Level Description:
|
||||
There are 5 entities.
|
||||
One terrain entity ('terrain') with PhysX Terrain,
|
||||
Two sphere entities ('empty_sphere' and 'default_sphere') with PhysX Rigid Body and PhysX Sphere Collider,
|
||||
Two box entities ('empty_box' and 'default_box') with PhysX Rigid Body and PhysX Box Collider,
|
||||
|
||||
The spheres are positioned above the terrain, and the boxes are placed on the terrain.
|
||||
The "empty" entities are assigned a material library that contains no materials. The "default" entities are assigned
|
||||
the default material from the default material library.
|
||||
|
||||
Expected behavior:
|
||||
The spheres fall and bounce the same height.
|
||||
The boxes are pushed and travel the same distance.
|
||||
|
||||
Test Steps:
|
||||
1) Open level and enter game mode
|
||||
2) Find entities
|
||||
3) Wait for spheres to bounce
|
||||
4) Compare 'default_sphere' to 'empty_sphere'
|
||||
5) Push the boxes and wait for them to come to rest
|
||||
6) Compare 'default_box' to 'empty_box'
|
||||
7) Exit game mode and close 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 as bus
|
||||
import azlmbr.components
|
||||
import azlmbr.physics
|
||||
import azlmbr.math as lymath
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
TIMEOUT = 3.0
|
||||
DISTANCE_TOLERANCE = 0.001
|
||||
|
||||
class Entity:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(self.name)
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
class Box(Entity):
|
||||
def __init__(self, name):
|
||||
Entity.__init__(self, name)
|
||||
self.start_position = self.position
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return velocity.IsZero()
|
||||
|
||||
def push(self):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", self.id, FORCE_IMPULSE)
|
||||
|
||||
class Sphere(Entity):
|
||||
def __init__(self, name):
|
||||
Entity.__init__(self, name)
|
||||
self.hit_terrain_position = None
|
||||
self.hit_terrain = False
|
||||
self.max_bounce = 0.0
|
||||
self.reached_max_bounce = False
|
||||
|
||||
def on_collision_enter(args):
|
||||
entering = args[0]
|
||||
for sphere in [default_sphere, empty_sphere]:
|
||||
if sphere.id.Equal(entering):
|
||||
if not sphere.hit_terrain:
|
||||
sphere.hit_terrain_position = sphere.position
|
||||
sphere.hit_terrain = True
|
||||
|
||||
# region wait_for_condition() functions
|
||||
def wait_for_bounce():
|
||||
for sphere in [default_sphere, empty_sphere]:
|
||||
if sphere.hit_terrain:
|
||||
current_bounce_height = sphere.position.z - sphere.hit_terrain_position.z
|
||||
if current_bounce_height >= sphere.max_bounce:
|
||||
sphere.max_bounce = current_bounce_height
|
||||
elif sphere.max_bounce > 0.0:
|
||||
sphere.reached_max_bounce = True
|
||||
return default_sphere.reached_max_bounce and empty_sphere.reached_max_bounce
|
||||
|
||||
def boxes_moved():
|
||||
return not default_box.is_stationary() and not empty_box.is_stationary()
|
||||
|
||||
def boxes_are_stationary():
|
||||
return default_box.is_stationary() and empty_box.is_stationary()
|
||||
|
||||
# endregion
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_EmptyLibraryUsesDefault")
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# 2) Find entities
|
||||
terrain_id = general.find_game_entity("terrain")
|
||||
default_box = Box("default_box")
|
||||
empty_box = Box("empty_box")
|
||||
default_sphere = Sphere("default_sphere")
|
||||
empty_sphere = Sphere("empty_sphere")
|
||||
|
||||
Report.result(Tests.find_terrain, terrain_id.IsValid())
|
||||
Report.result(Tests.find_default_box, default_box.id.IsValid())
|
||||
Report.result(Tests.find_empty_box, empty_box.id.IsValid())
|
||||
Report.result(Tests.find_default_sphere, default_sphere.id.IsValid())
|
||||
Report.result(Tests.find_empty_sphere, empty_sphere.id.IsValid())
|
||||
|
||||
# Setup terrain collision handler
|
||||
handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
handler.connect(terrain_id)
|
||||
handler.add_callback("OnCollisionBegin", on_collision_enter)
|
||||
|
||||
# 3) Wait for spheres to bounce
|
||||
helper.wait_for_condition(wait_for_bounce, TIMEOUT)
|
||||
Report.result(Tests.default_sphere_bounced, default_sphere.reached_max_bounce)
|
||||
Report.result(Tests.empty_sphere_bounced, empty_sphere.reached_max_bounce)
|
||||
|
||||
# 4) Compare 'default_sphere' to 'empty_sphere'
|
||||
sphere_bounces_equal = lymath.Math_IsClose(default_sphere.max_bounce, empty_sphere.max_bounce, DISTANCE_TOLERANCE)
|
||||
Report.result(Tests.default_sphere_equals_empty, sphere_bounces_equal)
|
||||
|
||||
# 5) Push the boxes and wait for them to come to rest
|
||||
default_box.push()
|
||||
empty_box.push()
|
||||
Report.result(Tests.boxes_moved, helper.wait_for_condition(boxes_moved, TIMEOUT))
|
||||
Report.result(Tests.boxes_at_rest, helper.wait_for_condition(boxes_are_stationary, TIMEOUT))
|
||||
|
||||
# 6) Compare 'default_box' to 'empty_box'
|
||||
default_distance = default_box.position.GetDistance(default_box.start_position)
|
||||
empty_distance = empty_box.position.GetDistance(empty_box.start_position)
|
||||
box_distances_equal = lymath.Math_IsClose(default_distance, empty_distance, DISTANCE_TOLERANCE)
|
||||
Report.result(Tests.default_box_equals_empty, box_distances_equal)
|
||||
|
||||
# 7) Exit game mode and close editor
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_EmptyLibraryUsesDefault)
|
||||
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
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 : C4044456
|
||||
# Test Case Title : Verify that when two objects with different materials collide, the friction combine works
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_ramp = ("Ramp entity found", "Ramp entity not found")
|
||||
find_box_minimum = ("Box entity 'minimum' found", "Box entity 'minimum' not found")
|
||||
find_box_multiply = ("Box entity 'multiply' found", "Box entity 'multiply' not found")
|
||||
find_box_average = ("Box entity 'average' found", "Box entity 'average' not found")
|
||||
find_box_maximum = ("Box entity 'maximum' found", "Box entity 'maximum' not found")
|
||||
box_at_rest_start_minimum = ("Box 'minimum ' began test motionless", "Box 'minimum' did not begin test motionless")
|
||||
box_at_rest_start_multiply = ("Box 'multiply' began test motionless", "Box 'multiply' did not begin test motionless")
|
||||
box_at_rest_start_average = ("Box 'average' began test motionless", "Box 'average' did not begin test motionless")
|
||||
box_at_rest_start_maximum = ("Box 'maximum' began test motionless", "Box 'maximum' did not begin test motionless")
|
||||
box_was_pushed_minimum = ("Box 'minimum' moved", "Box 'minimum' did not move before timeout")
|
||||
box_was_pushed_multiply = ("Box 'multiply' moved", "Box 'multiply' did not move before timeout")
|
||||
box_was_pushed_average = ("Box 'average' moved", "Box 'average' did not move before timeout")
|
||||
box_was_pushed_maximum = ("Box 'maximum' moved", "Box 'maximum' did not move before timeout")
|
||||
box_at_rest_end_minimum = ("Box 'minimum' came to rest", "Box 'minimum' did not come to rest before timeout")
|
||||
box_at_rest_end_multiply = ("Box 'multiply' came to rest", "Box 'multiply' did not come to rest before timeout")
|
||||
box_at_rest_end_average = ("Box 'average' came to rest", "Box 'average' did not come to rest before timeout")
|
||||
box_at_rest_end_maximum = ("Box 'maximum' came to rest", "Box 'maximum' did not come to rest before timeout")
|
||||
minimum_equals_multiply = ("Box 'minimum' and 'multiply' traveled equal distances", "Box 'minimum' and 'multiply' did not travel equal distances")
|
||||
distance_ordered = ("Box travel distance was ordered as expected", "Box travel distance was not ordered as expected")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_FrictionCombine():
|
||||
"""
|
||||
Summary:
|
||||
|
||||
Level Description:
|
||||
Four boxes sit on a horizontal 'ramp'. The boxes are identical, save for their physX material:
|
||||
|
||||
A new material library was created with 4 materials, minimum, multiply, average, and maximum.
|
||||
Each material has its 'friction combine' mode assigned as named; as well as the following properties:
|
||||
dynamic friction: 0.1
|
||||
static friction: 0.1
|
||||
restitution: 0.1
|
||||
|
||||
An additional material was created for the ramp entity. It has the following properties:
|
||||
dynamic friction: 1.0
|
||||
static friction: 1.0
|
||||
restitution: 1.0
|
||||
friction combine: Average
|
||||
|
||||
Each box is assigned its corresponding friction material
|
||||
Each box also has a PhysX box collider with default settings
|
||||
The COM of the boxes is placed on the plane (0, 0, -0.5), so as to remove any torque moments and resulting rotations
|
||||
|
||||
Expected Behavior:
|
||||
For each box, this script will apply a force impulse in the world X direction
|
||||
Boxes with greater friction combine mode results should travel a shorter distance.
|
||||
minimum: 0.1 vs 1 -> 0.1
|
||||
multiply: 0.1 * 1 -> 0.1
|
||||
average: (0.1 + 1) / 2 -> 0.55
|
||||
maximum: 0.1 vs 1 -> 1
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find the ramp
|
||||
|
||||
For each box:
|
||||
4) Find the box
|
||||
5) Ensure the box is stationary
|
||||
6) Push the box and wait for it to come to rest
|
||||
|
||||
7) Special case: assert that minimum and multiply travel the same distance
|
||||
8) Assert that greater friction combine modes travel a shorter distance
|
||||
9) Exit game mode
|
||||
10) 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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
VECTOR_TOLERANCE = 0.001
|
||||
DISTANCE_TOLERANCE = 0.002
|
||||
TIMEOUT = 5
|
||||
|
||||
class Box:
|
||||
def __init__(self, name, valid_test, stationary_start_test, moved_test, stationary_end_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.distance = 0.0
|
||||
self.valid_test = valid_test
|
||||
self.stationary_start_test = stationary_start_test
|
||||
self.moved_test = moved_test
|
||||
self.stationary_end_test = stationary_end_test
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return vector_is_close_to_zero(velocity)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def vector_is_close_to_zero(vector):
|
||||
return (
|
||||
abs(vector.x) <= VECTOR_TOLERANCE
|
||||
and abs(vector.y) <= VECTOR_TOLERANCE
|
||||
and abs(vector.z) <= VECTOR_TOLERANCE
|
||||
)
|
||||
|
||||
def push(box):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", box.id, FORCE_IMPULSE)
|
||||
|
||||
def float_is_close(value, target, tolerance):
|
||||
return abs(value - target) <= tolerance
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_FrictionCombine")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# fmt: off
|
||||
# Set up our boxes
|
||||
box_minimum = Box(
|
||||
name = "Minimum",
|
||||
valid_test = Tests.find_box_minimum,
|
||||
stationary_start_test = Tests.box_at_rest_start_minimum,
|
||||
moved_test = Tests.box_was_pushed_minimum,
|
||||
stationary_end_test = Tests.box_at_rest_end_minimum
|
||||
)
|
||||
box_multiply = Box(
|
||||
name = "Multiply",
|
||||
valid_test = Tests.find_box_multiply,
|
||||
stationary_start_test = Tests.box_at_rest_start_multiply,
|
||||
moved_test = Tests.box_was_pushed_multiply,
|
||||
stationary_end_test = Tests.box_at_rest_end_multiply
|
||||
)
|
||||
box_average = Box(
|
||||
name = "Average",
|
||||
valid_test = Tests.find_box_average,
|
||||
stationary_start_test = Tests.box_at_rest_start_average,
|
||||
moved_test = Tests.box_was_pushed_average,
|
||||
stationary_end_test = Tests.box_at_rest_end_average
|
||||
)
|
||||
box_maximum = Box(
|
||||
name = "Maximum",
|
||||
valid_test = Tests.find_box_maximum,
|
||||
stationary_start_test = Tests.box_at_rest_start_maximum,
|
||||
moved_test = Tests.box_was_pushed_maximum,
|
||||
stationary_end_test = Tests.box_at_rest_end_maximum
|
||||
)
|
||||
all_boxes = (box_minimum, box_multiply, box_average, box_maximum)
|
||||
# fmt: on
|
||||
|
||||
# 3) Find the ramp
|
||||
ramp_id = general.find_game_entity("Ramp")
|
||||
Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
|
||||
|
||||
for box in all_boxes:
|
||||
Report.info("********Pushing Box {}********".format(box.name))
|
||||
# 4) Find the box
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
# 5) Ensure the box is stationary
|
||||
Report.result(box.stationary_start_test, box.is_stationary())
|
||||
# 6) Push the box
|
||||
push(box)
|
||||
Report.result(box.moved_test, helper.wait_for_condition(lambda: not box.is_stationary(), TIMEOUT))
|
||||
Report.result(box.stationary_end_test, helper.wait_for_condition(lambda: box.is_stationary(), TIMEOUT))
|
||||
|
||||
end_position = box.get_position()
|
||||
box.distance = end_position.GetDistance(box.start_position)
|
||||
Report.info("Box {} travelled {:.3f} meters".format(box.name, box.distance))
|
||||
|
||||
# 7) Special case: assert that minimum and multiply travel the same distance
|
||||
boxes_are_close = float_is_close(box_minimum.distance, box_multiply.distance, DISTANCE_TOLERANCE)
|
||||
Report.result(Tests.minimum_equals_multiply, boxes_are_close)
|
||||
|
||||
# 8) Assert that greater coefficients result in shorter travelled distance
|
||||
distance_ordered = boxes_are_close and box_minimum.distance > box_average.distance > box_maximum.distance
|
||||
Report.result(Tests.distance_ordered, distance_ordered)
|
||||
|
||||
# 9) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_FrictionCombine)
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
"""
|
||||
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 : C18977601
|
||||
# Test Case Title : Verify that when two objects with different materials collide, the friction combine priority works
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_box_average = ("Box entity 'average' found", "Box entity 'average' not found")
|
||||
find_box_minimum = ("Box entity 'minimum' found", "Box entity 'minimum' not found")
|
||||
find_box_multiply = ("Box entity 'multiply' found", "Box entity 'multiply' not found")
|
||||
find_box_maximum = ("Box entity 'maximum' found", "Box entity 'maximum' not found")
|
||||
find_ramp_average = ("Ramp entity 'average' found", "Ramp entity 'average' not found")
|
||||
find_ramp_minimum = ("Ramp entity 'minimum' found", "Ramp entity 'minimum' not found")
|
||||
find_ramp_multiply = ("Ramp entity 'multiply' found", "Ramp entity 'multiply' not found")
|
||||
find_ramp_maximum = ("Ramp entity 'maximum' found", "Ramp entity 'maximum' not found")
|
||||
|
||||
# Test 0, first row of matrix
|
||||
boxes_at_rest_start_0 = ("Test 0): All boxes began test motionless", "Test 0): All boxes did not begin test motionless")
|
||||
boxes_were_pushed_0 = ("Test 0): All boxes moved", "Test 0): All boxes did not move before timeout")
|
||||
boxes_at_rest_end_0 = ("Test 0): All boxes came to rest", "Test 0): All boxes did not come to rest before timeout")
|
||||
|
||||
# Test 1, second row of matrix
|
||||
boxes_at_rest_start_1 = ("Test 1): All boxes began test motionless", "Test 1): All boxes did not begin test motionless")
|
||||
boxes_were_pushed_1 = ("Test 1): All boxes moved", "Test 1): All boxes did not move before timeout")
|
||||
boxes_at_rest_end_1 = ("Test 1): All boxes came to rest", "Test 1): All boxes did not come to rest before timeout")
|
||||
|
||||
# Test 2, third row of matrix
|
||||
boxes_at_rest_start_2 = ("Test 2): All boxes began test motionless", "Test 2): All boxes did not begin test motionless")
|
||||
boxes_were_pushed_2 = ("Test 2): All boxes moved", "Test 2): All boxes did not move before timeout")
|
||||
boxes_at_rest_end_2 = ("Test 2): All boxes came to rest", "Test 2): All boxes did not come to rest before timeout")
|
||||
|
||||
# Test 3, fourth row of matrix
|
||||
boxes_at_rest_start_3 = ("Test 3): All boxes began test motionless", "Test 3): All boxes did not begin test motionless")
|
||||
boxes_were_pushed_3 = ("Test 3): All boxes moved", "Test 3): All boxes did not move before timeout")
|
||||
boxes_at_rest_end_3 = ("Test 3): All boxes came to rest", "Test 3): All boxes did not come to rest before timeout")
|
||||
|
||||
basis_row_unique = ("All distances in Test 0 were unique", "All distances in Test 0 were not unique")
|
||||
basis_row_ordered = ("All distances in Test 0 were correctly ordered", "All distances in Test 0 were correctly ordered")
|
||||
distance_matrix_valid = ("The resulting distance matrix was valid", "The resulting distance matrix was invalid")
|
||||
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_FrictionCombinePriorityOrder():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to ensure that the friction combine mode is assigned according to the correct priority.
|
||||
|
||||
Level Description:
|
||||
Four boxes sit on one of 4 horizontal ramps.
|
||||
The ramps are identical, as are the boxes, save for their physX material:
|
||||
|
||||
A new material library was created with 8 materials:
|
||||
minimum_box, multiply_box, average_box, maximum_box, minimum_ramp, multiply_ramp, average_ramp, maximum_ramp,
|
||||
Each 'box' material has its 'friction combine' mode assigned as named; as well as the following properties:
|
||||
dynamic friction: 0.25
|
||||
static friction: 0.25
|
||||
restitution: 0.25
|
||||
The 'ramp' materials are assigned similarly, with the following values:
|
||||
dynamic friction: 0.5
|
||||
static friction: 0.5
|
||||
restitution: 0.5
|
||||
|
||||
The values were specifically chosen to give a well-ordered and distributed result across all 4 combine modes
|
||||
(each progressive tier in priority gives a result 0.125 away from the last)
|
||||
|
||||
Each box and ramp is assigned its corresponding friction material
|
||||
Each box and ramp also has a PhysX box collider with default settings
|
||||
The COM of the boxes is placed on the plane (0, 0, -0.5), so as to remove any torque moments and resulting rotations
|
||||
|
||||
Expected Behavior:
|
||||
When two bodies with different materials are in contact, the physics system chooses which combine mode to use based
|
||||
on which combine mode has the highest priority.
|
||||
|
||||
The priority order is as follows: Average < Minimum < Multiply < Maximum.
|
||||
|
||||
For each ramp, this script applies a force impulse in the world X direction to all four boxes.
|
||||
|
||||
Upon collecting all data, the script evaluates the traveled distances against an expected pattern.
|
||||
This pattern is derived from the priority order, to determine which combine mode should "win" over the others.
|
||||
Boxes with greater friction combine coefficients should travel a shorter distance.
|
||||
|
||||
[Coefficient Combination Mode Results]
|
||||
average: (0.25 + 0.5) / 2 -> 0.375
|
||||
minimum: 0.25 vs 0.5 -> 0.25
|
||||
multiply: 0.25 * 0.5 -> 0.125
|
||||
maximum: 0.25 vs 0.5 -> 0.5
|
||||
|
||||
[Coefficient Combination Matrix]
|
||||
Boxes
|
||||
avg min mul max
|
||||
avg 0.375 0.25 0.125 0.5 # Test 0
|
||||
Ramps min 0.25 0.25 0.125 0.5 # Test 1
|
||||
mul 0.125 0.125 0.125 0.5 # Test 2
|
||||
max 0.5 0.5 0.5 0.5 # Test 3
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Validate entities
|
||||
|
||||
For each ramp:
|
||||
4) Replace the ramp under the boxes
|
||||
5) Ensure all boxes are stationary
|
||||
6) Push the boxes and wait for them to come to rest
|
||||
|
||||
7) Validate matrix
|
||||
8) Exit game mode
|
||||
9) 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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
NUMBER_OF_TESTS = 4
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
STANDBY_OFFSET = lymath.Vector3(0.0, 0.0, 4.0)
|
||||
DISTANCE_TOLERANCE = 0.002
|
||||
TIMEOUT = 5
|
||||
|
||||
# region Entity Classes
|
||||
class Box:
|
||||
def __init__(self, name, valid_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.valid_test = valid_test
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return velocity.IsZero()
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
class Ramp:
|
||||
def __init__(self, name, valid_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.valid_test = valid_test
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def set_position(self, value):
|
||||
return azlmbr.components.TransformBus(bus.Event, "SetWorldTranslation", self.id, value)
|
||||
|
||||
def get_test(test_name, test_number):
|
||||
return Tests.__dict__["{}_{}".format(test_name, test_number)]
|
||||
|
||||
class TestInfo:
|
||||
def __init__(self):
|
||||
self.at_rest_start_tests = []
|
||||
self.moved_tests = []
|
||||
self.at_rest_end_tests = []
|
||||
for i in range(NUMBER_OF_TESTS):
|
||||
self.at_rest_start_tests.append(get_test("boxes_at_rest_start", i))
|
||||
self.moved_tests.append(get_test("boxes_were_pushed", i))
|
||||
self.at_rest_end_tests.append(get_test("boxes_at_rest_end", i))
|
||||
|
||||
# endregion
|
||||
|
||||
# region wait_for_condition() Functions
|
||||
def push_boxes():
|
||||
for box in all_boxes:
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", box.id, FORCE_IMPULSE)
|
||||
|
||||
def all_boxes_stationary():
|
||||
for box in all_boxes:
|
||||
if not box.is_stationary():
|
||||
return False
|
||||
return True
|
||||
|
||||
def all_boxes_moving():
|
||||
for box in all_boxes:
|
||||
if box.is_stationary():
|
||||
return False
|
||||
return True
|
||||
|
||||
# endregion
|
||||
|
||||
# region Matrix Validation
|
||||
def list_is_unique(target_list):
|
||||
return len(set(target_list)) == len(target_list)
|
||||
|
||||
def float_is_close(value, target, tolerance):
|
||||
return abs(value - target) <= tolerance
|
||||
|
||||
def validate_matrix(matrix):
|
||||
# type: (list[list]) -> bool
|
||||
"""
|
||||
Returns True if the matrix matches the pattern expected based on the friction combine priority.
|
||||
|
||||
:param matrix: the distance matrix
|
||||
|
||||
:return: True if the matrix closely matches the expected pattern
|
||||
"""
|
||||
# The first test represents a basis value for what is expected when each of the combine modes "wins" the other.
|
||||
# This is because every mode beats 'average' (the first ramp we test on). We can compare the rest of the matrix
|
||||
# against this basis row to validate the rest of the matrix as long as we also guarantee that the basis is valid
|
||||
#
|
||||
# Resulting matrix should follow the pattern:
|
||||
# A B C D <- Test 0
|
||||
# B B C D <- Test 1
|
||||
# C C C D <- Test 2
|
||||
# D D D D <- Test 3
|
||||
|
||||
basis_row = matrix[0]
|
||||
Report.critical_result(Tests.basis_row_unique, list_is_unique(basis_row))
|
||||
|
||||
average = basis_row[0]
|
||||
minimum = basis_row[1]
|
||||
multiply = basis_row[2]
|
||||
maximum = basis_row[3]
|
||||
# Based on the resulting coefficients, we can expect each slide distance to be ordered a specific way
|
||||
Report.critical_result(Tests.basis_row_ordered, maximum < average < minimum < multiply)
|
||||
|
||||
def report_failure(test_index, box_index, expected):
|
||||
box_name = all_boxes[box_index].name
|
||||
Report.info(
|
||||
"Matrix validation failure:\n"
|
||||
"Distance for box '{}' on test {} was not close to the expected basis value\n"
|
||||
"Actual: {:.3f}\n"
|
||||
"Expected: {:.3f}".format(box_name, test_index, matrix[test_index][box_index], expected)
|
||||
)
|
||||
|
||||
valid = True
|
||||
for row_index, row in enumerate(matrix):
|
||||
for column_index, value in enumerate(row):
|
||||
max_index = max(row_index, column_index)
|
||||
|
||||
if not float_is_close(value, basis_row[max_index], DISTANCE_TOLERANCE):
|
||||
report_failure(row_index, column_index, basis_row[max_index])
|
||||
valid = False
|
||||
return valid
|
||||
|
||||
def log_matrix(matrix):
|
||||
matrix_display_string = "\nResulting Distance Matrix:\n"
|
||||
for row in matrix:
|
||||
for value in row:
|
||||
matrix_display_string += "{:.3f},".format(value)
|
||||
matrix_display_string += "\n"
|
||||
Report.info(matrix_display_string)
|
||||
|
||||
# endregion
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_FrictionCombinePriorityOrder")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# Set up our boxes
|
||||
box_average = Box("Average", Tests.find_box_average)
|
||||
box_minimum = Box("Minimum", Tests.find_box_minimum)
|
||||
box_multiply = Box("Multiply", Tests.find_box_multiply)
|
||||
box_maximum = Box("Maximum", Tests.find_box_maximum)
|
||||
all_boxes = (box_average, box_minimum, box_multiply, box_maximum)
|
||||
|
||||
# Set up our ramps
|
||||
ramp_average = Ramp("RampAverage", Tests.find_ramp_average)
|
||||
ramp_minimum = Ramp("RampMinimum", Tests.find_ramp_minimum)
|
||||
ramp_multiply = Ramp("RampMultiply", Tests.find_ramp_multiply)
|
||||
ramp_maximum = Ramp("RampMaximum", Tests.find_ramp_maximum)
|
||||
all_ramps = (ramp_average, ramp_minimum, ramp_multiply, ramp_maximum)
|
||||
|
||||
# Init our tests
|
||||
test_info = TestInfo()
|
||||
|
||||
# 3) Validate entities
|
||||
for box in all_boxes:
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
|
||||
for ramp in all_ramps:
|
||||
Report.critical_result(ramp.valid_test, ramp.id.IsValid())
|
||||
|
||||
# Setup ramp active and standby positions
|
||||
active_position = ramp_average.get_position()
|
||||
stand_by_position = active_position.Subtract(STANDBY_OFFSET)
|
||||
|
||||
# fmt: off
|
||||
distance_matrix = [[0.0, 0.0, 0.0, 0.0], # Test 0 - Ramp 'Average'
|
||||
[0.0, 0.0, 0.0, 0.0], # Test 1 - Ramp 'Minimum'
|
||||
[0.0, 0.0, 0.0, 0.0], # Test 2 - Ramp 'Multiply'
|
||||
[0.0, 0.0, 0.0, 0.0]] # Test 3 - Ramp 'Maximum'
|
||||
# fmt: on
|
||||
|
||||
for row_index in range(NUMBER_OF_TESTS):
|
||||
Report.info("********Starting Test {}********".format(row_index))
|
||||
|
||||
# 4) Replace the ramp under the boxes
|
||||
ramp = all_ramps[row_index]
|
||||
ramp.set_position(active_position)
|
||||
|
||||
# 5) Ensure all boxes are stationary
|
||||
Report.result(
|
||||
test_info.at_rest_start_tests[row_index], helper.wait_for_condition(all_boxes_stationary, TIMEOUT)
|
||||
)
|
||||
|
||||
# 6) Push the boxes and wait for them to come to rest
|
||||
push_boxes()
|
||||
|
||||
moved_test = test_info.moved_tests[row_index]
|
||||
at_rest_end_test = test_info.at_rest_end_tests[row_index]
|
||||
Report.result(moved_test, helper.wait_for_condition(all_boxes_moving, TIMEOUT))
|
||||
Report.result(at_rest_end_test, helper.wait_for_condition(all_boxes_stationary, TIMEOUT))
|
||||
|
||||
for column_index in range(NUMBER_OF_TESTS):
|
||||
# Register the distance the boxes travelled
|
||||
box = all_boxes[column_index]
|
||||
end_position = box.get_position()
|
||||
distance = end_position.GetDistance(box.start_position)
|
||||
|
||||
distance_matrix[row_index][column_index] = distance
|
||||
Report.info("Box {} travelled {:.3f} meters".format(box.name, distance))
|
||||
box.start_position = end_position
|
||||
|
||||
ramp.set_position(stand_by_position)
|
||||
|
||||
# 7) Validate matrix
|
||||
log_matrix(distance_matrix)
|
||||
Report.result(Tests.distance_matrix_valid, validate_matrix(distance_matrix))
|
||||
|
||||
# 8) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_FrictionCombinePriorityOrder)
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
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 : 4044455
|
||||
# Test Case Title : Verify that any change in any of the values including the name of the material,
|
||||
# once saved, is immediately reflected in the component and functionality
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Entered game mode 0", "Failed to enter game mode 0")
|
||||
exit_game_mode_0 = ("Exited game mode 0", "Couldn't exit game mode 0")
|
||||
enter_game_mode_1 = ("Entered game mode 1", "Failed to enter game mode 1")
|
||||
exit_game_mode_1 = ("Exited game mode 1", "Couldn't exit game mode 1")
|
||||
terrain_found_0 = ("terrain entity found 0", "terrain entity not found 0")
|
||||
block_found_0 = ("block entity found 0", "block entity not found 0")
|
||||
trigger_found_0 = ("trigger entity found 0", "trigger entity not found 0")
|
||||
terrain_found_1 = ("terrain entity found 1", "terrain entity not found 1")
|
||||
block_found_1 = ("block entity found 1", "block entity not found 1")
|
||||
trigger_found_1 = ("trigger entity found 1", "trigger entity not found 1")
|
||||
material_changes = ("material changes were made", "material changes couldn't be made")
|
||||
# Material Modifications
|
||||
static_friction = ("Static friction was modified", "Static friction wasn't modified")
|
||||
dynamic_friction = ("Dynamic friction was modified", "Dynamic friction wasn't modified")
|
||||
restitution = ("Restitution was modified", "Restition wasn't modified")
|
||||
friction_combine = ("Friction combine was modified", "Friction combine wasn't modified")
|
||||
restitution_combine = ("Restition combine was modified", "Restitution combine wasn't modified")
|
||||
delete_material = ("Material deleted successfully", "Material wasn't deleted")
|
||||
|
||||
# sphere_0 test 0
|
||||
sphere_0_found_0 = ("Test 0: sphere_0 found", "Test 0: sphere_0 not found")
|
||||
sphere_0_initial_position_0 = ("Test 0: sphere_0 is in valid position", "Test 0: sphere_0 isn't in valid position")
|
||||
sphere_0_initial_velocity_0 = ("Test 0: sphere_0 initial velocity valid", "Test 0: sphere_0 initial velocity invalid")
|
||||
sphere_0_collision_0 = ("Test 0: sphere_0 collided with terrain", "Test 0: sphere_0 collided with terrain")
|
||||
sphere_0_final_position_0 = ("Test 0: sphere_0 final position valid", "Test 0: sphere_0 final position invalid")
|
||||
sphere_0_final_velocity_0 = ("Test 0: sphere_0 final velocity valid", "Test 0: sphere_0 final velocity invalid")
|
||||
# sphere_0 test 1
|
||||
sphere_0_found_1 = ("Test 1: sphere_0 found", "Test 1: sphere_0 not found")
|
||||
sphere_0_initial_position_1 = ("Test 1: sphere_0 is in valid position", "Test 1: sphere_0 isn't in valid position")
|
||||
sphere_0_initial_velocity_1 = ("Test 1: sphere_0 initial velocity valid", "Test 1: sphere_0 initial velocity invalid")
|
||||
sphere_0_collision_1 = ("Test 1: sphere_0 collided with terrain", "Test 1: sphere_0 collided with terrain")
|
||||
sphere_0_final_position_1 = ("Test 1: sphere_0 final position valid", "Test 1: sphere_0 final position invalid")
|
||||
sphere_0_final_velocity_1 = ("Test 1: sphere_0 final velocity valid", "Test 1: sphere_0 final velocity invalid")
|
||||
# sphere_1 test 0
|
||||
sphere_1_found_0 = ("Test 0: sphere_1 found", "Test 0: sphere_1 not found")
|
||||
sphere_1_initial_position_0 = ("Test 0: sphere_1 is in valid position", "Test 0: sphere_1 isn't in valid position")
|
||||
sphere_1_initial_velocity_0 = ("Test 0: sphere_1 initial velocity valid", "Test 0: sphere_1 initial velocity invalid")
|
||||
sphere_1_collision_0 = ("Test 0: sphere_1 collided with terrain", "Test 0: sphere_1 collided with terrain")
|
||||
sphere_1_final_position_0 = ("Test 0: sphere_1 final position valid", "Test 0: sphere_1 final position invalid")
|
||||
sphere_1_final_velocity_0 = ("Test 0: sphere_1 final velocity valid", "Test 0: sphere_1 final velocity invalid")
|
||||
# sphere_1 test 1
|
||||
sphere_1_found_1 = ("Test 1: sphere_1 found", "Test 1: sphere_1 not found")
|
||||
sphere_1_initial_position_1 = ("Test 1: sphere_1 is in valid position", "Test 1: sphere_1 isn't in valid position")
|
||||
sphere_1_initial_velocity_1 = ("Test 1: sphere_1 initial velocity valid", "Test 1: sphere_1 initial velocity invalid")
|
||||
sphere_1_collision_1 = ("Test 1: sphere_1 collided with terrain", "Test 1: sphere_1 collided with terrain")
|
||||
sphere_1_final_position_1 = ("Test 1: sphere_1 final position valid", "Test 1: sphere_1 final position invalid")
|
||||
sphere_1_final_velocity_1 = ("Test 1: sphere_1 final velocity valid", "Test 1: sphere_1 final velocity invalid")
|
||||
# sphere_2 test 0
|
||||
sphere_2_found_0 = ("Test 0: sphere_2 found", "Test 0: sphere_2 not found")
|
||||
sphere_2_initial_position_0 = ("Test 0: sphere_2 is in valid position", "Test 0: sphere_2 isn't in valid position")
|
||||
sphere_2_initial_velocity_0 = ("Test 0: sphere_2 initial velocity valid", "Test 0: sphere_2 initial velocity invalid")
|
||||
sphere_2_collision_0 = ("Test 0: sphere_2 collided with terrain", "Test 0: sphere_2 collided with terrain")
|
||||
sphere_2_final_position_0 = ("Test 0: sphere_2 final position valid", "Test 0: sphere_2 final position invalid")
|
||||
sphere_2_final_velocity_0 = ("Test 0: sphere_2 final velocity valid", "Test 0: sphere_2 final velocity invalid")
|
||||
# sphere_2 test 1
|
||||
sphere_2_found_1 = ("Test 1: sphere_2 found", "Test 1: sphere_2 not found")
|
||||
sphere_2_initial_position_1 = ("Test 1: sphere_2 is in valid position", "Test 1: sphere_2 isn't in valid position")
|
||||
sphere_2_initial_velocity_1 = ("Test 1: sphere_2 initial velocity valid", "Test 1: sphere_2 initial velocity invalid")
|
||||
sphere_2_collision_1 = ("Test 1: sphere_2 collided with terrain", "Test 1: sphere_2 collided with terrain")
|
||||
sphere_2_final_position_1 = ("Test 1: sphere_2 final position valid", "Test 1: sphere_2 final position invalid")
|
||||
sphere_2_final_velocity_1 = ("Test 1: sphere_2 final velocity valid", "Test 1: sphere_2 final velocity invalid")
|
||||
|
||||
# cube_0 test 0
|
||||
cube_0_found_0 = ("Test 0: cube_0 found", "Test 0: cube_0 not found")
|
||||
cube_0_initial_position_0 = ("Test 0: cube_0 is in correct position", "Test 0: cube_0 isn't in correct position")
|
||||
cube_0_initial_velocity_0 = ("Test 0: cube_0 initial velocity valid", "Test 0: cube_0 initial velocity invalid")
|
||||
cube_0_final_position_0 = ("Test 0: cube_0 has stopped moving", "Test 0: cube_0 hasn't stopped moving")
|
||||
cube_0_final_velocity_0 = ("Test 0: cube_0 final velocity valid", "Test 0: cube_0 final velocity invalid")
|
||||
# cube_0 test 1
|
||||
cube_0_found_1 = ("Test 1: cube_0 found", "Test 1: cube_0 not found")
|
||||
cube_0_initial_position_1 = ("Test 1: cube_0 is in correct position", "Test 1: cube_0 isn't in correct position")
|
||||
cube_0_initial_velocity_1 = ("Test 1: cube_0 initial velocity valid", "Test 1: cube_0 initial velocity invalid")
|
||||
cube_0_final_position_1 = ("Test 1: cube_0 has stopped moving", "Test 1: cube_0 has not stopped moving")
|
||||
cube_0_final_velocity_1 = ("Test 1: cube_0 final velocity valid", "Test 1: cube_0 final velocity invalid")
|
||||
# cube_1 test 0
|
||||
cube_1_found_0 = ("Test 0: cube_1 found", "Test 0: cube_1 not found")
|
||||
cube_1_initial_position_0 = ("Test 0: cube_1 is in correct position", "Test 0: cube_1 isn't in correct position")
|
||||
cube_1_initial_velocity_0 = ("Test 0: cube_1 initial velocity valid", "Test 0: cube_1 initial velocity invalid")
|
||||
cube_1_final_position_0 = ("Test 0: cube_1 has stopped moving", "Test 0: cube_1 hasn't stopped moving")
|
||||
cube_1_final_velocity_0 = ("Test 0: cube_1 final velocity valid", "Test 0: cube_1 final velocity invalid")
|
||||
# cube_1 test 1
|
||||
cube_1_found_1 = ("Test 1: cube_1 found", "Test 1: cube_1 not found")
|
||||
cube_1_initial_position_1 = ("Test 1: cube_1 is in correct position", "Test 1: cube_1 isn't in correct position")
|
||||
cube_1_initial_velocity_1 = ("Test 1: cube_1 initial velocity valid", "Test 1: cube_1 initial velocity invalid")
|
||||
cube_1_final_position_1 = ("Test 1: cube_1 has stopped moving", "Test 1: cube_1 hasn't stopped moving")
|
||||
cube_1_final_velocity_1 = ("Test 1: cube_1 final velocity valid", "Test 1: cube_1 final velocity invalid")
|
||||
# cube_2 test 0
|
||||
cube_2_found_0 = ("Test 0: cube_2 found", "Test 0: cube_2 not found")
|
||||
cube_2_initial_position_0 = ("Test 0: cube_2 is in correct position", "Test 0: cube_2 isn't in correct position")
|
||||
cube_2_initial_velocity_0 = ("Test 0: cube_2 initial velocity valid", "Test 0: cube_2 initial velocity invalid")
|
||||
cube_2_final_position_0 = ("Test 0: cube_2 has stopped moving", "Test 0: cube_2 hasn't stopped moving")
|
||||
cube_2_final_velocity_0 = ("Test 0: cube_2 final velocity valid", "Test 0: cube_2 final velocity invalid")
|
||||
# cube_2 test 1
|
||||
cube_2_found_1 = ("Test 1: cube_2 found", "Test 1: cube_2 not found")
|
||||
cube_2_initial_position_1 = ("Test 1: cube_2 is in correct position", "Test 1: cube_2 isn't in correct position")
|
||||
cube_2_initial_velocity_1 = ("Test 1: cube_2 initial velocity valid", "Test 1: cube_2 initial velocity invalid")
|
||||
cube_2_final_position_1 = ("Test 1: cube_2 has stopped moving", "Test 1: cube_2 hasn't stopped moving")
|
||||
cube_2_final_velocity_1 = ("Test 1: cube_2 final velocity valid", "Test 1: cube_2 final velocity invalid")
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryChangesReflectInstantly():
|
||||
"""
|
||||
Summary: Verify that any change in any of the values of the material, once saved, is immediately reflected
|
||||
in the component and functionality
|
||||
|
||||
Level Description:
|
||||
Three sphere entities (sphere_0, sphere_1, sphere_2) - They start between the terrain and trigger with
|
||||
velocity of 10 m/s in the negative z direction; has physx collider with sphere shape, physx rigid body,
|
||||
sphere shape, has "to_change_restitution", "to_change_restitution_combine", and "to_delete" materials
|
||||
applied respectively.
|
||||
Three cube entities (cube_0, cube_1, cube_2) - On top of the negative y side of the block, gravity enabled, no
|
||||
initial velocity, 0.0 linear damping; has physx collider with box shape, physx rigid body, box shape, and
|
||||
has "to_change_static_friction", "to_change_dynamic_friction", and "to_change_friction_combine" materials
|
||||
applied respectively
|
||||
trigger - Stationary trigger above the three spheres, used to indicate if the material was modified correctly; has
|
||||
physx collider with box shape (20.0, 5.0, 0.25) and trigger enabled and box shape (20.0, 5.0, 0.25)
|
||||
block - Stationary block that has all cubes sitting on it. Used as a controlled surface for friction testing; has
|
||||
physx collider with box shape (10.0, 10.0, 10.0) and box shape (10.0, 10.0, 10.0)
|
||||
terrain - terrain component holder lined up with terrain default height; has terrain component
|
||||
|
||||
Material Library: Contains a different material for each entity with distinct collider shape. These materials are
|
||||
designed to provide the largest difference in result after change (sphere: velocity, cube: distance). All spheres
|
||||
should not bounce off of the terrain initially but will be able to hit the trigger post change. The cubes will
|
||||
experience higher friction after the change and not travel as far along the ramp entity.
|
||||
|
||||
Expected Behavior: Before editing the material library the spheres in both levels will not bounce off of the terrain
|
||||
and the cubes will go some distance along the ramp. After the material file is edited the spheres will bounce off
|
||||
of the terrain and hit the trigger and the cubes will travel a smaller distance than before
|
||||
|
||||
Main Script Steps:
|
||||
1) Open Level
|
||||
2) Create test objects
|
||||
3) Run test 0
|
||||
4) Modify material library
|
||||
5) Run test 1
|
||||
6) Validate results
|
||||
7) Close Editor
|
||||
|
||||
Test Loop Steps:
|
||||
1) Enter game mode
|
||||
2) Find and Validate entities
|
||||
3) Wait for spheres to collide with terrain
|
||||
4) Wait for spheres to enter the trigger
|
||||
5) Log sphere results
|
||||
6) Push cubes
|
||||
7) Wait for cubes to stop moving
|
||||
8) Log and validate cube results
|
||||
9) Exit game mode
|
||||
|
||||
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
|
||||
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.bus
|
||||
import azlmbr.math as math
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
|
||||
# Constants
|
||||
FLOAT_THRESHOLD = 0.001
|
||||
# Timeout in seconds
|
||||
TIMEOUT = 2.0
|
||||
CUBE_IMPULSE = math.Vector3(0.0, 5.0, 0.0)
|
||||
CUBE_Y_POSITION = 536.0
|
||||
CUBE_INITIAL_VELOCITY = math.Vector3(0.0, 0.0, 0.0)
|
||||
PROPAGATION_FRAMES = 500
|
||||
|
||||
# Helper Functions
|
||||
class Entity:
|
||||
terrain_id = None
|
||||
def __init__(self, name, test_index):
|
||||
# Type (str, int, int, Entity) -> None
|
||||
self.id = general.find_game_entity(name)
|
||||
self.name = name
|
||||
self.test_index = test_index
|
||||
self.collision_happened = False
|
||||
self.hit_trigger = False
|
||||
# Check Entity ID
|
||||
found = Tests.__dict__["{}_found_{}".format(self.name, self.test_index)]
|
||||
Report.critical_result(found, self.id.IsValid())
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
@property
|
||||
def is_moving_up(self):
|
||||
# Type () -> bool
|
||||
return (
|
||||
abs(self.velocity.x) < FLOAT_THRESHOLD
|
||||
and abs(self.velocity.y) < FLOAT_THRESHOLD
|
||||
and self.velocity.z > 0.0
|
||||
)
|
||||
|
||||
@property
|
||||
def is_not_moving(self):
|
||||
# Type () -> bool
|
||||
return (
|
||||
abs(self.velocity.x) < FLOAT_THRESHOLD
|
||||
and abs(self.velocity.y) < FLOAT_THRESHOLD
|
||||
and abs(self.velocity.z) < FLOAT_THRESHOLD
|
||||
)
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
# Type ([]) -> None
|
||||
if Entity.terrain_id.equal(args[0]):
|
||||
self.collision_happened = True
|
||||
|
||||
class Sphere(Entity):
|
||||
def __init__(self, name, test_index):
|
||||
Entity.__init__(self, name, test_index)
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
class Material_Test:
|
||||
def __init__(self, index):
|
||||
self.index = index
|
||||
self.sphere_list = None
|
||||
# List to hold how far the cube traveled
|
||||
self.cube_distances = []
|
||||
# List to hold wether the sphere hit the trigger and its velocities
|
||||
self.sphere_values = []
|
||||
|
||||
def verify_sphere_initial_position(self, sphere, terrain, trigger):
|
||||
# Type (Entity, Entity, Entity) -> None
|
||||
# Validates sphere is where it should be
|
||||
position_valid = terrain.position.z < sphere.position.z < trigger.position.z
|
||||
initial_position = Tests.__dict__["{}_initial_position_{}".format(sphere.name, self.index)]
|
||||
Report.critical_result(initial_position, position_valid)
|
||||
|
||||
def verify_sphere_initial_velocity(self, sphere):
|
||||
# Type (Entity) -> None
|
||||
# Validates that sphere in moving in the correct direction
|
||||
initial_velocity = Tests.__dict__["{}_initial_velocity_{}".format(sphere.name, self.index)]
|
||||
Report.critical_result(initial_velocity, not sphere.is_moving_up)
|
||||
|
||||
def verify_sphere_collision(self, sphere):
|
||||
# Type (Entity) -> None
|
||||
# Reports sphere collision, ends test if it hasn't occurred
|
||||
collision = Tests.__dict__["{}_collision_{}".format(sphere.name, self.index)]
|
||||
Report.critical_result(collision, sphere.collision_happened)
|
||||
|
||||
def verify_sphere_final_velocity(self, sphere):
|
||||
# Type (Entity) -> None
|
||||
# Validates that sphere is moving in the correct direction
|
||||
final_velocity = Tests.__dict__["{}_final_velocity_{}".format(sphere.name, self.index)]
|
||||
Report.result(final_velocity, sphere.is_moving_up or sphere.is_not_moving)
|
||||
|
||||
def verify_sphere_final_position(self, sphere, terrain):
|
||||
# Type (Entity, Entity) -> None
|
||||
# Validats that sphere is not where it shouldn't be
|
||||
final_position = Tests.__dict__["{}_final_position_{}".format(sphere.name, self.index)]
|
||||
Report.result(final_position, sphere.position.z > terrain.position.z)
|
||||
|
||||
def verify_cube_initial_position(self, cube, block):
|
||||
# Type (Entity, Entity) -> None
|
||||
# Cube initially starts at a standstill
|
||||
initial_position = Tests.__dict__["{}_initial_position_{}".format(cube.name, self.index)]
|
||||
Report.result(
|
||||
initial_position,
|
||||
cube.position.z > block.position.z and abs(cube.position.y - CUBE_Y_POSITION) < FLOAT_THRESHOLD,
|
||||
)
|
||||
|
||||
def verify_cube_initial_velocity(self, cube):
|
||||
# Type (Entity) -> None
|
||||
# Ensures that the cube starts not moving
|
||||
initial_velocity = Tests.__dict__["{}_initial_velocity_{}".format(cube.name, self.index)]
|
||||
Report.result(initial_velocity, cube.velocity.IsClose(CUBE_INITIAL_VELOCITY, 0.01))
|
||||
|
||||
def push_cubes(self, cube_list):
|
||||
# Type ([Entity]) -> None
|
||||
# Imparts a velocity into each cube in the y-direction
|
||||
for cube in cube_list:
|
||||
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", cube.id, CUBE_IMPULSE)
|
||||
|
||||
def verify_cube_final_velocity(self, cube):
|
||||
# Type (Entity) -> None
|
||||
# Ensures that cube has stopped moving
|
||||
final_velocity = Tests.__dict__["{}_final_velocity_{}".format(cube.name, self.index)]
|
||||
Report.result(final_velocity, cube.velocity.IsClose(CUBE_INITIAL_VELOCITY, 0.01))
|
||||
|
||||
def verify_cube_final_position(self, cube, block):
|
||||
# Type (Entity, Entity) -> None
|
||||
# Validates that cube is not somewhere it shouldn't be
|
||||
final_position = Tests.__dict__["{}_final_position_{}".format(cube.name, self.index)]
|
||||
Report.result(final_position, cube.position.z > block.position.z)
|
||||
|
||||
def log_values(self, entity):
|
||||
# Type (Entity) -> None
|
||||
# Logs needed values for comparison
|
||||
if isinstance(entity, Sphere):
|
||||
self.sphere_values.append([entity.velocity, entity.hit_trigger])
|
||||
else:
|
||||
self.cube_distances.append(entity.position)
|
||||
|
||||
def set_trigger(self, trigger):
|
||||
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.handler.connect(trigger.id)
|
||||
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
|
||||
def on_trigger_enter(self, args):
|
||||
for sphere in self.sphere_list:
|
||||
if sphere.id.equal(args[0]):
|
||||
sphere.hit_trigger = True
|
||||
|
||||
def modify_material_library():
|
||||
# Type () -> bool
|
||||
# Uses a Physmaterial_Editor option to modify the material library associated with this level.
|
||||
# Changes are made to maximize the in level affect.
|
||||
material_library = Physmaterial_Editor("c4044455_material_librarychangesinstantly.physmaterial")
|
||||
dynamic_friction_modified = material_library.modify_material("to_change_dynamic_friction", "DynamicFriction", 10.0)
|
||||
static_friction_modified = material_library.modify_material("to_change_static_friction", "StaticFriction", 10.0)
|
||||
friction_combine_modified = material_library.modify_material(
|
||||
"to_change_friction_combine", "FrictionCombine", "Maximum"
|
||||
)
|
||||
restitution_combine_modified = material_library.modify_material(
|
||||
"to_change_restitution_combine", "RestitutionCombine", "Maximum"
|
||||
)
|
||||
restitution_modified = material_library.modify_material("to_change_restitution", "Restitution", 1.0)
|
||||
material_deleted = material_library.delete_material("to_delete")
|
||||
material_library.save_changes()
|
||||
return (
|
||||
material_deleted
|
||||
and dynamic_friction_modified
|
||||
and static_friction_modified
|
||||
and friction_combine_modified
|
||||
and restitution_combine_modified
|
||||
and restitution_modified
|
||||
)
|
||||
|
||||
def check_sphere(sphere_values_0, sphere_values_1, index):
|
||||
# Type ([[vector3, bool]], [[vector3, bool]]) -> bool
|
||||
hit_trigger = not sphere_values_0[index][1] and sphere_values_1[index][1]
|
||||
velocity_valid = sphere_values_0[index][0].z < sphere_values_1[index][0].z
|
||||
return hit_trigger and velocity_valid
|
||||
|
||||
def check_static_friction(cube_distances_0, cube_distances_1):
|
||||
# Type ([float],[float]) -> bool
|
||||
return cube_distances_0[0].y > cube_distances_1[0].y
|
||||
|
||||
def check_dynamic_friction(cube_distances_0, cube_distances_1):
|
||||
# Type ([float],[float]) -> bool
|
||||
return cube_distances_0[1].y > cube_distances_1[1].y
|
||||
|
||||
def check_friction_combine(cube_distances_0, cube_distances_1):
|
||||
# Type ([float],[float]) -> bool
|
||||
return cube_distances_0[2].y > cube_distances_1[2].y
|
||||
|
||||
def run_test(test):
|
||||
# Type (Material_Test) -> None
|
||||
# This loop runs the test steps and logs data to the given Material_Test object
|
||||
# 1) Enter game mode
|
||||
helper.enter_game_mode(Tests.__dict__["enter_game_mode_{}".format(test.index)])
|
||||
|
||||
# 2) Find and Validate entities
|
||||
terrain = Entity("terrain", test.index)
|
||||
Entity.terrain_id = terrain.id
|
||||
block = Entity("block", test.index)
|
||||
trigger = Entity("trigger", test.index)
|
||||
sphere_0 = Sphere("sphere_0", test.index)
|
||||
sphere_1 = Sphere("sphere_1", test.index)
|
||||
sphere_2 = Sphere("sphere_2", test.index)
|
||||
sphere_list = [sphere_0, sphere_1, sphere_2]
|
||||
cube_0 = Entity("cube_0", test.index)
|
||||
cube_1 = Entity("cube_1", test.index)
|
||||
cube_2 = Entity("cube_2", test.index)
|
||||
cube_list = [cube_0, cube_1, cube_2]
|
||||
test.sphere_list = sphere_list
|
||||
test.set_trigger(trigger)
|
||||
|
||||
for sphere in sphere_list:
|
||||
test.verify_sphere_initial_position(sphere, terrain, trigger)
|
||||
test.verify_sphere_initial_velocity(sphere)
|
||||
|
||||
for cube in cube_list:
|
||||
test.verify_cube_initial_position(cube, block)
|
||||
test.verify_cube_initial_velocity(cube)
|
||||
|
||||
# 3) Wait for spheres to collide with terrain
|
||||
helper.wait_for_condition(lambda: all([sphere.collision_happened for sphere in sphere_list]), TIMEOUT)
|
||||
|
||||
# 4) Wait for spheres to enter the trigger
|
||||
helper.wait_for_condition(lambda: all([sphere.hit_trigger for sphere in sphere_list]), TIMEOUT)
|
||||
for sphere in sphere_list:
|
||||
test.log_values(sphere)
|
||||
|
||||
# 5) Log sphere results
|
||||
for sphere in sphere_list:
|
||||
test.verify_sphere_collision(sphere)
|
||||
test.verify_sphere_final_position(sphere, terrain)
|
||||
test.verify_sphere_final_velocity(sphere)
|
||||
|
||||
# 6) Push cubes
|
||||
test.push_cubes(cube_list)
|
||||
|
||||
# 7) Wait for cubes to stop moving
|
||||
helper.wait_for_condition(lambda: all([cube.is_not_moving for cube in cube_list]), TIMEOUT)
|
||||
|
||||
# 8) Log and validate cube results
|
||||
for cube in cube_list:
|
||||
test.verify_cube_final_position(cube, block)
|
||||
test.verify_cube_final_velocity(cube)
|
||||
test.log_values(cube)
|
||||
|
||||
# 9) Exit game mode
|
||||
helper.exit_game_mode(Tests.__dict__["exit_game_mode_{}".format(test.index)])
|
||||
|
||||
# Main Script
|
||||
helper.init_idle()
|
||||
# 1) Open Level
|
||||
helper.open_level("Physics", "C4044455_Material_LibraryChangesInstantly")
|
||||
|
||||
# 2) Create test objects
|
||||
test_0 = Material_Test(0)
|
||||
test_1 = Material_Test(1)
|
||||
|
||||
# 3) Run test 0
|
||||
run_test(test_0)
|
||||
|
||||
# 4) Modify material library
|
||||
Report.result(Tests.material_changes, modify_material_library())
|
||||
|
||||
# Wait for modifications to the material library to propagate.
|
||||
general.idle_wait_frames(PROPAGATION_FRAMES)
|
||||
|
||||
# 5) Run test 1
|
||||
run_test(test_1)
|
||||
|
||||
# 6) Validate results
|
||||
# Restitution Modification Successful
|
||||
Report.result(Tests.restitution, check_sphere(test_0.sphere_values, test_1.sphere_values, index=0))
|
||||
# Static Friction Modification Successful
|
||||
Report.result(Tests.static_friction, check_static_friction(test_0.cube_distances, test_1.cube_distances))
|
||||
# Dynamic Friction Modification Successful
|
||||
Report.result(Tests.dynamic_friction, check_dynamic_friction(test_0.cube_distances, test_1.cube_distances))
|
||||
# Friction Combine Modification Successful
|
||||
Report.result(Tests.friction_combine, check_friction_combine(test_0.cube_distances, test_1.cube_distances))
|
||||
# Restitution Combine Modification Successful
|
||||
Report.result(Tests.restitution_combine, check_sphere(test_0.sphere_values, test_1.sphere_values, index=1))
|
||||
# Material Delete Successful
|
||||
Report.result(Tests.delete_material, check_sphere(test_0.sphere_values, test_1.sphere_values, index=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryChangesReflectInstantly)
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
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 : C15096740
|
||||
Test Case Title : Verify that clearing a material library on all systems that use it,
|
||||
assigns the default material library
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
create_entity = ("Entity created successfully", "Failed to create Entity")
|
||||
add_physx_component = ("PhysX Component added successfully", "Failed to add PhysX Component")
|
||||
override_default_library = ("Material library overrided successfully", "Failed to override material library")
|
||||
update_to_default_library = ("Library updated to default", "Failed to update library to default")
|
||||
new_library_updated = ("New library updated successfully", "Failed to add new library")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryClearingAssignsDefault():
|
||||
|
||||
"""
|
||||
Summary:
|
||||
Load level with Entity having PhysX Component. Override the material library to be the same one as the
|
||||
default material library. Change the default material library into another one.
|
||||
|
||||
Expected Behavior:
|
||||
The material library gets updated correctly when the default material is changed.
|
||||
|
||||
Test Steps:
|
||||
1) Load the level
|
||||
2) Create new Entity with PhysX Character Controller
|
||||
3) Override the material library to be the same one as the default material library
|
||||
4) Switch it back again to the default material library.
|
||||
5) Change the default material library into another one.
|
||||
6) 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
|
||||
"""
|
||||
|
||||
# Built-in Imports
|
||||
import os
|
||||
|
||||
|
||||
# Helper file Imports
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
|
||||
# Open 3D Engine Imports
|
||||
import azlmbr.asset as azasset
|
||||
|
||||
# Constants
|
||||
library_property_path = "Configuration|Physics Material|Library"
|
||||
|
||||
default_material_path = os.path.join("assets", "physics", "surfacetypemateriallibrary.physmaterial")
|
||||
new_material_path = os.path.join("physicssurfaces", "default_phys_materials.physmaterial")
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Load the level
|
||||
helper.open_level("Physics", "Base")
|
||||
|
||||
# 2) Create new Entity with PhysX Character Controller
|
||||
test_entity = EditorEntity.create_editor_entity("TestEntity")
|
||||
Report.result(Tests.create_entity, test_entity.id.IsValid())
|
||||
|
||||
test_component = test_entity.add_component("PhysX Character Controller")
|
||||
Report.result(Tests.add_physx_component, test_entity.has_component("PhysX Character Controller"))
|
||||
|
||||
# 3) Override the material library to be the same one as the default material library
|
||||
default_asset = Asset.find_asset_by_path(default_material_path)
|
||||
test_component.set_component_property_value(library_property_path, default_asset.id)
|
||||
default_asset.id = test_component.get_component_property_value(library_property_path)
|
||||
Report.result(Tests.override_default_library, default_asset.get_path() == default_material_path.replace(os.sep, '/'))
|
||||
|
||||
# 4) Switch it back again to the default material library.
|
||||
test_component.set_component_property_value(library_property_path, azasset.AssetId())
|
||||
Report.result(
|
||||
Tests.update_to_default_library,
|
||||
test_component.get_component_property_value(library_property_path) == azasset.AssetId(),
|
||||
)
|
||||
|
||||
# 5) Change the default material library into another one.
|
||||
new_asset = Asset.find_asset_by_path(new_material_path)
|
||||
test_component.set_component_property_value(library_property_path, new_asset.id)
|
||||
new_asset.id = test_component.get_component_property_value(library_property_path)
|
||||
Report.result(Tests.new_library_updated, new_asset.get_path() == new_material_path.replace(os.sep, '/'))
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryClearingAssignsDefault)
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
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 : C15563573
|
||||
# Test Case Title : Check that any change (Add/Delete/Modify) made to the material surface in the material library reflects immediately in the PhysX Character Controller
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
|
||||
find_default_controller_0 = ("Test 0) The default controller entity was found", "Test 0) The default controller entity was not found")
|
||||
find_modified_controller_0 = ("Test 0) The modified controller entity was found", "Test 0) The modified controller entity was not found")
|
||||
find_on_default_box_0 = ("Test 0) Box on default was found", "Test 0) Box on default was not found")
|
||||
find_on_modified_box_0 = ("Test 0) Box on modified was found", "Test 0) Box on modified was not found")
|
||||
boxes_moved_0 = ("Test 0) All boxes moved", "Test 0) Boxes failed to move")
|
||||
boxes_at_rest_0 = ("Test 0) All boxes came to rest", "Test 0) Boxes failed to come to rest")
|
||||
exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
|
||||
default_less_than_modified = ("Test 0) Modified box traveled farther than default", "Test 0) Modified box did not travel farther than default")
|
||||
|
||||
enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
|
||||
find_default_controller_1 = ("Test 1) The default controller entity was found", "Test 1) The default controller entity was not found")
|
||||
find_modified_controller_1 = ("Test 1) The modified controller entity was found", "Test 1) The modified controller entity was not found")
|
||||
find_on_default_box_1 = ("Test 1) Box on default was found", "Test 1) Box on default was not found")
|
||||
find_on_modified_box_1 = ("Test 1) Box on modified was found", "Test 1) Box on modified was not found")
|
||||
boxes_moved_1 = ("Test 1) All boxes moved", "Test 1) Boxes failed to move")
|
||||
boxes_at_rest_1 = ("Test 1) All boxes came to rest", "Test 1) Boxes failed to come to rest")
|
||||
exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
|
||||
modified_less_than_default = ("Test 1) Modified box traveled less than default", "Test 1) Modified box traveled farther than default")
|
||||
|
||||
enter_game_mode_2 = ("Test 2) Entered game mode", "Test 2) Failed to enter game mode")
|
||||
find_default_controller_2 = ("Test 2) The default controller entity was found", "Test 2) The default controller entity was not found")
|
||||
find_modified_controller_2 = ("Test 2) The modified controller entity was found", "Test 2) The modified controller entity was not found")
|
||||
find_on_default_box_2 = ("Test 2) Box on default was found", "Test 2) Box on default was not found")
|
||||
find_on_modified_box_2 = ("Test 2) Box on modified was found", "Test 2) Box on modified was not found")
|
||||
boxes_moved_2 = ("Test 2) All boxes moved", "Test 2) Boxes failed to move")
|
||||
boxes_at_rest_2 = ("Test 2) All boxes came to rest", "Test 2) Boxes failed to come to rest")
|
||||
exit_game_mode_2 = ("Test 2) Exited game mode", "Test 2) Failed to exit game mode")
|
||||
default_equals_modified = ("Test 2) Modified and Default boxes traveled the same distance", "Test 2) Modified and Default boxes did not travel the same distance")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryCrudOperationsReflectOnCharacterController():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that any change (Add/Delete/Modify) made to the material surface in the material
|
||||
library reflects immediately in the PhysX Character Controller
|
||||
|
||||
Level Description:
|
||||
There are two groups of entities, one for "modified" and one for "default".
|
||||
Each group has two entities:
|
||||
one box, with PhysX Rigid Body and PhysX Box Collider
|
||||
one character controller, with PhysX Character Controller - configured as a box shape
|
||||
|
||||
The box entity for each group sits on top of its respective character controller entity. The boxes are identical and
|
||||
have the default physX material assigned.
|
||||
|
||||
The character controller "default_controller" is assigned the default physx material.
|
||||
A new material library was created with 1 material, called "Modified", this is assigned to "modified_controller"
|
||||
dynamic friction: 0.25
|
||||
static friction: 0.5
|
||||
restitution: 0.5
|
||||
|
||||
Expected behavior:
|
||||
For every iteration this test applies a force impulse in the X direction to each box. The boxes save their traveled
|
||||
distances each iteration, to verify different behavior between each setup.
|
||||
|
||||
First the test verifies the two controllers are assigned differing materials, without changing anything. With a
|
||||
lower dynamic friction coefficient, the box 'on_modified' should travel a longer distance than 'on_default'
|
||||
|
||||
Next, the test modifies the dynamic friction value for 'modified_controller' (from 0.25 to 0.75). 'on_modified'
|
||||
should travel a shorter distance than it did in the previous test, and less than 'default'
|
||||
|
||||
Finally, we delete the 'modified' material entirely. The box 'on_modified' should then behave as 'on_default' box,
|
||||
and travel the same distance.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Collect basis values without modifying anything
|
||||
2.1) Enter game mode
|
||||
2.2) Find entities
|
||||
2.3) Push the boxes and wait for them to come to rest
|
||||
2.4) Exit game mode
|
||||
3) Modify the dynamic friction value of 'modified'
|
||||
3.1 - 3.4) <same as above>
|
||||
4) Delete 'modified's' material
|
||||
4.1 - 4.4) <same as above>
|
||||
5) Close editor
|
||||
|
||||
Notes:
|
||||
- As of 20/02/2020, we do not have any capabilities to automate the UI part of the test case. Nor can we 'Add' any
|
||||
new mesh surface in a material library by modifying the ".physmaterial" file as it requires a UUID. Hence, in order
|
||||
to validate that the modification/deletion of mesh surfaces from material library are reflected in the allocated
|
||||
material in Character Controller, we will verify the change in behaviour of the Character Controller occurring due
|
||||
to change in mesh surfaces, during the game mode.
|
||||
|
||||
|
||||
- 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.math as lymath
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
from AddModifyDelete_Utils import Box
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
TIMEOUT = 3.0
|
||||
DISTANCE_TOLERANCE = 0.001
|
||||
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name]
|
||||
|
||||
def run_test(test_number):
|
||||
# x.1) Enter game mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
|
||||
|
||||
# x.2) Find entities
|
||||
default_controller_id = general.find_game_entity("default_controller")
|
||||
modified_controller_id = general.find_game_entity("modified_controller")
|
||||
Report.result(get_test("find_default_controller_{}".format(test_number)), default_controller_id.IsValid())
|
||||
Report.result(get_test("find_modified_controller_{}".format(test_number)), modified_controller_id.IsValid())
|
||||
Report.result(get_test("find_on_default_box_{}".format(test_number)), default_box.find())
|
||||
Report.result(get_test("find_on_modified_box_{}".format(test_number)), modified_box.find())
|
||||
|
||||
# x.3) Push the boxes and wait for them to come to rest
|
||||
default_box.push(FORCE_IMPULSE)
|
||||
modified_box.push(FORCE_IMPULSE)
|
||||
|
||||
def boxes_are_moving():
|
||||
return not default_box.is_stationary() and not modified_box.is_stationary()
|
||||
|
||||
def boxes_are_stationary():
|
||||
return default_box.is_stationary() and modified_box.is_stationary()
|
||||
|
||||
Report.result(
|
||||
get_test("boxes_moved_{}".format(test_number)),
|
||||
helper.wait_for_condition(boxes_are_moving, TIMEOUT),
|
||||
)
|
||||
Report.result(
|
||||
get_test("boxes_at_rest_{}".format(test_number)),
|
||||
helper.wait_for_condition(boxes_are_stationary, TIMEOUT),
|
||||
)
|
||||
|
||||
default_box.distances.append(default_box.position.GetDistance(default_box.start_position))
|
||||
modified_box.distances.append(modified_box.position.GetDistance(modified_box.start_position))
|
||||
|
||||
# x.4) Exit game mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_LibraryCrudOperationsReflectOnCharacterController")
|
||||
|
||||
# Setup persisting entities
|
||||
default_box = Box("on_default")
|
||||
modified_box = Box("on_modified")
|
||||
|
||||
# 2) Collect basis values without modifying anything
|
||||
run_test(0)
|
||||
# While sitting on a character controller with friction of 0.25, 'on_modified' should travel farther than 'default'
|
||||
Report.result(Tests.default_less_than_modified, default_box.distances[0] < modified_box.distances[0])
|
||||
|
||||
# 3) Modify the dynamic friction value of 'modified'
|
||||
material_editor = Physmaterial_Editor("c15563573_material_addmodifydeleteoncharactercontroller.physmaterial")
|
||||
material_editor.modify_material("Modified", "DynamicFriction", 0.75)
|
||||
material_editor.save_changes()
|
||||
run_test(1)
|
||||
# With greater friction, 'on_modified' should now travel a shorter distance than it did in the previous test.
|
||||
Report.result(Tests.modified_less_than_default, default_box.distances[0] > modified_box.distances[1])
|
||||
|
||||
# 4) Delete 'modified's material
|
||||
material_editor.delete_material("Modified")
|
||||
material_editor.save_changes()
|
||||
run_test(2)
|
||||
Report.result(
|
||||
Tests.default_equals_modified,
|
||||
lymath.Math_IsClose(default_box.distances[2], modified_box.distances[2], DISTANCE_TOLERANCE),
|
||||
)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryCrudOperationsReflectOnCharacterController)
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
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 : C4888315
|
||||
# Test Case Title : Check that any change (Add/Delete/Modify) made to the material surface in the material library reflects immediately in the PhysX Collider component
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
|
||||
find_terrain_0 = ("Test 0) The Terrain was found", "Test 0) The Terrain was not found")
|
||||
find_default_box_0 = ("Test 0) Default box was found", "Test 0) Default box was not found")
|
||||
find_modified_box_0 = ("Test 0) Modified box was found", "Test 0) Modified box was not found")
|
||||
boxes_moved_0 = ("Test 0) All boxes moved", "Test 0) Boxes failed to move")
|
||||
boxes_at_rest_0 = ("Test 0) All boxes came to rest", "Test 0) Boxes failed to come to rest")
|
||||
exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
|
||||
default_less_than_modified = ("Test 0) Modified box traveled farther than default", "Test 0) Modified box did not travel farther than default")
|
||||
|
||||
enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
|
||||
find_terrain_1 = ("Test 1) The Terrain was found", "Test 1) The Terrain was not found")
|
||||
find_default_box_1 = ("Test 1) Default box was found", "Test 1) Default box was not found")
|
||||
find_modified_box_1 = ("Test 1) Modified box was found", "Test 1) Modified box was not found")
|
||||
boxes_moved_1 = ("Test 1) All boxes moved", "Test 1) Boxes failed to move")
|
||||
boxes_at_rest_1 = ("Test 1) All boxes came to rest", "Test 1) Boxes failed to come to rest")
|
||||
exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
|
||||
modified_less_than_previous = ("Test 1) Modified box traveled less than previous", "Test 1) Modified box traveled further than previous")
|
||||
|
||||
enter_game_mode_2 = ("Test 2) Entered game mode", "Test 2) Failed to enter game mode")
|
||||
find_terrain_2 = ("Test 2) The Terrain was found", "Test 2) The Terrain was not found")
|
||||
find_default_box_2 = ("Test 2) Default box was found", "Test 2) Default box was not found")
|
||||
find_modified_box_2 = ("Test 2) Modified box was found", "Test 2) Modified box was not found")
|
||||
boxes_moved_2 = ("Test 2) All boxes moved", "Test 2) Boxes failed to move")
|
||||
boxes_at_rest_2 = ("Test 2) All boxes came to rest", "Test 2) Boxes failed to come to rest")
|
||||
exit_game_mode_2 = ("Test 2) Exited game mode", "Test 2) Failed to exit game mode")
|
||||
default_equals_modified = ("Test 2) Modified and Default boxes traveled the same distance", "Test 2) Modified and Default boxes did not travel the same distance")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryCrudOperationsReflectOnCollider():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that any change (Add/Delete/Modify) made to the material surface in the material
|
||||
library reflects immediately in the PhysX Collider component
|
||||
|
||||
Level Description:
|
||||
Two boxes ("default" and "modified") sit on the terrain. The boxes are identical, save for their physX material.
|
||||
The box "default" is assigned the default physx material.
|
||||
A new material library was created with 1 material, called "Modified", this is assigned to the "modified" box:
|
||||
dynamic friction: 0.25
|
||||
static friction: 0.5
|
||||
restitution: 0.5
|
||||
|
||||
Expected behavior:
|
||||
For every iteration this test applies a force impulse in the X direction. The boxes save their traveled distances
|
||||
each iteration, to verify different behavior between each setup.
|
||||
|
||||
First the test verifies the two entities are assigned differing materials, without changing anything. With a lower
|
||||
dynamic friction coefficient, the 'modified' should travel a longer distance than 'default'
|
||||
|
||||
Next, the test modifies the dynamic friction value for 'modified' (from 0.25 to 0.75). 'modified' should travel a
|
||||
shorter distance than it did in the previous test.
|
||||
|
||||
Finally, we delete the 'modified' material entirely. The 'modified' box should then behave as the 'default' box, and
|
||||
travel the same distance.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Collect basis values without modifying anything
|
||||
2.1) Enter game mode
|
||||
2.2) Find entities
|
||||
2.3) Push the boxes and wait for them to come to rest
|
||||
2.4) Exit game mode
|
||||
3) Modify the dynamic friction value of 'modified'
|
||||
3.1 - 3.4) <same as above>
|
||||
4) Delete 'modified's' material
|
||||
4.1 - 4.4) <same as above>
|
||||
5) Close 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.math as lymath
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
from AddModifyDelete_Utils import Box
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
TIMEOUT = 3.0
|
||||
DISTANCE_TOLERANCE = 0.001
|
||||
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name]
|
||||
|
||||
def run_test(test_number):
|
||||
# x.1) Enter game mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
|
||||
|
||||
# x.2) Find entities
|
||||
Report.result(get_test("find_terrain_{}".format(test_number)), general.find_game_entity("terrain").IsValid())
|
||||
Report.result(get_test("find_default_box_{}".format(test_number)), default_box.find())
|
||||
Report.result(get_test("find_modified_box_{}".format(test_number)), modified_box.find())
|
||||
|
||||
# x.3) Push the boxes and wait for them to come to rest
|
||||
default_box.push(FORCE_IMPULSE)
|
||||
modified_box.push(FORCE_IMPULSE)
|
||||
|
||||
def boxes_are_moving():
|
||||
return not default_box.is_stationary() and not modified_box.is_stationary()
|
||||
|
||||
def boxes_are_stationary():
|
||||
return default_box.is_stationary() and modified_box.is_stationary()
|
||||
|
||||
Report.result(
|
||||
get_test("boxes_moved_{}".format(test_number)),
|
||||
helper.wait_for_condition(boxes_are_moving, TIMEOUT),
|
||||
)
|
||||
Report.result(
|
||||
get_test("boxes_at_rest_{}".format(test_number)),
|
||||
helper.wait_for_condition(boxes_are_stationary, TIMEOUT),
|
||||
)
|
||||
|
||||
default_box.distances.append(default_box.position.GetDistance(default_box.start_position))
|
||||
modified_box.distances.append(modified_box.position.GetDistance(modified_box.start_position))
|
||||
|
||||
# x.4) Exit game mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_LibraryCrudOperationsReflectOnCollider")
|
||||
|
||||
# Setup persisting entities
|
||||
default_box = Box("default")
|
||||
modified_box = Box("modified")
|
||||
|
||||
# 2) Collect basis values without modifying anything
|
||||
run_test(0)
|
||||
# With a friction of 0.25, 'modified' should travel farther than 'default'
|
||||
Report.result(Tests.default_less_than_modified, default_box.distances[0] < modified_box.distances[0])
|
||||
|
||||
# 3) Modify the dynamic friction value of 'modified'
|
||||
material_editor = Physmaterial_Editor("c4888315_material_addmodifydeleteoncollider.physmaterial")
|
||||
material_editor.modify_material("Modified", "DynamicFriction", 0.75)
|
||||
material_editor.save_changes()
|
||||
run_test(1)
|
||||
# With greater friction, 'modified' should now travel a shorter distance than it did in the previous test.
|
||||
Report.result(Tests.modified_less_than_previous, modified_box.distances[0] > modified_box.distances[1])
|
||||
|
||||
# 4) Delete 'modified's material
|
||||
material_editor.delete_material("Modified")
|
||||
material_editor.save_changes()
|
||||
run_test(2)
|
||||
Report.result(
|
||||
Tests.default_equals_modified,
|
||||
lymath.Math_IsClose(default_box.distances[2], modified_box.distances[2], DISTANCE_TOLERANCE),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryCrudOperationsReflectOnCollider)
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
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 : C4925582
|
||||
# Test Case Title : Check that any change (Add/Delete/Modify) made to the material surface in the material library reflects immediately in the ragdoll bones
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
|
||||
find_terrain_0 = ("Test 0) The Terrain was found", "Test 0) The Terrain was not found")
|
||||
find_default_ragdoll_0 = ("Test 0) Default ragdoll was found", "Test 0) Default ragdoll was not found")
|
||||
find_modified_ragdoll_0 = ("Test 0) Modified ragdoll was found", "Test 0) Modified ragdoll was not found")
|
||||
default_ragdoll_bounced_0 = ("Test 0) Default ragdoll bounced", "Test 0) Default ragdoll did not bounce")
|
||||
modified_ragdoll_bounced_0 = ("Test 0) Modified ragdoll bounced", "Test 0) Modified ragdoll did not bounce")
|
||||
exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
|
||||
modified_less_than_default = ("Test 0) Modified ragdoll's bounce height was shorter than default", "Test 0) Modified ragdoll's bounce height was greater than default")
|
||||
|
||||
enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
|
||||
find_terrain_1 = ("Test 1) The Terrain was found", "Test 1) The Terrain was not found")
|
||||
find_default_ragdoll_1 = ("Test 1) Default ragdoll was found", "Test 1) Default ragdoll was not found")
|
||||
find_modified_ragdoll_1 = ("Test 1) Modified ragdoll was found", "Test 1) Modified ragdoll was not found")
|
||||
default_ragdoll_bounced_1 = ("Test 1) Default ragdoll bounced", "Test 1) Default ragdoll did not bounce")
|
||||
modified_ragdoll_bounced_1 = ("Test 1) Modified ragdoll bounced", "Test 1) Modified ragdoll did not bounce")
|
||||
exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
|
||||
modified_greater_than_default = ("Test 1) Modified ragdoll's bounce height was higher than default's", "Test 1) Modified ragdoll's bounce height was not higher than default's")
|
||||
|
||||
enter_game_mode_2 = ("Test 2) Entered game mode", "Test 2) Failed to enter game mode")
|
||||
find_terrain_2 = ("Test 2) The Terrain was found", "Test 2) The Terrain was not found")
|
||||
find_default_ragdoll_2 = ("Test 2) Default ragdoll was found", "Test 2) Default ragdoll was not found")
|
||||
find_modified_ragdoll_2 = ("Test 2) Modified ragdoll was found", "Test 2) Modified ragdoll was not found")
|
||||
default_ragdoll_bounced_2 = ("Test 2) Default ragdoll bounced", "Test 2) Default ragdoll did not bounce")
|
||||
modified_ragdoll_bounced_2 = ("Test 2) Modified ragdoll bounced", "Test 2) Modified ragdoll did not bounce")
|
||||
exit_game_mode_2 = ("Test 2) Exited game mode", "Test 2) Failed to exit game mode")
|
||||
default_equals_modified = ("Test 2) Modified and default ragdoll's bounce height were equal", "Test 2) Modified and default ragdoll's bounce height were not equal")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryCrudOperationsReflectOnRagdollBones():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that any change (Add/Delete/Modify) made to the material surface in the material
|
||||
library reflects immediately in the ragdoll bones
|
||||
|
||||
Level Description:
|
||||
Two ragdolls ("default_ragdoll" and "modified_ragdoll") sit above a terrain. The ragdolls are identical, save for
|
||||
their physX material.
|
||||
|
||||
The ragdoll "default_ragdoll" is assigned the default physx material.
|
||||
A new material library was created with 1 material, called "Modified", this is assigned to "modified_ragdoll":
|
||||
dynamic friction: 0.5
|
||||
static friction: 0.5
|
||||
restitution: 0.25
|
||||
|
||||
Expected behavior:
|
||||
For every iteration this test measures the bounce height of each entity. The ragdolls save their traveled distances
|
||||
each iteration, to verify different behavior between each setup.
|
||||
|
||||
First the test verifies the two entities are assigned differing materials, without changing anything. With a lower
|
||||
restitution value, the 'modified' should bounce much lower than 'default'
|
||||
|
||||
Next, the test modifies the restitution value for 'modified' (from 0.25 to 0.75). 'modified' should bounce height
|
||||
than it did in the previous test, and greater than default.
|
||||
|
||||
Finally, we delete the 'modified' material entirely. 'modified_ragdoll' should then behave the same as
|
||||
'default_ragdoll' box, and bounce the same distance.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Collect basis values without modifying anything
|
||||
2.1) Enter game mode
|
||||
2.2) Find entities
|
||||
2.3) Wait for entities to bounce
|
||||
2.4) Exit game mode
|
||||
3) Modify the restitution value of 'modified'
|
||||
3.1 - 3.4) <same as above>
|
||||
4) Delete 'modified_ragdoll's material
|
||||
4.1 - 4.4) <same as above>
|
||||
5) Close 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 as bus
|
||||
import azlmbr.components
|
||||
import azlmbr.physics
|
||||
import azlmbr.math as lymath
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
TIMEOUT = 3.0
|
||||
BOUNCE_TOLERANCE = 0.05
|
||||
|
||||
class Ragdoll:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.bounces = []
|
||||
|
||||
def find_and_reset(self):
|
||||
self.hit_terrain_position = None
|
||||
self.hit_terrain = False
|
||||
self.max_bounce = 0.0
|
||||
self.reached_max_bounce = False
|
||||
self.id = general.find_game_entity(self.name)
|
||||
return self.id.IsValid()
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name]
|
||||
|
||||
def run_test(test_number):
|
||||
# x.1) Enter game mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
|
||||
|
||||
# x.2) Find entities
|
||||
terrain_id = general.find_game_entity("terrain")
|
||||
Report.result(get_test("find_terrain_{}".format(test_number)), terrain_id.IsValid())
|
||||
Report.result(get_test("find_default_ragdoll_{}".format(test_number)), default_ragdoll.find_and_reset())
|
||||
Report.result(get_test("find_modified_ragdoll_{}".format(test_number)), modified_ragdoll.find_and_reset())
|
||||
|
||||
def on_collision_enter(args):
|
||||
entering = args[0]
|
||||
for ragdoll in ragdolls:
|
||||
if ragdoll.id.Equal(entering):
|
||||
if not ragdoll.hit_terrain:
|
||||
ragdoll.hit_terrain_position = ragdoll.position
|
||||
ragdoll.hit_terrain = True
|
||||
|
||||
handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
handler.connect(terrain_id)
|
||||
handler.add_callback("OnCollisionBegin", on_collision_enter)
|
||||
|
||||
def wait_for_bounce():
|
||||
for ragdoll in ragdolls:
|
||||
if ragdoll.hit_terrain:
|
||||
current_bounce_height = ragdoll.position.z - ragdoll.hit_terrain_position.z
|
||||
if current_bounce_height >= ragdoll.max_bounce:
|
||||
ragdoll.max_bounce = current_bounce_height
|
||||
elif ragdoll.max_bounce > 0.0:
|
||||
ragdoll.reached_max_bounce = True
|
||||
return default_ragdoll.reached_max_bounce and modified_ragdoll.reached_max_bounce
|
||||
|
||||
# x.3) Wait for entities to bounce
|
||||
helper.wait_for_condition(wait_for_bounce, TIMEOUT)
|
||||
Report.result(get_test("default_ragdoll_bounced_{}".format(test_number)), default_ragdoll.reached_max_bounce)
|
||||
Report.result(get_test("modified_ragdoll_bounced_{}".format(test_number)), modified_ragdoll.reached_max_bounce)
|
||||
|
||||
for ragdoll in ragdolls:
|
||||
ragdoll.bounces.append(ragdoll.max_bounce)
|
||||
|
||||
# x.4) Exit game mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_LibraryCrudOperationsReflectOnRagdollBones")
|
||||
|
||||
# Setup persisting entities
|
||||
default_ragdoll = Ragdoll("default")
|
||||
modified_ragdoll = Ragdoll("modified")
|
||||
ragdolls = [default_ragdoll, modified_ragdoll]
|
||||
|
||||
# 2) Collect basis values without modifying anything
|
||||
run_test(0)
|
||||
Report.result(Tests.modified_less_than_default, default_ragdoll.bounces[0] > modified_ragdoll.bounces[0])
|
||||
|
||||
# 3) Modify the restitution value of 'modified'
|
||||
material_editor = Physmaterial_Editor("ragdollbones.physmaterial")
|
||||
material_editor.modify_material("Modified", "Restitution", 0.75)
|
||||
material_editor.save_changes()
|
||||
run_test(1)
|
||||
Report.result(Tests.modified_greater_than_default, default_ragdoll.bounces[0] < modified_ragdoll.bounces[1])
|
||||
|
||||
# 4) Delete 'modified's material
|
||||
material_editor.delete_material("Modified")
|
||||
material_editor.save_changes()
|
||||
run_test(2)
|
||||
Report.result(
|
||||
Tests.default_equals_modified,
|
||||
lymath.Math_IsClose(default_ragdoll.bounces[2], modified_ragdoll.bounces[2], BOUNCE_TOLERANCE),
|
||||
)
|
||||
|
||||
Report.info("Default hit terrain: " + str(default_ragdoll.hit_terrain))
|
||||
Report.info("Modified hit terrain: " + str(modified_ragdoll.hit_terrain))
|
||||
|
||||
Report.info("Default max bounce: " + str(default_ragdoll.reached_max_bounce))
|
||||
Report.info("Modified max bouce: " + str(modified_ragdoll.reached_max_bounce))
|
||||
|
||||
Report.info("Default max bounce: " + str(default_ragdoll.bounces[0]))
|
||||
Report.info("Modified max bouce: " + str(modified_ragdoll.bounces[0]))
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryCrudOperationsReflectOnRagdollBones)
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
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 : C4925579
|
||||
# Test Case Title : Check that any change (Add/Delete/Modify) made to the material surface in the material library reflects immediately in the PhysX Terrain layers
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode_0 = ("Test 0) Entered game mode", "Test 0) Failed to enter game mode")
|
||||
find_terrain_0 = ("Test 0) The Terrain was found", "Test 0) The Terrain was not found")
|
||||
find_on_default_box_0 = ("Test 0) Box on default was found", "Test 0) Box on default was not found")
|
||||
find_on_modified_box_0 = ("Test 0) Box on modified was found", "Test 0) Box on modified was not found")
|
||||
boxes_moved_0 = ("Test 0) All boxes moved", "Test 0) Boxes failed to move")
|
||||
boxes_at_rest_0 = ("Test 0) All boxes came to rest", "Test 0) Boxes failed to come to rest")
|
||||
exit_game_mode_0 = ("Test 0) Exited game mode", "Test 0) Failed to exit game mode")
|
||||
on_default_less_than_on_modified = ("Test 0) Box on modified traveled farther than default", "Test 0) Box on modified did not travel farther than default")
|
||||
|
||||
enter_game_mode_1 = ("Test 1) Entered game mode", "Test 1) Failed to enter game mode")
|
||||
find_terrain_1 = ("Test 1) The Terrain was found", "Test 1) The Terrain was not found")
|
||||
find_on_default_box_1 = ("Test 1) Box on default was found", "Test 1) Box on default was not found")
|
||||
find_on_modified_box_1 = ("Test 1) Box on modified was found", "Test 1) Box on modified was not found")
|
||||
boxes_moved_1 = ("Test 1) All boxes moved", "Test 1) Boxes failed to move")
|
||||
boxes_at_rest_1 = ("Test 1) All boxes came to rest", "Test 1) Boxes failed to come to rest")
|
||||
exit_game_mode_1 = ("Test 1) Exited game mode", "Test 1) Failed to exit game mode")
|
||||
on_modified_less_than_previous = ("Test 1) Box on modified traveled less than previous", "Test 1) Box on modified traveled further than previous")
|
||||
|
||||
enter_game_mode_2 = ("Test 2) Entered game mode", "Test 2) Failed to enter game mode")
|
||||
find_terrain_2 = ("Test 2) The Terrain was found", "Test 2) The Terrain was not found")
|
||||
find_on_default_box_2 = ("Test 2) Box on default was found", "Test 2) Box on default was not found")
|
||||
find_on_modified_box_2 = ("Test 2) Box on modified was found", "Test 2) Box on modified was not found")
|
||||
boxes_moved_2 = ("Test 2) All boxes moved", "Test 2) Boxes failed to move")
|
||||
boxes_at_rest_2 = ("Test 2) All boxes came to rest", "Test 2) Boxes failed to come to rest")
|
||||
exit_game_mode_2 = ("Test 2) Exited game mode", "Test 2) Failed to exit game mode")
|
||||
on_default_equals_on_modified = ("Test 2) The boxes on modified and default traveled the same distance", "Test 2) The boxes on modified and default did not travel the same distance")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryCrudOperationsReflectOnTerrain():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that any change (Add/Delete/Modify) made to the material surface in the material
|
||||
library reflects immediately in the PhysX Terrain layer component
|
||||
|
||||
Level Description:
|
||||
Two boxes ("on_default" and "on_modified") sit on a terrain.
|
||||
|
||||
The box "on_default" is placed on the terrain where the painted layer is the default physx material.
|
||||
A new material library was created with 1 material, called "Modified", this is painted on the terrain beneath "on_modified"
|
||||
dynamic friction: 0.25
|
||||
static friction: 0.5
|
||||
restitution: 0.5
|
||||
|
||||
Expected behavior:
|
||||
For every iteration this test applies a force impulse in the X direction. The boxes save their traveled distances
|
||||
each iteration, to verify different behavior between each setup.
|
||||
|
||||
First the test verifies the two entities sit upon differing materials, without changing anything. With a lower
|
||||
dynamic friction coefficient, the box 'on_modified' should travel a longer distance than 'on_default'
|
||||
|
||||
Next, the test modifies the dynamic friction value for 'modified' (from 0.25 to 0.75). 'on_modified' should travel a
|
||||
shorter distance than it did in the previous test.
|
||||
|
||||
Finally, we delete the 'modified' material entirely. The 'on_modified' box should then behave as the 'on_default'
|
||||
box, and travel the same distance.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Collect basis values without modifying anything
|
||||
2.1) Enter game mode
|
||||
2.2) Find entities
|
||||
2.3) Push the boxes and wait for them to come to rest
|
||||
2.4) Exit game mode
|
||||
3) Modify the dynamic friction value of 'modified'
|
||||
3.1 - 3.4) <same as above>
|
||||
4) Delete 'on_modified's material
|
||||
4.1 - 4.4) <same as above>
|
||||
5) Close 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.math as lymath
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
from AddModifyDelete_Utils import Box
|
||||
|
||||
FORCE_IMPULSE = lymath.Vector3(5.0, 0.0, 0.0)
|
||||
TIMEOUT = 3.0
|
||||
DISTANCE_TOLERANCE = 0.001
|
||||
|
||||
def get_test(test_name):
|
||||
return Tests.__dict__[test_name]
|
||||
|
||||
def run_test(test_number):
|
||||
|
||||
# x.1) Enter game mode
|
||||
helper.enter_game_mode(get_test("enter_game_mode_{}".format(test_number)))
|
||||
|
||||
# x.2) Find entities
|
||||
Report.result(get_test("find_terrain_{}".format(test_number)), general.find_game_entity("terrain").IsValid())
|
||||
Report.result(get_test("find_on_default_box_{}".format(test_number)), default_box.find())
|
||||
Report.result(get_test("find_on_modified_box_{}".format(test_number)), modified_box.find())
|
||||
|
||||
# x.3) Push the boxes and wait for them to come to rest
|
||||
default_box.push(FORCE_IMPULSE)
|
||||
modified_box.push(FORCE_IMPULSE)
|
||||
|
||||
def boxes_are_moving():
|
||||
return not default_box.is_stationary() and not modified_box.is_stationary()
|
||||
|
||||
def boxes_are_stationary():
|
||||
return default_box.is_stationary() and modified_box.is_stationary()
|
||||
|
||||
Report.result(
|
||||
get_test("boxes_moved_{}".format(test_number)), helper.wait_for_condition(boxes_are_moving, TIMEOUT),
|
||||
)
|
||||
Report.result(
|
||||
get_test("boxes_at_rest_{}".format(test_number)), helper.wait_for_condition(boxes_are_stationary, TIMEOUT),
|
||||
)
|
||||
|
||||
default_box.distances.append(default_box.position.GetDistance(default_box.start_position))
|
||||
modified_box.distances.append(modified_box.position.GetDistance(modified_box.start_position))
|
||||
|
||||
# x.4) Exit game mode
|
||||
helper.exit_game_mode(get_test("exit_game_mode_{}".format(test_number)))
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_LibraryCrudOperationsReflectOnTerrain")
|
||||
|
||||
# Setup persisting entities
|
||||
default_box = Box("on_default")
|
||||
modified_box = Box("on_modified")
|
||||
|
||||
# 2) Collect basis values without modifying anything
|
||||
run_test(0)
|
||||
# While sitting on a terrain with friction of 0.25, 'on_modified' should travel farther than 'default'
|
||||
Report.result(Tests.on_default_less_than_on_modified, default_box.distances[0] < modified_box.distances[0])
|
||||
|
||||
# 3) Modify the dynamic friction value of 'modified'
|
||||
material_editor = Physmaterial_Editor("c4925579_material_addmodifydeleteonterrain.physmaterial")
|
||||
material_editor.modify_material("Modified", "DynamicFriction", 0.75)
|
||||
material_editor.save_changes()
|
||||
run_test(1)
|
||||
# With greater friction, 'on_modified' should now travel a shorter distance than it did in the previous test.
|
||||
Report.result(Tests.on_modified_less_than_previous, modified_box.distances[0] > modified_box.distances[1])
|
||||
|
||||
# 4) Delete 'modified's material
|
||||
material_editor.delete_material("Modified")
|
||||
material_editor.save_changes()
|
||||
run_test(2)
|
||||
Report.result(
|
||||
Tests.on_default_equals_on_modified,
|
||||
lymath.Math_IsClose(default_box.distances[2], modified_box.distances[2], DISTANCE_TOLERANCE),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryCrudOperationsReflectOnTerrain)
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
"""
|
||||
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 : C15425935
|
||||
# Test Case Title : Verify that the change in Material Library gets updated across levels
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
# Game Mode 0
|
||||
enter_game_mode_0 = ("Entered game mode 0", "Failed to enter game mode 0")
|
||||
modify_sphere_0_found = ("Test 0: modify_sphere was found", "Test 0: modify_sphere was not found")
|
||||
delete_sphere_0_found = ("Test 0: delete_sphere was found", "Test 0: delete_sphere was not found")
|
||||
terrain_0_found = ("Test 0: terrain Entity found", "Test 0: terrain Entity was not found")
|
||||
trigger_0_found = ("Test 0: trigger entity found", "Test 0: trigger entity wasn't found")
|
||||
sphere_initial_position_0 = ("Test 0: spheres initial position valid", "Test 0: spheres initial position not valid")
|
||||
sphere_initial_velocity_0 = ("Test 0: spheres initial velocity valid", "Test 0: spheres initial velocity not valid")
|
||||
sphere_collision_0 = ("Test 0: Both spheres collided", "Test 0: Both spheres did not collide")
|
||||
exit_game_mode_0 = ("Exited game mode 0", "Couldn't exit game mode 0")
|
||||
# Game Mode 1
|
||||
enter_game_mode_1 = ("Entered game mode 1", "Failed to enter game mode 1")
|
||||
modify_sphere_1_found = ("Test 1: modify_sphere was found", "Test 1: modify_sphere was not found")
|
||||
delete_sphere_1_found = ("Test 1: delete_sphere was found", "Test 1: delete_sphere was not found")
|
||||
terrain_1_found = ("Test 1: terrain Entity found", "Test 1: terrain Entity was not found")
|
||||
trigger_1_found = ("Test 1: trigger entity found", "Test 1: trigger entity wasn't found")
|
||||
sphere_initial_position_1 = ("Test 1: spheres initial position valid", "Test 1: spheres initial position not valid")
|
||||
sphere_initial_velocity_1 = ("Test 1: spheres initial velocity valid", "Test 1: spheres initial velocity not valid")
|
||||
sphere_collision_1 = ("Test 1: Both spheres collided", "Test 1: Both spheres did not collide")
|
||||
exit_game_mode_1 = ("Exited game mode 1", "Couldn't exit game mode 1")
|
||||
# Game Mode 2
|
||||
enter_game_mode_2 = ("Entered game mode 2", "Failed to enter game mode 2")
|
||||
modify_sphere_2_found = ("Test 2: modify_sphere was found", "Test 2: modify_sphere was not found")
|
||||
delete_sphere_2_found = ("Test 2: delete_sphere was found", "Test 2: delete_sphere was not found")
|
||||
terrain_2_found = ("Test 2: terrain Entity found", "Test 2: terrain Entity was not found")
|
||||
trigger_2_found = ("Test 2: trigger entity found", "Test 2: trigger entity wasn't found")
|
||||
sphere_initial_position_2 = ("Test 2: spheres initial position valid", "Test 2: spheres initial position not valid")
|
||||
sphere_initial_velocity_2 = ("Test 2: spheres initial velocity valid", "Test 2: spheres initial velocity not valid")
|
||||
sphere_collision_2 = ("Test 2: Both spheres collided", "Test 2: Both spheres did not collide")
|
||||
exit_game_mode_2 = ("Exited game mode 2", "Couldn't exit game mode 2")
|
||||
# Game Mode 3
|
||||
enter_game_mode_3 = ("Entered game mode 3", "Failed to enter game mode 3")
|
||||
modify_sphere_3_found = ("Test 3: modify_sphere was found", "Test 3: modify_sphere was not found")
|
||||
delete_sphere_3_found = ("Test 3: delete_sphere was found", "Test 3: delete_sphere was not found")
|
||||
terrain_3_found = ("Test 3: terrain Entity found", "Test 3: terrain Entity was not found")
|
||||
trigger_3_found = ("Test 3: trigger entity found", "Test 3: trigger entity wasn't found")
|
||||
sphere_initial_position_3 = ("Test 3: spheres initial position valid", "Test 3: spheres initial position not valid")
|
||||
sphere_initial_velocity_3 = ("Test 3: spheres initial velocity valid", "Test 3: spheres initial velocity not valid")
|
||||
sphere_collision_3 = ("Test 3: Both spheres collided", "Test 3: Both spheres did not collide")
|
||||
exit_game_mode_3 = ("Test 3: Exited game mode 3", "Couldn't exit game mode 3")
|
||||
|
||||
# Test Verification
|
||||
baseline_verified = ("Both levels are the same", "Both levels aren't the same")
|
||||
material_delete_verified = ("Material delete updated spheres", "Material delete not updated spheres")
|
||||
material_modify_verified = ("Material modify updated spheres", "Material modify not updated spheres")
|
||||
post_change_verified = ("Both levels are still the same", "Both levels are not the same")
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_LibraryUpdatedAcrossLevels():
|
||||
"""
|
||||
Summary: Verify that the change in a physmaterial library gets updated across levels
|
||||
|
||||
Level Description: There are two levels that are being compared. Each are exact replicas with a shared
|
||||
material library
|
||||
modify_sphere - Starts between terrain and trigger with initial velocity in negative z direction and gravity disabled;
|
||||
has physx collider in sphere shape with material "to_delete", had physx rigid body, and sphere_shape
|
||||
delete_sphere - Starts between terrain and trigger with initial velocity in negative z direction and gravity disabled;
|
||||
has physx collider in sphere shape with material "to_modify", had physx rigid body, and sphere_shape
|
||||
terrain - Default terrain with transform inline; has physx terrain component
|
||||
trigger - Above the spheres, trigger is enabled; has physx collider in box shape with dimensions (5.0, 10.0, 0.25)
|
||||
and box shape with the same dimensions
|
||||
|
||||
Expected Behavior: Materials deleted or modified have their changes update across levels. Initially the spheres will
|
||||
not bounce off the terrain after the change to the material library they will bounce up and hit the trigger
|
||||
|
||||
Material Tests:
|
||||
Test 0 - Tests level 0 before the material change
|
||||
Test 1 - Tests level 1 before the material change
|
||||
Test 2 - Tests level 0 after the material change
|
||||
Test 3 - Tests level 1 after the material change
|
||||
|
||||
Test 0 and 1 should be exactly the same. Test 2 and 3 should be exactly the same. Both modification to the material
|
||||
library should allow the spheres to bounce in Test 2 and 3. Therefore, both spheres will have a higher velocity and
|
||||
be able to trigger in Test 2 and 3 as compared to 0 and 1.
|
||||
|
||||
Iterated Game Mode steps:
|
||||
1) Open the correct level for the test
|
||||
2) Open Game Mode
|
||||
3) Create and Verify Entities
|
||||
4) Wait for Sphere collision with Terrain Entity
|
||||
5) Wait for spheres to have a chance to hit trigger
|
||||
6) Modify Material Library
|
||||
7) Exit Game Mode
|
||||
|
||||
Test Steps:
|
||||
1) Create Test Objects
|
||||
2) Run Game Mode steps once for each test
|
||||
3) Verify that spheres acted as expected
|
||||
4) Close 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
|
||||
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.bus
|
||||
import azlmbr.math as math
|
||||
|
||||
from Physmaterial_Editor import Physmaterial_Editor
|
||||
|
||||
# Constants
|
||||
FLOAT_THRESHOLD = sys.float_info.epsilon
|
||||
TIMEOUT = 1
|
||||
INITIAL_VELOCITY = math.Vector3(0.0, 0.0, -10.0)
|
||||
VELOCITY_THRESHOLD = 0.1
|
||||
|
||||
# Helper Functions
|
||||
class Entity:
|
||||
def __init__(self, name, index):
|
||||
self.id = general.find_game_entity(name)
|
||||
self.name = name
|
||||
self.collision_happened = False
|
||||
self.index = index
|
||||
# ID validation
|
||||
self.found = Tests.__dict__[self.name + "_{}_found".format(index)]
|
||||
Report.critical_result(self.found, self.id.IsValid())
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
class Sphere(Entity):
|
||||
terrain_id = None
|
||||
|
||||
def __init__(self, name, index):
|
||||
Entity.__init__(self, name, index)
|
||||
# Set Handler
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
if args[0].equal(Sphere.terrain_id):
|
||||
self.collision_happened = True
|
||||
|
||||
|
||||
class Material_Test:
|
||||
def __init__(self, index, level_index):
|
||||
# index is the test index 0-3 this allows for tests from the Tests class to be fetched
|
||||
self.index = index
|
||||
# level_index determins which level will be opened at the start of the test loop
|
||||
self.level_index = level_index
|
||||
# Data
|
||||
self.modify_sphere_hit_trigger = False
|
||||
self.delete_sphere_hit_trigger = False
|
||||
self.entity_list = None
|
||||
self.modify_sphere_final_velocity = None
|
||||
self.delete_sphere_final_velocity = None
|
||||
|
||||
def sphere_initial_position(self, modify_sphere_position, delete_sphere_position, terrain_position, trigger_position):
|
||||
position_valid = (
|
||||
modify_sphere_position.z == delete_sphere_position.z
|
||||
and modify_sphere_position.z > terrain_position.z
|
||||
and trigger_position.z > modify_sphere_position.z
|
||||
)
|
||||
initial_position = Tests.__dict__["sphere_initial_position_{}".format(self.index)]
|
||||
Report.critical_result(initial_position, position_valid)
|
||||
|
||||
def sphere_initial_velocity(self, modify_sphere_velocity, delete_sphere_velocity):
|
||||
initial_velocity_string = Tests.__dict__["sphere_initial_velocity_{}".format(self.index)]
|
||||
Report.critical_result(
|
||||
initial_velocity_string,
|
||||
modify_sphere_velocity.IsClose(INITIAL_VELOCITY, VELOCITY_THRESHOLD) and delete_sphere_velocity.IsClose(INITIAL_VELOCITY, VELOCITY_THRESHOLD),
|
||||
)
|
||||
|
||||
def log_velocity(self):
|
||||
self.modify_sphere_final_velocity = self.entity_list[0].velocity
|
||||
self.delete_sphere_final_velocity = self.entity_list[1].velocity
|
||||
|
||||
def set_trigger(self):
|
||||
# Type (Entity) -> None
|
||||
# Sets handler for trigger
|
||||
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.handler.connect(self.entity_list[3].id)
|
||||
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
|
||||
|
||||
def on_trigger_enter(self, args):
|
||||
# Type () -> None
|
||||
# When trigger entered the correct sphere is found boolean is flipped
|
||||
if self.entity_list[0].id.Equal(args[0]):
|
||||
self.modify_sphere_hit_trigger = True
|
||||
if self.entity_list[1].id.Equal(args[0]):
|
||||
self.delete_sphere_hit_trigger = True
|
||||
|
||||
def are_levels_consistent(level_a, level_b):
|
||||
triggers_0 = level_a.modify_sphere_hit_trigger == level_b.modify_sphere_hit_trigger
|
||||
triggers_1 = level_a.delete_sphere_hit_trigger == level_b.delete_sphere_hit_trigger
|
||||
modify_sphere_velocities = (
|
||||
abs(level_a.modify_sphere_final_velocity.z - level_b.modify_sphere_final_velocity.z) < FLOAT_THRESHOLD
|
||||
)
|
||||
delete_sphere_velocities = (
|
||||
abs(level_a.delete_sphere_final_velocity.z - level_b.delete_sphere_final_velocity.z) < FLOAT_THRESHOLD
|
||||
)
|
||||
return triggers_0 and triggers_1 and modify_sphere_velocities and delete_sphere_velocities
|
||||
|
||||
def check_material_delete(test_0, test_3):
|
||||
triggers = test_0.modify_sphere_hit_trigger != test_3.modify_sphere_hit_trigger
|
||||
modify_sphere_velocities = test_0.modify_sphere_final_velocity.z < test_3.modify_sphere_final_velocity.z
|
||||
return triggers and modify_sphere_velocities
|
||||
|
||||
def check_material_modify(test_0, test_3):
|
||||
triggers = test_0.delete_sphere_hit_trigger != test_3.delete_sphere_hit_trigger
|
||||
delete_sphere_velocities = test_0.delete_sphere_final_velocity.z < test_3.delete_sphere_final_velocity.z
|
||||
return triggers and delete_sphere_velocities
|
||||
|
||||
def modify_material_library():
|
||||
physmaterial_object = Physmaterial_Editor("Material_LibraryUpdatedAcrossLevels.physmaterial")
|
||||
physmaterial_object.delete_material("to_delete")
|
||||
physmaterial_object.modify_material("to_modify", "Restitution", 1.0)
|
||||
physmaterial_object.save_changes()
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Create Test Objects
|
||||
# Each test object is given an index that will determine what tuples are pulled from the Tests class and are indicative of the order that they will be run.
|
||||
# Each test object also has a level_index to determine which level will be opened during the test loop. Both levels 0 and 1 are looked at before and after
|
||||
# the change to the material library
|
||||
test_0 = Material_Test(index=0, level_index=0)
|
||||
test_1 = Material_Test(index=1, level_index=1)
|
||||
test_2 = Material_Test(index=2, level_index=0)
|
||||
test_3 = Material_Test(index=3, level_index=1)
|
||||
# Test list of all the tests in order of index
|
||||
test_list = [test_0, test_1, test_2, test_3]
|
||||
|
||||
# 2) Run Game Mode steps once for each test
|
||||
for test in test_list:
|
||||
# 1) Open the correct level for the test
|
||||
helper.open_level(
|
||||
"physics",
|
||||
"Material_LibraryUpdatedAcrossLevels\\Material_LibraryUpdatedAcrossLevels_{}".format(
|
||||
test.level_index
|
||||
),
|
||||
)
|
||||
|
||||
# 2) Open Game Mode
|
||||
helper.enter_game_mode(Tests.__dict__["enter_game_mode_{}".format(test.index)])
|
||||
|
||||
# 3) Create and Verify Entities
|
||||
terrain = Entity("terrain", test.index)
|
||||
Sphere.terrain_id = terrain.id
|
||||
modify_sphere = Sphere("modify_sphere", test.index)
|
||||
delete_sphere = Sphere("delete_sphere", test.index)
|
||||
trigger = Entity("trigger", test.index)
|
||||
test.entity_list = [modify_sphere, delete_sphere, terrain, trigger]
|
||||
test.set_trigger()
|
||||
|
||||
test.sphere_initial_position(modify_sphere.position, delete_sphere.position, terrain.position, trigger.position)
|
||||
test.sphere_initial_velocity(modify_sphere.velocity, delete_sphere.velocity)
|
||||
|
||||
# 4) Wait for Sphere collision with Terrain Entity
|
||||
collisions_happened = helper.wait_for_condition(lambda: modify_sphere.collision_happened and delete_sphere.collision_happened, TIMEOUT)
|
||||
Report.result(Tests.__dict__["sphere_collision_{}".format(test.index)], collisions_happened)
|
||||
|
||||
# 5) Wait for spheres to have a chance to hit trigger
|
||||
helper.wait_for_condition(lambda: test.modify_sphere_hit_trigger and test.delete_sphere_hit_trigger, TIMEOUT)
|
||||
# Report trigger
|
||||
Report.info("modify_sphere{} hit trigger in test {}".format("" if test.modify_sphere_hit_trigger else " didn't", test.index))
|
||||
Report.info("delete_sphere{} hit trigger in test {}".format("" if test.delete_sphere_hit_trigger else " didn't", test.index))
|
||||
|
||||
test.log_velocity()
|
||||
|
||||
# 6) Modify Material Library
|
||||
if test.index == 1:
|
||||
modify_material_library()
|
||||
|
||||
# 7) Exit Game Mode
|
||||
helper.exit_game_mode(Tests.__dict__["exit_game_mode_{}".format(test.index)])
|
||||
|
||||
# 3) Verify that spheres acted as expected
|
||||
Report.result(Tests.baseline_verified, are_levels_consistent(test_0, test_1))
|
||||
Report.result(Tests.material_delete_verified, check_material_delete(test_0, test_3))
|
||||
Report.result(Tests.material_modify_verified, check_material_modify(test_0, test_3))
|
||||
Report.result(Tests.post_change_verified, are_levels_consistent(test_2, test_3))
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_LibraryUpdatedAcrossLevels)
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
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 : C5296614
|
||||
# Test Case Title : Check that unless you assign a shape to a physX collider component,
|
||||
# the material assigned to it does not take affect
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
# level
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
collider_1_found = ("collider_1 was found", "collider_1 was not found")
|
||||
collider_2_found = ("collider_2 was found", "collider_2 was not found")
|
||||
ball_1_found = ("ball_1 was found", "ball_1 was not found")
|
||||
ball_1_gravity = ("ball_1 gravity is disabled", "ball_1 gravity is enabled")
|
||||
ball_1_collision = ("ball_1 collided with collider_1", "ball_1 passed through collider_1")
|
||||
ball_2_found = ("ball_2 was found", "ball_2 was not found")
|
||||
ball_2_gravity = ("ball_2 gravity is disabled", "ball_2 gravity is enabled")
|
||||
ball_2_collision = ("ball_2 passed through collider_2", "ball_2 collided with collider_2")
|
||||
trigger_1_found = ("trigger_1 was found", "trigger_1 was not found")
|
||||
trigger_2_found = ("trigger_2 was found", "trigger_2 was not found")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_NoEffectIfNoColliderShape():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to verify that unless you assign a shape to a PhysX collider component,
|
||||
the material assigned to it does not take affect
|
||||
|
||||
Level Description:
|
||||
4 colliders named "collider_1", "collider_2", "ball_1" and "ball_2", all with PhysX Collider component.
|
||||
collider_1 has no shape assigned to it, but collider_2 has box shape.
|
||||
ball_1 and ball_2 have sphere shape, PhysX Rigid Body component, gravity disabled and initial linear velocity
|
||||
of 20 m/s on Y axis.
|
||||
Each ball is positioned in front of its respective collider.
|
||||
|
||||
Expected Behavior:
|
||||
The balls are supposed to move towards the colliders.
|
||||
Ball_1 should pass through collider_1 WITHOUT collision, and enter trigger_1.
|
||||
Ball_2 should collide with collider_2 and NOT enter trigger_2.
|
||||
|
||||
Test Steps:
|
||||
1) Load the level
|
||||
2) Enter game mode
|
||||
3) Setup entities
|
||||
4) Wait for balls to collide with colliders and/or triggers
|
||||
5) Report results
|
||||
6) Exit game mode
|
||||
7) Close editor
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
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.bus
|
||||
|
||||
# Constants
|
||||
TIME_OUT = 2.0
|
||||
|
||||
def get_test(entity_name, suffix):
|
||||
return Tests.__dict__[entity_name + suffix]
|
||||
|
||||
class Entity:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.validate_ID()
|
||||
|
||||
def validate_ID(self):
|
||||
self.id = general.find_game_entity(self.name)
|
||||
found_tuple = get_test(self.name, "_found")
|
||||
Report.critical_result(found_tuple, self.id.IsValid())
|
||||
|
||||
class Ball(Entity):
|
||||
def __init__(self, name, collider, trigger):
|
||||
Entity.__init__(self, name)
|
||||
self.collider = collider
|
||||
self.trigger = trigger
|
||||
self.collided_with_collider = False
|
||||
self.collided_with_trigger = False
|
||||
self.validate_gravity()
|
||||
self.setup_collision_handler()
|
||||
|
||||
def validate_gravity(self):
|
||||
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
|
||||
gravity_tuple = get_test(self.name, "_gravity")
|
||||
Report.critical_result(gravity_tuple, not gravity_enabled)
|
||||
|
||||
def collider_hit(self, args):
|
||||
colliding_entity_id = args[0]
|
||||
if colliding_entity_id.Equal(self.id):
|
||||
Report.info(self.name + " collided with " + self.collider.name)
|
||||
self.collided_with_collider = True
|
||||
|
||||
def trigger_hit(self, args):
|
||||
colliding_entity_id = args[0]
|
||||
if colliding_entity_id.Equal(self.id):
|
||||
Report.info(self.name + " collided with " + self.trigger.name)
|
||||
self.collided_with_trigger = True
|
||||
|
||||
def setup_collision_handler(self):
|
||||
self.collider.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.collider.handler.connect(self.collider.id)
|
||||
self.collider.handler.add_callback("OnCollisionBegin", self.collider_hit)
|
||||
self.trigger.handler = azlmbr.physics.TriggerNotificationBusHandler()
|
||||
self.trigger.handler.connect(self.trigger.id)
|
||||
self.trigger.handler.add_callback("OnTriggerEnter", self.trigger_hit)
|
||||
|
||||
def both_balls_have_moved():
|
||||
return ball_1.collided_with_trigger and ball_2.collided_with_collider
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Load the level
|
||||
helper.open_level("Physics", "Material_NoEffectIfNoColliderShape")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# 3) Setup entities
|
||||
collider_1 = Entity("collider_1")
|
||||
collider_2 = Entity("collider_2")
|
||||
trigger_1 = Entity("trigger_1")
|
||||
trigger_2 = Entity("trigger_2")
|
||||
ball_1 = Ball("ball_1", collider_1, trigger_1)
|
||||
ball_2 = Ball("ball_2", collider_2, trigger_2)
|
||||
|
||||
# 4) Wait for balls to collide
|
||||
helper.wait_for_condition(both_balls_have_moved, TIME_OUT)
|
||||
|
||||
# 5) Report results
|
||||
Report.result(Tests.ball_1_collision, not ball_1.collided_with_collider and ball_1.collided_with_trigger)
|
||||
Report.result(Tests.ball_2_collision, ball_2.collided_with_collider and not ball_2.collided_with_trigger)
|
||||
|
||||
# 6) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_NoEffectIfNoColliderShape)
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
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 : C4044697
|
||||
# Test Case Title : Verify that each surface picks up the material assigned to it and behaves accordingly.
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
initial_orientaition_valid = ("Initial entity orientation valid", "Initial entity orientation not valid")
|
||||
final_orientation_valid = ("Final entity orientation valid", "Final entity orientation not valid")
|
||||
speed_comparision = ("Sphere 1 is faster than Sphere 2", "Sphere 1 is not faster than Sphere 2")
|
||||
|
||||
# Sphere 0
|
||||
Sphere_0_found = ("Sphere 0 is valid", "Sphere 0 is not valid")
|
||||
Sphere_0_position_found = ("Sphere 0 position is found", "Sphere 0 position is not found")
|
||||
Sphere_0_velocity_found = ("Sphere 0 velocity is found", "Sphere 0 velocity is not found")
|
||||
Sphere_0_velocity_valid = ("Sphere 0 velocity is valid", "Sphere 0 velocity is not valid")
|
||||
Sphere_0_collided_with_perface = ("Sphere 0 collided w/Perface Entity", "Sphere 0 has not collided")
|
||||
Sphere_0_final_velocity_valid = ("Sphere 0 final velocity is valid", "Sphere 0 final velocity is not valid")
|
||||
|
||||
# Sphere 1
|
||||
Sphere_1_found = ("Sphere 1 is valid", "Sphere 1 is not valid")
|
||||
Sphere_1_position_found = ("Sphere 1 position is found", "Sphere 1 position is not found")
|
||||
Sphere_1_velocity_found = ("Sphere 1 velocity is found", "Sphere 1 velocity is not found")
|
||||
Sphere_1_velocity_valid = ("Sphere 1 velocity is valid", "Sphere 1 velocity is not valid")
|
||||
Sphere_1_collided_with_perface = ("Sphere 1 collided w/Perface Entity", "Sphere 1 has not collided")
|
||||
Sphere_1_final_velocity_valid = ("Sphere 1 final velocity is valid", "Sphere 1 final velocity is not valid")
|
||||
|
||||
# Sphere 2
|
||||
Sphere_2_found = ("Sphere 2 is valid", "Sphere 2 is not valid")
|
||||
Sphere_2_position_found = ("Sphere 2 position is found", "Sphere 2 position is not found")
|
||||
Sphere_2_velocity_found = ("Sphere 2 velocity is found", "Sphere 2 velocity is not found")
|
||||
Sphere_2_velocity_valid = ("Sphere 2 velocity is valid", "Sphere 2 velocity is not valid")
|
||||
Sphere_2_collided_with_perface = ("Sphere 2 collided w/Perface Entity", "Sphere 2 has not collided")
|
||||
Sphere_2_final_velocity_valid = ("Sphere 2 final velocity is valid", "Sphere 2 final velocity is not valid")
|
||||
|
||||
# Perface Entity
|
||||
Perface_Entity_found = ("Perface entity is valid", "Perface entity is not valid")
|
||||
Perface_Entity_position_found = ("Perface entity position found", "Perface entity position not found")
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_PerFaceMaterialGetsCorrectMaterial():
|
||||
"""
|
||||
Summary: The perface has three different faces that can pick up different materials. To check that each face
|
||||
picks up the material assigned to it I send three spheres of the same material at each face to see the
|
||||
different reactions. If each sphere bounces away with the correct relative velocity it can be assumed
|
||||
that the Perface entity is picking up the materials properly.
|
||||
|
||||
Level Description:
|
||||
Perface Entity - The perface entity is an entity with a custom mesh that allows for multiple materials to be
|
||||
applied to different parts of the mesh. In this case there seems to be three different areas of the mesh
|
||||
that can be assigned with different materials and interacted. One of three spheres is lined up to interact
|
||||
with one of each of the three areas. The mesh is included in the level "test.fbx". The entity is stationary
|
||||
with three spheres inline along the x and y axis: has a PhysX collider and a Mesh component.
|
||||
Sphere 0 - This entity is inline with the perface entity on the y axis and heading torward it with a velocity in
|
||||
the -y direction: has a sphere shaped PhysX Collider, a PhysX Rigid Body, and a Sphere Shape.
|
||||
Sphere 1 - This entity is inline with the perface entity on the x axis and heading torward it with a velocity in
|
||||
the -x direction: has a sphere shaped PhysX Collider, a PhysX Rigid Body, and a Sphere Shape.
|
||||
Sphere 2 - This entity is inline with the perface entity on the x axis and heading torward it with a velocity in
|
||||
the +x direction: has a sphere shaped PhysX Collider, a PhysX Rigid Body, and a Sphere Shape.
|
||||
|
||||
Materials:
|
||||
Bounce - All three spheres and the Perface mesh area lined up with Sphere 1 have the Bounce material applied to
|
||||
them. This material interacts with other materials by bouncing with the restitution factor of an average of
|
||||
each entity that collides restitution value. Has restitution value: 1
|
||||
PartialBounce - The Perface mesh area lined up with Sphere 2 has the partial bounce material applied. This material
|
||||
interacts with other materials by responding with a restitution factor that is an average of the two materials
|
||||
that interact. Has restitution value: 0
|
||||
NoBounce - The Perface mesh area lined up with Sphere 0 have the NoBounce material. This material interacts with
|
||||
other materials by bouncing with the restitution factor of the material with the lowest restitution value.
|
||||
Has restitution value: 0
|
||||
|
||||
Expected Behavior: Sphere 0 will not bounce, Sphere 1 will bounce away from the Perface Entity faster than
|
||||
Sphere 2 will.
|
||||
|
||||
Test Steps:
|
||||
1) Open Level
|
||||
2) Enter Game Mode
|
||||
3) Create Entity objects
|
||||
4) Iterate through all entities and validate them
|
||||
5) Validate that Entities Exist
|
||||
6) Iterate through each of the three spheres and test their bounces
|
||||
7) Further evaluate that sphere entities exist
|
||||
8) Validate Initial Positions and Velocities
|
||||
9) Set up handler and wait for collision
|
||||
10) Get and Validate Final Positions and Velocities
|
||||
11) Log Results
|
||||
12) Validate Orientations and Final Velocities
|
||||
13) Exit Game Mode
|
||||
14) Close 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
|
||||
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.bus
|
||||
import azlmbr.math as math
|
||||
|
||||
# Constants
|
||||
FLOAT_THRESHOLD = sys.float_info.epsilon
|
||||
FINAL_VELOCITY_THRESHOLD = 0.01
|
||||
STATIONARY_SPHERE_THRESHOLD = 2
|
||||
TIMEOUT = 1.0
|
||||
|
||||
# Helper Functions
|
||||
class Entity:
|
||||
def __init__(self, name, expected_initial_velocity=None, expected_final_velocity=None):
|
||||
self.id = general.find_game_entity(name)
|
||||
self.name = name
|
||||
self.EXPECTED_INITIAL_VELOCITY = expected_initial_velocity
|
||||
self.EXPECTED_FINAL_VELOCITY = expected_final_velocity
|
||||
self.initial_velocity = None
|
||||
self.final_velocity = None
|
||||
self.initial_position = None
|
||||
self.final_position = None
|
||||
self.collision_happened = False
|
||||
self.handler = None
|
||||
|
||||
class Entity_Tests:
|
||||
found = None
|
||||
found_position = None
|
||||
found_velocity = None
|
||||
valid_init_velocity = None
|
||||
valid_final_velocity = None
|
||||
collision_happened = None
|
||||
|
||||
def check_id(self):
|
||||
self.Entity_Tests.found = Tests.__dict__[self.name + "_found"]
|
||||
Report.critical_result(self.Entity_Tests.found, self.id.isValid())
|
||||
|
||||
def activate_entity(self):
|
||||
Report.info("Activating Entity : " + self.name)
|
||||
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.id)
|
||||
|
||||
def values_found(self):
|
||||
self.Entity_Tests.found_position = Tests.__dict__[self.name + "_position_found"]
|
||||
self.Entity_Tests.found_velocity = Tests.__dict__[self.name + "_velocity_found"]
|
||||
|
||||
Report.critical_result(self.Entity_Tests.found_position, vector_valid(self.initial_position, False))
|
||||
Report.critical_result(self.Entity_Tests.found_velocity, vector_valid(self.initial_velocity, False))
|
||||
|
||||
def perface_values_found(self):
|
||||
self.Entity_Tests.found_position = Tests.__dict__[self.name + "_position_found"]
|
||||
Report.critical_result(self.Entity_Tests.found_position, vector_valid(self.initial_position, False))
|
||||
|
||||
def get_initial_position_and_velocity(self):
|
||||
self.initial_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
self.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_final_position_and_velocity(self):
|
||||
self.final_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
|
||||
self.final_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def validate_sphere_velocity(self):
|
||||
if self.collision_happened:
|
||||
velocity_valid = (
|
||||
abs(self.final_velocity.x - self.EXPECTED_FINAL_VELOCITY.x) < FINAL_VELOCITY_THRESHOLD
|
||||
and abs(self.final_velocity.y - self.EXPECTED_FINAL_VELOCITY.y) < FINAL_VELOCITY_THRESHOLD
|
||||
and abs(self.final_velocity.z - self.EXPECTED_FINAL_VELOCITY.z) < FINAL_VELOCITY_THRESHOLD
|
||||
)
|
||||
self.Entity_Tests.valid_final_velocity = Tests.__dict__[self.name + "_final_velocity_valid"]
|
||||
Report.result(self.Entity_Tests.valid_final_velocity, velocity_valid)
|
||||
else:
|
||||
velocity_valid = (
|
||||
abs(self.initial_velocity.x - self.EXPECTED_INITIAL_VELOCITY.x) < FLOAT_THRESHOLD
|
||||
and abs(self.initial_velocity.y - self.EXPECTED_INITIAL_VELOCITY.y) < FLOAT_THRESHOLD
|
||||
and abs(self.initial_velocity.z - self.EXPECTED_INITIAL_VELOCITY.z) < FLOAT_THRESHOLD
|
||||
)
|
||||
self.Entity_Tests.valid_init_velocity = Tests.__dict__[self.name + "_velocity_valid"]
|
||||
Report.critical_result(self.Entity_Tests.valid_init_velocity, velocity_valid)
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
if self.id.equal(args[0]):
|
||||
self.collision_happened = True
|
||||
|
||||
def set_handler(self, id):
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
def report_sphere_values(entity):
|
||||
Report.info_vector3(entity.initial_position, "{} initial position: ".format(entity.name))
|
||||
Report.info_vector3(entity.initial_velocity, "{} initial velocity: ".format(entity.name))
|
||||
|
||||
Report.info_vector3(entity.final_position, "{} final position: ".format(entity.name))
|
||||
Report.info_vector3(entity.final_velocity, "{} final velocity: ".format(entity.name))
|
||||
|
||||
def report_perface_values(entity):
|
||||
Report.info_vector3(entity.initial_position, "{} initial position: ".format(entity.name))
|
||||
|
||||
def validate_positions():
|
||||
# Initial orientation is confirmed by z axis values, if there are further issues a collision
|
||||
# will not as expected.
|
||||
Report.info("Checking Initial Orientation")
|
||||
initial_orientaition = (
|
||||
sphere_0.initial_position.z
|
||||
== sphere_1.initial_position.z
|
||||
== sphere_2.initial_position.z
|
||||
== perface_entity.initial_position.z
|
||||
)
|
||||
Report.result(Tests.initial_orientaition_valid, initial_orientaition)
|
||||
# Final orientation is confirmed if Sphere 0 stopped next to the Perface Entity.
|
||||
Report.info("Checking Final Orientation")
|
||||
final_orientation = (
|
||||
abs(perface_entity.final_position.x - sphere_0.final_position.x) < FLOAT_THRESHOLD
|
||||
and abs(perface_entity.final_position.z - sphere_0.final_position.z) < FLOAT_THRESHOLD
|
||||
and abs(perface_entity.final_position.y - sphere_0.final_position.y) < STATIONARY_SPHERE_THRESHOLD
|
||||
)
|
||||
|
||||
Report.result(Tests.final_orientation_valid, final_orientation)
|
||||
|
||||
def vector_valid(vector, can_be_zero):
|
||||
if can_be_zero:
|
||||
return vector != None
|
||||
else:
|
||||
return vector != None and not vector.IsZero()
|
||||
|
||||
# Main Script
|
||||
helper.init_idle()
|
||||
# 1) Open Level
|
||||
helper.open_level("Physics", "Material_PerFaceMaterialGetsCorrectMaterial")
|
||||
|
||||
# 2) Enter Game Mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# 3) Create entity objects
|
||||
sphere_0 = Entity("Sphere_0", math.Vector3(0.0, -10.0, 0.0), math.Vector3(0.0, 0.0, 0.0))
|
||||
sphere_1 = Entity("Sphere_1", math.Vector3(-10.0, 0.0, 0.0), math.Vector3(10.09, 1.85, 1.18))
|
||||
sphere_2 = Entity("Sphere_2", math.Vector3(10.0, 0.0, 0.0), math.Vector3(-5.0, 0.0, 0.0))
|
||||
perface_entity = Entity("Perface_Entity")
|
||||
entity_list = [sphere_0, sphere_1, sphere_2, perface_entity]
|
||||
spheres = [sphere_0, sphere_1, sphere_2]
|
||||
|
||||
# 4) Iterate through all entities and validate them
|
||||
for entity in entity_list:
|
||||
# 5) Validate that Entities Exist
|
||||
entity.check_id()
|
||||
# Extra steps for Perface Entity as it will no longer be iterated
|
||||
perface_entity.get_initial_position_and_velocity()
|
||||
perface_entity.perface_values_found()
|
||||
perface_entity.get_final_position_and_velocity()
|
||||
report_perface_values(perface_entity)
|
||||
|
||||
# 6) Iterate through each of the three spheres and test their bounces
|
||||
for entity in spheres:
|
||||
# 7) Further evaluate that sphere entities exist
|
||||
entity.activate_entity()
|
||||
entity.get_initial_position_and_velocity()
|
||||
|
||||
# 8) Validate Initial Positions and Velocities
|
||||
entity.values_found()
|
||||
entity.validate_sphere_velocity()
|
||||
|
||||
# 9) Set up handler and wait for collision
|
||||
entity.set_handler(perface_entity.id)
|
||||
|
||||
# Wait for collision
|
||||
helper.wait_for_condition(lambda: entity.collision_happened, TIMEOUT)
|
||||
# Report Collision
|
||||
entity.Entity_Tests.collision_happened = Tests.__dict__[entity.name + "_collided_with_perface"]
|
||||
Report.result(entity.Entity_Tests.collision_happened, entity.collision_happened)
|
||||
|
||||
# 10) Get and Validate Final Positions and Velocities
|
||||
entity.get_final_position_and_velocity()
|
||||
entity.validate_sphere_velocity()
|
||||
|
||||
# 11) Log Results
|
||||
report_sphere_values(entity)
|
||||
|
||||
# 12) Validate Orientations and Final Velocities
|
||||
validate_positions()
|
||||
Report.result(
|
||||
Tests.speed_comparision, sphere_1.final_velocity.GetLength() > sphere_2.final_velocity.GetLength()
|
||||
)
|
||||
|
||||
# 13) Exit Game Mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_PerFaceMaterialGetsCorrectMaterial)
|
||||
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
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 : C4925580
|
||||
# Test Case Title : Verify that Material can be assigned to Ragdoll Bones and they behave as per their material
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests:
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
terrain_found_valid = ("PhysX Terrain found and validated", "PhysX Terrain not found and validated")
|
||||
concrete_ragdoll_found_valid = ("Concrete Ragdoll found and validated", "Concrete Ragdoll not found and validated")
|
||||
rubber_ragdoll_found_valid = ("Rubber Ragdoll found and validated", "Rubber Ragdoll not found and validated")
|
||||
concrete_ragdoll_above_terrain = ("Concrete Ragdoll is above terrain", "Concrete Ragdoll is not above terrain")
|
||||
rubber_ragdoll_above_terrain = ("Rubber Ragdoll is above terrain", "Rubber Ragdoll is not above terrain")
|
||||
terrain_collision_detected = ("Collision was detected on a ragdoll with terrain", "Collision detection timed out")
|
||||
concrete_ragdoll_contacted_terrain = ("Concrete Ragdoll contacted terrain", "Concrete Ragdoll did not contact terrain")
|
||||
rubber_ragdoll_contacted_terrain = ("Rubber Ragdoll contacted terrain", "Rubber Ragdoll did not contact terrain")
|
||||
rubber_ragdoll_bounced_higher = ("Rubber Ragdoll bounced higher than Concrete Ragdoll", "Rubber Ragdoll did not bounce higher than Concrete Ragdoll")
|
||||
concrete_ragdoll_bounced_as_expected = ("Concrete ragdoll bounced to expected height", "Concrete ragdoll did not bounce to expected height")
|
||||
rubber_ragdoll_bounced_as_expected = ("Rubber ragdoll bounced to expected height", "Rubber ragdoll did not bounce to expected height")
|
||||
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_RagdollBones():
|
||||
"""
|
||||
Summary:
|
||||
This script runs an automated test to verify that assigning material to the skeleton of an actor entity with PhysX
|
||||
ragdoll will cause the entity to behave according to the nature of the material.
|
||||
|
||||
Level Description:
|
||||
Two ragdoll entities (entity: Concrete Ragdoll) and (entity: Rubber Ragdoll) are above a PhysX terrain (entity:
|
||||
PhysX Terrain). Each ragdoll has an actor, an animation graph, and a PhysX ragdoll component. Gravity is enabled for
|
||||
each joint which is present on the ragdolls. The ragdolls are identical except for their textures, skeleton
|
||||
materials, and x-positions. Concrete Ragdoll's texture is blue, while Rubber Ragdoll's texture is red. Concrete
|
||||
Ragdoll's skeleton material is concrete, while Rubber Ragdoll's skeleton material is rubber.
|
||||
|
||||
Expected behavior:
|
||||
The ragdolls will fall and hit the terrain at the same time. The rubber ragdoll will bounce higher than the concrete
|
||||
ragdoll.
|
||||
|
||||
Test Steps:
|
||||
1) Open level and enter game mode
|
||||
2) Retrieve and validate entities
|
||||
3) Check that each ragdoll is above the terrain
|
||||
4) Wait for the initial collision between a ragdoll and the terrain or timeout
|
||||
5) Check for the maximum bounce height of each ragdoll for a given period of time
|
||||
6) Verify that the rubber ragdoll bounced higher than the concrete ragdoll
|
||||
7) Verify that each ragdoll bounced approximately to its expected maximum height
|
||||
8) Exit game mode and close 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
|
||||
"""
|
||||
# Setup path
|
||||
import os
|
||||
import sys
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus
|
||||
import azlmbr.components
|
||||
import azlmbr.physics
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
# Constants
|
||||
TIME_OUT_SECONDS = 3.0
|
||||
TERRAIN_START_Z = 32.0
|
||||
CONCRETE_EXPECTED_MAX_BOUNCE_HEIGHT = 0.039
|
||||
RUBBER_EXPECTED_MAX_BOUNCE_HEIGHT = 1.2
|
||||
TOLERANCE = 0.5
|
||||
|
||||
class Entity:
|
||||
def __init__(self, name, found_valid_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.found_valid_test = found_valid_test
|
||||
|
||||
class Ragdoll(Entity):
|
||||
def __init__(self, name, found_valid_test, target_terrain, above_terrain_test, contacted_terrain_test):
|
||||
Entity.__init__(self, name, found_valid_test)
|
||||
self.target_terrain = target_terrain
|
||||
self.above_terrain_test = above_terrain_test
|
||||
self.contacted_terrain_test = contacted_terrain_test
|
||||
self.contacted_terrain = False
|
||||
self.max_bounce_height = 0
|
||||
self.reached_max_bounce = False
|
||||
|
||||
# Set up collision notification handler
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
def get_z_position(self):
|
||||
z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", self.id)
|
||||
return z_position
|
||||
|
||||
# Set up collision detection with the terrain
|
||||
def on_collision_begin(self, args):
|
||||
other_id = args[0]
|
||||
if other_id.Equal(self.target_terrain.id):
|
||||
Report.info("{} collision began with {}".format(self.name, self.target_terrain.name))
|
||||
if not self.contacted_terrain:
|
||||
self.hit_terrain_z = self.get_z_position()
|
||||
self.contacted_terrain = True
|
||||
|
||||
# 1) Open level and enter game mode
|
||||
helper.init_idle()
|
||||
helper.open_level("Physics", "Material_RagdollBones")
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# 2) Retrieve and validate entities
|
||||
terrain = Entity("PhysX Terrain", Tests.terrain_found_valid)
|
||||
Report.critical_result(terrain.found_valid_test, terrain.id.IsValid())
|
||||
|
||||
concrete_ragdoll = Ragdoll(
|
||||
"Concrete Ragdoll",
|
||||
Tests.concrete_ragdoll_found_valid,
|
||||
terrain,
|
||||
Tests.concrete_ragdoll_above_terrain,
|
||||
Tests.concrete_ragdoll_contacted_terrain,
|
||||
)
|
||||
|
||||
rubber_ragdoll = Ragdoll(
|
||||
"Rubber Ragdoll",
|
||||
Tests.rubber_ragdoll_found_valid,
|
||||
terrain,
|
||||
Tests.rubber_ragdoll_above_terrain,
|
||||
Tests.rubber_ragdoll_contacted_terrain,
|
||||
)
|
||||
|
||||
ragdolls = [concrete_ragdoll, rubber_ragdoll]
|
||||
for ragdoll in ragdolls:
|
||||
Report.critical_result(ragdoll.found_valid_test, ragdoll.id.IsValid())
|
||||
|
||||
# 3) Check that each ragdoll is above the terrain
|
||||
Report.critical_result(ragdoll.above_terrain_test, ragdoll.get_z_position() > TERRAIN_START_Z)
|
||||
|
||||
# 4) Wait for the initial collision between the ragdolls and the terrain or timeout
|
||||
terrain_collision_detected = helper.wait_for_condition(
|
||||
lambda: concrete_ragdoll.contacted_terrain and rubber_ragdoll.contacted_terrain, TIME_OUT_SECONDS
|
||||
)
|
||||
Report.critical_result(Tests.terrain_collision_detected, terrain_collision_detected)
|
||||
for ragdoll in ragdolls:
|
||||
Report.result(ragdoll.contacted_terrain_test, ragdoll.contacted_terrain)
|
||||
|
||||
# 5) Check for the maximum bounce height of each ragdoll for a given period of time
|
||||
def check_for_max_bounce_heights(ragdolls):
|
||||
for ragdoll in ragdolls:
|
||||
if ragdoll.contacted_terrain:
|
||||
bounce_height = ragdoll.get_z_position() - ragdoll.hit_terrain_z
|
||||
if bounce_height >= ragdoll.max_bounce_height:
|
||||
ragdoll.max_bounce_height = bounce_height
|
||||
elif ragdoll.max_bounce_height > 0.0:
|
||||
ragdoll.reached_max_bounce = True
|
||||
return concrete_ragdoll.reached_max_bounce and rubber_ragdoll.reached_max_bounce
|
||||
|
||||
helper.wait_for_condition(lambda: check_for_max_bounce_heights(ragdolls), TIME_OUT_SECONDS)
|
||||
for ragdoll in ragdolls:
|
||||
Report.info("{}'s maximum bounce height: {}".format(ragdoll.name, ragdoll.max_bounce_height))
|
||||
|
||||
# 6) Verify that the rubber ragdoll bounced higher than the concrete ragdoll
|
||||
Report.result(
|
||||
Tests.rubber_ragdoll_bounced_higher, rubber_ragdoll.max_bounce_height > concrete_ragdoll.max_bounce_height
|
||||
)
|
||||
|
||||
# 7) Verify that each ragdoll bounced approximately to its expected maximum height
|
||||
Report.result(
|
||||
Tests.concrete_ragdoll_bounced_as_expected,
|
||||
abs(concrete_ragdoll.max_bounce_height - CONCRETE_EXPECTED_MAX_BOUNCE_HEIGHT) < TOLERANCE,
|
||||
)
|
||||
Report.result(
|
||||
Tests.rubber_ragdoll_bounced_as_expected,
|
||||
abs(rubber_ragdoll.max_bounce_height - RUBBER_EXPECTED_MAX_BOUNCE_HEIGHT) < TOLERANCE,
|
||||
)
|
||||
|
||||
# 8) Exit game mode and close editor
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_RagdollBones)
|
||||
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
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 : C4044461
|
||||
# Test Case Title : Verify the functionality of restitution
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_ramp = ("Ramp entity found", "Ramp entity not found")
|
||||
find_box_zero = ("Box entity 'zero' found", "Box entity 'zero' not found")
|
||||
find_box_low = ("Box entity 'low' found", "Box entity 'low' not found")
|
||||
find_box_mid = ("Box entity 'mid' found", "Box entity 'mid' not found")
|
||||
find_box_high = ("Box entity 'high' found", "Box entity 'high' not found")
|
||||
box_fell_zero = ("Box 'zero' fell", "Box 'zero' did not fall")
|
||||
box_fell_low = ("Box 'low' fell", "Box 'low' did not fall")
|
||||
box_fell_mid = ("Box 'mid' fell", "Box 'mid' did not fall")
|
||||
box_fell_high = ("Box 'high' fell", "Box 'high' did not fall")
|
||||
box_hit_ramp_zero = ("Box 'zero' hit the ramp", "Box 'zero' did not hit the ramp before timeout")
|
||||
box_hit_ramp_low = ("Box 'low' hit the ramp", "Box 'low' did not hit the ramp before timeout")
|
||||
box_hit_ramp_mid = ("Box 'mid' hit the ramp", "Box 'mid' did not hit the ramp before timeout")
|
||||
box_hit_ramp_high = ("Box 'high' hit the ramp", "Box 'high' did not hit the ramp before timeout")
|
||||
box_peaked_zero = ("Box 'zero' reached its max height", "Box 'zero' did not reach max height before timeout")
|
||||
box_peaked_low = ("Box 'low' reached its max height", "Box 'low' did not reach max height before timeout")
|
||||
box_peaked_mid = ("Box 'mid' reached its max height", "Box 'mid' did not reach max height before timeout")
|
||||
box_peaked_high = ("Box 'high' reached its max height", "Box 'high' did not reach max height before timeout")
|
||||
box_zero_did_not_bounce = ("Box 'zero' did not bounce", "Box 'zero' bounced - this should not happen")
|
||||
bounce_height_ordered = ("Boxes with greater restitution values bounced higher", "Boxes with greater restitution values did not bounce higher")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_Restitution():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to ensure that greater restitution coefficient settings on a physX material results in
|
||||
rigid bodies (with that material) that bounce higher
|
||||
|
||||
Level Description:
|
||||
Four boxes sit above a horizontal 'ramp'. Gravity on each rigid body component is set to disabled.
|
||||
The boxes are identical, save for their physX material.
|
||||
|
||||
A new material library was created with 4 materials and their restitution coefficient:
|
||||
zero_restitution: 0.00
|
||||
low_restitution: 0.30
|
||||
mid_restitution: 0.60
|
||||
high_restitution: 1.00
|
||||
Each material is identical otherwise
|
||||
Each box is assigned its corresponding physX material
|
||||
|
||||
Expected Behavior:
|
||||
For each box, this script will enable gravity, then wait for the box to collide with the ramp.
|
||||
It then measures the height of the bounce relative to when it first came in contact with the ramp.
|
||||
When the z component of the box's velocity reaches zero (or below zero), the box latches its bounce height.
|
||||
The box is then frozen in place and the steps run for the next box in the list.
|
||||
|
||||
Boxes with greater restitution values should retain more energy between collisions, therefore bouncing higher
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find the ramp
|
||||
|
||||
For each box:
|
||||
4) Find the box
|
||||
5) Drop the box
|
||||
6) Ensure the box collides with the ramp
|
||||
7) Ensure the box reaches its peak height
|
||||
|
||||
8) Special case: assert that a box with zero restitution does not bounce
|
||||
9) Assert that greater restitution coefficients result in higher bounces
|
||||
10) Exit game mode
|
||||
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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
ZERO_RESTITUTION_BOUNCE_TOLERANCE = 0.001
|
||||
TIMEOUT = 5
|
||||
FALLING_TIMEOUT = 0.1
|
||||
|
||||
class Box:
|
||||
def __init__(self, name, valid_test, fell_test, hit_ramp_test, peaked_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.hit_ramp = False
|
||||
self.hit_ramp_position = None
|
||||
self.bounce_height = 0.0
|
||||
self.valid_test = valid_test
|
||||
self.fell_test = fell_test
|
||||
self.hit_ramp_test = hit_ramp_test
|
||||
self.peaked_test = peaked_test
|
||||
self.set_gravity_enabled(False)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
def set_velocity(self, value):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", self.id, value)
|
||||
|
||||
def set_gravity_enabled(self, value):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetGravityEnabled", self.id, value)
|
||||
|
||||
def on_collision_begin(args):
|
||||
other_id = args[0]
|
||||
for box in all_boxes:
|
||||
if box.id.Equal(other_id):
|
||||
box.hit_ramp_position = box.get_position()
|
||||
box.hit_ramp = True
|
||||
|
||||
def reached_max_height(box):
|
||||
current_position = box.get_position()
|
||||
current_height = current_position.z - box.hit_ramp_position.z
|
||||
current_linear_velocity = box.get_velocity()
|
||||
if current_linear_velocity.z > 0.0:
|
||||
box.bounce_height = current_height
|
||||
return False
|
||||
else:
|
||||
Report.info("Box {} reached {:.3f}M high".format(box.name, box.bounce_height))
|
||||
return True
|
||||
|
||||
def is_falling(box):
|
||||
return box.get_velocity().z < 0.0
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_Restitution")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# fmt: off
|
||||
# Set up our boxes
|
||||
box_zero = Box(
|
||||
name = "Zero",
|
||||
valid_test = Tests.find_box_zero,
|
||||
fell_test = Tests.box_fell_zero,
|
||||
hit_ramp_test = Tests.box_hit_ramp_zero,
|
||||
peaked_test = Tests.box_peaked_zero
|
||||
)
|
||||
box_low = Box(
|
||||
name = "Low",
|
||||
valid_test = Tests.find_box_low,
|
||||
fell_test = Tests.box_fell_low,
|
||||
hit_ramp_test = Tests.box_hit_ramp_low,
|
||||
peaked_test = Tests.box_peaked_low
|
||||
)
|
||||
box_mid = Box(
|
||||
name = "Mid",
|
||||
valid_test = Tests.find_box_mid,
|
||||
fell_test = Tests.box_fell_mid,
|
||||
hit_ramp_test = Tests.box_hit_ramp_mid,
|
||||
peaked_test = Tests.box_peaked_mid
|
||||
)
|
||||
box_high = Box(
|
||||
name = "High",
|
||||
valid_test = Tests.find_box_high,
|
||||
fell_test = Tests.box_fell_high,
|
||||
hit_ramp_test = Tests.box_hit_ramp_high,
|
||||
peaked_test = Tests.box_peaked_high
|
||||
)
|
||||
all_boxes = (box_zero, box_low, box_mid, box_high)
|
||||
# fmt:on
|
||||
|
||||
# 3) Find the ramp
|
||||
ramp_id = general.find_game_entity("Ramp")
|
||||
Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
|
||||
|
||||
handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
handler.connect(ramp_id)
|
||||
handler.add_callback("OnCollisionBegin", on_collision_begin)
|
||||
|
||||
for box in all_boxes:
|
||||
Report.info("********Dropping Box {}********".format(box.name))
|
||||
# 4) Find the box
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
|
||||
# 5) Drop the box
|
||||
box.set_gravity_enabled(True)
|
||||
Report.critical_result(box.fell_test, helper.wait_for_condition(lambda: is_falling(box), FALLING_TIMEOUT))
|
||||
|
||||
# 6) Wait for the box to hit the ramp
|
||||
Report.result(box.hit_ramp_test, helper.wait_for_condition(lambda: box.hit_ramp, TIMEOUT))
|
||||
|
||||
# 7) Measure the bounce height
|
||||
Report.result(box.peaked_test, helper.wait_for_condition(lambda: reached_max_height(box), TIMEOUT))
|
||||
|
||||
# Freeze the box so it does not interfere with the other boxes
|
||||
box.set_velocity(lymath.Vector3(0.0, 0.0, 0.0))
|
||||
box.set_gravity_enabled(False)
|
||||
|
||||
# 8) Special case: Assert the a box with zero restitution did not bounce
|
||||
Report.result(Tests.box_zero_did_not_bounce, box_zero.bounce_height < ZERO_RESTITUTION_BOUNCE_TOLERANCE)
|
||||
|
||||
# 9) Assert that greater restitution coefficients result in higher bounces
|
||||
ordered_bounces = box_high.bounce_height > box_mid.bounce_height > box_low.bounce_height > box_zero.bounce_height
|
||||
Report.result(Tests.bounce_height_ordered, ordered_bounces)
|
||||
|
||||
# 10) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_Restitution)
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
"""
|
||||
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 : C4044457
|
||||
# Test Case Title : Verify that when two objects with different materials collide, the restitution combine works
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_ramp = ("Ramp entity found", "Ramp entity not found")
|
||||
find_box_minimum = ("Box entity 'minimum' found", "Box entity 'minimum' not found")
|
||||
find_box_multiply = ("Box entity 'multiply' found", "Box entity 'multiply' not found")
|
||||
find_box_average = ("Box entity 'average' found", "Box entity 'average' not found")
|
||||
find_box_maximum = ("Box entity 'maximum' found", "Box entity 'maximum' not found")
|
||||
box_fell_minimum = ("Box 'minimum' fell", "Box 'minimum' did not fall")
|
||||
box_fell_multiply = ("Box 'multiply' fell", "Box 'multiply' did not fall")
|
||||
box_fell_average = ("Box 'average' fell", "Box 'average' did not fall")
|
||||
box_fell_maximum = ("Box 'maximum' fell", "Box 'maximum' did not fall")
|
||||
box_hit_ramp_minimum = ("Box 'minimum' hit the ramp", "Box 'minimum' did not hit the ramp before timeout")
|
||||
box_hit_ramp_multiply = ("Box 'multiply' hit the ramp", "Box 'multiply' did not hit the ramp before timeout")
|
||||
box_hit_ramp_average = ("Box 'average' hit the ramp", "Box 'average' did not hit the ramp before timeout")
|
||||
box_hit_ramp_maximum = ("Box 'maximum' hit the ramp", "Box 'maximum' did not hit the ramp before timeout")
|
||||
box_peaked_minimum = ("Box 'minimum' reached its max height", "Box 'minimum' did not reach its' max height before timeout")
|
||||
box_peaked_multiply = ("Box 'multiply' reached its max height", "Box 'multiply' did not reach its' max height before timeout")
|
||||
box_peaked_average = ("Box 'average' reached its max height", "Box 'average' did not reach its' max height before timeout")
|
||||
box_peaked_maximum = ("Box 'maximum' reached its max height", "Box 'maximum' did not reach its' max height before timeout")
|
||||
minimum_equals_multiply = ("Box 'minimum' and 'multiply' bounced equal heights", "Box 'minimum' and 'multiply' did not bounce equal heights")
|
||||
distance_ordered = ("Boxes with greater restitution values bounced higher", "Boxes with greater restitution values did not bounce higher")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_RestitutionCombine():
|
||||
"""
|
||||
Summary:
|
||||
|
||||
Level Description:
|
||||
Four boxes sit above a horizontal 'ramp'. Gravity on each rigidbody component is set to disabled.
|
||||
The boxes are identical, save for their physX material.
|
||||
|
||||
A new material library was created with 4 materials, minimum, multiply, average, and maximum.
|
||||
Each material has its 'restitution combine' mode assigned as named; as well as the following properties:
|
||||
dynamic friction: 0.1
|
||||
static friction: 0.1
|
||||
restitution: 0.1
|
||||
|
||||
An additional material was created for the ramp entity. It has the following properties:
|
||||
dynamic friction: 1.0
|
||||
static friction: 1.0
|
||||
restitution: 1.0
|
||||
friction combine: Average
|
||||
|
||||
Each box is assigned its corresponding material
|
||||
Each box also has a PhysX box collider with default settings
|
||||
|
||||
Expected Behavior:
|
||||
For each box, this script will enable gravity, then wait for the box to collide with the ramp.
|
||||
It then measures the height of the bounce relative to when it first came in contact with the ramp.
|
||||
When the z component of the box's velocity reaches zero (or below zero), the box latches its bounce height.
|
||||
The box is then frozen in place and the steps run for the next box in the list.
|
||||
|
||||
Boxes with greater restitution combine mode retain more energy between collisions, therefore bouncing higher.
|
||||
minimum: 0.1 vs 1 -> 0.1
|
||||
multiply: 0.1 * 1 -> 0.1
|
||||
average: (0.1 + 1) / 2 -> 0.55
|
||||
maximum: 0.1 vs 1 -> 1
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find the ramp
|
||||
|
||||
For each box:
|
||||
4) Find the box
|
||||
5) Drop the box
|
||||
6) Ensure the box collides with the ramp
|
||||
7) Ensure the box reaches its peak height
|
||||
|
||||
8) Special case: assert that minimum and multiply bounce the same height
|
||||
9) Assert that greater restitution combine modes bounce higher
|
||||
10) Exit game mode
|
||||
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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
DISTANCE_TOLERANCE = 0.005
|
||||
TIMEOUT = 5
|
||||
|
||||
class Box:
|
||||
def __init__(self, name, valid_test, fell_test, hit_ramp_test, peaked_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.hit_ramp = False
|
||||
self.hit_ramp_position = None
|
||||
self.bounce_height = 0.0
|
||||
self.valid_test = valid_test
|
||||
self.fell_test = fell_test
|
||||
self.hit_ramp_test = hit_ramp_test
|
||||
self.peaked_test = peaked_test
|
||||
self.set_gravity_enabled(False)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def get_velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
def set_velocity(self, value):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", self.id, value)
|
||||
|
||||
def set_gravity_enabled(self, value):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetGravityEnabled", self.id, value)
|
||||
|
||||
def on_collision_begin(args):
|
||||
other_id = args[0]
|
||||
for box in all_boxes:
|
||||
if box.id.Equal(other_id):
|
||||
box.hit_ramp_position = box.get_position()
|
||||
box.hit_ramp = True
|
||||
|
||||
def reached_max_height(box):
|
||||
current_position = box.get_position()
|
||||
current_height = current_position.z - box.hit_ramp_position.z
|
||||
current_linear_velocity = box.get_velocity()
|
||||
if current_linear_velocity.z > 0.0:
|
||||
box.bounce_height = current_height
|
||||
return False
|
||||
else:
|
||||
Report.info("Box {} reached {:.3f}M high".format(box.name, box.bounce_height))
|
||||
return True
|
||||
|
||||
def is_falling(box):
|
||||
return box.get_velocity().z < 0.0
|
||||
|
||||
def float_is_close(value, target, tolerance):
|
||||
return abs(value - target) <= tolerance
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_RestitutionCombine")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# fmt: off
|
||||
# Set up our boxes
|
||||
box_minimum = Box(
|
||||
name = "Minimum",
|
||||
valid_test = Tests.find_box_minimum,
|
||||
fell_test = Tests.box_fell_minimum,
|
||||
hit_ramp_test = Tests.box_hit_ramp_minimum,
|
||||
peaked_test = Tests.box_peaked_minimum,
|
||||
)
|
||||
box_multiply = Box(
|
||||
name = "Multiply",
|
||||
valid_test = Tests.find_box_multiply,
|
||||
fell_test = Tests.box_fell_multiply,
|
||||
hit_ramp_test = Tests.box_hit_ramp_multiply,
|
||||
peaked_test = Tests.box_peaked_multiply,
|
||||
)
|
||||
box_average = Box(
|
||||
name = "Average",
|
||||
valid_test = Tests.find_box_average,
|
||||
fell_test = Tests.box_fell_average,
|
||||
hit_ramp_test = Tests.box_hit_ramp_average,
|
||||
peaked_test = Tests.box_peaked_average,
|
||||
)
|
||||
box_maximum = Box(
|
||||
name = "Maximum",
|
||||
valid_test = Tests.find_box_maximum,
|
||||
fell_test = Tests.box_fell_maximum,
|
||||
hit_ramp_test = Tests.box_hit_ramp_maximum,
|
||||
peaked_test = Tests.box_peaked_maximum,
|
||||
)
|
||||
all_boxes = (box_minimum, box_multiply, box_average, box_maximum)
|
||||
# fmt: on
|
||||
|
||||
# 3) Find the ramp
|
||||
ramp_id = general.find_game_entity("Ramp")
|
||||
Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
|
||||
|
||||
handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
handler.connect(ramp_id)
|
||||
handler.add_callback("OnCollisionBegin", on_collision_begin)
|
||||
|
||||
for box in all_boxes:
|
||||
Report.info("********Dropping Box {}********".format(box.name))
|
||||
# 4) Find the box
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
|
||||
# 5) Drop the box
|
||||
box.set_gravity_enabled(True)
|
||||
Report.critical_result(box.fell_test, helper.wait_for_condition(lambda: is_falling(box), TIMEOUT))
|
||||
|
||||
# 6) Wait for the box to hit the ground
|
||||
Report.result(box.hit_ramp_test, helper.wait_for_condition(lambda: box.hit_ramp, TIMEOUT))
|
||||
|
||||
# 7) Measure the bounce height
|
||||
Report.result(box.peaked_test, helper.wait_for_condition(lambda: reached_max_height(box), TIMEOUT))
|
||||
|
||||
# Freeze the box so it does not interfere with the other boxes
|
||||
box.set_velocity(lymath.Vector3(0.0, 0.0, 0.0))
|
||||
box.set_gravity_enabled(False)
|
||||
|
||||
# 8) Special case: assert that minimum and multiply bounce the same height
|
||||
boxes_are_close = float_is_close(box_minimum.bounce_height, box_multiply.bounce_height, DISTANCE_TOLERANCE)
|
||||
Report.result(Tests.minimum_equals_multiply, boxes_are_close)
|
||||
|
||||
# 9) Assert that greater coefficients result in higher bounces
|
||||
distance_ordered = (
|
||||
boxes_are_close and box_minimum.bounce_height < box_average.bounce_height < box_maximum.bounce_height
|
||||
)
|
||||
Report.result(Tests.distance_ordered, distance_ordered)
|
||||
|
||||
# 10) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_RestitutionCombine)
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
"""
|
||||
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 : C18981526
|
||||
# Test Case Title : Verify when two objects with different materials collide, the restitution combine priority works
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_box_average = ("Box entity 'average' found", "Box entity 'average' not found")
|
||||
find_box_minimum = ("Box entity 'minimum' found", "Box entity 'minimum' not found")
|
||||
find_box_multiply = ("Box entity 'multiply' found", "Box entity 'multiply' not found")
|
||||
find_box_maximum = ("Box entity 'maximum' found", "Box entity 'maximum' not found")
|
||||
find_ramp_average = ("Ramp entity 'average' found", "Ramp entity 'average' not found")
|
||||
find_ramp_minimum = ("Ramp entity 'minimum' found", "Ramp entity 'minimum' not found")
|
||||
find_ramp_multiply = ("Ramp entity 'multiply' found", "Ramp entity 'multiply' not found")
|
||||
find_ramp_maximum = ("Ramp entity 'maximum' found", "Ramp entity 'maximum' not found")
|
||||
|
||||
# Test 0, first row of matrix
|
||||
boxes_fell_0 = ("Test 0): All boxes fell", "Test 0): All boxes did not fall")
|
||||
boxes_hit_ramp_0 = ("Test 0): All boxes hit the ramp", "Test 0): All boxes did not hit the ramp")
|
||||
boxes_peaked_0 = ("Test 0): All boxes reached their max height", "Test 0): All boxes did not reach their max height before timeout")
|
||||
|
||||
# Test 1, second row of matrix
|
||||
boxes_fell_1 = ("Test 1): All boxes fell", "Test 1): All boxes did not fall")
|
||||
boxes_hit_ramp_1 = ("Test 1): All boxes hit the ramp", "Test 1): All boxes did not hit the ramp")
|
||||
boxes_peaked_1 = ("Test 1): All boxes reached their max height", "Test 1): All boxes did not reach their max height before timeout")
|
||||
|
||||
# Test 2, third row of matrix
|
||||
boxes_fell_2 = ("Test 2): All boxes fell", "Test 2): All boxes did not fall")
|
||||
boxes_hit_ramp_2 = ("Test 2): All boxes hit the ramp", "Test 2): All boxes did not hit the ramp")
|
||||
boxes_peaked_2 = ("Test 2): All boxes reached their max height", "Test 2): All boxes did not reach their max height before timeout")
|
||||
|
||||
# Test 3, fourth row of matrix
|
||||
boxes_fell_3 = ("Test 3): All boxes fell", "Test 3): All boxes did not fall")
|
||||
boxes_hit_ramp_3 = ("Test 3): All boxes hit the ramp", "Test 3): All boxes did not hit the ramp")
|
||||
boxes_peaked_3 = ("Test 3): All boxes reached their max height", "Test 3): All boxes did not reach their max height before timeout")
|
||||
|
||||
basis_row_unique = ("All distances in Test 0 were unique", "All distances in Test 0 were not unique")
|
||||
basis_row_ordered = ("All distances in Test 0 were correctly ordered", "All distances in Test 0 were not correctly ordered")
|
||||
height_matrix_valid = ("The resulting height matrix was valid", "The resulting height matrix was invalid")
|
||||
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_RestitutionCombinePriorityOrder():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to ensure that the restitution combine mode is assigned according to the correct priority.
|
||||
|
||||
Level Description:
|
||||
Four boxes sit above one of 4 horizontal ramps.
|
||||
The ramps are identical, as are the boxes, save for their physX material:
|
||||
|
||||
A new material library was created with 8 materials:
|
||||
minimum_box, multiply_box, average_box, maximum_box, minimum_ramp, multiply_ramp, average_ramp, maximum_ramp,
|
||||
Each 'box' material has its 'restitution combine' mode assigned as named; as well as the following properties:
|
||||
dynamic friction: 0.25
|
||||
static friction: 0.25
|
||||
restitution: 0.25
|
||||
The 'ramp' materials are assigned similarly, with the following values:
|
||||
dynamic friction: 0.5
|
||||
static friction: 0.5
|
||||
restitution: 0.5
|
||||
|
||||
The values were specifically chosen to give a well-ordered and distributed result across all 4 combine modes
|
||||
(each progressive tier in priority gives a result 0.125 away from the last)
|
||||
|
||||
Each box and ramp is assigned its corresponding restitution material
|
||||
Each box and ramp also has a PhysX box collider with default settings
|
||||
|
||||
Expected Behavior:
|
||||
When two bodies with different materials are in contact, the physics system chooses which combine mode to use based
|
||||
on which combine mode has the highest priority.
|
||||
|
||||
The priority order is as follows: Average < Minimum < Multiply < Maximum.
|
||||
|
||||
For each ramp, this script drops the four boxes and measures their bounce height
|
||||
|
||||
Upon collecting all data, the script evaluates the bounce height against an expected pattern.
|
||||
This pattern is derived from the priority order, to determine which combine mode should "win" over the others.
|
||||
Boxes with greater restitution combine coefficients should bounce higher.
|
||||
|
||||
[Coefficient Combination Mode Results]
|
||||
average: (0.25 + 0.5) / 2 -> 0.375
|
||||
minimum: 0.25 vs 0.5 -> 0.25
|
||||
multiply: 0.25 * 0.5 -> 0.125
|
||||
maximum: 0.25 vs 0.5 -> 0.5
|
||||
|
||||
[Coefficient Combination Matrix]
|
||||
Boxes
|
||||
avg min mul max
|
||||
avg 0.375 0.25 0.125 0.5 # Test 0
|
||||
Ramps min 0.25 0.25 0.125 0.5 # Test 1
|
||||
mul 0.125 0.125 0.125 0.5 # Test 2
|
||||
max 0.5 0.5 0.5 0.5 # Test 3
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Validate entities
|
||||
|
||||
For each ramp:
|
||||
4) Replace the ramp under the boxes
|
||||
5) Drop the boxes
|
||||
6) Wait for the box to hit the ground
|
||||
7) Measure the bounce height
|
||||
|
||||
8) Validate matrix
|
||||
9) Exit game mode
|
||||
10) 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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
NUMBER_OF_TESTS = 4
|
||||
DISTANCE_TOLERANCE = 0.005
|
||||
TIMEOUT = 5.0
|
||||
STANDBY_OFFSET = lymath.Vector3(0.0, 0.0, 4.0)
|
||||
SET_PHYSICS_WAIT = 10
|
||||
|
||||
# region Entity Classes
|
||||
class Box:
|
||||
def __init__(self, name, valid_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.hit_ramp_position = None
|
||||
self.valid_test = valid_test
|
||||
self.peaked = False
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def set_position(self, value):
|
||||
return azlmbr.components.TransformBus(bus.Event, "SetWorldTranslation", self.id, value)
|
||||
|
||||
def get_velocity(self):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
|
||||
def set_velocity(self, value):
|
||||
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", self.id, value)
|
||||
|
||||
def set_gravity_enabled(self, value):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetGravityEnabled", self.id, value)
|
||||
|
||||
def set_physics_enabled(self, value):
|
||||
if value:
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "EnablePhysics", self.id)
|
||||
else:
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "DisablePhysics", self.id)
|
||||
|
||||
def force_awake(self):
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ForceAwake", self.id)
|
||||
|
||||
class Ramp:
|
||||
def __init__(self, name, valid_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.valid_test = valid_test
|
||||
self.create_handler()
|
||||
self.collided_with_boxes = set()
|
||||
|
||||
def on_collision_begin(self, args):
|
||||
other_id = args[0]
|
||||
for box in all_boxes:
|
||||
if box.id.Equal(other_id):
|
||||
Report.info("Collided with {}".format(box.name))
|
||||
self.collided_with_boxes.add(box)
|
||||
box.hit_ramp_position = box.get_position()
|
||||
|
||||
def create_handler(self):
|
||||
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
|
||||
self.handler.connect(self.id)
|
||||
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def set_position(self, value):
|
||||
return azlmbr.components.TransformBus(bus.Event, "SetWorldTranslation", self.id, value)
|
||||
|
||||
def all_boxes_hit(self):
|
||||
return len(self.collided_with_boxes) == 4
|
||||
|
||||
class TestInfo:
|
||||
def __init__(self):
|
||||
self.fell_tests = []
|
||||
self.hit_ramp_tests = []
|
||||
self.peaked_tests = []
|
||||
for i in range(NUMBER_OF_TESTS):
|
||||
self.fell_tests.append(get_test("boxes_fell", i))
|
||||
self.hit_ramp_tests.append(get_test("boxes_hit_ramp", i))
|
||||
self.peaked_tests.append(get_test("boxes_peaked", i))
|
||||
# endregion
|
||||
|
||||
# region Helper Functions
|
||||
def get_test(test_name, test_number):
|
||||
return Tests.__dict__["{}_{}".format(test_name, test_number)]
|
||||
|
||||
def reset_boxes():
|
||||
for box in all_boxes:
|
||||
box.peaked = False
|
||||
box.set_physics_enabled(False)
|
||||
|
||||
# We can't enable the boxes as kinematic and set their position on the same frame
|
||||
general.idle_wait_frames(SET_PHYSICS_WAIT)
|
||||
|
||||
for box in all_boxes:
|
||||
box.set_position(box.start_position)
|
||||
|
||||
general.idle_wait_frames(SET_PHYSICS_WAIT)
|
||||
|
||||
for box in all_boxes:
|
||||
box.set_physics_enabled(True)
|
||||
box.force_awake()
|
||||
# endregion
|
||||
|
||||
# region wait_for_condition() Functions
|
||||
def drop_boxes():
|
||||
for box in all_boxes:
|
||||
box.set_gravity_enabled(True)
|
||||
|
||||
def all_boxes_falling():
|
||||
for box in all_boxes:
|
||||
if box.get_velocity().z >= 0.0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def all_boxes_peaked():
|
||||
peaked_boxes = 0
|
||||
for box in all_boxes:
|
||||
if box.peaked:
|
||||
peaked_boxes += 1
|
||||
else:
|
||||
current_position = box.get_position()
|
||||
current_height = current_position.z - box.hit_ramp_position.z
|
||||
current_linear_velocity = box.get_velocity()
|
||||
if current_linear_velocity.z > 0.0:
|
||||
box.bounce_height = current_height
|
||||
else:
|
||||
Report.info("Box {} reached {:.3f}M high".format(box.name, box.bounce_height))
|
||||
box.set_gravity_enabled(False)
|
||||
box.set_velocity(lymath.Vector3(0.0, 0.0, 0.0))
|
||||
box.peaked = True
|
||||
return peaked_boxes == 4
|
||||
# endregion
|
||||
|
||||
# region Matrix Validation
|
||||
def validate_matrix(matrix):
|
||||
# type: (list[list]) -> bool
|
||||
"""
|
||||
Returns True if the matrix matches the pattern expected based on the friction combine priority.
|
||||
|
||||
:param matrix: the height matrix
|
||||
|
||||
:return: True if the matrix closely matches the expected pattern
|
||||
"""
|
||||
# The first test represents a basis value for what is expected when each of the combine modes "wins" the other.
|
||||
# This is because every mode beats 'average' (the first ramp we test with) We can compare the rest of the matrix
|
||||
# against this basis row to validate the rest of the matrix as long as we also guarantee that the basis is valid
|
||||
#
|
||||
# Resulting matrix should follow the pattern:
|
||||
# A B C D <- Test 0
|
||||
# B B C D <- Test 1
|
||||
# C C C D <- Test 2
|
||||
# D D D D <- Test 3
|
||||
|
||||
basis_row = matrix[0]
|
||||
Report.critical_result(Tests.basis_row_unique, list_is_unique(basis_row))
|
||||
|
||||
average = basis_row[0]
|
||||
minimum = basis_row[1]
|
||||
multiply = basis_row[2]
|
||||
maximum = basis_row[3]
|
||||
# Based on the resulting coefficients, we can expect each bounce height to be ordered in a specific way
|
||||
Report.critical_result(Tests.basis_row_ordered, maximum > average > minimum > multiply)
|
||||
|
||||
def report_failure(test_index, box_index, expected):
|
||||
box_name = all_boxes[box_index].name
|
||||
Report.info(
|
||||
"Matrix validation failure:\n"
|
||||
"Bounce height for box '{}' on test {} was not close to the expected basis value\n"
|
||||
"Bounce height: {:.3f}\n"
|
||||
"Expected: {:.3f}".format(box_name, test_index, matrix[test_index][box_index], expected)
|
||||
)
|
||||
|
||||
valid = True
|
||||
for row_index, row in enumerate(matrix):
|
||||
for column_index, value in enumerate(row):
|
||||
max_index = max(row_index, column_index)
|
||||
|
||||
if not float_is_close(value, basis_row[max_index], DISTANCE_TOLERANCE):
|
||||
report_failure(row_index, column_index, basis_row[max_index])
|
||||
valid = False
|
||||
return valid
|
||||
|
||||
def log_matrix(matrix):
|
||||
matrix_display_string = "\nResulting Height Matrix:\n"
|
||||
for row in matrix:
|
||||
for value in row:
|
||||
matrix_display_string += "{:.3f},".format(value)
|
||||
matrix_display_string += "\n"
|
||||
Report.info(matrix_display_string)
|
||||
|
||||
def list_is_unique(target_list):
|
||||
return len(set(target_list)) == len(target_list)
|
||||
|
||||
def float_is_close(value, target, tolerance):
|
||||
return abs(value - target) <= tolerance
|
||||
# endregion
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_RestitutionCombinePriorityOrder")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# Set up our boxes
|
||||
box_average = Box("Average", Tests.find_box_average)
|
||||
box_minimum = Box("Minimum", Tests.find_box_minimum)
|
||||
box_multiply = Box("Multiply", Tests.find_box_multiply)
|
||||
box_maximum = Box("Maximum", Tests.find_box_maximum)
|
||||
all_boxes = (box_average, box_minimum, box_multiply, box_maximum)
|
||||
|
||||
# Set up our ramps
|
||||
ramp_average = Ramp("RampAverage", Tests.find_ramp_average)
|
||||
ramp_minimum = Ramp("RampMinimum", Tests.find_ramp_minimum)
|
||||
ramp_multiply = Ramp("RampMultiply", Tests.find_ramp_multiply)
|
||||
ramp_maximum = Ramp("RampMaximum", Tests.find_ramp_maximum)
|
||||
all_ramps = (ramp_average, ramp_minimum, ramp_multiply, ramp_maximum)
|
||||
|
||||
# Init our tests
|
||||
test_info = TestInfo()
|
||||
|
||||
# 3) Validate entities
|
||||
for box in all_boxes:
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
|
||||
for ramp in all_ramps:
|
||||
Report.critical_result(ramp.valid_test, ramp.id.IsValid())
|
||||
|
||||
# Setup ramp active position. The 'average' ramp is the first ramp, so we init to that.
|
||||
active_position = ramp_average.get_position()
|
||||
|
||||
# fmt: off
|
||||
height_matrix = [[0.0, 0.0, 0.0, 0.0], # Test 0 - Ramp 'Average'
|
||||
[0.0, 0.0, 0.0, 0.0], # Test 1 - Ramp 'Minimum'
|
||||
[0.0, 0.0, 0.0, 0.0], # Test 2 - Ramp 'Multiply'
|
||||
[0.0, 0.0, 0.0, 0.0]] # Test 3 - Ramp 'Maximum'
|
||||
# fmt: on
|
||||
|
||||
for row_index in range(len(height_matrix)):
|
||||
Report.info("********Starting Test {}********".format(row_index))
|
||||
reset_boxes()
|
||||
|
||||
# 4) Replace the ramp under the boxes
|
||||
ramp = all_ramps[row_index]
|
||||
ramp.set_position(active_position)
|
||||
|
||||
# 5) Drop the boxes
|
||||
drop_boxes()
|
||||
|
||||
fell_test = test_info.fell_tests[row_index]
|
||||
Report.critical_result(fell_test, helper.wait_for_condition(all_boxes_falling, TIMEOUT))
|
||||
|
||||
# 6) Wait for the box to hit the ground
|
||||
hit_ramp_test = test_info.hit_ramp_tests[row_index]
|
||||
Report.critical_result(hit_ramp_test, helper.wait_for_condition(ramp.all_boxes_hit, TIMEOUT))
|
||||
|
||||
# 7) Measure the bounce height
|
||||
peaked_test = test_info.peaked_tests[row_index]
|
||||
Report.critical_result(peaked_test, helper.wait_for_condition(all_boxes_peaked, TIMEOUT))
|
||||
|
||||
for column_index in range(len(height_matrix[row_index])):
|
||||
# Register the height the boxes bounced
|
||||
box = all_boxes[column_index]
|
||||
height_matrix[row_index][column_index] = box.bounce_height
|
||||
|
||||
ramp.set_position(ramp.start_position.Subtract(STANDBY_OFFSET))
|
||||
|
||||
# 8) Validate matrix
|
||||
log_matrix(height_matrix)
|
||||
Report.result(Tests.height_matrix_valid, validate_matrix(height_matrix))
|
||||
|
||||
# 9) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_RestitutionCombinePriorityOrder)
|
||||
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
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 : C4044460
|
||||
# Test Case Title : Verify the functionality of static friction
|
||||
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests():
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
find_ramp = ("Ramp entity found", "Ramp entity not found")
|
||||
find_box_zero = ("Box entity 'zero' found", "Box entity 'zero' not found")
|
||||
find_box_low = ("Box entity 'low' found", "Box entity 'low' not found")
|
||||
find_box_mid = ("Box entity 'mid' found", "Box entity 'mid' not found")
|
||||
find_box_high = ("Box entity 'high' found", "Box entity 'high' not found")
|
||||
box_at_rest_zero = ("Box 'zero' began test motionless", "Box 'zero' did not begin test motionless")
|
||||
box_at_rest_low = ("Box 'low' began test motionless", "Box 'low' did not begin test motionless")
|
||||
box_at_rest_mid = ("Box 'mid' began test motionless", "Box 'mid' did not begin test motionless")
|
||||
box_at_rest_high = ("Box 'high' began test motionless", "Box 'high' did not begin test motionless")
|
||||
box_was_pushed_zero = ("Box 'zero' moved", "Box 'zero' did not move before timeout")
|
||||
box_was_pushed_low = ("Box 'low' moved", "Box 'low' did not move before timeout")
|
||||
box_was_pushed_mid = ("Box 'mid' moved", "Box 'mid' did not move before timeout")
|
||||
box_was_pushed_high = ("Box 'high' moved", "Box 'high' did not move before timeout")
|
||||
force_impulse_ordered = ("Boxes with greater static friction required greater impulses", "Boxes with greater static friction did not require greater impulses")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def Material_StaticFriction():
|
||||
"""
|
||||
Summary:
|
||||
Runs an automated test to ensure that greater static friction coefficient settings on a physX material results in
|
||||
rigidbodys (with that material) requiring a greater force in order to be set into motion
|
||||
|
||||
Level Description:
|
||||
Four boxes sit on a horizontal 'ramp'. The boxes are identical, save for their physX material.
|
||||
|
||||
A new material library was created with 4 materials and their static friction coefficient:
|
||||
zero_static_friction: 0.00
|
||||
low_static_friction: 0.50
|
||||
mid_static_friction: 1.00
|
||||
high_static_friction: 1.50
|
||||
Each material is identical otherwise
|
||||
Each box is assigned its corresponding friction material, the ramp is assigned low_static_friction
|
||||
The COM of the boxes is placed on the plane (0, 0, -0.5), so as to remove any torque moments and resulting rotations
|
||||
|
||||
Expected Behavior:
|
||||
For each box, this script will apply a force impulse in the world X direction (starting at magnitude 0.0).
|
||||
Every frame, it checks if the box moved:
|
||||
If it didn't, we increase the magnitude slightly and try again
|
||||
If it did, the box retains the magnitude required to move it, and we move to the next box.
|
||||
|
||||
Boxes with greater static friction coefficients should require greater forces in order to set them in motion.
|
||||
|
||||
Test Steps:
|
||||
1) Open level
|
||||
2) Enter game mode
|
||||
3) Find the ramp
|
||||
|
||||
For each box:
|
||||
4) Find the box
|
||||
5) Ensure the box is stationary
|
||||
6) Push the box until it moves
|
||||
|
||||
7) Assert that greater coefficients result in greater required force impulses
|
||||
8) Exit game mode
|
||||
9) 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
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.math as lymath
|
||||
|
||||
FORCE_IMPULSE_INCREMENT = 0.005 # How much we increase the force every frame
|
||||
MIN_MOVE_DISTANCE = 0.02 # Distance magnitude that a box must travel in order to be considered moved
|
||||
STATIONARY_TOLERANCE = 0.0001 # Boxes must have velocities under this magnitude in order to be stationary
|
||||
TIMEOUT = 10
|
||||
|
||||
class Box:
|
||||
def __init__(self, name, valid_test, stationary_test, moved_test):
|
||||
self.name = name
|
||||
self.id = general.find_game_entity(name)
|
||||
self.start_position = self.get_position()
|
||||
self.force_impulse = 0.0
|
||||
self.valid_test = valid_test
|
||||
self.stationary_test = stationary_test
|
||||
self.moved_test = moved_test
|
||||
|
||||
def is_stationary(self):
|
||||
velocity = azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
|
||||
return vector_close_to_zero(velocity, STATIONARY_TOLERANCE)
|
||||
|
||||
def get_position(self):
|
||||
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
|
||||
|
||||
def vector_close_to_zero(vector, tolerance):
|
||||
return abs(vector.x) <= tolerance and abs(vector.y) <= tolerance and abs(vector.z) <= tolerance
|
||||
|
||||
def push(box):
|
||||
delta = box.start_position.Subtract(box.get_position())
|
||||
if vector_close_to_zero(delta, MIN_MOVE_DISTANCE):
|
||||
box.force_impulse += FORCE_IMPULSE_INCREMENT
|
||||
impulse_vector = lymath.Vector3(box.force_impulse, 0.0, 0.0)
|
||||
azlmbr.physics.RigidBodyRequestBus(bus.Event, "ApplyLinearImpulse", box.id, impulse_vector)
|
||||
return False
|
||||
else:
|
||||
Report.info("Box {} required force was {:.3f}".format(box.name, box.force_impulse))
|
||||
return True
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Open level
|
||||
helper.open_level("Physics", "Material_StaticFriction")
|
||||
|
||||
# 2) Enter game mode
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
|
||||
# fmt: off
|
||||
# Set up our boxes
|
||||
box_zero = Box(
|
||||
name = "Zero",
|
||||
valid_test = Tests.find_box_zero,
|
||||
stationary_test = Tests.box_at_rest_zero,
|
||||
moved_test = Tests.box_was_pushed_zero
|
||||
)
|
||||
box_low = Box(
|
||||
name = "Low",
|
||||
valid_test = Tests.find_box_low,
|
||||
stationary_test = Tests.box_at_rest_low,
|
||||
moved_test = Tests.box_was_pushed_low
|
||||
)
|
||||
box_mid = Box(
|
||||
name = "Mid",
|
||||
valid_test = Tests.find_box_mid,
|
||||
stationary_test = Tests.box_at_rest_mid,
|
||||
moved_test = Tests.box_was_pushed_mid
|
||||
)
|
||||
box_high = Box(
|
||||
name = "High",
|
||||
valid_test = Tests.find_box_high,
|
||||
stationary_test = Tests.box_at_rest_high,
|
||||
moved_test = Tests.box_was_pushed_high
|
||||
)
|
||||
all_boxes = (box_zero, box_low, box_mid, box_high)
|
||||
# fmt: on
|
||||
|
||||
# 3) Find the ramp
|
||||
ramp_id = general.find_game_entity("Ramp")
|
||||
Report.critical_result(Tests.find_ramp, ramp_id.IsValid())
|
||||
|
||||
for box in all_boxes:
|
||||
Report.info("********Pushing Box {}********".format(box.name))
|
||||
# 4) Find the box
|
||||
Report.critical_result(box.valid_test, box.id.IsValid())
|
||||
# 5) Ensure the box is stationary
|
||||
Report.result(box.stationary_test, box.is_stationary())
|
||||
# 6) Push the box until it moves
|
||||
Report.critical_result(box.moved_test, helper.wait_for_condition(lambda: push(box), TIMEOUT))
|
||||
|
||||
# 7) Assert that greater coefficients result in greater required force impulses
|
||||
ordered_impulses = box_high.force_impulse > box_mid.force_impulse > box_low.force_impulse > box_zero.force_impulse
|
||||
Report.result(Tests.force_impulse_ordered, ordered_impulses)
|
||||
|
||||
# 8) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(Material_StaticFriction)
|
||||
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
import os
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
class Physmaterial_Editor:
|
||||
"""
|
||||
This class is used to adjust physmaterial files for use with Open 3D Engine.
|
||||
|
||||
NOTEWORTHY:
|
||||
- Must use save_changes() for library modifications to take affect
|
||||
- Once file is overwritten there is a small lag before the editor applies these changes. Tests
|
||||
must be set up to allow time for this lag.
|
||||
- You can use parse() to overwrite the Physmaterial_Editor object with a new file
|
||||
|
||||
Methods:
|
||||
- __init__ (self, document_filename = None): Sets up Physmaterial Instance
|
||||
- document_filename (type: string): the full path of your physmaterial file
|
||||
- parse_file (self): Loads the material library into memory and creates and indexable root object.
|
||||
- save_changes (self): Overwrites the contents of the input file with the modified library. Unless
|
||||
this is called no changes will occur
|
||||
- modify_material (self, material, attribute, value): Modifies a given material. Adjusts values
|
||||
if possible, throws errors if not
|
||||
- material (type: string): The name of the material, must be exact
|
||||
- attribute (type: string): Name of the attribute, must be exact. Restrictions outlined below
|
||||
- value (type: string, int, or float): New value for the given attribute. Restrictions
|
||||
outlined below
|
||||
- delete_material (self, material): Deletes given material from the library.
|
||||
- material (type: string): The name of the material, must be exact
|
||||
|
||||
Properties:
|
||||
- number_of_materials: Number of materials in the material library
|
||||
|
||||
Input Restrictions:
|
||||
- Attribute: Can only be one of the five following values
|
||||
- 'Dynamic Friction'
|
||||
- 'Static Friction'
|
||||
- 'Restitution'
|
||||
- 'Friction Combine'
|
||||
- 'Restitution Combine'
|
||||
- Friction Values: Must be a number either int or float
|
||||
- Restitution Values: Must be a number either int or float between 0 and 1
|
||||
- Combine Values: Can only be one of the four following values
|
||||
- 'Average'
|
||||
- 'Minimum'
|
||||
- 'Maximum'
|
||||
- 'Multiply'
|
||||
|
||||
notes:
|
||||
- Due to the setup of material libraries root has a lot of indices that must be used to get to the
|
||||
actual library portion. There does not seem to be an easy way to remedy this issue as it makes
|
||||
for a difficult rewrite process
|
||||
- parse_file must only be called if the file path is not given during initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, document=None):
|
||||
self.document_filename = document
|
||||
self.project_folder = general.get_game_folder()
|
||||
self._set_path()
|
||||
self.parse_file()
|
||||
|
||||
def parse_file(self):
|
||||
# type: (str) -> None
|
||||
# See if a file exists at the given path
|
||||
if not os.path.exists(self.document_filename):
|
||||
raise ValueError("Given file, {} ,does not exist".format(self.document_filename))
|
||||
# Brings Material Library contents into memory
|
||||
try:
|
||||
self.dom = ElementTree.parse(self.document_filename)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise ValueError('{} not valid'.format(self.document_filename))
|
||||
# Turn parsed xml into usable form
|
||||
self.root = self.dom.getroot()
|
||||
# Check if file is a material library
|
||||
asset_typename = self.root[0].get('name')
|
||||
if not asset_typename == "MaterialLibraryAsset":
|
||||
if asset_typename:
|
||||
print("Given file is a {} file".format(self.root[0].get('name')))
|
||||
raise ValueError('File not valid')
|
||||
|
||||
def save_changes(self):
|
||||
# type: (None) -> None
|
||||
# Over writes file with modified material library contents
|
||||
content = ElementTree.tostring(self.root)
|
||||
try:
|
||||
with open(self.document_filename, "wb") as document:
|
||||
document.write(content)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Failed to save changes to script")
|
||||
|
||||
# Temporary fix, will need to use OnAssetReloaded callbacks
|
||||
general.idle_wait(0.5)
|
||||
|
||||
def delete_material(self, material):
|
||||
# type: (str) -> bool
|
||||
# Deletes a material from the library
|
||||
index = self._find_material_index(material)
|
||||
if index != None:
|
||||
self.root[0][1].remove(self.root[0][1][index])
|
||||
return True
|
||||
else:
|
||||
print("{} not found in library. No deletion occurred.".format(material))
|
||||
return False
|
||||
|
||||
def modify_material(self, material, attribute, value):
|
||||
# type: (str, str, float) -> bool
|
||||
# Modifies attributes of a given material in the library
|
||||
index = self._find_material_index(material)
|
||||
attribute_index = Physmaterial_Editor._get_attribute_index(attribute)
|
||||
formated_value = Physmaterial_Editor._value_formater(value, 'Restitution' == attribute, 'Combine' in attribute)
|
||||
if index != None:
|
||||
self.root[0][1][index][0][attribute_index].set('value', formated_value)
|
||||
return True
|
||||
else:
|
||||
print("{} not found in library. No modification of {} occurred.".format(material, attribute))
|
||||
return False
|
||||
|
||||
@property
|
||||
def number_of_materials(self):
|
||||
# type: (str) -> int
|
||||
materials = self.root[0][1].findall(".//Class[@name='MaterialFromAssetConfiguration']")
|
||||
return len(materials)
|
||||
|
||||
def _set_path(self):
|
||||
# type: (str) -> str
|
||||
if self.document_filename == None:
|
||||
self.document_filename = os.path.join(self.project_folder, "assets", "physics", "surfacetypemateriallibrary.physmaterial")
|
||||
else:
|
||||
for (root, directories, root_files) in os.walk(self.project_folder):
|
||||
for root_file in root_files:
|
||||
if root_file == self.document_filename:
|
||||
self.document_filename = os.path.join(root, root_file)
|
||||
break
|
||||
|
||||
def _find_material_index(self, material):
|
||||
# type: (str) -> int
|
||||
found = False
|
||||
material_index = None
|
||||
for index, child in enumerate(self.root[0][1]):
|
||||
if child.findall(".//Class[@value='{}']".format(material)):
|
||||
if not found:
|
||||
found = True
|
||||
material_index = index
|
||||
return material_index
|
||||
|
||||
@staticmethod
|
||||
def _value_formater(value, is_restitution, is_combine):
|
||||
# type: (float/int/str, bool, bool) -> str
|
||||
# Constants
|
||||
MIN_RESTITUTION = 0.0000000
|
||||
MAX_RESTITUTION = 1.0000000
|
||||
|
||||
if is_combine:
|
||||
value = Physmaterial_Editor._get_combine_id(value)
|
||||
else:
|
||||
if isinstance(value, int) or isinstance(value, float):
|
||||
if is_restitution:
|
||||
value = max(min(value, MAX_RESTITUTION), MIN_RESTITUTION)
|
||||
value = "{:.7f}".format(value)
|
||||
else:
|
||||
raise ValueError("Must enter int or float. Entered value was of type {}.".format(type(value)))
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _get_combine_id(combine_name):
|
||||
# type: (str) -> int
|
||||
# Maps the Combine mode to its enumerated value used by the Open 3D Engine Editor
|
||||
combine_dictionary = {"Average": "0", "Minimum": "1", "Maximum": "2", "Multiply": "3"}
|
||||
if combine_name not in combine_dictionary:
|
||||
raise ValueError("Invalid Combine Value given. {} is not in combine map".format(combine_name))
|
||||
return combine_dictionary[combine_name]
|
||||
|
||||
@staticmethod
|
||||
def _get_attribute_index(attribute):
|
||||
# type: (str) -> int
|
||||
# Maps the attribute names to their corresponding index relative to the line defining the material name.
|
||||
attribute_dictionary = {
|
||||
"DynamicFriction": 1,
|
||||
"StaticFriction": 2,
|
||||
"Restitution": 3,
|
||||
"FrictionCombine": 4,
|
||||
"RestitutionCombine": 5,
|
||||
}
|
||||
if attribute not in attribute_dictionary:
|
||||
raise ValueError("Invalid Material Attribute given. {} is not in attribute map".format(attribute))
|
||||
return attribute_dictionary[attribute]
|
||||
Reference in New Issue
Block a user