rename to tmp name

This commit is contained in:
AMZN-AlexOteiza
2021-09-10 14:28:40 +02:00
parent c3e285cff7
commit ddd662f6b9
160 changed files with 0 additions and 0 deletions
@@ -0,0 +1,102 @@
"""
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 : C6131473
# Test Case Title : Verify a static slice is not spawned automatically everytime a dynamic slice with
# PhysX Components is spawned
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_entity = ("Entities are found", "Entities are not found")
dynamic_slice_entity = ("Dynamic slice entity not found", "Dynamic slice entity found")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Physics_DynamicSliceWithPhysNotSpawnsStaticSlice():
"""
Summary:
Verify a static slice is not spawned automatically everytime a dynamic slice with PhysX Components is spawned
Level Description:
Spawner (entity) - Entity with a spawner component with a dynamic slice attached to it.
"Spawn on activate" is enabled for the spawner component.
The Spawner is attached to a dynamic slice "RigidBody" with components
PhysX Rigid body,
PhysX Collider with shape as Sphere and radius as 1.0,
Rendering mesh with mesh as primitive_sphere
Expected Behavior:
We are checking if the dynamic slice "RigidBody" is present in the current level.
The test fails if the dynamic slice is found in the level.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Verify if the dynamic slice entity exists in the current level
5) Exit game mode
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
"""
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.entity as entity
# Constants
DYNAMIC_SLICE_NAME = "RigidBody"
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "Physics_DynamicSliceWithPhysNotSpawnsStaticSlice")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
spawner_id = general.find_game_entity("SpawnerEntity")
Report.result(Tests.find_entity, spawner_id.IsValid())
# 4) Verify if the dynamic slice entity exists in the current level
# Get all the entity ids in the level
searchFilter = entity.SearchFilter()
searchFilter.Names = ["*"]
entity_ids = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", searchFilter)
entity_names = [
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", entity_id)
for entity_id in entity_ids
]
# check if the dynamic slice ("RigidBody") exists in the list if entities
Report.result(Tests.dynamic_slice_entity, DYNAMIC_SLICE_NAME not in entity_names)
# 5) 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(Physics_DynamicSliceWithPhysNotSpawnsStaticSlice)
@@ -0,0 +1,90 @@
"""
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 : C15425929
# Test Case Title : Verify that undo - redo operations do not create any error
# fmt: off
class Tests:
entity_found = ("Entity was initially found", "Entity COULD NOT be found initially")
entity_deleted = ("Entity was deleted", "Entity WAS NOT deleted")
deletion_undone = ("Undo worked", "Undo DID NOT work")
deletion_redone = ("Redo worked", "Redo DID NOT work")
no_error_occurred = ("Undo/redo completed without errors", "An error occurred during undo/redo")
# fmt: off
def Physics_UndoRedoWorksOnEntityWithPhysComponents():
"""
Summary:
Tests that no error messages arise when using the undo and redo functions in the editor.
Level Description:
DeleteMe - an entity that just exists above the terrain with a sphere shape component on it.
Steps:
1) Load level
2) Initially find the entity
3) Delete the entity
4) Undo the deletion
5) Redo the deletion
6) Look for errors
7) Close the editor
Expected Behavior:
The entity should be deleted, un-deleted, and re-deleted.
:return: None
"""
import os
import sys
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
import azlmbr.legacy.general as general
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "Physics_UndoRedoWorksOnEntityWithPhysComponents")
with Tracer() as error_tracer:
# Entity to delete and undo and re-delete
entity_name = "DeleteMe"
# 2) Find entity initially
entity_id = general.find_editor_entity(entity_name)
Report.critical_result(Tests.entity_found, entity_id.IsValid())
# 3) Delete entity
general.select_objects([entity_name])
general.delete_selected()
entity_id = general.find_editor_entity(entity_name)
Report.result(Tests.entity_deleted, not entity_id.IsValid())
# 4) Undo deletion
general.undo()
entity_id = general.find_editor_entity(entity_name)
Report.result(Tests.deletion_undone, entity_id.IsValid())
# 5) Redo deletion
general.redo()
entity_id = general.find_editor_entity(entity_name)
Report.result(Tests.deletion_redone, not entity_id.IsValid())
# 6) Look for errors
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Physics_UndoRedoWorksOnEntityWithPhysComponents)
@@ -0,0 +1,105 @@
"""
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 : C5689529
# Test Case Title : Create an entity with PhysX Terrain component and add
# PhysX Rigid Body PhysX, PhysX Collider and Rendering Mesh to it and
# verify that it works in game mode
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_entity = ("Entity found", "Entity not found")
gravity_enabled = ("Gravity is enabled", "Gravity is disabled")
mass_equal = ("Mass of rigid body equal to the expected mass", "Mass of rigid body not equal to the expected mass")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Physics_VerifyColliderRigidBodyMeshAndTerrainWorkTogether():
"""
Summary:
Create an entity with PhysX Terrain component and add PhysX Rigid Body PhysX,
PhysX Collider and Rendering Mesh to it and verify that it works in game mode
Level Description:
PhysXRigidBody (entity) - Entity with components PhysX Rigid Body, PhysX Collider, Terrain and Rendering Mesh
Expected Behavior:
The rigid body entity should be working in the game mode.
We are checking if entity id is valid and extracting some properties of rigid body.
Also we are waiting for few frames to ensure that the editor did not crash.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Waiting for WAIT_FRAMES to ensure that the editor did not crash in the game mode
5) Check the properties of rigid body like Gravity(enabled) and Mass(1.0kg)
6) Exit game mode
7) 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
# Constants
WAIT_FRAMES = 2
EXPECTED_MASS = 1.0 # Default mass of a rigid body component
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "Physics_VerifyColliderRigidBodyMeshAndTerrainWorkTogether")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
rigid_body_id = general.find_game_entity("PhysXRigidBody")
Report.critical_result(Tests.find_entity, rigid_body_id.IsValid())
class RigidBody:
gravity_enabled = False
collison_occued = False
mass = 0.0
# 4) Waiting for WAIT_FRAMES to ensure that the editor did not crash in the game mode
general.idle_wait_frames(WAIT_FRAMES)
Report.info("Editor did not crash after waiting for " + str(WAIT_FRAMES) + " frames")
# 5) Check the properties of rigid body like Gravity(enabled) and Mass(1.0kg)
RigidBody.gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", rigid_body_id)
Report.critical_result(Tests.gravity_enabled, RigidBody.gravity_enabled)
RigidBody.mass = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", rigid_body_id)
Report.critical_result(Tests.mass_equal, RigidBody.mass == EXPECTED_MASS)
# 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(Physics_VerifyColliderRigidBodyMeshAndTerrainWorkTogether)
@@ -0,0 +1,153 @@
"""
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 : C29032500
# Test Case Title : Check that WorldRequestBus works with editor components
# fmt: off
class Tests():
find_staticshapebox = ("Found StaticShapeBox", "Failed to find StaticShapeBox")
find_staticsphere = ("Found StaticSphere", "Failed to find StaticSphere")
find_staticbox = ("Found StaticBox", "Failed to find StaticBox")
find_staticcapsule = ("Found StaticCapsule", "Failed to find StaticCapsule")
find_staticmesh = ("Found StaticMesh", "Failed to find StaticMesh")
find_shapebox = ("Found ShapeBox", "Failed to find ShapeBox")
find_sphere = ("Found Sphere", "Failed to find Sphere")
find_box = ("Found Box", "Failed to find Box")
find_capsule = ("Found Capsule", "Failed to find Capsule")
find_mesh = ("Found Mesh", "Failed to find Mesh")
aabb_staticshapebox = ("Correct AABB for StaticShapeBox", "Incorrect AABB for StaticShapeBox")
aabb_staticsphere = ("Correct AABB for StaticSphere", "Incorrect AABB for StaticSphere")
aabb_staticbox = ("Correct AABB for StaticBox", "Incorrect AABB for StaticBox")
aabb_staticcapsule = ("Correct AABB for StaticCapsule", "Incorrect AABB for StaticCapsule")
aabb_staticmesh = ("Correct AABB for StaticMesh", "Incorrect AABB for StaticMesh")
aabb_shapebox = ("Correct AABB for ShapeBox", "Incorrect AABB for ShapeBox")
aabb_sphere = ("Correct AABB for Sphere", "Incorrect AABB for Sphere")
aabb_box = ("Correct AABB for Box", "Incorrect AABB for Box")
aabb_capsule = ("Correct AABB for Capsule", "Incorrect AABB for Capsule")
aabb_mesh = ("Correct AABB for Mesh", "Incorrect AABB for Mesh")
raycast_staticshapebox = ("Correct raycast for StaticShapeBox", "Incorrect raycast for StaticShapeBox")
raycast_staticsphere = ("Correct raycast for StaticSphere", "Incorrect raycast for StaticSphere")
raycast_staticbox = ("Correct raycast for StaticBox", "Incorrect raycast for StaticBox")
raycast_staticcapsule = ("Correct raycast for StaticCapsule", "Incorrect raycast for StaticCapsule")
raycast_staticmesh = ("Correct raycast for StaticMesh", "Incorrect raycast for StaticMesh")
raycast_shapebox = ("Correct raycast for ShapeBox", "Incorrect raycast for ShapeBox")
raycast_sphere = ("Correct raycast for Sphere", "Incorrect raycast for Sphere")
raycast_box = ("Correct raycast for Box", "Incorrect raycast for Box")
raycast_capsule = ("Correct raycast for Capsule", "Incorrect raycast for Capsule")
raycast_mesh = ("Correct raycast for Mesh", "Incorrect raycast for Mesh")
# fmt: on
def Physics_WorldBodyBusWorksOnEditorComponents():
r"""
Summary:
Runs a test to make sure that a WorldBodyBus works property for components
Level Description:
- Dynamic
- Sphere: Sphere with Rigid body
- Box: Box with Rigid body
- Capsule: Capsule with Rigid body
- Mesh: Sedan car Mesh with Rigid body
- ShapeBox: Shape Collider component + Box with rigidBody
- Static
- StaticSphere: Sphere with only Coollider component
- StaticBox: Box with only Coollider component
- StaticCapsule: Capsule with only Coollider component
- StaticMesh: Sedan car Mesh with only Coollider component
- StaticShapeBox: Only Shape Collider component + Box
TopDown view:
____
[!] o [ ] ( ) (____)
ShapeBox Sphere Box Capsule Mesh
____
[!] o [ ] ( ) (____)
StaticShapeBox StaticSphere StaticBox StaticCapsule StaticMesh
Expected Outcome:
Checks AABB and RayCast functions of WorldBodyBus against the All the entities in the level
:return:
"""
import os
import sys
import azlmbr.legacy.general as general
import azlmbr.bus
import math
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import vector3_str, aabb_str
AABB_THRESHOLD = 0.01 # Entities won't move in the simulation
helper.init_idle()
helper.open_level("Physics", "Physics_WorldBodyBusWorksOnEditorComponents")
def create_aabb(aabb_min_tuple, aabb_max_tuple):
return azlmbr.math.Aabb_CreateFromMinMax(azlmbr.math.Vector3(aabb_min_tuple[0], aabb_min_tuple[1], aabb_min_tuple[2]),
azlmbr.math.Vector3(aabb_max_tuple[0], aabb_max_tuple[1], aabb_max_tuple[2]))
class EntityData:
def __init__(self, name, target_aabb):
self.name = name
self.target_aabb = target_aabb
def get_test_tuple_for_entity(testprefix, entity_name):
return Tests.__dict__[testprefix.lower() + "_" + entity_name.lower()]
ENTITY_DATA = [ EntityData("ShapeBox", create_aabb((509.82, 523.08, 32.81), (510.82, 524.08, 33.81))),
EntityData("Sphere", create_aabb((509.82, 526.39, 32.81), (510.82, 527.39, 33.81))),
EntityData("Box", create_aabb((509.82, 529.66, 32.81), (510.82, 530.66, 33.81))),
EntityData("Capsule", create_aabb((510.07, 533.70, 32.81), (510.57, 534.20, 33.81))),
EntityData("Mesh", create_aabb((509.48, 536.30, 33.31), (511.16, 540.38, 34.38))),
EntityData("StaticShapeBox", create_aabb((512.08, 523.08, 32.81), (513.08, 524.08, 33.81))),
EntityData("StaticSphere", create_aabb((512.08, 526.39, 32.81), (513.08, 527.39, 33.81))),
EntityData("StaticBox", create_aabb((512.08, 529.66, 32.81), (513.08, 530.66, 33.81))),
EntityData("StaticCapsule", create_aabb((512.33, 533.70, 32.81), (512.83, 534.20, 33.81))),
EntityData("StaticMesh", create_aabb((511.74, 536.30, 33.31), (513.42, 540.38, 34.38))) ] # AABB data obtained by observation
for entity_data in ENTITY_DATA:
entity_id = general.find_editor_entity(entity_data.name);
Report.result(get_test_tuple_for_entity("find", entity_data.name), entity_id.IsValid())
if entity_id.IsValid():
# AABB test
aabb = azlmbr.physics.WorldBodyRequestBus(azlmbr.bus.Event, "GetAabb", entity_id)
Report.info("%s AABB -> %s" % (entity_data.name, aabb_str(aabb)))
Report.info("%s expected AABB -> %s" % (entity_data.name, aabb_str(entity_data.target_aabb)))
is_expected_aabb_size = aabb.min.IsClose(entity_data.target_aabb.min, AABB_THRESHOLD) and aabb.max.IsClose(entity_data.target_aabb.max, AABB_THRESHOLD)
Report.result(get_test_tuple_for_entity("aabb", entity_data.name), is_expected_aabb_size)
# Raycast test
entity_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTM", entity_id).GetPosition()
raycast_request = azlmbr.physics.RayCastRequest()
raycast_request.Start = entity_pos.Add(azlmbr.math.Vector3(0.0, 0.0, 100.0))
raycast_request.Distance = 500.0
raycast_request.Direction = azlmbr.math.Vector3(0.0, 0.0, -1.0)
result = azlmbr.physics.WorldBodyRequestBus(azlmbr.bus.Event, "RayCast", entity_id, raycast_request)
if result:
# Following line crashes due to a hydra bug, use distance for now
# has_hit = ragdoll_id.Equal(result.EntityId)
has_hit = result.Distance > 0.1 and math.isclose(result.Position.x, entity_pos.x) and math.isclose(result.Position.y, entity_pos.y)
Report.info("Hit: %s" % vector3_str(result.Position))
Report.result(get_test_tuple_for_entity("raycast", entity_data.name), has_hit)
else:
Report.failure(get_test_tuple_for_entity("raycast", entity_data.name))
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Physics_WorldBodyBusWorksOnEditorComponents)
@@ -0,0 +1,92 @@
"""
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 : C14654881
# Test Case Title : Switching levels from a level containing a character controller component
# should not lead to a crash
# fmt: off
class Tests:
# level
level1_enter_game_mode = ("Entered game mode level 1", "Failed to enter game mode level 1")
level1_exit_game_mode = ("Exited game mode level 1", "Couldn't exit game mode level 1")
CharacterController_found = ("Character controller was found", "Character controller was not found")
level2_enter_game_mode = ("Entered game mode level 2", "Failed to enter game mode level 2")
level2_exit_game_mode = ("Exited game mode level 2", "Couldn't exit game mode level 2")
# fmt: on
def CharacterController_SwitchLevels():
"""
Summary:
Runs an automated test to verify that switching levels from a level containing a character controller component
does not lead to a crash
Level Description:
There are 2 levels used in this test:
One contains an entity with a PhysX Character Controller component,
the other one is empty.
Expected Behavior:
It should enter and then exit game mode without any errors in both levels.
Test Steps:
1.1) Load the level with PhysX Character Controller
1.2) Enter game mode
1.3) Find the entity with PhysX Character Controller component
1.4) Exit game mode
2.1) Load the empty level
2.2) Enter game mode
2.3) Exit game mode
3) 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
WAIT_FOR_ERRORS = 3.0
helper.init_idle()
# 1.1) Load level 1 (with character controller)
helper.open_level("Physics", "CharacterController_SwitchLevels")
# 1.2) Enter game mode
helper.enter_game_mode(Tests.level1_enter_game_mode)
# 1.3) Find and validate character controller entity
characterController_id = general.find_game_entity("CharacterController")
Report.critical_result(Tests.CharacterController_found, characterController_id.IsValid())
# 1.4) Exit Game mode
helper.exit_game_mode(Tests.level1_exit_game_mode)
# 2.1) Load level 2 (empty level)
helper.open_level("Physics", "Base")
# 2.2) Enter game mode
helper.enter_game_mode(Tests.level2_enter_game_mode)
general.idle_wait(WAIT_FOR_ERRORS)
# 2.3) Exit Game mode
helper.exit_game_mode(Tests.level2_exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(CharacterController_SwitchLevels)
@@ -0,0 +1,86 @@
"""
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 : C4976236
Test Case Title : Verify that you can add the physX collider component to an entity
without it throwing an error or warning
"""
# fmt: off
class Tests():
create_test_entity = ("Entity created successfully", "Failed to create Entity")
add_physx_collider = ("PhysX Collider component added", "Failed to add PhysX Collider component")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
# fmt: on
def Collider_AddColliderComponent():
"""
Summary:
Opens an empty level and creates an Entity with PhysX Collider. Verify that editor remains stable in Game mode.
Expected Behavior:
The Editor is stable there are no warnings or errors.
Test Steps:
1) Load the level
2) Create test entity
3) Start the Tracer to catch any errors and warnings
4) Add the PhysX Collider component and change shape to box
5) Enter game mode
6) Verify there are no errors and warnings in the logs
7) Exit game mode
8) Close the editor
:return: None
"""
# 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.utils import Tracer
from editor_python_test_tools.asset_utils import Asset
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "Base")
# 2) Create test entity
test_entity = EditorEntity.create_editor_entity("TestEntity")
Report.result(Tests.create_test_entity, test_entity.id.IsValid())
# 3) Start the Tracer to catch any errors and warnings
with Tracer() as section_tracer:
# 4) Add the PhysX Collider component and change shape to box
collider_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.add_physx_collider, test_entity.has_component("PhysX Collider"))
collider_component.set_component_property_value('Shape Configuration|Shape', azlmbr.physics.ShapeType_Box)
# 5) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 6) Verify there are no errors and warnings in the logs
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
Report.result(Tests.no_errors_and_warnings_found, success_condition)
if not success_condition:
if section_tracer.has_warnings:
Report.info(f"Warnings found: {section_tracer.warnings}")
if section_tracer.has_errors:
Report.info(f"Errors found: {section_tracer.errors}")
# 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(Collider_AddColliderComponent)
@@ -0,0 +1,83 @@
"""
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 : C4976227
# Test Case Title : Validate that a Collision Group can be added
# Level has entity with custom collision group added.
# If level enters game mode, collision group addition is validated.
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere entity found", "Sphere entity not found")
collision_group = ("Collision group addition validated", "Collision group addition not valid")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_AddingNewGroupWorks():
"""
Summary:
Runs an automated test to ensure that a collision group can be added.
Level Description:
Sphere (Entity) - PhysX Collider(shape:sphere): Collision Layer (Default), Collides With (Test_Group)
Test_Group (Collision Group) - Collision Group that is custom made for this test.
Requires a custom ".physxconfiguration" file in addition to the level file
Expected Behavior:
When game mode is entered the entity id should be valid and position should be found
Test Steps:
1) Open Level
2) Enter game mode
3) Validate entities
4) Exit game mode
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
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Collider_AddingNewGroupWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate entities
sphere_id = general.find_game_entity("Sphere")
Report.result(Tests.find_sphere, sphere_id.IsValid())
sphere_position = None
sphere_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
Report.result(Tests.collision_group, sphere_id.isValid() and sphere_position != None)
# 4) 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(Collider_AddingNewGroupWorks)
@@ -0,0 +1,105 @@
"""
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 : C4982801
Test Case Title : Verify that the shape Box can be selected from drop downlist and the value for its dimensions in x,y,z can be set after that
"""
# fmt: off
class Tests():
entity_created = ("Test Entity created successfully", "Failed to create Test Entity")
collider_added = ("PhysX Collider added successfully", "Failed to add PhysX Collider")
collider_shape_changed = ("PhysX Collider shape changed successfully", "Failed change PhysX Collider shape")
shape_dimensions_changed = ("Shape dimensions modified successfully", "Failed to modify Shape dimensions")
# fmt: on
def Collider_BoxShapeEditting():
"""
Summary:
Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions
Expected Behavior:
Box shape can be selected for the Shape Component and the value for X, Y, and Z dimensions can be changed
Test Steps:
1) Load the empty level
2) Create the test entity
3) Add PhysX Collider component to test entity
4) Change the PhysX Collider shape and store the original dimensions
5) Modify the dimensions
6) Verify they have been changed
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
"""
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Open 3D Engine Imports
import azlmbr.math as math
BOX_SHAPETYPE_ENUM = 1
SET_SIZE = 2.5
SIZE_TOLERANCE = 0.5
def check_dimensions_changed(set_dimension_value, modified_dimensions, tolerance):
def compare_values(value_name, set_value, grabbed_value, tolerance):
within_tolerance = math.Math_IsClose(set_value, grabbed_value, tolerance)
if not within_tolerance:
assert (
False
), f"The modified value for {value_name} was not within the allowed tolerance\nExpected:{set_value}\nActual: {grabbed_value}"
return within_tolerance
# Check for X, Y, & Z values
x_value = compare_values("x_value", set_dimension_value, modified_dimensions.x, tolerance)
y_value = compare_values("y_value", set_dimension_value, modified_dimensions.y, tolerance)
z_value = compare_values("z_value", set_dimension_value, modified_dimensions.z, tolerance)
return x_value and y_value and z_value
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create the test entity
test_entity = Entity.create_editor_entity("Test Entity")
Report.result(Tests.entity_created, test_entity.id.IsValid())
# 3) Add PhysX Collider component to test entity
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.collider_added, test_entity.has_component("PhysX Collider"))
# 4) Change the PhysX Collider shape and store the original dimensions
test_component.set_component_property_value("Shape Configuration|Shape", BOX_SHAPETYPE_ENUM)
add_check = test_component.get_component_property_value("Shape Configuration|Shape") == BOX_SHAPETYPE_ENUM
Report.result(Tests.collider_shape_changed, add_check)
# 5) Modify the dimensions
Report.info(f"Attempting to set XYZ values to {SET_SIZE}")
test_component.set_component_property_value(
"Shape Configuration|Box|Dimensions", math.Vector3(SET_SIZE, SET_SIZE, SET_SIZE)
)
mod_dimensions = test_component.get_component_property_value("Shape Configuration|Box|Dimensions")
# 6) Verify they have been changed
dimensions_successfully_changed = check_dimensions_changed(SET_SIZE, mod_dimensions, SIZE_TOLERANCE)
Report.result(Tests.shape_dimensions_changed, dimensions_successfully_changed)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_BoxShapeEditting)
@@ -0,0 +1,105 @@
"""
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 : C4982802
Test Case Title : Verify that the shape capsule can be selected from drop downlist and the value for its height and radius can be set after that
"""
# fmt: off
class Tests():
entity_created = ("Test Entity created successfully", "Failed to create Test Entity")
collider_added = ("PhysX Collider added successfully", "Failed to add PhysX Collider")
collider_shape_changed = ("PhysX Collider shape changed successfully", "Failed change PhysX Collider shape")
shape_dimensions_changed = ("Shape dimensions modified successfully", "Failed to modify Shape dimensions")
# fmt: on
def Collider_CapsuleShapeEditting():
"""
Summary:
Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions
Expected Behavior:
Capsule shape can be selected for the Shape Component and the value for Height and Radius can be changed
Test Steps:
1) Load the empty level
2) Create the test entity
3) Add PhysX Collider component to test entity
4) Change the PhysX Collider shape
5) Modify the dimensions
6) Verify they have been changed
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
"""
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Open 3D Engine Imports
import azlmbr.math as math
CAPSULE_SHAPETYPE_ENUM = 2
# Note from Editor Console: Height must exceed twice the radius in capsule configuration
SIZE_RADIUS = 4.0
SIZE_HEIGHT = SIZE_RADIUS * 2 + 1.0
SIZE_TOLERANCE = 0.5
def change_dimension_value(component, component_property_path, value):
Report.info(f"Attempting to set value for {component_property_path} to {value}")
component.set_component_property_value(component_property_path, value)
returning_value = component.get_component_property_value(component_property_path)
Report.info(f"Value for {component_property_path} is currently {returning_value}")
return returning_value
def check_dimension_change(value_name, value_set, grabbed_value, tolerance):
within_tolerance = math.Math_IsClose(value_set, grabbed_value, tolerance)
if not within_tolerance:
assert (
False
), f"The modified value for {value_name} was not within the allowed tolerance\nExpected:{value_set}\nActual: {grabbed_value}"
return within_tolerance
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create the test entity
test_entity = Entity.create_editor_entity("Test Entity")
Report.result(Tests.entity_created, test_entity.id.IsValid())
# 3) Add PhysX Collider component to test entity
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.collider_added, test_entity.has_component("PhysX Collider"))
# 4) Change the PhysX Collider shape
test_component.set_component_property_value("Shape Configuration|Shape", CAPSULE_SHAPETYPE_ENUM)
add_check = test_component.get_component_property_value("Shape Configuration|Shape") == CAPSULE_SHAPETYPE_ENUM
Report.result(Tests.collider_shape_changed, add_check)
# 5) Modify the dimensions
mod_height = change_dimension_value(test_component, "Shape Configuration|Capsule|Height", SIZE_HEIGHT)
mod_radius = change_dimension_value(test_component, "Shape Configuration|Capsule|Radius", SIZE_RADIUS)
# 6) Verify they have been changed
resulting_check = check_dimension_change(
"Height", SIZE_HEIGHT, mod_height, SIZE_TOLERANCE
) and check_dimension_change("Radius", SIZE_RADIUS, mod_radius, SIZE_TOLERANCE)
Report.result(Tests.shape_dimensions_changed, resulting_check)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_CapsuleShapeEditting)
@@ -0,0 +1,72 @@
"""
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 : C14861500
Test Case Title : Verify Default shape is Physics Asset
"""
# fmt: off
class Tests():
create_collider_entity = ("Created Collider Entity", "Failed to create Collider Entity")
add_physx_collider = ("PhysX Collider added", "Failed to add PhysX Collider")
shape_is_correct = ("PhysX Collider Shape is correct", "PhysX Collider Shape is not PhysicsAsset")
# fmt: on
def Collider_CheckDefaultShapeSettingIsPxMesh():
"""
Summary:
Check the default for Shape on the PhysX Collider component
Expected Behavior:
When adding the PhysX Collider, the default Shape should be PhysX Asset
Test Steps:
1) Load empty level
2) Create an entity to hold the PhysX Shape Collider component
3) Add the PhysX Collider component
4) Check value of Shape property on PhysX Collider
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
"""
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Open 3D Engine Imports
import azlmbr.legacy.general as general
PHYSICS_ASSET_INDEX = 7 # Hardcoded enum index value for Shape property
helper.init_idle()
# 1) Load empty level
helper.open_level("Physics", "Base")
# 2) Create an entity to hold the PhysX Shape Collider component
collider_entity = Entity.create_editor_entity("Collider")
Report.result(Tests.create_collider_entity, collider_entity.id.IsValid())
# 3) Add the PhysX Collider component
test_component = collider_entity.add_component("PhysX Collider")
Report.result(Tests.add_physx_collider, collider_entity.has_component("PhysX Collider"))
# 4) Check value of Shape property on PhysX Collider
value_to_test = test_component.get_component_property_value("Shape Configuration|Shape")
Report.result(Tests.shape_is_correct, value_to_test == PHYSICS_ASSET_INDEX)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_CheckDefaultShapeSettingIsPxMesh)
@@ -0,0 +1,334 @@
"""
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 : C4982797
# Test Case Title : Check that collision offsets trigger collision events,
# not entity transform locations
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
boxes_found = ("All boxes were validated", "Couldn't validate at least one box")
spheres_found = ("All spheres were validated", "Not all the spheres could be validated")
test_completed = ("The test completed", "The test timed out")
target_spheres_passed = ("All spheres intended to collide with Target Boxes DID", "At least one sphere intended to collide with a Target Box DID NOT")
pass_spheres_passed = ("All spheres intended to pass through Target Boxes DID", "At least one sphere intended to pass through a Target Box DID NOT")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_ColliderPositionOffset():
# type: () -> None
"""
Summary:
Runs an automated test to ensure that PhysXCollider offsets work properly. Collisions should be
calculated based on the location of the colliders, not the geometry of the entity's Transform
Level Description:
There are five classes of entities in the level: Target Spheres, Pass Spheres, Target Boxes, Pass Boxes
and Fail Boxes. All entities have gravity disabled, are positioned above the terrain, and have the same
collision group/layer (Default/All).
Target Boxes: These are the actual boxes that have their colliders offset. There are three of these
boxes, one for each offset axis (rightly labeled Box_[X, Y, or Z]_Target).
Target Spheres: These spheres are positioned near the Target Boxes' collision offset areas. These entities are
initialized with a velocity that will send them on course to collide with the collision geometry for the
Target Boxes. They are appropriately labeled for which Target box they are to collide with
Sphere_[X, Y, or Z]_Target
Pass Spheres: The spheres are positioned near the Target Boxes as well, and are also initialized with a velocity
that will will send them towards their Target Box. The difference here is that they aligned to "collide"
with their Target Box's actual transform (not their collider offset). They too are appropriately named
Sphere_[X, Y, or Z]_Pass
Pass Boxes: Pass Boxes are positioned on the other side of the Pass Spheres from their target Box. They serve
as a trigger to register that the Pass Sphere successfully passed through the respective Target Box's
transform geometry. They are rightfully named Box_[X, Y, or Z]_Pass
Fail Boxes: Fail boxes (like Pass Boxes) are positioned on the other side of their respective Target Box,
but they are arranged opposite the Target Sphere (rather than the Pass Sphere). They act as a fail safe
if the Target Sphere happens the pass through the Target Box's collider offset. They are named following the
same convention: Box_[X, Y, or Z]_Fail
Note: All boxes are set to "kinematic" so they do not move when a collision happens.
Expected Behavior:
Upon entering game mode, the spheres should follow their initial velocities. The Target Spheres should collide and
bounce off of the Target Boxes' collision offsets, and the Pass Spheres should pass through the visible transforms
of their Target Boxes and collide with their respective Pass Boxes.
Test Steps:
1) Loads the level
2) Enters game mode
3) Retrieve and validate entities
4) Ensures that the test objects are located
5) Wait for test to complete or time out
6) Log the results
7) Exit game mode and 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
"""
# System imports
import os
import sys
# Internal editor imports
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
# ******** Global Variables ********
# Entity data organization class
class EntityData:
def __init__(self, name):
self.name = name
self.id = None
self.init_pos = None
self.current_pos = None
self.result = None
# fmt: off
# Establish entity sets
target_spheres = [EntityData("Sphere_X_Target"), EntityData("Sphere_Y_Target"), EntityData("Sphere_Z_Target")]
pass_spheres = [EntityData("Sphere_X_Pass"), EntityData("Sphere_Y_Pass"), EntityData("Sphere_Z_Pass")]
target_boxes = [EntityData("Box_X_Target"), EntityData("Box_Y_Target"), EntityData("Box_Z_Target")]
pass_boxes = [EntityData("Box_X_Pass"), EntityData("Box_Y_Pass"), EntityData("Box_Z_Pass")]
fail_boxes = [EntityData("Box_X_Fail"), EntityData("Box_Y_Fail"), EntityData("Box_Z_Fail")]
all_spheres = target_spheres + pass_spheres
all_boxes = pass_boxes + fail_boxes + target_boxes
all_entities = all_spheres + all_boxes
# Maps a Sphere to its last anticipated collision/position (by name)
final_expected_collisions = {
"Sphere_X_Target": "Box_X_Fail",
"Sphere_Y_Target": "Box_Y_Fail",
"Sphere_Z_Target": "Box_Z_Fail",
"Sphere_X_Pass": "Box_X_Pass",
"Sphere_Y_Pass": "Box_Y_Pass",
"Sphere_Z_Pass": "Box_Z_Pass",
}
# Possible results
PASS = "pass"
FAIL = "fail"
TARGET = "target"
# fmt: on
# ******** Helper Functions ********
# Validate entities' IDs and initial positions.
# Fast Fails if there are any problems retrieving vital information
def validate_entities(entity_list, test_tuple):
# type: ([EntityData], (str, str)) -> None
passed = True
for entity in entity_list:
valid = True
entity.id = general.find_game_entity(entity.name)
if not entity.id.IsValid():
valid = False
Report.info("Entity: {} could not be validated".format(entity.name))
entity.init_pos = entity.current_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", entity.id
)
if entity.init_pos is None or entity.init_pos.IsZero():
valid = False
Report.info("Entity: {}'s initial position could not be found".format(entity.name))
if not valid:
passed = False
break
Report.critical_result(test_tuple, passed)
# Updates entities' current position
def update_positions(entity_list):
# type: ([EntityData]) -> None
for entity in entity_list:
entity.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity.id)
# Looks for implicit failure cases for moving spheres
# by checking if they have moved through their "final expected collision" object
def check_for_failure(sphere_entities):
# type: ([EntityData]) -> None
for sphere in sphere_entities:
expected_name = final_expected_collisions[sphere.name]
expected_entity_list = [entity for entity in all_entities if entity.name == expected_name]
if len(expected_entity_list) == 0:
# Just in case we can't find the entity's "final expected collision" entity
Report.info("Failed finding {} in expected entities list:".format(expected_name))
Report.info(" {}".format(expected_entity_list))
helper.fail_fast()
expected_entity = expected_entity_list[0]
if sphere.result != FAIL and has_passed_through(sphere, expected_entity):
sphere.result = FAIL
Report.info("{} has unexpectedly passed through {}".format(sphere.name, expected_name))
# Checks for unexpected movement in stationary objects
# Fast Fails and writes to the log if there is a difference between initial position and current position
def check_for_unexpected_movement(entity_list):
# type: ([EntityData]) -> None
for entity in entity_list:
if not entity.init_pos.IsClose(entity.current_pos, CLOSE_ENOUGH_THRESHOLD):
helper.fail_fast("{} has unexpectedly moved".format(entity.name))
# Verifies the results for the entities passed in.
# Returns a count of the verified results
def verify_results(entity_list, expected_result):
# type: ([EntityData], str) -> int
results_verified = 0
for entity in entity_list:
if entity.result == expected_result:
results_verified += 1
else:
Report.info("{} had unexpected result: {}".format(entity.name, entity.result))
return results_verified
# Batch assign event handlers
def set_handlers(entity_list, callback, event="OnCollisionBegin"):
# type: ([EntityData], function, str) -> [handler]
handlers = []
for entity in entity_list:
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(entity.id)
handler.add_callback(event, callback)
handlers.append(handler)
return handlers
# Checks to see if we are done collecting results for the test
def done_collecting_results(entity_list, num_results):
# type: ([EntityData], int) -> bool
result_count = 0
for entity in entity_list:
if entity.result is not None:
result_count += 1
# When all spheres have a result we are done
return result_count == num_results
# Checks if a moving entity has passed through a stationary entity.
# There is an assumption that the stationary entity should not have substantial movement
def has_passed_through(moving_entity, stationary_entity):
# type: (EntityData, EntityData) -> bool
init_diff = stationary_entity.init_pos.Subtract(moving_entity.init_pos).Unary()
current_diff = stationary_entity.current_pos.Subtract(moving_entity.current_pos).Unary()
angle = init_diff.AngleSafeDeg(current_diff)
# Angle > 90 degrees represents a change of sides
result = angle > 90.0
return result
def test_completed():
update_positions(all_entities)
check_for_failure(all_spheres)
check_for_unexpected_movement(all_boxes)
return done_collecting_results(all_spheres, TOTAL_SPHERES)
# ******** Event Handlers ********
# General collision handler
def on_collision_begin(collider_id, result):
# type: (EntityId, str) -> None
for sphere in all_spheres:
if sphere.id.Equal(collider_id):
Report.info("Entity: {} collided with a {} box".format(sphere.name, result))
if result is FAIL or sphere.result is None:
# Set result if not set yet OR the result is a failure (failure overrides success)
sphere.result = result
return
# It wasn't a sphere that collided, something went wrong
if collider_id.IsValid():
entity_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", collider_id)
Report.info("{} box collided with unexpected entity: {}".format(result, entity_name))
# Fail Box collision event handler
def on_collision_begin_fail_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], FAIL)
# Success Box Collision Event Handler
def on_collision_begin_pass_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], PASS)
# Target box collision event handler
def on_collision_begin_target_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], TARGET)
# ******** Execution Code *********
# Local Constants
TIME_OUT = 2.0
CLOSE_ENOUGH_THRESHOLD = 0.1
TOTAL_TARGET_SPHERES = 3
TOTAL_PASS_SPHERES = 3
TOTAL_SPHERES = TOTAL_TARGET_SPHERES + TOTAL_PASS_SPHERES
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "Collider_ColliderPositionOffset")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
validate_entities(all_boxes, Tests.boxes_found)
validate_entities(all_spheres, Tests.spheres_found)
# Assign handlers
handlers = []
handlers = handlers + set_handlers(fail_boxes, on_collision_begin_fail_box)
handlers = handlers + set_handlers(pass_boxes, on_collision_begin_pass_box)
handlers = handlers + set_handlers(target_boxes, on_collision_begin_target_box)
# 4) Wait for either time out or for the test to complete
Report.result(Tests.test_completed, helper.wait_for_condition(test_completed, TIME_OUT))
# 5) Log results
# Verify entities results
pass_spheres_passed = verify_results(pass_spheres, PASS)
target_spheres_passed = verify_results(target_spheres, TARGET)
# Report results
Report.result(Tests.target_spheres_passed, target_spheres_passed == TOTAL_TARGET_SPHERES)
Report.result(Tests.pass_spheres_passed, pass_spheres_passed == TOTAL_PASS_SPHERES)
# Data dump at bottom of log
Report.info("******** Collected Data *********")
for entity in all_entities:
Report.info("Entity: {}".format(entity.name))
Report.info_vector3(entity.init_pos, " Initial position:")
Report.info_vector3(entity.current_pos, " Final position:")
Report.info(" Result: {}".format(entity.result))
Report.info("********************************")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_ColliderPositionOffset)
@@ -0,0 +1,302 @@
"""
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 : C4982798
# Test Case Title : Verify that when the x,y,z values are defined in the offset, the collider frame
# rotates from its original orientation in the direction defined by the x,y,z units
import os
import sys
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
boxes_found = ("All boxes were found", "Couldn't find at least one box")
spheres_found = ("All spheres were found", "Not all the spheres could be found")
test_completed = ("The test completed", "The test timed out")
target_spheres_passed = ("All spheres intended to collide with Target Boxes DID", "At least one sphere intended to collide with a Target Box DID NOT")
pass_thru_spheres_passed = ("All spheres intended to pass through Target Boxes DID", "At least one sphere intended to pass through a Target Box DID NOT")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_ColliderRotationOffset():
# type: () -> None
"""
Summary:
Runs an automated test to ensure that PhysXCollider rotational offsets work properly. Collisions should be
calculated based on the location of the colliders, including their rotational offsets in x, y, and/or z
Level Description:
There are five classes of entities in the level: Target Spheres, Pass Spheres, Target Boxes, Pass Boxes
and Fail Boxes. All entities have gravity disabled, are positioned above the terrain, and have the same
collision group/layer (Default/All).
Target Boxes: These are the actual boxes that have their collider offsets rotated by 90 degrees.
There are three of these boxes, one for each rotation axis (rightly labeled Box_[X, Y, or Z]_Target).
Target Spheres: These spheres are positioned near the Target Boxes' rotated collision areas. These entities are
initialized with a velocity that will send them on course to collide with the collision geometry for the
Target Boxes. They are appropriately labeled for which Target box they are to collide with
Sphere_[X, Y, or Z]_Target
Pass Thru Spheres: These spheres are positioned near the Target Boxes as well, and are also initialized with a
velocity that will will send them towards their Target Box. The difference here is that they are aligned to
"collide" with their Target Box's actual transform (not their rotated collider). They too are appropriately
named Sphere_[X, Y, or Z]_Pass
Pass Thru Boxes: Pass Boxes are positioned on the other side of the Pass Spheres from their target Box. They serve
as a trigger to register that the Pass Sphere successfully passed through the respective Target Box's
transform geometry. They are rightfully named Box_[X, Y, or Z]_Pass
Fail Boxes: Fail boxes (like Pass Boxes) are positioned on the other side of their respective Target Box,
but they are arranged opposite the Target Sphere (rather than the Pass Sphere). They act as a fail condition
if the Target Sphere happens the pass through the Target Box's rotated collider. They are named following the
same convention: Box_[X, Y, or Z]_Fail
Note: All boxes are set to "kinematic" so they do not move when a collision happens.
Expected Behavior:
Upon entering game mode, the spheres should follow their initial velocities. The Target Spheres should collide and
bounce off of the Target Boxes' rotated colliders, and the Pass Spheres should pass through the visible transforms
of their Target Boxes and collide with their respective Pass Boxes.
Test Steps:
1) Loads the level
2) Enters game mode
3) Retrieve and validate entities and starting locations
4) Wait for test to complete or time out
5) Log the results
6) Exit game mode and 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
"""
# Internal editor imports
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
# ******** Global Variables ********
# Entity data organization class
class EntityData:
def __init__(self, name):
self.name = name
self.id = None
self.init_pos = None
self.current_pos = None
self.result = None
# fmt: off
# Establish entity sets
target_spheres = [EntityData("Sphere_X_Target"), EntityData("Sphere_Y_Target"), EntityData("Sphere_Z_Target")]
pass_thru_spheres = [EntityData("Sphere_X_Pass_Thru"), EntityData("Sphere_Y_Pass_Thru"), EntityData("Sphere_Z_Pass_Thru")]
target_boxes = [EntityData("Box_X_Target"), EntityData("Box_Y_Target"), EntityData("Box_Z_Target")]
pass_thru_boxes = [EntityData("Box_X_Pass_Thru"), EntityData("Box_Y_Pass_Thru"), EntityData("Box_Z_Pass_Thru")]
fail_boxes = [EntityData("Box_X_Fail"), EntityData("Box_Y_Fail"), EntityData("Box_Z_Fail")]
all_spheres = target_spheres + pass_thru_spheres
all_boxes = pass_thru_boxes + fail_boxes + target_boxes
all_entities = all_spheres + all_boxes
# Possible results
PASS_THRU = "pass thru"
FAIL = "fail"
TARGET = "target"
# fmt: on
# ******** Helper Functions ********
# Retrieve entities' IDs and initial positions.
# Fast Fails if there are any problems retrieving vital information
def retrieve_entities(entity_list, test_tuple):
# type: ([EntityData], (str, str)) -> None
passed = True
for entity in entity_list:
valid = True
entity.id = general.find_game_entity(entity.name)
if not entity.id.IsValid():
valid = False
Report.info("Entity: {} could not be found".format(entity.name))
entity.init_pos = entity.current_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", entity.id
)
if entity.init_pos is None or entity.init_pos.IsZero():
valid = False
Report.info("Entity: {}'s initial position could not be found".format(entity.name))
if not valid:
passed = False
break
Report.critical_result(test_tuple, passed)
# Updates entities' current position
def refresh_positions(entity_list):
# type: ([EntityData]) -> None
for entity in entity_list:
entity.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity.id)
# Checks for unexpected movement in stationary objects
# Fast Fails and writes to the log if there is a difference between initial position and current position
def check_for_unexpected_movement(entity_list):
# type: ([EntityData]) -> None
for entity in entity_list:
if not entity.init_pos.IsClose(entity.current_pos, CLOSE_ENOUGH_THRESHOLD):
helper.fail_fast("{} has unexpectedly moved".format(entity.name))
# Verifies the results for the entities passed in.
# Returns a count of the verified results
def verify_results(entity_list, expected_result):
# type: ([EntityData], str) -> int
results_verified = 0
for entity in entity_list:
if entity.result == expected_result:
results_verified += 1
else:
Report.info("{} had unexpected result:".format(entity.name))
Report.info(" expected: {}".format(expected_result))
Report.info(" found: {}".format(entity.result))
return results_verified
# Batch assign event handlers
def set_handlers(entity_list, callback, event="OnCollisionBegin"):
# type: ([EntityData], function, str) -> [handler]
handlers = []
for entity in entity_list:
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(entity.id)
handler.add_callback(event, callback)
handlers.append(handler)
return handlers
# Checks to see if we are done collecting results for the test
def done_collecting_results(entity_list, num_results):
# type: ([EntityData], int) -> bool
result_count = 0
for entity in entity_list:
if entity.result is not None:
result_count += 1
# When all spheres have a result we are done
return result_count == num_results
# Callback function to be passed to wait_for_condition
# Updates game entities' data, checks for failures and unexpected results,
# then returns True if we are done observing the test.
def test_completed():
refresh_positions(all_entities)
# check_for_failure(all_spheres)
check_for_unexpected_movement(all_boxes)
return done_collecting_results(all_spheres, TOTAL_SPHERES)
# ******** Event Handlers ********
# General collision handler
def on_collision_begin(collider_id, result):
# type: (EntityId, str) -> None
for sphere in all_spheres:
if sphere.id.Equal(collider_id):
Report.info("Entity: {} collided with a {} box".format(sphere.name, result))
if result is FAIL or sphere.result is None:
# Set result if not set yet OR the result is a failure (failure overrides success)
sphere.result = result
return
# It wasn't a sphere that collided, something went wrong
if collider_id.IsValid():
entity_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", collider_id)
Report.info("{} box collided with unexpected entity: {}".format(result, entity_name))
# Fail Box collision event handler
def on_collision_begin_fail_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], FAIL)
# Success Box Collision Event Handler
def on_collision_begin_pass_thru_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], PASS_THRU)
# Target box collision event handler
def on_collision_begin_target_box(args):
# type: ([EntityId, ...]) -> None
on_collision_begin(args[0], TARGET)
# ******** Execution Code *********
# Local Constants
TIME_OUT = 2.0
CLOSE_ENOUGH_THRESHOLD = 0.0001
TOTAL_TARGET_SPHERES = 3
TOTAL_PASS_THRU_SPHERES = 3
TOTAL_SPHERES = TOTAL_TARGET_SPHERES + TOTAL_PASS_THRU_SPHERES
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "Collider_ColliderRotationOffset")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
retrieve_entities(all_boxes, Tests.boxes_found)
retrieve_entities(all_spheres, Tests.spheres_found)
# Assign handlers
handlers = []
handlers.extend(set_handlers(fail_boxes, on_collision_begin_fail_box))
handlers.extend(set_handlers(pass_thru_boxes, on_collision_begin_pass_thru_box))
handlers.extend(set_handlers(target_boxes, on_collision_begin_target_box))
# 4) Wait for either time out or for the test to complete
Report.result(Tests.test_completed, helper.wait_for_condition(test_completed, TIME_OUT))
# 5) Log results
# Verify entities results
pass_thru_spheres_passed = verify_results(pass_thru_spheres, PASS_THRU)
target_spheres_passed = verify_results(target_spheres, TARGET)
# Report results
Report.result(Tests.target_spheres_passed, target_spheres_passed == TOTAL_TARGET_SPHERES)
Report.result(Tests.pass_thru_spheres_passed, pass_thru_spheres_passed == TOTAL_PASS_THRU_SPHERES)
# Data dump at bottom of log
Report.info("******** Collected Data *********")
for entity in all_entities:
Report.info("Entity: {}".format(entity.name))
Report.info_vector3(entity.init_pos, " Initial position:")
Report.info_vector3(entity.current_pos, " Final position:")
Report.info(" Result: {}".format(entity.result))
Report.info("********************************")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_ColliderRotationOffset)
@@ -0,0 +1,361 @@
"""
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 : C3510644
# Test Case Title : Check that the collision layer and collision group of the terrain can be changed
# and the collision behavior of the terrain changes accordingly
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
box_1_a_valid = ("Box 1 A has been validated", "Box 1 A COULD NOT be validated")
box_2_a_valid = ("Box 2 A has been validated", "Box 2 A COULD NOT be validated")
terrain_a_valid = ("Terrain A has been validated", "Terrain A COULD NOT be validated")
box_1_b_valid = ("Box 1 B has been validated", "Box 1 B COULD NOT be validated")
box_2_b_valid = ("Box 2 B has been validated", "Box 2 B COULD NOT be validated")
terrain_b_valid = ("Terrain B has been validated", "Terrain B COULD NOT be validated")
box_1_a_pos_found = ("Box 1 A position found", "Box 1 A position NOT found")
box_2_a_pos_found = ("Box 2 A position found", "Box 2 A position NOT found")
terrain_a_pos_found = ("Terrain A position found", "Terrain A position NOT found")
box_1_b_pos_found = ("Box 1 B position found", "Box 1 B position NOT found")
box_2_b_pos_found = ("Box 2 B position found", "Box 2 B position NOT found")
terrain_b_pos_found = ("Terrain B position found", "Terrain B position NOT found")
box_1_a_did_collide_with_terrain = ("Box 1 A did collide with terrain", "Box 1 A DID NOT collide with terrain")
box_1_a_did_not_pass_through_terrain = ("Box 1 A did not fall past the terrain", "Box 1 A DID fall past the terrain")
box_2_a_did_not_collide_with_terrain = ("Box 2 A did not collide with terrain", "Box 2 A DID collide with terrain")
box_2_a_did_pass_through_terrain = ("Box 2 A did fall past the terrain", "Box 2 A DID NOT fall past the terrain")
box_1_b_did_not_collide_with_terrain = ("Box 1 B did not collide with terrain", "Box 1 B DID collide with terrain")
box_1_b_did_pass_through_terrain = ("Box 1 B did fall past the terrain", "Box 1 B DID NOT fall past the terrain")
box_2_b_did_collide_with_terrain = ("Box 2 B did collide with terrain", "Box 2 B DID NOT collide with terrain")
box_2_b_did_not_pass_through_terrain = ("Box 2 B did not fall past the terrain", "Box 2 B DID fall past the terrain")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_CollisionGroupsWorkflow():
# type: () -> None
"""
Summary:
Runs an automated test to ensure PhysX collision groups dictate whether collisions happen or not.
The test has two phases (A and B) for testing collision groups under different circumstances. Phase A
is run first and upon success Phase B starts.
Level Description:
Entities can be divided into 2 groups for the two phases, A and B. Each phase has identical entities with exception
to Terrain, where Terrain_A has a collision group/layer set for demo_group1/demo1 and Terrain_B has a collision
group/layer set for demo_group2/demo2.
Each Phase has two boxes, Box_1 and Box_2, where each box has it's collision group/layer set to it's number
(1 or 2). Each box is positioned just above the Terrain with gravity enabled.
All entities for Phase B are deactivated by default. If Phase A is setup and executed successfully it's
entities are deactivated and Phase B's entities are activated and validated before running the Phase B test.
Expected behavior:
When Phase A starts, it's two boxes should fall toward the terrain. Once the boxes' behavior is validated the
entities from Phase A are deactivated and Phase B's entities are activated. Like in Phase A, the boxes in Phase B
should fall towards the terrain. If all goes as expected Box_1_A and Box_2_B should collide with teh terrain, and
Box_2A and Box_1_B should fall through the terrain.
Test Steps:
0) [Define helper classes and functions]
1) Load the level
2) Enter game mode
3) Retrieve and validate entities
4) Phase A
a) set up
b) execute test
c) log results (deactivate Phase A entities)
5) Phase B
a) set up (activate Phase B entities)
b) execute test
c) log results
6) 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.
- The level for this test uses two PhysX Terrains and must be run with cmdline argument "-autotest_mode"
to suppress the warning for having multiple terrains.
: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
# ******* Helper Classes ********
# Phase A's test results
class PhaseATestData:
total_results = 2
box_1_collided = False
box_1_fell_through = True
box_2_collided = False
box_2_fell_through = False
box_1 = None
box_2 = None
terrain = None
box_1_pos = None
box_2_pos = None
terrain_pos = None
@staticmethod
# Quick check for validating results for Phase A
def valid():
return (
PhaseATestData.box_1_collided
and PhaseATestData.box_2_fell_through
and not PhaseATestData.box_1_fell_through
and not PhaseATestData.box_2_collided
)
# Phase B's test results
class PhaseBTestData:
total_results = 2
box_1_collided = False
box_1_fell_through = False
box_2_collided = False
box_2_fell_through = True
box_1 = None
box_2 = None
terrain = None
box_1_pos = None
box_2_pos = None
terrain_pos = None
@staticmethod
# Quick check for validating results for Phase B
def valid():
return (
not PhaseBTestData.box_1_collided
and not PhaseBTestData.box_2_fell_through
and PhaseBTestData.box_1_fell_through
and PhaseBTestData.box_2_collided
)
# **** Helper Functions ****
# ** Validation helpers **
# Attempts to validate an entity based on the name parameter
def validate_entity(entity_name, msg_tuple):
# type: (str, (str, str)) -> EntityId
entity_id = general.find_game_entity(entity_name)
Report.critical_result(msg_tuple, entity_id.IsValid())
return entity_id
# Attempts to retrieve an entity's initial position and logs result
def validate_initial_position(entity_id, msg_tuple):
# type: (EntityId, (str, str)) -> azlmbr.math.Vector3
# Attempts to validate and return the entity's initial position.
# logs the result to Report.result() using the tuple parameter
pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity_id)
valid = not (pos is None or pos.IsZero())
entity_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", entity_id)
Report.critical_result(msg_tuple, valid)
Report.info_vector3(pos, "{} initial position:".format(entity_name))
return pos
# ** Phase completion checks checks **
# Checks if we are done collecting data for phase A
def done_collecting_results_a():
# type: () -> bool
# Update positions
PhaseATestData.box_1_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", PhaseATestData.box_1
)
PhaseATestData.box_2_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", PhaseATestData.box_2
)
# Check for boxes to fall through terrain
if PhaseATestData.box_1_pos.z < PhaseATestData.terrain_pos.z:
PhaseATestData.box_1_fell_through = True
else:
PhaseATestData.box_1_fell_through = False
if PhaseATestData.box_2_pos.z < PhaseATestData.terrain_pos.z:
PhaseATestData.box_2_fell_through = True
else:
PhaseATestData.box_2_fell_through = False
results = 0
if PhaseATestData.box_1_collided or PhaseATestData.box_1_fell_through:
results += 1
if PhaseATestData.box_2_collided or PhaseATestData.box_2_fell_through:
results += 1
return results == PhaseATestData.total_results
# Checks if we are done collecting data for phase B
def done_collecting_results_b():
# type: () -> bool
# Update positions
PhaseBTestData.box_1_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", PhaseBTestData.box_1
)
PhaseBTestData.box_2_pos = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", PhaseBTestData.box_2
)
# Check for boxes to fall through terrain
if PhaseBTestData.box_1_pos.z < PhaseBTestData.terrain_pos.z:
PhaseBTestData.box_1_fell_through = True
else:
PhaseBTestData.box_1_fell_through = False
if PhaseBTestData.box_2_pos.z < PhaseBTestData.terrain_pos.z:
PhaseBTestData.box_2_fell_through = True
else:
PhaseBTestData.box_2_fell_through = False
results = 0
if PhaseBTestData.box_1_collided or PhaseBTestData.box_1_fell_through:
results += 1
if PhaseBTestData.box_2_collided or PhaseBTestData.box_2_fell_through:
results += 1
return results == PhaseBTestData.total_results
# **** Event Handlers ****
# Collision even handler for Phase A
def on_collision_begin_a(args):
# type: ([EntityId]) -> None
collider_id = args[0]
if (not PhaseATestData.box_1_collided) and PhaseATestData.box_1.Equal(collider_id):
Report.info("Box_1_A / Terrain_A collision detected")
PhaseATestData.box_1_collided = True
if (not PhaseATestData.box_2_collided) and PhaseATestData.box_2.Equal(collider_id):
Report.info("Box_2_A / Terrain_A collision detected")
PhaseATestData.box_2_collided = True
# Collision event handler for Phase B
def on_collision_begin_b(args):
# type: ([EntityId]) -> None
collider_id = args[0]
if (not PhaseBTestData.box_1_collided) and PhaseBTestData.box_1.Equal(collider_id):
Report.info("Box_1_B / Terrain_B collision detected")
PhaseBTestData.box_1_collided = True
if (not PhaseBTestData.box_2_collided) and PhaseBTestData.box_2.Equal(collider_id):
Report.info("Box_2_B / Terrain_B collision detected")
PhaseBTestData.box_2_collided = True
TIME_OUT = 1.5
# 1) Open level
helper.init_idle()
helper.open_level("Physics", "Collider_CollisionGroupsWorkflow")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
PhaseATestData.box_1 = validate_entity("Box_1_A", Tests.box_1_a_valid)
PhaseATestData.box_2 = validate_entity("Box_2_A", Tests.box_2_a_valid)
PhaseATestData.terrain = validate_entity("Terrain_Entity_A", Tests.terrain_a_valid)
PhaseBTestData.box_1 = validate_entity("Box_1_B", Tests.box_1_b_valid)
PhaseBTestData.box_2 = validate_entity("Box_2_B", Tests.box_2_b_valid)
PhaseBTestData.terrain = validate_entity("Terrain_Entity_B", Tests.terrain_b_valid)
# Make sure Phase B objects are disabled
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseBTestData.box_1)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseBTestData.box_2)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseBTestData.terrain)
# 4) *********** Phase A *****************
# 4.a) ** Set Up **
Report.info(" **** Beginning Phase A **** ")
# Locate Phase A entities
PhaseATestData.box_1_pos = validate_initial_position(PhaseATestData.box_1, Tests.box_1_a_pos_found)
PhaseATestData.box_2_pos = validate_initial_position(PhaseATestData.box_2, Tests.box_2_a_pos_found)
PhaseATestData.terrain_pos = validate_initial_position(PhaseATestData.terrain, Tests.terrain_a_pos_found)
# Assign Phase A event handler
handler_a = azlmbr.physics.CollisionNotificationBusHandler()
handler_a.connect(PhaseATestData.terrain)
handler_a.add_callback("OnCollisionBegin", on_collision_begin_a)
# 4.b) Execute Phase A
if not helper.wait_for_condition(done_collecting_results_a, TIME_OUT):
Report.info("Phase A timed out: make sure the level is set up properly or adjust time out threshold")
# 4.c) Log results for Phase A
Report.result(Tests.box_1_a_did_collide_with_terrain, PhaseATestData.box_1_collided)
Report.result(Tests.box_1_a_did_not_pass_through_terrain, not PhaseATestData.box_1_fell_through)
Report.info_vector3(PhaseATestData.box_1_pos, "Box_1_A's final position:")
Report.result(Tests.box_2_a_did_pass_through_terrain, PhaseATestData.box_2_fell_through)
Report.result(Tests.box_2_a_did_not_collide_with_terrain, not PhaseATestData.box_2_collided)
Report.info_vector3(PhaseATestData.box_2_pos, "Box_2_A's final position:")
if not PhaseATestData.valid():
Report.info("Phase A failed test")
# Deactivate entities for Phase A
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseATestData.box_1)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseATestData.box_2)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", PhaseATestData.terrain)
# 5) *********** Phase B *****************
# 5.a) ** Set Up **
Report.info(" *** Beginning Phase B *** ")
# Activate entities for Phase B
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", PhaseBTestData.box_1)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", PhaseBTestData.box_2)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", PhaseBTestData.terrain)
# Initialize positions for Phase B
PhaseBTestData.box_1_pos = validate_initial_position(PhaseBTestData.box_1, Tests.box_1_b_pos_found)
PhaseBTestData.box_2_pos = validate_initial_position(PhaseBTestData.box_2, Tests.box_2_b_pos_found)
PhaseBTestData.terrain_pos = validate_initial_position(PhaseBTestData.terrain, Tests.terrain_b_pos_found)
# Assign Phase B event handler
handler_b = azlmbr.physics.CollisionNotificationBusHandler()
handler_b.connect(PhaseBTestData.terrain)
handler_b.add_callback("OnCollisionBegin", on_collision_begin_b)
# 5.b) Execute Phase B
if not helper.wait_for_condition(done_collecting_results_b, TIME_OUT):
Report.info("Phase B timed out: make sure the level is set up properly or adjust time out threshold")
# 5.c) Log results for Phase B
Report.result(Tests.box_1_b_did_not_collide_with_terrain, not PhaseBTestData.box_1_collided)
Report.result(Tests.box_1_b_did_pass_through_terrain, PhaseBTestData.box_1_fell_through)
Report.info_vector3(PhaseBTestData.box_1_pos, "Box_1_B's final position:")
Report.result(Tests.box_2_b_did_not_pass_through_terrain, not PhaseBTestData.box_2_fell_through)
Report.result(Tests.box_2_b_did_collide_with_terrain, PhaseBTestData.box_2_collided)
Report.info_vector3(PhaseBTestData.box_2_pos, "Box_2_B's final position:")
if not PhaseBTestData.valid():
Report.info("Phase B failed test")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info(" **** TEST FINISHED ****")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_CollisionGroupsWorkflow)
@@ -0,0 +1,230 @@
"""
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 : C4982593
# Test Case Title : Check that two entities with different collision groups and layers do not collide.
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
moving_entity_found = ("Moving entity found", "Moving entity not found")
stationary_entity_found = ("Stationary entity found", "Stationary entity not found")
moving_pos_found = ("Moving sphere position found", "Moving sphere position not found")
stationary_pos_found = ("Stationary sphere position found", "Stationary sphere position not found")
spheres_share_x_axis = ("Both spheres are aligned properly", "Spheres are not aligned properly")
spheres_not_collided = ("No collision was detected", "A collision was detected")
spheres_switched_sides = ("The moving sphere passed through the other", "Moving sphere did not pass through")
no_y_movement = ("There was no Y movement", "Some Y movement was detected")
no_z_movement = ("There was no Z movement", "Some Z movement was detected")
timed_out = ("Test did not time out", "Test TIMED OUT")
stationary_didnt_move = ("Stationary sphere did not move", "Stationary sphere moved")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_DiffCollisionGroupDiffCollidingLayersNotCollide():
# type: () -> None
"""
Summary:
Runs an automated test to ensure to rigid bodies on the different collision group and collision layer
DO NOT collide.
Level Description:
Moving (entity) - a spherical entity (colored yellow) that is set up with a collision layer of "demo1"
collision group of "demo_group1" gravity as disabled and an initial velocity of (3, 0, 0).
Stationary (entity) - a spherical entity (colored purple) that is set up with a collision layer of "demo2"
collision group of "demo_group2" gravity disabled, and is positioned at location (+2, 0, 0) relative to
Moving's starting position.
Expected Behavior:
When game mode is entered, Moving will begin moving in the positive X direction. The entity should pass through
Stationary with no collision detection triggered.
Test Steps:
1) Loads the level / Enter game mode
2) Retrieve test entities
3) Ensures that the test objects (Moving and Stationary) are located
3.5) set up variables and handlers
4) Wait for Moving to pass through Stationary
5) Logs results
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
"""
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
# **** Helper class ****
class Sphere:
def __init__(self, name):
self.id = None
self.name = name
self.initial_pos = None
self.current_position = None
# Tests for significant change between a set of float pairs
# returns a list of booleans where the i-th index hold the result for the i-th float pair
def detect_significant_change(float_pairs):
# type: ([(float, float)]) -> [bool]
return [abs(p[0] - p[1]) >= CLOSE_ENOUGH_THRESHOLD for p in float_pairs]
# *** Executable Code ***
# Constants
TIME_OUT = 1.5
CLOSE_ENOUGH_THRESHOLD = 0.0001
SPHERE_RADIUS = 0.5 # Radius of both sphere entities
# 1) Open level / Enter game mode
helper.init_idle()
helper.open_level("Physics", "Collider_DiffCollisionGroupDiffCollidingLayersNotCollide")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve/validate entities
moving = Sphere("Moving")
moving.id = general.find_game_entity(moving.name)
Report.critical_result(Tests.moving_entity_found, moving.id.IsValid())
stationary = Sphere("Stationary")
stationary.id = general.find_game_entity(stationary.name)
Report.critical_result(Tests.stationary_entity_found, stationary.id.IsValid())
# 3) Log starting positions
moving.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", moving.id)
stationary.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", stationary.id)
# Ensure that spheres are aligned properly along the y and z axises (so x axis movement will cause collision)
spheres_aligned = (abs(moving.initial_pos.y - stationary.initial_pos.y) < SPHERE_RADIUS) and (
abs(moving.initial_pos.z - stationary.initial_pos.z) < SPHERE_RADIUS
)
# Report critical level integrity results
Report.critical_result(Tests.moving_pos_found, moving.initial_pos is not None and not moving.initial_pos.IsZero())
Report.critical_result(
Tests.stationary_pos_found, stationary.initial_pos is not None and not moving.initial_pos.IsZero()
)
Report.critical_result(
Tests.spheres_share_x_axis,
spheres_aligned,
"Please check the level to make sure Moving Sphere and Stationary Sphere share y and z positions",
)
# 3.5) Set up variables and handler for observing force region interaction
class TestData:
collision_occurred = False
spheres_switched = False
# Force Region Event Handler
def on_collision_begin(args):
collider_id = args[0]
if collider_id.Equal(moving.id):
if not TestData.collision_occurred:
TestData.collision_occurred = True
Report.info("Collision detected")
# Assign the handler
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(stationary.id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
moving.current_pos = moving.initial_pos
stationary.current_pos = stationary.initial_pos
# Tests if we are done collecting results and can exit the test
def done_collecting_results():
COMPARISON_BUFFER = 0.2
if not TestData.collision_occurred:
moving.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", moving.id)
stationary.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", stationary.id)
if moving.current_pos.x > stationary.current_pos.x + COMPARISON_BUFFER:
# Moving sphere passed the stationary sphere's x coordinate
TestData.spheres_switched = True
return True
else:
# A collision was detected
Report.info("A collision was detected unfortunately")
return True
return False
# 4) Wait for results to be collected or for time out
Report.result(Tests.timed_out, helper.wait_for_condition(done_collecting_results, TIME_OUT))
# 5) Log results
Report.result(Tests.spheres_switched_sides, TestData.spheres_switched)
Report.result(Tests.spheres_not_collided, not TestData.collision_occurred)
# Look for movement in Y direction. Report results
y_movement = detect_significant_change(
[(moving.initial_pos.y, moving.current_pos.y), (stationary.initial_pos.y, stationary.current_pos.y)]
)
if not any(y_movement):
Report.success(Tests.no_y_movement)
else:
Report.failure(Tests.no_y_movement)
if y_movement[0]:
Report.info("Moving entity Y movement detected. This should not happen")
if y_movement[1]:
Report.info("Stationary entity Y movement detected. This should not happen")
# Look for movement in Z direction. Report Results
z_movement = detect_significant_change(
[(moving.initial_pos.z, moving.current_pos.z), (stationary.initial_pos.z, stationary.current_pos.z)]
)
if not any(z_movement):
Report.success(Tests.no_z_movement)
else:
Report.failure(Tests.no_z_movement)
if z_movement[0]:
Report.info("Moving entity Z movement detected. This should not happen")
if z_movement[1]:
Report.info("Stationary entity Z movement detected. This should not happen")
Report.result(Tests.stationary_didnt_move, stationary.current_pos.Equal(stationary.initial_pos))
# Collected data dump
Report.info(" ********** Collected Data ***************")
Report.info("Moving sphere's positions:")
Report.info_vector3(moving.initial_pos, " initial:")
Report.info_vector3(moving.current_pos, " final:")
Report.info("*****************************")
Report.info("Stationary sphere's positions:")
Report.info_vector3(stationary.initial_pos, " initial:")
Report.info_vector3(stationary.current_pos, " final:")
Report.info("*****************************")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_DiffCollisionGroupDiffCollidingLayersNotCollide)
@@ -0,0 +1,109 @@
"""
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 : C4044695
Test Case Title : Verify that when you add a multiple surface fbx in PxMesh in PhysxCollider,
multiple number of Material Slots populate in the Materials Section
"""
# fmt: off
class Tests():
create_entity = ("Created test entity", "Failed to create test entity")
mesh_added = ("Added Mesh component", "Failed to add Mesh component")
physx_collider_added = ("Added PhysX Collider component", "Failed to add PhysX Collider component")
shape_is_correct = ("PhysX Collider Shape is correct", "PhysX Collider Shape is not PhysicsAsset")
assign_mesh_asset = ("Assigned Mesh asset to Mesh component", "Failed to assign mesh asset to Mesh component")
assign_px_mesh_asset = ("Assigned PxMesh asset to Collider component", "Failed to assign PxMesh asset to Collider component")
count_mesh_surface = ("Multiple slots show under materials", "Failed to show required surface materials")
# fmt: on
def Collider_MultipleSurfaceSlots():
"""
Summary:
Create entity with Mesh and PhysX Collider components and assign a fbx file in both the components.
Verify that the fbx is properly fitting the mesh.
Expected Behavior:
1) The fbx is properly fitting the mesh.
2) Multiple material slots show up under Materials section in the PhysX Collider component and that
they correspond to the number of surfaces as designed in the mesh.
Test Steps:
1) Load the empty level
2) Create an entity
3) Add Mesh and Physics collider components
4) Select the PhysicsAsset shape in the PhysX Collider component
5) Assign the fbx file in PhysX Mesh and Mesh component
6) Check if multiple material slots show up under Materials section in the PhysX Collider component
:return: None
"""
# Builtins
import os
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
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
# Constants
PHYSICS_ASSET_INDEX = 7 # Hardcoded enum index value for Shape property
SURFACE_TAG_COUNT = 4 # Number of surface tags included in used asset
# Asset paths
STATIC_MESH = os.path.join("assets", "Physics", "Collider_MultipleSurfaceSlots", "test.azmodel")
PHYSX_MESH = os.path.join("assets", "Physics","Collider_MultipleSurfaceSlots", "test.pxmesh")
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create an entity
test_entity = Entity.create_editor_entity("test_entity")
Report.result(Tests.create_entity, test_entity.id.IsValid())
# 3) Add Mesh and Physics collider components
mesh_component = test_entity.add_component("Mesh")
Report.result(Tests.mesh_added, test_entity.has_component("Mesh"))
collider_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.physx_collider_added, test_entity.has_component("PhysX Collider"))
# 4) Select the PhysicsAsset shape in the PhysX Collider component
collider_component.set_component_property_value("Shape Configuration|Shape", PHYSICS_ASSET_INDEX)
value_to_test = collider_component.get_component_property_value("Shape Configuration|Shape")
Report.result(Tests.shape_is_correct, value_to_test == PHYSICS_ASSET_INDEX)
# 5) Assign the fbx file in PhysX Mesh and Mesh component
px_asset = Asset.find_asset_by_path(PHYSX_MESH)
collider_component.set_component_property_value("Shape Configuration|Asset|PhysX Mesh", px_asset.id)
px_asset.id = collider_component.get_component_property_value("Shape Configuration|Asset|PhysX Mesh")
Report.result(Tests.assign_px_mesh_asset, px_asset.get_path().lower() == PHYSX_MESH.replace(os.sep, "/").lower())
mesh_asset = Asset.find_asset_by_path(STATIC_MESH)
mesh_component.set_component_property_value("Controller|Configuration|Mesh Asset", mesh_asset.id)
mesh_asset.id = mesh_component.get_component_property_value("Controller|Configuration|Mesh Asset")
Report.result(Tests.assign_mesh_asset, mesh_asset.get_path().lower() == STATIC_MESH.replace(os.sep, "/").lower())
# 6) Check if multiple material slots show up under Materials section in the PhysX Collider component
pte = collider_component.get_property_tree()
def get_surface_count():
count = pte.get_container_count("Collider Configuration|Physics Materials|Slots")
return count.GetValue()
Report.result(
Tests.count_mesh_surface, helper.wait_for_condition(lambda: get_surface_count() == SURFACE_TAG_COUNT, 1.0)
)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_MultipleSurfaceSlots)
@@ -0,0 +1,222 @@
"""
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 : C4976245
# Test Case Title : Check that two entities of collision group "None" do not collide,
# even though they have the same collision layer
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
moving_entity_found = ("Moving entity found", "Moving entity not found")
stationary_entity_found = ("Stationary entity found", "Stationary entity not found")
moving_pos_found = ("Moving sphere position found", "Moving sphere position not found")
stationary_pos_found = ("Stationary sphere position found", "Stationary sphere position not found")
spheres_share_x_axis = ("Both spheres are aligned properly", "Spheres are not aligned properly")
spheres_not_collided = ("No collision was detected", "A collision was detected")
spheres_switched_sides = ("The moving sphere passed through the other", "Moving sphere did not pass through")
no_y_movement = ("There was no Y movement", "Some Y movement was detected")
no_z_movement = ("There was no Z movement", "Some Z movement was detected")
timed_out = ("Test did not time out", "Test TIMED OUT")
stationary_didnt_move = ("Stationary sphere did not move", "Stationary sphere moved")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_NoneCollisionGroupSameLayerNotCollide():
# type: () -> None
"""
Summary:
Runs an automated test to ensure to rigid bodies on the same collision layer, but no collision group
DO NOT collide.
Level Description:
Moving (entity) - a spherical entity (colored yellow) that is set up with a collision layer of "demo1"
collision group of "None" gravity as disabled and an initial velocity of (3, 0, 0).
Stationary (entity) - a spherical entity (colored purple) that is set up with a collision layer of "demo1"
collision group of "None" gravity disabled, and is positioned at location (+2, 0, 0) relative to
Moving's starting position with no initial velocity.
Expected Behavior:
When game mode is entered, Moving will begin moving in the positive X direction. The entity should pass through
Stationary with no collision detection triggered.
Test Steps:
1) Loads the level / Enter game mode
2) Retrieve test entities
3) Ensures that the test objects (Moving and Stationary) are located
4) set up variables and handlers
5) Wait for Moving to pass through Stationary
6) Logs results
7) 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
# **** Helper class ****
class Sphere:
def __init__(self, name):
self.id = None
self.name = name
self.initial_pos = None
self.current_position = None
# Tests the deltas for for significant change. Prints a message to log if change is detected.
# returns True if no change detected between both deltas-- False otherwise
def no_movement(delta_moving, delta_stationary, axis):
result = True
if delta_moving > CLOSE_ENOUGH_THRESHOLD:
Report.info("Moving entity {} movement detected. This should not happen".format(axis))
result = False
if delta_stationary > CLOSE_ENOUGH_THRESHOLD:
Report.info("Stationary entity {} movement detected. This should not happen".format(axis))
result = False
return result
# *** Executable Code ***
# Constants
TIME_OUT = 1.5
CLOSE_ENOUGH_THRESHOLD = 0.0001
SPHERE_RADIUS = 0.5 # Radius of both sphere entities
COMPARISON_BUFFER = 0.2 # used for position comparisons to offset game physics anomalies
# 1) Open level / Enter game mode
helper.init_idle()
helper.open_level("Physics", "Collider_NoneCollisionGroupSameLayerNotCollide")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve/validate entities
moving = Sphere("Moving")
moving.id = general.find_game_entity(moving.name)
Report.critical_result(Tests.moving_entity_found, moving.id.IsValid())
stationary = Sphere("Stationary")
stationary.id = general.find_game_entity(stationary.name)
Report.critical_result(Tests.stationary_entity_found, stationary.id.IsValid())
# 3) Log starting positions
moving.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", moving.id)
stationary.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", stationary.id)
# Ensure that spheres are aligned properly along the y and z axises (so x axis movement will cause collision)
spheres_aligned = (abs(moving.initial_pos.y - stationary.initial_pos.y) < SPHERE_RADIUS) and (
abs(moving.initial_pos.z - stationary.initial_pos.z) < SPHERE_RADIUS
)
# Report critical level integrity results
Report.critical_result(Tests.moving_pos_found, moving.initial_pos is not None and not moving.initial_pos.IsZero())
Report.critical_result(
Tests.stationary_pos_found, stationary.initial_pos is not None and not moving.initial_pos.IsZero()
)
Report.critical_result(
Tests.spheres_share_x_axis,
spheres_aligned,
"Please check the level to make sure Moving Sphere and Stationary Sphere share y and z positions",
)
# 4) Set up variables and handler for observing force region interaction
class TestData:
collision_occurred = False
spheres_switched = False
# Force Region Event Handler
def on_collision_begin(args):
collider_id = args[0]
if collider_id.Equal(moving.id):
if not TestData.collision_occurred:
TestData.collision_occurred = True
Report.info("Collision detected")
# Assign the handler
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(stationary.id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
moving.current_pos = moving.initial_pos
stationary.current_pos = stationary.initial_pos
# Tests if we are done collecting results and can exit the test
def done_collecting_results():
if not TestData.collision_occurred:
moving.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", moving.id)
stationary.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", stationary.id)
if moving.current_pos.x > (stationary.current_pos.x + COMPARISON_BUFFER):
# Moving sphere passed the stationary sphere's x coordinate
TestData.spheres_switched = True
return True
else:
# A collision was detected
Report.info("A collision was detected unfortunately")
return True
return False
# 5) Wait for results to be collected or for time out
Report.result(Tests.timed_out, helper.wait_for_condition(done_collecting_results, TIME_OUT))
# 6) Log results
Report.result(Tests.spheres_switched_sides, TestData.spheres_switched)
Report.result(Tests.spheres_not_collided, not TestData.collision_occurred)
# Look for movement in Y direction. Report results
no_movement_y = no_movement(
abs(moving.initial_pos.y - moving.current_pos.y), abs(stationary.initial_pos.y - stationary.current_pos.y), "Y"
)
Report.result(Tests.no_y_movement, no_movement_y)
# Look for movement in Z direction. Report results
no_movement_z = no_movement(
abs(moving.initial_pos.z - moving.current_pos.z), abs(stationary.initial_pos.z - stationary.current_pos.z), "Z"
)
Report.result(Tests.no_z_movement, no_movement_z)
Report.result(Tests.stationary_didnt_move, stationary.current_pos.Equal(stationary.initial_pos))
# Collected data dump
Report.info(" ********** Collected Data ***************")
Report.info("Moving sphere's positions:")
Report.info_vector3(moving.initial_pos, " initial:")
Report.info_vector3(moving.current_pos, " final:")
Report.info("*****************************")
Report.info("Stationary sphere's positions:")
Report.info_vector3(stationary.initial_pos, " initial:")
Report.info_vector3(stationary.current_pos, " final:")
Report.info("*****************************")
# 7) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_NoneCollisionGroupSameLayerNotCollide)
@@ -0,0 +1,93 @@
"""
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 : C14861501
Test Case Title : Verify PxMesh is auto-assigned when Collider component is added after Rendering Mesh component
"""
# fmt: off
class Tests():
create_entity = ("Created test entity", "Failed to create test entity")
mesh_added = ("Added Mesh component", "Failed to add Mesh component")
physx_collider_added = ("Added PhysX Collider component", "Failed to add PhysX Collider component")
assign_mesh_asset = ("Assigned Mesh asset to Mesh component", "Failed to assign mesh asset to Mesh component")
automatic_shape_change = ("Shape was changed automatically", "Shape failed to change automatically")
# fmt: on
def Collider_PxMeshAutoAssignedWhenAddingRenderMeshComponent():
"""
Summary:
Create entity with Mesh component and assign a render mesh to the Mesh component. Add Physics Collider component
and Verify that the physics mesh asset is auto-assigned.
Expected Behavior:
The physics asset in PhysX Collider component is auto-assigned
Test Steps:
1) Load the empty level
2) Create an entity
3) Add Mesh component
4) Assign a render mesh asset to Mesh component (the fbx mesh having both Static mesh and PhysX collision Mesh)
5) Add PhysX Collider component
6) The physics asset in PhysX Collider component is auto-assigned.
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
"""
# Builtins
import os
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
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
# Asset paths
STATIC_MESH = os.path.join("assets", "Physics", "Collider_PxMeshAutoAssigned", "spherebot", "r0-b_body.azmodel")
PHYSX_MESH = os.path.join(
"assets", "Physics", "Collider_PxMeshAutoAssigned", "spherebot", "r0-b_body.pxmesh"
)
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create an entity
test_entity = Entity.create_editor_entity("test_entity")
Report.result(Tests.create_entity, test_entity.id.IsValid())
# 3) Add Mesh component
mesh_component = test_entity.add_component("Mesh")
Report.result(Tests.mesh_added, test_entity.has_component("Mesh"))
# 4) Assign a render mesh asset to Mesh component (the fbx mesh having both Static mesh and PhysX collision Mesh)
mesh_asset = Asset.find_asset_by_path(STATIC_MESH)
mesh_component.set_component_property_value("Controller|Configuration|Mesh Asset", mesh_asset.id)
mesh_asset.id = mesh_component.get_component_property_value("Controller|Configuration|Mesh Asset")
Report.result(Tests.assign_mesh_asset, mesh_asset.get_path().lower() == STATIC_MESH.replace(os.sep, "/").lower())
# 5) Add PhysX Collider component
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.physx_collider_added, test_entity.has_component("PhysX Collider"))
# 6) The physics asset in PhysX Collider component is auto-assigned.
asset_id = test_component.get_component_property_value("Shape Configuration|Asset|PhysX Mesh")
test_asset = Asset(asset_id)
Report.result(Tests.automatic_shape_change, test_asset.get_path().lower() == PHYSX_MESH.replace(os.sep, "/").lower())
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_PxMeshAutoAssignedWhenAddingRenderMeshComponent)
@@ -0,0 +1,95 @@
"""
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 : C14861502
Test Case Title : Verify PxMesh is auto-assigned in collider when Mesh is assigned in Rendering Mesh component
"""
# fmt: off
class Tests():
create_entity = ("Created test entity", "Failed to create test entity")
mesh_added = ("Added Mesh component", "Failed to add Mesh component")
physx_collider_added = ("Added PhysX Collider component", "Failed to add PhysX Collider component")
shape_default_at_start = ("Shape was correct initially", "Default shape was not correct")
automatic_shape_change = ("Shape was changed automatically", "Shape failed to change automatically")
# fmt: on
def Collider_PxMeshAutoAssignedWhenModifyingRenderMeshComponent():
"""
Summary:
Create entity with Mesh and PhysX Collider components, then assign a render mesh to the Mesh component
Expected Behavior:
The physics asset in PhysX Collider component is auto-assigned after adding the render mesh
Test Steps:
1) Load the empty level
2) Create an entity
3) Add Mesh and PhysX Collider component
4) Verify no physics asset is auto-assigned.
5) Assign a render mesh asset to Mesh component (the fbx mesh having both Static mesh and PhysX collision Mesh)
6) The physics asset in PhysX Collider component is auto-assigned.
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
"""
# Builtins
import os
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
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.legacy.general as general
MESH_ASSET_PATH = os.path.join("Objects", "SphereBot", "r0-b_body.azmodel")
MESH_PROPERTY_PATH = "Controller|Configuration|Mesh Asset"
TESTED_PROPERTY_PATH = "Shape Configuration|Asset|PhysX Mesh"
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create an entity
test_entity = Entity.create_editor_entity("test_entity")
Report.result(Tests.create_entity, test_entity.id.IsValid())
# 3) Add Mesh and PhysX Collider component
mesh_component = test_entity.add_component("Mesh")
Report.result(Tests.mesh_added, test_entity.has_component("Mesh"))
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.physx_collider_added, test_entity.has_component("PhysX Collider"))
# 4) Verify no physics asset is auto-assigned
value_to_test = test_component.get_component_property_value(TESTED_PROPERTY_PATH)
Report.result(Tests.shape_default_at_start, value_to_test == azlmbr.asset.AssetId())
# 5) Assign a render mesh asset to Mesh component (the fbx mesh having both Static mesh and PhysX collision Mesh)
asset_value = Asset.find_asset_by_path(MESH_ASSET_PATH)
mesh_component.set_component_property_value(MESH_PROPERTY_PATH, asset_value.id)
# 6) The physics asset in PhysX Collider component is auto-assigned.
general.idle_wait(1.0) # Gives the script a moment for the value to update before grabbing it
value_to_test = test_component.get_component_property_value(TESTED_PROPERTY_PATH)
asset = Asset(value_to_test)
Report.result(Tests.automatic_shape_change, "r0-b_body" in asset.get_path())
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_PxMeshAutoAssignedWhenModifyingRenderMeshComponent)
@@ -0,0 +1,143 @@
"""
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 : C4982803
Test Case Title : Verify that when the shape Physics Asset is selected,
PxMesh option gets enabled and a Px Mesh can be selected and assigned to the object
"""
# fmt: off
class Tests():
create_collider_entity = ("Entity created successfully", "Failed to create Entity")
mesh_added = ("Added Mesh component", "Failed to add Mesh component")
physx_collider_added = ("Added PhysX Collider component", "Failed to add PhysX Collider component")
physx_rigid_body_added = ("Added PhysX Rigid Body component", "Failed to add PhysX Rigid Body component")
add_physics_asset_shape = ("Added shape as physics asset", "Failed to add shape ")
assign_fbx_mesh = ("Assigned fbx mesh", "Failed to assign fbx mesh")
create_terrain = ("Terrain entity created successfully", "Failed to create Terrain Entity")
add_physx_shape_collider = ("Added PhysX Shape Collider", "Failed to add PhysX Shape Collider")
add_box_shape = ("Added Box Shape", "Failed to add Box Shape")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
test_collision = ("Entity collided with terrain", "Failed to collide with terrain")
# fmt: on
def Collider_PxMeshConvexMeshCollides():
"""
Summary:
Load level with Entity above ground having PhysX Collider, PhysX Rigid Body and Mesh components.
Verify that the entity falls on the ground and collides with the terrain when fbx convex mesh is added to collider.
Expected Behavior:
The entity falls on the ground and collides with the terrain.
Test Steps:
1) Load the level
2) Create test entity above the ground
3) Add PhysX Collider, PhysX Rigid Body and Mesh components.
4) Add the Shape as PhysicsAsset and select fbx convex mesh in PhysX Collider component.
5) Create terrain entity with physx terrain
6) Enter game mode
7) Verify that the entity falls on the ground and collides with the terrain.
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
"""
# Builtins
import os
# Helper Files
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
import azlmbr.math as math
# Open 3D Engine Imports
import azlmbr
import azlmbr.legacy.general as general
# Constants
PHYSICS_ASSET_INDEX = 7 # Hardcoded enum index value for Shape property
MESH_ASSET_PATH = os.path.join("assets", "Physics", "Collider_PxMeshConvexMeshCollides", "spherebot", "r0-b_body.pxmesh")
TIMEOUT = 2.0
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "Base")
# 2) Create test entity
collider = EditorEntity.create_editor_entity_at([512.0, 512.0, 33.0], "Collider")
Report.result(Tests.create_collider_entity, collider.id.IsValid())
# 3) Add PhysX Collider, PhysX Rigid Body and Mesh components.
collider_component = collider.add_component("PhysX Collider")
Report.result(Tests.physx_collider_added, collider.has_component("PhysX Collider"))
collider.add_component("PhysX Rigid Body")
Report.result(Tests.physx_rigid_body_added, collider.has_component("PhysX Rigid Body"))
collider.add_component("Mesh")
Report.result(Tests.mesh_added, collider.has_component("Mesh"))
# 4) Add the Shape as PhysicsAsset and select fbx convex mesh in PhysX Collider component.
collider_component.set_component_property_value("Shape Configuration|Shape", PHYSICS_ASSET_INDEX)
value_to_test = collider_component.get_component_property_value("Shape Configuration|Shape")
Report.result(Tests.add_physics_asset_shape, value_to_test == PHYSICS_ASSET_INDEX)
mesh_asset = Asset.find_asset_by_path(MESH_ASSET_PATH)
collider_component.set_component_property_value("Shape Configuration|Asset|PhysX Mesh", mesh_asset.id)
mesh_asset.id = collider_component.get_component_property_value("Shape Configuration|Asset|PhysX Mesh")
Report.result(Tests.assign_fbx_mesh, mesh_asset.get_path().lower() == MESH_ASSET_PATH.replace(os.sep, "/").lower())
# 5) Create terrain entity with physx terrain
terrain = EditorEntity.create_editor_entity_at([512.0, 512.0, 31.0], "Terrain")
Report.result(Tests.create_terrain, terrain.id.IsValid())
terrain.add_component("PhysX Shape Collider")
Report.result(Tests.add_physx_shape_collider, terrain.has_component("PhysX Shape Collider"))
box_shape_component = terrain.add_component("Box Shape")
Report.result(Tests.add_box_shape, terrain.has_component("Box Shape"))
box_shape_component.set_component_property_value("Box Shape|Box Configuration|Dimensions",
math.Vector3(1024.0, 1024.0, 1.0))
# 6) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 7) Verify that the entity falls on the ground and collides with the terrain
class Collider:
id = general.find_game_entity("Collider")
touched_ground = False
terrain_id = general.find_game_entity("Terrain")
def on_collision_begin(args):
other_id = args[0]
if other_id.Equal(terrain_id):
Report.info("Touched ground")
Collider.touched_ground = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(Collider.id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
Report.result(Tests.test_collision, Collider.touched_ground)
# 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(Collider_PxMeshConvexMeshCollides)
@@ -0,0 +1,84 @@
"""
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 : C14861498
# Test Case Title : Confirm that when a PhysXCollider has no physics asset, the physics asset collider \
# shape throw an error
# fmt:off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
found_entity = ("Entity was found", "Entity WAS NOT found")
warning_message_logged = ("The expected warning was logged", "The expected message was not logged")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt:on
def Collider_PxMeshErrorIfNoMesh():
"""
Summary:
This test looks for the presence of an error when an entity with a PhysXCollider has no physics mesh, but has its
collider shape set to a physics mesh.
Level Description:
One entity with a PhysXCollider with the collider shape set to "physics asset" and no actual physics mesh.
That's it!
Steps:
1) Load the level / enter game mode
2) Find the entity
3) Look for warning
4) Exit game mode
5) Close the editor
[Log Monitor] make sure error lines are present in the log
Expected Behavior:
The editor should open, load the level and (seemingly) instantly close. The two error lines specified should
print to the log.
:return: None
"""
import os
import sys
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
import azlmbr.legacy.general as general
helper.init_idle()
with Tracer() as warning_tracer:
def has_physx_warning():
return warning_tracer.has_warnings and any(
'PhysX' in warningInfo.window and
'EditorColliderComponent' in warningInfo.message for warningInfo in warning_tracer.warnings)
# 1) Load level / enter game mode
helper.open_level("Physics", "Collider_PxMeshErrorIfNoMesh")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Find game entity
id = general.find_game_entity("test_entity")
Report.result(Tests.found_entity, id.IsValid())
# 3) Look for warning
helper.wait_for_condition(has_physx_warning, 1.0)
Report.result(Tests.warning_message_logged, has_physx_warning())
# 4) 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(Collider_PxMeshErrorIfNoMesh)
@@ -0,0 +1,104 @@
"""
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 : C14861504
Test Case Title : Verify if Rendering Mesh does not have a PhysX Collision Mesh fbx, then PxMesh is not auto-assigned
"""
# fmt: off
class Tests():
create_entity = ("Created test entity", "Failed to create test entity")
mesh_added = ("Added Mesh component", "Failed to add Mesh component")
physx_collider_added = ("Added PhysX Collider component", "Failed to add PhysX Collider component")
assign_mesh_asset = ("Assigned Mesh asset to Mesh component", "Failed to assign mesh asset to Mesh component")
shape_not_assigned = ("Shape is not auto assigned", "Shape auto assigned unexpectedly")
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
warnings_found = ("Warnings found in logs", "No warnings found in logs")
# fmt: on
def Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx():
"""
Summary:
Create entity with Mesh component and assign a render mesh that has no physics asset to the Mesh component.
Add Physics Collider component and Verify that the physics mesh asset is not auto-assigned.
Expected Behavior:
Following warning is logged in Game mode:
"(PhysX) - EditorColliderComponent::BuildGameEntity. No asset assigned to Collider Component. Entity: <Entity Name>"
Test Steps:
1) Load the empty level
2) Create an entity
3) Add Mesh component
4) Assign a render mesh asset to Mesh component (the fbx mesh having only Static mesh and no PxMesh)
5) Add PhysX Collider component
6) The physics asset in PhysX Collider component is not auto-assigned.
7) Enter GameMode and check for warnings
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
"""
# Builtins
import os
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
from editor_python_test_tools.asset_utils import Asset
# Open 3D Engine Imports
import azlmbr.asset as azasset
# Asset paths
STATIC_MESH = os.path.join("assets", "Physics", "Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx", "test_asset.azmodel")
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create an entity
test_entity = Entity.create_editor_entity("test_entity")
Report.result(Tests.create_entity, test_entity.id.IsValid())
# 3) Add Mesh component
mesh_component = test_entity.add_component("Mesh")
Report.result(Tests.mesh_added, test_entity.has_component("Mesh"))
# 4) Assign a render mesh asset to Mesh component (the fbx mesh having both Static mesh and PhysX collision Mesh)
mesh_asset = Asset.find_asset_by_path(STATIC_MESH)
mesh_component.set_component_property_value("Controller|Configuration|Mesh Asset", mesh_asset.id)
mesh_asset.id = mesh_component.get_component_property_value("Controller|Configuration|Mesh Asset")
Report.result(Tests.assign_mesh_asset, mesh_asset.get_path().lower() == STATIC_MESH.replace(os.sep, "/").lower())
# 5) Add PhysX Collider component
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.physx_collider_added, test_entity.has_component("PhysX Collider"))
# 6) The physics asset in PhysX Collider component is not auto-assigned.
asset_id = test_component.get_component_property_value("Shape Configuration|Asset|PhysX Mesh")
# Comparing asset_id with Null/Invalid asset azlmbr.asset.AssetId() to check that asset is not auto assigned
Report.result(Tests.shape_not_assigned, asset_id == azasset.AssetId())
# 7) Enter GameMode and check for warnings
with Tracer() as section_tracer:
helper.enter_game_mode(Tests.enter_game_mode)
# Checking if warning exist and the exact warning is caught in the expected lines in Test file
Report.result(Tests.warnings_found, section_tracer.has_warnings)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx)
@@ -0,0 +1,126 @@
"""
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 : C4976243
# Test Case Title : Assign different collision layers and same collision group
# (such that this group has both these collision layers enabled) to two entities and verify that they collide
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_terrain = ("Terrain found", "Terrain not found")
find_entity1 = ("Entity1 found", "Entity1 not found")
find_entity2 = ("Entity2 found", "Entity2 not found")
gravity_enabled = ("Gravity is enabled", "Gravity is disabled")
gravity_disabled = ("Gravity is disabled", "Gravity is enabled")
collision_occurance = ("Entity1 and Entity2 collided", "Entity1 and Entity2 did not collide")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_SameCollisionGroupDiffLayersCollide():
"""
Summary:
Assign different collision layers and same collision group (such that this group has both these
collision layers enabled) to two entities and verify that they collide
Level Description:
Entity1 (entity) - Entity with components PhysX Rigid Body, PhysX Collider, Terrain and Rendering Mesh
"Collision Layer" as "A" and "Collides with" as "B" with gravity enabled.
Entity1 is placed exactly above Terrain and Entity2 along z axis
Entity2 (entity) - Entity with components PhysX Rigid Body, PhysX Collider, Terrain and Rendering Mesh
"Collision Layer" as "Default" and "Collides with" as "B" with gravity disabled
Entity2 is placed exactly in between the Terrain and Entity1 along z axis
Terrain (entity) - Entity with Terrain component.
"Collision Layer" as "Default" and "Collides with" as "All"
Expected Behavior:
Created entities should collide with each other.
We are checking created entities collided (Entity1 and Entity2)
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Check if gravity is enabled for entity1 and disabled for entity2
5) Create collision event handlers
6) Check for collisions between entities
7) Exit game mode
8) 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
# Constants
TIMEOUT = 2 # waits for 2 secs to verify if the collision occured
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "Collider_SameCollisionGroupDiffLayersCollide")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
terrain_id = general.find_game_entity("Terrain")
Report.info(dir(terrain_id))
entity1_id = general.find_game_entity("Entity1")
entity2_id = general.find_game_entity("Entity2")
Report.critical_result(Tests.find_terrain, terrain_id.IsValid())
Report.critical_result(Tests.find_entity1, entity1_id.IsValid())
Report.critical_result(Tests.find_entity2, entity2_id.IsValid())
# 4) Check if gravity is enabled for entity1 and disabled for entity2
is_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", entity1_id)
Report.info("Gravity check for entity1")
Report.result(Tests.gravity_enabled, is_gravity_enabled)
is_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", entity2_id)
Report.info("Gravity check for entity2")
Report.result(Tests.gravity_disabled, not is_gravity_enabled)
class Collision:
entity_collision = False
# 5) Create collision event handler
def on_collision_begin(args):
if args[0].Equal(entity1_id):
Report.info("Collision occurred")
Collision.entity_collision = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(entity2_id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
# 6) Check for collisions between entities
helper.wait_for_condition(lambda: Collision.entity_collision, TIMEOUT)
Report.critical_result(Tests.collision_occurance, Collision.entity_collision)
# 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(Collider_SameCollisionGroupDiffLayersCollide)
@@ -0,0 +1,184 @@
"""
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 : C4976244
# Test Case Title : Checks that two entities of similar custom layer collide
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
moving_sphere_found = ("Moving sphere found", "Moving sphere not found")
stationary_sphere_found = ("Stationary sphere found", "Stationary sphere not found")
velocities_before_collision_valid = ("Sphere velocities are valid", "Sphere velocities are not valid")
orientation_before_collision = ("Both spheres are aligned properly", "Spheres are not aligned properly")
spheres_collided = ("Collision was detected", "A collision was not detected")
orientation_after_collision = ("Spheres are aligned properly", "Spheres are not aligned properly")
velocities_after_collision_valid = ("Velocity after collision valid", "Velocity after collision not valid")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_SameCollisionGroupSameCustomLayerCollide():
# type: () -> None
"""
Summary:
Runs an automated test to ensure to rigid bodies on similar collision layer and group collide.
Level Description:
Moving Sphere (Entity) - On the same x axis as the Stationary Sphere, moving in the positive x direction;
has sphere shaped PhysX Collider, PhysX Rigid Body, Sphere Shape. Collision Group All, layer A
Stationary Sphere (Entity) - On the same x axis as the Moving Sphere;
has sphere shaped PhysX Collider, PhysX Rigid Body, Sphere Shape Collision Group All, layer A
Expected Behavior: The moving sphere will move torward the stationary sphere in the positive x direction
and collide with it. Both spheres will then separate and move in opposite directions.
Test Steps:
1) Load the level
2) Enter Game Mode
3) Validate entities
4) Validate positions and velocities
5) Start handlers
6) Wait for collision
7) Validated and logs results
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.legacy.general as general
import azlmbr.bus
# Constants
TIMEOUT = 1
FLOAT_THRESHOLD = sys.float_info.epsilon
# Helper functions
# Callback function for the collision handler
def on_collision_begin(args):
# type (list) -> None
Report.info("Collision Occurred")
other_id = args[0]
if other_id.Equal(moving_sphere.id):
stationary_sphere.collision_happened = True
class Entity:
def __init__(self, name):
self.id = general.find_game_entity(name)
self.name = name
self.initial_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
self.final_velocity = None
self.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
self.final_position = None
self.collision_happened = False
Report.info_vector3(self.initial_position, "{} initial position: ".format(self.name))
Report.info_vector3(self.initial_velocity, "{} initial velocity: ".format(self.name))
def get_final_position_and_velocity(self):
# type () -> None
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 report_final_values(self):
# type () -> None
Report.info_vector3(self.final_position, "{} final position: ".format(self.name))
Report.info_vector3(self.final_velocity, "{} final velocity: ".format(self.name))
def moving_in_x_direction(self, positive_x_direction):
# type (bool) -> bool
velocity_vector = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
if positive_x_direction:
correct_direction = velocity_vector.x > 0
else:
correct_direction = velocity_vector.x < 0
return (
correct_direction and abs(velocity_vector.y) < FLOAT_THRESHOLD and abs(velocity_vector.z) < FLOAT_THRESHOLD
)
# Checks if spheres are in the correct orientation
def validate_positions(moving_entity_position, stationary_entity_position):
# type (Vector3, Vector3) -> bool
return (
abs(moving_entity_position.z - stationary_entity_position.z) < FLOAT_THRESHOLD
and abs(moving_entity_position.y - stationary_entity_position.y) < FLOAT_THRESHOLD
and moving_entity_position.x < stationary_entity_position.x
)
# Main Script
# 1) Load the level
helper.init_idle()
helper.open_level("physics", "Collider_SameCollisionGroupSameCustomLayerCollide")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate entities
moving_sphere = Entity("Moving_Sphere")
stationary_sphere = Entity("Stationary_Sphere")
Report.critical_result(Tests.moving_sphere_found, moving_sphere.id.isValid())
Report.critical_result(Tests.stationary_sphere_found, stationary_sphere.id.isValid())
# 4) Validate positions and velocities
Report.critical_result(
Tests.orientation_before_collision,
validate_positions(moving_sphere.initial_position, stationary_sphere.initial_position),
)
Report.critical_result(
Tests.velocities_before_collision_valid,
moving_sphere.moving_in_x_direction(positive_x_direction = True)
and stationary_sphere.initial_velocity.IsZero(FLOAT_THRESHOLD),
)
# 5) Start handler
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(stationary_sphere.id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
# 6) Wait for collision
helper.wait_for_condition(lambda: stationary_sphere.collision_happened, TIMEOUT)
# 7) Validated and logs results
Report.result(Tests.spheres_collided, stationary_sphere.collision_happened)
moving_sphere.get_final_position_and_velocity()
stationary_sphere.get_final_position_and_velocity()
Report.result(
Tests.orientation_after_collision,
validate_positions(moving_sphere.final_position, stationary_sphere.final_position),
)
Report.result(
Tests.velocities_after_collision_valid,
moving_sphere.moving_in_x_direction(positive_x_direction = False) and stationary_sphere.moving_in_x_direction(positive_x_direction = True)
)
moving_sphere.report_final_values()
stationary_sphere.report_final_values()
# 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(Collider_SameCollisionGroupSameCustomLayerCollide)
@@ -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 : C4976242
# Test Case Title : Assign same collision layer and same collision group to two entities and
# verify that they collide or not
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_moving = ("Moving entity found", "Moving entity not found")
find_stationary = ("Stationary entity found", "Stationary entity not found")
find_terrain = ("Terrain entity found", "Terrain entity not found")
stationary_above_terrain = ("Stationary is above terrain", "Stationary is not above terrain")
moving_above_stationary = ("Moving is above stationary", "Moving is not above stationary")
gravity_works = ("Moving Sphere fell down", "Moving Sphere did not fall")
collisions = ("Collision occurred in between entities", "Collision did not occur between entities")
falls_below_terrain_height = ("Moving is below terrain", "Moving did not fall below terrain before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def Collider_SameCollisionGroupSameLayerCollide():
"""
Summary:
Open a Project that already has two entities with same collision layer and same collision group and verify collision
Level Description:
Moving and Stationary entities are created in level with same collision layer and same collision group.
Moving entity is placed above the Stationary entity.Terrain is placed below the Stationary entity.
So Moving and Stationary entities collide with each other and they go through terrain after collision.
Expected Behavior:
The Moving and Stationary entities should collide with each other.After Collision,they go through terrain.
Test Steps:
1) Open level and Enter game mode
2) Retrieve and validate Entities
3) Get the starting z position of the Moving entity,Stationary entity and Terrain
4) Check and report that the entities are at the correct heights before collision
5) Check that the gravity works and the Moving entity falls down
6) Check Spheres collide only with each other, but not with terrain
7) Check Moving Entity should be below terrain after collision
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.legacy.general as general
import azlmbr.bus
# Constants
TIMEOUT = 2.0
TERRAIN_HEIGHT = 32.0 # Default height of the terrain
MIN_BELOW_TERRAIN = 0.5 # Minimum height below terrain the sphere must be in order to be 'under' it
CLOSE_ENOUGH_THRESHOLD = 0.0001
helper.init_idle()
# 1) Open level and Enter game mode
helper.open_level("Physics", "Collider_SameCollisionGroupSameLayerCollide")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve and validate Entities
moving_id = general.find_game_entity("Sphere_Moving")
Report.critical_result(Tests.find_moving, moving_id.IsValid())
stationary_id = general.find_game_entity("Sphere_Stationary")
Report.critical_result(Tests.find_stationary, stationary_id.IsValid())
terrain_id = general.find_game_entity("Terrain")
Report.critical_result(Tests.find_terrain, terrain_id.IsValid())
# 3) Get the starting z position of the Moving entity,Stationary entity and Terrain
class Sphere:
"""
Class to hold values for test checks.
Attributes:
start_position_z: The initial z position of the sphere
position_z : The z position of the sphere
fell : When the sphere falls any distance below its original position, the value should be set True
below_terrain : When the box falls below the specified terrain height, the value should be set True
"""
start_position_z = None
position_z = None
fell = False
below_terrain = False
Sphere.start_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", moving_id)
stationary_start_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", stationary_id)
# 4)Check and report that the entities are at the correct heights before collision
Report.info(
"Terrain Height: {} \n Stationary Sphere height: {} \n Moving Sphere height: {}".format(
TERRAIN_HEIGHT, stationary_start_z, Sphere.start_position_z
)
)
Report.result(Tests.stationary_above_terrain, TERRAIN_HEIGHT < (stationary_start_z - CLOSE_ENOUGH_THRESHOLD))
Report.result(
Tests.moving_above_stationary, stationary_start_z < (Sphere.start_position_z - CLOSE_ENOUGH_THRESHOLD)
)
# 5)Check that the gravity works and the Moving entity falls down
def sphere_fell():
if not Sphere.fell:
Sphere.position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", moving_id)
if Sphere.position_z < (Sphere.start_position_z - CLOSE_ENOUGH_THRESHOLD):
Report.info("Sphere position is now lower than the starting position")
Sphere.fell = True
return Sphere.fell
helper.wait_for_condition(sphere_fell, TIMEOUT)
Report.result(Tests.gravity_works, Sphere.fell)
# 6) Check Spheres collide only with each other, but not with terrain
class Collision:
entity_collision = False
terrain_collision = False
class CollisionHandler:
def __init__(self, id, func):
self.id = id
self.func = func
self.create_collision_handler()
def on_collision_begin(self, args):
self.func(args[0])
def create_collision_handler(self):
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
def on_collision_terrain(other_id):
Collision.terrain_collision = True
Report.info("Collision occured in between Moving or Stationary entity with Terrain")
def on_moving_entity_collision(other_id):
if other_id.Equal(stationary_id):
Collision.entity_collision = True
# collision handler for entities
CollisionHandler(terrain_id, on_collision_terrain)
CollisionHandler(moving_id, on_moving_entity_collision)
# wait till timeout to check for any collisions happening in the level
helper.wait_for_condition(lambda: Collision.entity_collision, TIMEOUT)
Report.result(Tests.collisions, Collision.entity_collision and not Collision.terrain_collision)
# 7)Check Moving Entity should be below terrain after collision
def sphere_below_terrain():
if not Sphere.below_terrain:
Sphere.position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", moving_id)
if Sphere.position_z < (TERRAIN_HEIGHT - MIN_BELOW_TERRAIN):
Sphere.below_terrain = True
return Sphere.below_terrain
sphere_under_terrain = helper.wait_for_condition(sphere_below_terrain, TIMEOUT)
Report.result(Tests.falls_below_terrain_height, sphere_under_terrain)
# 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(Collider_SameCollisionGroupSameLayerCollide)
@@ -0,0 +1,93 @@
"""
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 : C4982800
Test Case Title : Verify that the shape Sphere can be selected from the drop downlist and the value for its radius can be set
"""
# fmt: off
class Tests():
entity_created = ("Test Entity created successfully", "Failed to create Test Entity")
collider_added = ("PhysX Collider added successfully", "Failed to add PhysX Collider")
collider_shape_changed = ("PhysX Collider shape changed successfully", "Failed change PhysX Collider shape")
shape_dimensions_changed = ("Shape dimensions modified successfully", "Failed to modify Shape dimensions")
# fmt: on
def Collider_SphereShapeEditting():
"""
Summary:
Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions
Expected Behavior:
Sphere shape can be selected for the Shape Component and the value for Radius can be changed
Test Steps:
1) Load the empty level
2) Create the test entity
3) Add PhysX Collider component to test entity
4) Change the PhysX Collider shape and store the original dimensions
5) Modify the dimensions
6) Verify they have been changed
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
"""
# Helper Files
from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Open 3D Engine Imports
import azlmbr.math as math
SPHERE_SHAPETYPE_ENUM = 0
DIMENSION_TO_SET = 2.5
DIMENSION_SIZE_TOLERANCE = 0.5
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create the test entity
test_entity = Entity.create_editor_entity("Test Entity")
Report.result(Tests.entity_created, test_entity.id.IsValid())
# 3) Add PhysX Collider component to test entity
test_component = test_entity.add_component("PhysX Collider")
Report.result(Tests.collider_added, test_entity.has_component("PhysX Collider"))
# 4) Change the PhysX Collider shape and store the original dimensions
test_component.set_component_property_value("Shape Configuration|Shape", SPHERE_SHAPETYPE_ENUM)
add_check = test_component.get_component_property_value("Shape Configuration|Shape") == SPHERE_SHAPETYPE_ENUM
Report.result(Tests.collider_shape_changed, add_check)
# 5) Modify the dimensions
test_component.set_component_property_value("Shape Configuration|Sphere|Radius", DIMENSION_TO_SET)
# 6) Verify they have been changed
modified_dimensions = test_component.get_component_property_value("Shape Configuration|Sphere|Radius")
dimensions_successfully_modified = math.Math_IsClose(
DIMENSION_TO_SET, modified_dimensions, DIMENSION_SIZE_TOLERANCE
)
if not dimensions_successfully_modified:
assert (
False
), f"The modified value was not within the allowed tolerance\nExpected:{DIMENSION_TO_SET}\nActual: {modified_dimensions}"
Report.result(Tests.shape_dimensions_changed, dimensions_successfully_modified)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Collider_SphereShapeEditting)
@@ -0,0 +1,233 @@
"""
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 : C4982595
# Test Case Title : Verify that when the Trigger Checkbox is ticked, the object no longer collides with another object
# but simply passes through it
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
sphere_found_valid = ("Sphere found and validated", "Failed to find and validate Sphere")
trigger_box_found_valid = ("Trigger Box found and validated", "Failed to find and validate Trigger Box")
physical_box_found_valid = ("Physical Box found and validated", "Failed to find and validate Physical Box")
sphere_gravity_disabled = ("Gravity is disabled on Sphere", "Gravity is enabled on Sphere")
sphere_positive_initial_x_velocity = ("Sphere has positive initial x-velocity", "Sphere does not have positive initial x-velocity")
sphere_entered_trigger_box = ("Sphere entered Trigger Box", "Sphere did not enter Trigger Box")
sphere_exited_trigger_box = ("Sphere exited Trigger Box", "Sphere did not exit Trigger Box")
sphere_passed_through_trigger_box = ("Sphere passed through Trigger Box", "Sphere did not pass through Trigger Box")
sphere_collided_with_physical_box = ("Sphere collided with Physical Box", "Sphere did not collide with Physical Box")
sphere_bounced_back = ("Sphere bounced back from Physical Box", "Sphere did not bounce back from Physical Box")
sphere_did_not_pass_through_physical_box = ("Sphere did not pass through Physical Box", "Sphere passed through Physical Box")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
# fmt: on
def Collider_TriggerPassThrough():
"""
Summary:
This script runs an automated test to verify that when an entity's PhysX collider is set as a trigger, the entity no
longer collides with another entity.
Level Description:
Entity: Sphere: PhysX Rigid Body, PhysX Collider with sphere shape, and Mesh with sphere asset
Gravity disabled, Radius 1.0, Initial linear x-velocity +30 m/s, Position (36.0, 36.0, 36.0)
Entity: Trigger Box: PhysX Collider with box shape, and Mesh with cube asset
Trigger enabled, Dimensions (2.0, 2.0, 2.0), Z-offset 1.0, Position (42.0, 36.0, 35.0)
Entity: Physical Box: PhysX Collider with box shape, and Mesh with cube asset
Dimensions (2.0, 2.0, 2.0), Z-offset 1.0, Position (48.0, 36.0, 35.0)
The entities are aligned along the x-axis as follows:
_ _
O ---> !_! |_|
Sphere Trigger Box Physical Box
Expected behavior:
The sphere will pass through the trigger box, collide with the physical box, and bounce back without passing through
the physical box.
Test Steps:
1) Open level and enter game mode
2) Retrieve and validate entities
3) Check that gravity is disabled on the sphere
4) Check that the sphere has positive initial x-velocity
5) Wait for the sphere to enter the trigger box
6) Wait for the sphere to exit the trigger box
7) Check that the sphere passed through the trigger box
8) Wait for the sphere to collide with the physical box
9) Wait for the sphere to stop colliding with the physical box and check that the sphere bounced back
10) Check that the sphere did not pass through the physical box
11) 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
TIME_OUT_SECONDS = 3.0
SPHERE_RADIUS = 1.0
BOX_X_DIMENSION = 2.0
BOX_X_RADIUS = BOX_X_DIMENSION / 2
CLOSE_ENOUGH_BUFFER = 0.1
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
def get_x_position(self):
position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
Report.info_vector3(position, "{}'s position:".format(self.name))
return position.x
class Sphere(Entity):
def __init__(self, name, found_valid_test):
Entity.__init__(self, name, found_valid_test)
self.initial_x_velocity = self.get_x_velocity()
# Trigger state values
self.entered_trigger_target = False
self.trigger_enter_position = None
self.exited_trigger_target = False
self.trigger_exit_position = None
self.passed_through_trigger_target = False
# Collision state values
self.began_collision_with_collision_target = False
self.ended_collision_with_collision_target = False
self.bounced_back = False
self.did_not_pass_through_collision_target = None
def is_gravity_disabled(self):
return not azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
def get_x_velocity(self):
velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
Report.info_vector3(velocity, "{}'s velocity:".format(self.name))
return velocity.x
class TriggerBox(Entity):
def __init__(self, name, found_valid_test, trigger_target):
Entity.__init__(self, name, found_valid_test)
self.trigger_target = trigger_target
# Set up trigger notification handler
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
self.handler.add_callback("OnTriggerExit", self.on_trigger_exit)
def on_trigger_enter(self, args):
other_id = args[0]
if other_id.Equal(self.trigger_target.id) and not self.trigger_target.entered_trigger_target:
self.trigger_target.trigger_enter_position = self.trigger_target.get_x_position()
self.trigger_target.entered_trigger_target = True
def on_trigger_exit(self, args):
other_id = args[0]
if other_id.Equal(self.trigger_target.id) and not self.trigger_target.exited_trigger_target:
self.trigger_target.trigger_exit_position = self.trigger_target.get_x_position()
self.trigger_target.exited_trigger_target = True
# Check that the sphere's position traveled at least the approximate x-length of the box measured from
# the point on the sphere where it entered the trigger to the point on the sphere where it exited
if (
self.trigger_target.trigger_exit_position - SPHERE_RADIUS + CLOSE_ENOUGH_BUFFER
>= self.trigger_target.trigger_enter_position + BOX_X_DIMENSION + SPHERE_RADIUS
):
self.trigger_target.passed_through_trigger_target = True
class PhysicalBox(Entity):
def __init__(self, name, found_valid_test, collision_target):
Entity.__init__(self, name, found_valid_test)
self.collision_target = collision_target
# Set up collision notification handler
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
self.collision_handler.connect(self.id)
self.collision_handler.add_callback("OnCollisionBegin", self.on_collision_begin)
self.collision_handler.add_callback("OnCollisionEnd", self.on_collision_end)
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.collision_target.id):
self.collision_target.began_collision_with_collision_target = True
def on_collision_end(self, args):
other_id = args[0]
if other_id.Equal(self.collision_target.id):
self.collision_target.ended_collision_with_collision_target = True
# Check that the sphere reversed its x-direction
if self.collision_target.get_x_velocity() < 0:
self.collision_target.bounced_back = True
# Check that the whole sphere is to the negative x-direction of the box
if sphere.get_x_position() + SPHERE_RADIUS <= physical_box.get_x_position() - BOX_X_RADIUS:
self.collision_target.did_not_pass_through_collision_target = True
# 1) Open level and enter game mode
helper.init_idle()
helper.open_level("Physics", "Collider_TriggerPassThrough")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve and validate entities
sphere = Sphere("Sphere", Tests.sphere_found_valid)
trigger_box = TriggerBox("Trigger Box", Tests.trigger_box_found_valid, trigger_target=sphere)
physical_box = PhysicalBox("Physical Box", Tests.physical_box_found_valid, collision_target=sphere)
entities = (sphere, trigger_box, physical_box)
for entity in entities:
Report.critical_result(entity.found_valid_test, entity.id.IsValid())
# 3) Check that gravity is disabled on the sphere
Report.critical_result(Tests.sphere_gravity_disabled, sphere.is_gravity_disabled())
# 4) Check that the sphere has positive initial x-velocity
Report.critical_result(Tests.sphere_positive_initial_x_velocity, sphere.initial_x_velocity > 0)
# 5) Wait for the sphere to enter the trigger box
helper.wait_for_condition(lambda: sphere.entered_trigger_target, TIME_OUT_SECONDS)
Report.critical_result(Tests.sphere_entered_trigger_box, sphere.entered_trigger_target)
# 6) Wait for the sphere to exit the trigger box
helper.wait_for_condition(lambda: sphere.exited_trigger_target, TIME_OUT_SECONDS)
Report.critical_result(Tests.sphere_exited_trigger_box, sphere.exited_trigger_target)
# 7) Check that the sphere passed through the trigger box
Report.critical_result(Tests.sphere_passed_through_trigger_box, sphere.passed_through_trigger_target)
# 8) Wait for the sphere to collide with the physical box
helper.wait_for_condition(lambda: sphere.began_collision_with_collision_target, TIME_OUT_SECONDS)
Report.critical_result(Tests.sphere_collided_with_physical_box, sphere.began_collision_with_collision_target)
# 9) Wait for the sphere to stop colliding with the physical box and check that the sphere bounced back
helper.wait_for_condition(lambda: sphere.ended_collision_with_collision_target, TIME_OUT_SECONDS)
Report.result(Tests.sphere_bounced_back, sphere.bounced_back)
# 10) Check that the sphere did not pass through the physical box
Report.result(Tests.sphere_did_not_pass_through_physical_box, sphere.did_not_pass_through_collision_target)
# 11) 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(Collider_TriggerPassThrough)
@@ -0,0 +1,222 @@
"""
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 : C5959760
# Test Case Title : Check that force region (capsule) exerts point force
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
box_entity_found = ("Box was found in game", "Box COULD NOT be found in game")
capsule_entity_found = ("Capsule was found in game", "Capsule COULD NOT be found in game")
box_pos_found = ("Box position found", "Box position not found")
capsule_pos_found = ("Capsule position found", "Capsule position not found")
force_region_entered = ("Force region entered", "Force region never entered")
force_exertion_predicted = ("Force exerted was predictable", "The force exerted WAS NOT predicted")
box_fell = ("Box fell", "The box did not fall")
box_was_pushed_x_z = ("Box moved positive X, Z", "Box DID NOT move in positive X, Z direction")
box_no_y_movement = ("Box had no substantial Y movement", "Box HAD substantial Y movement")
capsule_no_move = ("Capsule did not move", "Capsule DID move")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
time_out = ("Test did not time out", "Test DID time out")
# fmt: on
def ForceRegion_CapsuleShapedForce():
# type: () -> None
"""
Summary:
Runs an automated test to ensure point force from a capsule force region is exerted on rigid body objects.
Level Description:
A cube (entity: Box) set above a capsule force region (entity: Capsule). The Capsule was assigned point force
with magnitude set to 1000. The Box has been set for "gravity enabled"
Expected behavior:
The Box will fall (due to gravity) into the Capsule's force region. The force region should exert the point
force on the Box, applying a positive X and Z force of substantial magnitude.
Test Steps:
1) Loads the level / Enters game mode
2) Retrieve entities
3) Ensures that the test objects (Box and Capsule) are located
3.5) set up variables and handlers for monitoring results
4) Waits for the box to fall into the force region
or for time out if something unexpected happens
5) Logs results
6) Closes 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
# Global constants
CLOSE_ENOUGH = 0.001
TIME_OUT = 1.5
# Base class
class EntityBase:
def __init__(self, name):
self.name = name
self.id = None
self.initial_pos = None
self.current_pos = None
# Box child class of EntityBase
class Box(EntityBase):
def __init__(self, name):
EntityBase.__init__(self, name)
self.triggered_pos = None
self.fell = False
self.force_observed = False
def check_for_fall(self):
FALL_BUFFER = 0.2
if not self.fell:
self.fell = (
self.initial_pos.z > self.current_pos.z + FALL_BUFFER
and abs(self.initial_pos.x - self.current_pos.x) < CLOSE_ENOUGH
and abs(self.initial_pos.y - self.current_pos.y) < CLOSE_ENOUGH
)
return self.fell
def check_for_force(self):
FORCE_BUFFER = 0.2
if not self.force_observed:
self.force_observed = (
self.current_pos.z > self.triggered_pos.z + FORCE_BUFFER
and self.current_pos.x > self.triggered_pos.x
and abs(self.triggered_pos.y - self.current_pos.y) < CLOSE_ENOUGH
)
return self.force_observed
# Force Region child class of EntityBase
class ForceRegion(EntityBase):
def __init__(self, name):
EntityBase.__init__(self, name)
self.expected_force_magnitude = None
self.actual_force_vector = None
self.actual_force_magnitude = None
self.forced_entity = None
self.triggered = False
# 1) Open level / Enter game mode
helper.init_idle()
helper.open_level("Physics", "ForceRegion_CapsuleShapedForce")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve entities
box = Box("Box")
box.id = general.find_game_entity(box.name)
capsule = ForceRegion("Capsule")
capsule.id = general.find_game_entity(capsule.name)
Report.critical_result(Tests.box_entity_found, box.id.IsValid())
Report.critical_result(Tests.capsule_entity_found, capsule.id.IsValid())
# 3) Log positions for Box and Capsule
box.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", box.id)
capsule.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", capsule.id)
box.current_pos = box.initial_pos
capsule.current_pos = capsule.initial_pos
# validate and print positions to confirm objects were found
Report.critical_result(Tests.box_pos_found, box.initial_pos is not None and not box.initial_pos.IsZero())
Report.critical_result(
Tests.capsule_pos_found, capsule.initial_pos is not None and not capsule.initial_pos.IsZero()
)
capsule.expected_force_magnitude = azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "GetMagnitude", capsule.id)
# 3.5) set up handler
# Force Region Event Handler
def on_force_calculated(args):
# Only store data for first force region calculation
if not capsule.triggered and capsule.id.Equal(args[0]):
capsule.triggered = True
capsule.forced_entity = args[1]
capsule.actual_force_vector = args[2]
capsule.actual_force_magnitude = args[3]
if capsule.forced_entity.Equal(box.id):
box.triggered_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", box.id)
Report.info("Force Region exerted force on {}".format(box.name))
# Assign the handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_force_calculated)
def done_collecting_results():
# Update entity positions
capsule.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", capsule.id)
box.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", box.id)
# Check for three "test complete" conditions
# ! Careful ordering for logic short circuiting. DO NOT SWAP ORDER !
return box.check_for_fall() and capsule.triggered and box.check_for_force()
# 4) wait for force region entry or time out
test_completed = helper.wait_for_condition(done_collecting_results, TIME_OUT)
Report.critical_result(Tests.time_out, test_completed)
# 5) Report findings
Report.result(Tests.box_fell, box.fell)
Report.result(Tests.force_region_entered, capsule.triggered)
Report.result(
Tests.force_exertion_predicted,
abs(capsule.expected_force_magnitude - capsule.actual_force_magnitude) < CLOSE_ENOUGH,
)
Report.result(Tests.box_was_pushed_x_z, box.force_observed)
Report.result(Tests.box_no_y_movement, abs(box.initial_pos.y - box.current_pos.y) < CLOSE_ENOUGH)
Report.result(Tests.capsule_no_move, capsule.initial_pos.IsClose(capsule.current_pos))
# Collected Data Dump
Report.info("******* Collected Data *******")
Report.info("Entity: {}".format(box.name))
Report.info_vector3(box.initial_pos, " Initial Position:")
Report.info_vector3(box.triggered_pos, " Trigger Position:")
Report.info_vector3(box.current_pos, " Final Position:")
Report.info(" Fell: {}".format(box.fell))
Report.info(" Force Observed: {}".format(box.force_observed))
Report.info("******************************")
Report.info("Entity: {}".format(capsule.name))
Report.info_vector3(capsule.initial_pos, " Initial Position:")
Report.info_vector3(capsule.current_pos, " Final Position:")
Report.info(" Expected Force Magnitude: {:.2f}".format(capsule.expected_force_magnitude))
Report.info_vector3(capsule.actual_force_vector, " Actual Force Vector:", capsule.actual_force_magnitude)
Report.info(" Triggered: {}".format(capsule.triggered))
Report.info(
" Triggered Entity: {}".format(
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", capsule.forced_entity)
)
)
Report.info("******************************")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_CapsuleShapedForce)
@@ -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 : C12868578
# Test Case Title : Check that World space and local space force direction doesn't affect magnitude of force exerted
# 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")
entity_position = ("All entities in good relative position", "Not all entities in correct position")
sphere_collisions = ("All spheres collided with Force Regions", "Not All spheres collided")
initial_velocity = ("Spheres started moving correctly", "Spheres not moving correctly")
velocity_updated = ("Sphere velocities updated", "Sphere velocities didn't update")
# Z Direction
sphere_0_found = ("sphere_0 is found", "sphere_0 is not found")
sphere_1_found = ("sphere_1 is found", "sphere_1 is not found")
force_region_0_found = ("force_region_0 is found", "force_region_0 is not found")
force_region_1_found = ("force_region_1 is found", "force_region_1 is not found")
local_force_mag_z = ("z-axis Local Space force magnitude valid", "z-axis Local Space force magnitude invalid")
local_force_dir_z = ("z-axis Local Space force direction valid", "z-axis Local Space force direction invalid")
world_force_mag_z = ("z-axis World Space force magnitude valid", "z-axis World Space force magnitude invalid")
world_force_dir_z = ("z-axis World Space force direction valid", "z-axis World Space force direction invalid")
# X Direction
sphere_2_found = ("sphere_2 is found", "sphere_2 is not found")
sphere_3_found = ("sphere_3 is found", "sphere_3 is not found")
force_region_2_found = ("force_region_2 is found", "force_region_2 is not found")
force_region_3_found = ("force_region_3 is found", "force_region_3 is not found")
local_force_mag_x = ("x-axis Local Space force magnitude valid", "x-axis Local Space force magnitude invalid")
local_force_dir_x = ("x-axis Local Space force direction valid", "x-axis Local Space force direction invalid")
world_force_mag_x = ("x-axis World Space force magnitude valid", "x-axis World Space force magnitude invalid")
world_force_dir_x = ("x-axis World Space force direction valid", "x-axis World Space force direction invalid")
# Y Direction
sphere_4_found = ("sphere_4 is found", "sphere_4 is not found")
sphere_5_found = ("sphere_5 is found", "sphere_5 is not found")
force_region_4_found = ("force_region_4 is found", "force_region_4 is not found")
force_region_5_found = ("force_region_5 is found", "force_region_5 is not found")
local_force_mag_y = ("y-axis Local Space force magnitude valid", "y-axis Local Space force magnitude invalid")
local_force_dir_y = ("y-axis Local Space force direction valid", "y-axis Local Space force direction invalid")
world_force_mag_y = ("y-axis World Space force magnitude valid", "y-axis World Space force magnitude invalid")
world_force_dir_y = ("y-axis World Space force direction valid", "y-axis World Space force direction invalid")
# fmt: on
def ForceRegion_DirectionHasNoAffectOnTotalForce():
"""
Summary: Check that world and local space force direction should not affect magnitude of force exerted on entity.
Level Description:
sphere_0 - Directly above force_region_0 with velocity of 10.0 in the negative z direction; has sphere shape
collider, rigid body, and sphere shape
sphere_1 - Directly above force_region_1 with velocity of 10.0 in the negative z direction; has sphere shape
collider, rigid body, and sphere shape
force_region_0 - Directly below sphere_0 with world space force of magnitude 100.0 and direction vector of
<0.0,0.0,999.0>; has box shape collider and force region
force_region_1 - Directly below sphere_1 with local space force of magnitude 100.0 and direction vector of
<0.0,0.0,999.0>; has box shape collider and force region
Expected Behavior: Both spheres bounce off of there respective force regions with a force of magnitude that is close
to 100.0 in positive z direction. The direction is normalized from the manual entered direction input.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Set up and validate entities
4) Wait for collision
5) Wait for velocities to become positive
6) Log and validate results
7) Exit Game Mode
8) 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
# Constants
FLOAT_THRESHOLD = sys.float_info.epsilon
TIMEOUT = 1
MAGNITUDE_THRESHOLD = 0.1
FORCE_VECTOR_THRESHOLD = 0.001
# Helper Functions
class Entity:
def __init__(self, name):
# type (str, hex) -> None
self.id = general.find_game_entity(name)
self.name = name
self.collision_happened = False
# ID validation
self.found = Tests.__dict__[self.name + "_found"]
Report.critical_result(self.found, self.id.isValid())
@property
def position(self):
# type () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Sphere(Entity):
def __init__(self, name, axis, force_region):
Entity.__init__(self, name)
self.paired_force_region = force_region
self.axis = axis
self.force_vector = None
self.force_magnitude = None
# Set Handler
self.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
self.handler.connect(None)
self.handler.add_callback("OnCalculateNetForce", self.on_calculate_net_force)
# Report initial values
Report.info_vector3(self.position, "{} initial position: ".format(self.name))
Report.info_vector3(self.velocity, "{} initial velocity: ".format(self.name))
@property
def velocity(self):
# type () -> Vector3
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
@property
def is_moving_in_positive_direction(self):
# type () -> bool
# A List of the attribute names for the velocity (Vector3)
axis = ["x", "y", "z"]
# Finds the index in the list of the attribute in which the sphere is moving in
index = axis.index(self.axis)
# Checking that we are moving along that axis
moving_component = getattr(self.velocity, axis[index]) > 0.0
# Getting rid of moving axis from list
axis.pop(index)
# Checking that the sphere is not moving along either of the remaining two axis.
stationary_components = (
abs(getattr(self.velocity, axis[0])) < FLOAT_THRESHOLD
and abs(getattr(self.velocity, axis[1])) < FLOAT_THRESHOLD
)
return moving_component and stationary_components
def report_values(self):
# type () -> None
# Reports final position and velocity information
Report.info_vector3(self.position, "{} final position: ".format(self.name))
Report.info_vector3(self.velocity, "{} final velocity: ".format(self.name))
def on_calculate_net_force(self, args):
# type (list) -> None
# Flips the collision happened boolean for the sphere object and prints the force values.
if self.paired_force_region.id.Equal(args[0]) and self.id.equal(args[1]) and not self.collision_happened:
self.collision_happened = True
self.force_vector = args[2]
self.force_magnitude = args[3]
# Report force vector information
Report.info_vector3(self.force_vector, "{} had following force vector applied".format(self.name))
Report.info("{} is the applied force magnitude".format(self.force_magnitude))
def validate_local_force_results(sphere):
# type (Sphere) -> None
local_force_direction = Tests.__dict__["local_force_dir_{}".format(sphere.axis)]
local_force_magnitude = Tests.__dict__["local_force_mag_{}".format(sphere.axis)]
Report.result(local_force_direction, check_applied_force_vector(sphere.force_vector))
force_region_magnitude = azlmbr.physics.ForceLocalSpaceRequestBus(azlmbr.bus.Event,"GetMagnitude", sphere.paired_force_region.id)
print(force_region_magnitude)
print("LOOKKKK ABOVE!")
Report.result(local_force_magnitude, abs(sphere.force_magnitude - force_region_magnitude) < MAGNITUDE_THRESHOLD)
def validate_world_force_results(sphere):
# type (Sphere) -> None
world_force_direction = Tests.__dict__["world_force_dir_{}".format(sphere.axis)]
world_force_magnitude = Tests.__dict__["world_force_mag_{}".format(sphere.axis)]
Report.result(world_force_direction, check_applied_force_vector(sphere.force_vector))
force_region_magnitude = azlmbr.physics.ForceWorldSpaceRequestBus(azlmbr.bus.Event,"GetMagnitude", sphere.paired_force_region.id)
Report.result(world_force_magnitude, abs(sphere.force_magnitude - force_region_magnitude) < MAGNITUDE_THRESHOLD)
def check_pair_position(sphere):
# type (Sphere) -> bool
# Ensures sphere lines up with its associated force region
force_region_position = sphere.paired_force_region.position
axis = ["x", "y", "z"]
index = axis.index(sphere.axis)
offset_component = getattr(force_region_position, axis[index]) < getattr(sphere.position, axis[index])
axis.pop(index)
zero_components = (
abs(getattr(force_region_position, axis[0]) - getattr(sphere.position, axis[0])) < FLOAT_THRESHOLD
and abs(getattr(force_region_position, axis[1]) - getattr(sphere.position, axis[1])) < FLOAT_THRESHOLD
)
return offset_component and zero_components
def check_applied_force_vector(vector):
# type (Sphere) -> bool
# Ensures the force vector is within expected threshold. The components of the vector can either be 0 or 1
axis = ["x", "y", "z"]
return all(
[
True
for component in axis
if abs(getattr(vector, component) - 1.00) < FORCE_VECTOR_THRESHOLD
or abs(getattr(vector, component)) < FORCE_VECTOR_THRESHOLD
]
)
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "ForceRegion_DirectionHasNoAffectOnTotalForce")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Set up and validate entities
force_region_0 = Entity("force_region_0")
force_region_1 = Entity("force_region_1")
force_region_2 = Entity("force_region_2")
force_region_3 = Entity("force_region_3")
force_region_4 = Entity("force_region_4")
force_region_5 = Entity("force_region_5")
sphere_0 = Sphere("sphere_0", "z", force_region_0)
sphere_1 = Sphere("sphere_1", "z", force_region_1)
sphere_2 = Sphere("sphere_2", "x", force_region_2)
sphere_3 = Sphere("sphere_3", "x", force_region_3)
sphere_4 = Sphere("sphere_4", "y", force_region_4)
sphere_5 = Sphere("sphere_5", "y", force_region_5)
sphere_list = [sphere_0, sphere_1, sphere_2, sphere_3, sphere_4, sphere_5]
local_force_list = [sphere_1, sphere_3, sphere_5]
world_force_list = [sphere_0, sphere_2, sphere_4]
Report.critical_result(
Tests.entity_position, all([check_pair_position(sphere) for sphere in sphere_list])
)
Report.critical_result(
Tests.initial_velocity,
all([not sphere.is_moving_in_positive_direction for sphere in sphere_list]),
)
# 4) Wait for collision
Report.critical_result(
Tests.sphere_collisions,
helper.wait_for_condition(
lambda: all([sphere.collision_happened for sphere in sphere_list]), TIMEOUT
),
)
# 5) Wait for velocities to become positive
Report.critical_result(
Tests.velocity_updated,
helper.wait_for_condition(
lambda: all([sphere.is_moving_in_positive_direction for sphere in sphere_list]), TIMEOUT
),
)
# 6) Log and validate results
[validate_local_force_results(sphere) for sphere in local_force_list]
[validate_world_force_results(sphere) for sphere in world_force_list]
[sphere.report_values() for sphere in sphere_list]
# 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(ForceRegion_DirectionHasNoAffectOnTotalForce)
@@ -0,0 +1,246 @@
"""
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 : C6321601
# Test Case Title : Check that very high values of direction axes of forces do not throw error
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_terrain = ("Terrain found", "Terrain not found")
find_sphere_world_space = ("Sphere above world force region found", "Sphere above world force region not found")
find_sphere_local_space = ("Sphere above local force region found", "Sphere above local force region not found")
find_sphere_point = ("Sphere above point force region found", "Sphere above point force region not found")
find_sphere_simple_drag = ("Sphere above simple drag force region found", "Sphere above simple drag force region not found")
find_sphere_linear_damping = ("Sphere above linear damping force region found", "Sphere above linear damping force region not found")
find_forcevol_world_space = ("World force region found", "World force region not found")
find_forcevol_local_space = ("Local force region found", "Local force region not found")
find_forcevol_point = ("Point force region found", "Point force region not found")
find_forcevol_simple_drag = ("Simple drag force region found", "Simple drag force region not found")
find_forcevol_linear_damping = ("Linear damping force region found", "Linear damping force region not found")
world_force_magnitude = ("World force magnitude equal to expected magnitude", "World force magnitude not equal to expected magnitude")
world_force_direction = ("World force direction equal to expected direction", "World force direction not equal to expected direction")
local_force_magnitude = ("Local force magnitude equal to expected magnitude", "Local force magnitude not equal to expected magnitude")
local_force_direction = ("Local force direction equal to expected direction", "Local force direction not equal to expected direction")
point_force_magnitude = ("Point force magnitude equal to expected magnitude", "Point force magnitude not equal to expected magnitude")
simp_drag_density = ("Simple Drag force density equal to expected value", "Simple Drag force density not equal to expected value")
lin_damp_damping = ("Linear Damping force damping equal to expected value", "Linear Damping force damping not equal to expected value")
error_not_found = ("Error not found", "Error found")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_HighValuesDirectionAxesWorkWithNoError():
"""
Summary:
Check that very high values of direction axes of forces do not throw error.
Level Description:
Sphere_World_Space, Sphere_Local_Space, Sphere_Point, Sphere_Simple_Drag, Sphere_Linear_Damping
(Entities) Entities with components:
- Physx Collider (Sphere shaped with radius 1.0)
- Mesh(Prmitive sphere mesh)
- PhysX Rigid Body Physics
Below are the entities with common components
- PhysX Collider (Trigger enabled)
- PhysX Force Region(Visible and Debug Forces enabled)
They differ in Force Region Force Type with the following properties:
1) ForceVol_World_Space
Type - World Space - Direction(0.0, 0.0, 999999.0) - Magnitude(999999.0)
2) ForceVol_Local_Space
Type - Local Space - Direction(0.0, 0.0, 999999.0) - Magnitude(999999.0)
3) ForceVol_Point
Type - Point - Magnitude(999999.0)
4) ForceVol_Simple_Drag
Type - Simple Drag - Region Density(999.0)
5) ForceVol_Linear_Damping
Type - Linear Damping - Damping(99.0)
Each sphere is placed above its corresponding force regions.
Each of the force regions are seperated by some distance
Expected Behavior:
The given force should be applied as it is without any error on the Sphere.
We are verifying if the force being applied on each sphere is equal to the expected value
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Add force region handler and validate the forces
5) Exit game mode
6) Close the editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Aed 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
from editor_python_test_tools.utils import Tracer
import azlmbr.legacy.general as general
import azlmbr.bus
import azlmbr.math as lymath
# Constants
TOLERANCE_PERCENT = 0.001
EXPECTED_DIRECTION = lymath.Vector3(0.0, 0.0, 1.0)
EXPECTED_DAMPING = 99.0
EXPECTED_DENSITY = 400.0
CLOSE_THRESHOLD = 0.0001
class SphereForceRegion:
def __init__(self, force_name):
self.force_name = force_name
self.sphere_name = "Sphere_{}".format(force_name)
self.force_region_name = "ForceVol_{}".format(force_name)
self.sphere_id = general.find_game_entity(self.sphere_name)
self.force_region_id = general.find_game_entity(self.force_region_name)
self.in_force_region = False
self.validate_entities()
def validate_entities(self):
Report.result(Tests.__dict__["find_{}".format(self.sphere_name.lower())], self.sphere_id.IsValid())
Report.result(
Tests.__dict__["find_{}".format(self.force_region_name.lower())], self.force_region_id.IsValid()
)
def validate_world_space_force(args):
Report.info("Validating world space force...")
world_expected_magnitude = azlmbr.physics.ForceWorldSpaceRequestBus(
azlmbr.bus.Event, "GetMagnitude", regions[0].force_region_id
)
world_actual_magnitude = args[3]
Report.info(
"Actual Magnitude: {}\t Expected Magnitude: {}".format(world_actual_magnitude, world_expected_magnitude)
)
Report.result(
Tests.world_force_magnitude,
abs(world_actual_magnitude - world_expected_magnitude) < TOLERANCE_PERCENT * world_expected_magnitude,
)
world_actual_direction = args[2]
Report.result(Tests.world_force_direction, world_actual_direction.IsClose(EXPECTED_DIRECTION, CLOSE_THRESHOLD))
def validate_local_space_force(args):
Report.info("Validating local space force...")
local_expected_magnitude = azlmbr.physics.ForceLocalSpaceRequestBus(
azlmbr.bus.Event, "GetMagnitude", regions[1].force_region_id
)
local_actual_magnitude = args[3]
Report.info(
"Actual Magnitude: {}\t Expected Magnitude: {}".format(local_actual_magnitude, local_expected_magnitude)
)
Report.result(
Tests.local_force_magnitude,
abs(local_actual_magnitude - local_expected_magnitude) < TOLERANCE_PERCENT * local_expected_magnitude,
)
local_actual_direction = args[2]
Report.result(Tests.local_force_direction, local_actual_direction.IsClose(EXPECTED_DIRECTION, CLOSE_THRESHOLD))
def validate_point_force(args):
Report.info("Validating Point space force...")
point_expected_magnitude = azlmbr.physics.ForcePointRequestBus(
azlmbr.bus.Event, "GetMagnitude", regions[2].force_region_id
)
point_actual_magnitude = args[3]
Report.info(
"Actual Magnitude: {}\t Expected Magnitude: {}".format(point_actual_magnitude, point_expected_magnitude)
)
Report.result(
Tests.point_force_magnitude,
abs(point_actual_magnitude - point_expected_magnitude) < TOLERANCE_PERCENT * point_expected_magnitude,
)
def validate_simple_drag_force(args):
Report.info("Validating Simple Drag force...")
simp_drag_density = azlmbr.physics.ForceSimpleDragRequestBus(
azlmbr.bus.Event, "GetDensity", regions[3].force_region_id
)
Report.info("Density: {}\t Expected Density: {}".format(simp_drag_density, EXPECTED_DENSITY))
Report.result(Tests.simp_drag_density, simp_drag_density == EXPECTED_DENSITY)
def validate_linear_damping_force(args):
Report.info("Validating Linear Damping force...")
lin_damp_damping = azlmbr.physics.ForceLinearDampingRequestBus(
azlmbr.bus.Event, "GetDamping", regions[4].force_region_id
)
Report.info("Damping: {}\t Expected Damping: {}".format(lin_damp_damping, EXPECTED_DAMPING))
Report.result(Tests.lin_damp_damping, lin_damp_damping == EXPECTED_DAMPING)
def on_calc_net_force(args):
"""
Args:
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
for index, region in enumerate(regions):
if args[0].Equal(region.force_region_id) and args[1].Equal(region.sphere_id) and not region.in_force_region:
region.in_force_region = True
force_validations[index][1](args)
helper.init_idle()
with Tracer() as entity_error_tracer:
def has_physx_error():
return entity_error_tracer.has_errors
# 1) Open level
helper.open_level("Physics", "ForceRegion_HighValuesDirectionAxesWorkWithNoError")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
# Terrain
terrain_id = general.find_game_entity("Terrain")
Report.result(Tests.find_terrain, terrain_id.IsValid())
force_validations = (
("World_Space", validate_world_space_force),
("Local_Space", validate_local_space_force),
("Point", validate_point_force),
("Simple_Drag", validate_simple_drag_force),
("Linear_Damping", validate_linear_damping_force),
)
regions = []
for item in force_validations:
regions.append(SphereForceRegion(item[0]))
# 4) Add force region handler and validate the forces
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
# Wait for 3 secs, because there is a known bug identified and filed in
# JIRA LY-107677
# The error "[Error] Huge object being added to a COctreeNode, name: 'MeshComponentRenderNode', objBox:"
# will show (if occured) in about 3 sec into the game mode.
helper.wait_for_condition(has_physx_error, 3.0)
# 5) Exit game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.result(Tests.error_not_found, not has_physx_error())
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_HighValuesDirectionAxesWorkWithNoError)
@@ -0,0 +1,165 @@
# coding=utf-8
"""
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 : C5959764
# Test Case Title : Check that rigid body (Cube) gets impulse from force region
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_cube = ("Entity Cube found", "Cube not found")
find_force_region = ("Force Region found", "Force Region not found")
cube_gained_height = ("Cube went up", "Cube didn't go up")
force_region_success = ("Force Region impulsed Cube", "Force Region didn't impulse Cube")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
tests_completed = ("Tests completed", "Tests did not complete")
# fmt: on
def ForceRegion_ImpulsesBoxShapedRigidBody():
"""
This run() function will open a a level and validate that a Cube gets impulsed by a force region.
It does this by:
1) Open level
2) Enters Game mode
3) Finds the entities in the scene
4) Gets the position of the Cube
5) Listens for Cube to enter the force region
6) Gets the vector and magnitude when Cube is in force region
7) Lets the Cube travel up
8) Gets new position of Cube
9) Validate the results
10) Exits game mode and editor
"""
# Setup path
import os, 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
class Cube:
id = None
gained_height = False # Did the Cube gain height
z_end_position = 0
z_start_position = 0
# Listen for Force Region events
class RegionData:
def __init__(self, ID):
self.force_region_id = ID
self.force_region_entered = False
self.force_vector = None
self.force_magnitude_on_cube = 0 # Magnitude applied on cube
self.force_region_magnitude = 0 # Magnitude value for the force region set in the editor
self.force_region_magnitude_range = (
0.01
) # Delta value allowed between magnitude force_magnitude_on_cube and force_region_magnitude
def force_region_in_range(self):
return abs(self.force_magnitude_on_cube - self.force_region_magnitude) < 0.01 # 0.01 for buffer room
def ifVectorClose(vec1, vec2):
return abs(vec1 - vec2) < 0.01 # 0.01 For buffer room for dif in the two vectors.
helper.init_idle()
# *****Variables*****
TIME_OUT = 4.0 # Seconds
UPWARD_Z_VECTOR = 1.0
# Open level
helper.open_level("Physics", "ForceRegion_ImpulsesBoxShapedRigidBody")
# Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# Get Entities
Cube.id = general.find_game_entity("Cube")
Report.critical_result(Tests.find_cube, Cube.id.IsValid())
RegionObject = RegionData(general.find_game_entity("Force Region"))
Report.critical_result(Tests.find_force_region, RegionObject.force_region_id.IsValid())
# Set values for cube and force region
Cube.z_start_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Cube.id)
RegionObject.force_region_magnitude = azlmbr.physics.ForcePointRequestBus(
azlmbr.bus.Event, "GetMagnitude", RegionObject.force_region_id
)
Report.info("Cube start z position = {}".format(Cube.z_start_position))
# Force Region Event Handler
def on_calculate_net_force(args):
"""
Called when there is a collision in the level
Args:
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
assert RegionObject.force_region_id.Equal(args[0])
vect = args[2]
mag = args[3]
if not RegionObject.force_region_entered:
RegionObject.force_region_entered = True
RegionObject.force_vector = vect
RegionObject.force_magnitude_on_cube = mag
Report.info("Force Region entered")
# Assign the handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_calculate_net_force)
# Give cube time to travel. Exit when cube is done moving or time runs out.
def test_completed():
Cube.z_end_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Cube.id)
Cube.gained_height = Cube.z_end_position > (Cube.z_start_position + 0.5) # 0.5 for buffer
# Validate if cube gained height and entered force region
if Cube.gained_height and RegionObject.force_region_entered:
Report.success(Tests.cube_gained_height)
return True
return False
# Wait for test to complete
test_is_completed = helper.wait_for_condition(test_completed, TIME_OUT)
if not test_is_completed:
Report.info("The test timed out, check log for possible solutions or adjust the time out time")
Report.failure(Tests.tests_completed)
else:
# Did Force Region succeed. True if vector z is close to 1 and force region magnitude is close to magnitude applied on cube
force_region_result = (
ifVectorClose(RegionObject.force_vector.z, UPWARD_Z_VECTOR) and RegionObject.force_region_in_range()
)
Report.result(Tests.force_region_success, force_region_result)
Report.success(Tests.tests_completed)
# Report test info to log
Report.info("******* FINAL ENTITY INFORMATION *********")
Report.info("Cube Entered force region = {}".format(RegionObject.force_region_entered))
Report.info("Start Z Position = {} End Z Position = {}".format(Cube.z_start_position, Cube.z_end_position))
Report.info("Cube Gained height = {}".format(Cube.gained_height))
Report.info("Vector = {} Magnitude = {}".format(RegionObject.force_vector.z, RegionObject.force_magnitude_on_cube))
# Exit game mode and close the editor
Report.result(Tests.tests_completed, test_is_completed)
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ImpulsesBoxShapedRigidBody)
@@ -0,0 +1,169 @@
# coding=utf-8
"""
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 : C5959764
# Test Case Title : Check that rigid body (Capsule) gets impulse from force region
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_capsule = ("Entity Capsule found", "Capsule not found")
find_force_region = ("Force Region found", "Force Region not found")
capsule_gained_height = ("Capsule went up", "Capsule didn't go up")
force_region_success = ("Force Region impulsed Capsule", "Force Region didn't impulse Capsule")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
tests_completed = ("Tests completed", "Tests did not complete")
# fmt: on
def ForceRegion_ImpulsesCapsuleShapedRigidBody():
"""
This run() function will open a a level and validate that a Capsule gets impulsed by a force region.
It does this by:
1) Open level
2) Enters Game mode
3) Finds the entities in the scene
4) Gets the position of the Capsule
5) Listens for Capsule to enter the force region
6) Gets the vector and magnitude when Capsule is in force region
7) Lets the Capsule travel up
8) Gets new position of Capsule
9) Validate the results
10) Exits game mode and editor
"""
# Setup path
import os, 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
class Capsule:
id = None
gained_height = False # Did the Capsule gain height
z_end_position = 0
z_start_position = 0
# Listen for Force Region events
class RegionData:
def __init__(self, ID):
self.force_region_id = ID
self.force_region_entered = False
self.force_vector = None
self.force_magnitude_on_capsule = 0 # Magnitude applied on Capsule
self.force_region_magnitude = 0 # Magnitude value for the force region set in the editor
self.force_region_magnitude_range = (
0.01
) # Delta value allowed between magnitude force_magnitude_on_capsule and force_region_magnitude
def force_region_in_range(self):
return abs(self.force_magnitude_on_capsule - self.force_region_magnitude) < 0.01 # 0.01 for buffer room
def ifVectorClose(vec1, vec2):
return abs(vec1 - vec2) < 0.01 # 0.01 For buffer room for dif in the two vectors.
helper.init_idle()
# *****Variables*****
TIME_OUT = 4.0 # Seconds
UPWARD_Z_VECTOR = 1.0
# Open level
helper.open_level("Physics", "ForceRegion_ImpulsesCapsuleShapedRigidBody")
# Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# Get Entities
Capsule.id = general.find_game_entity("Capsule")
Report.critical_result(Tests.find_capsule, Capsule.id.IsValid())
RegionObject = RegionData(general.find_game_entity("Force Region"))
Report.critical_result(Tests.find_force_region, RegionObject.force_region_id.IsValid())
# Set values for Capsule and force region
Capsule.z_start_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Capsule.id)
RegionObject.force_region_magnitude = azlmbr.physics.ForcePointRequestBus(
azlmbr.bus.Event, "GetMagnitude", RegionObject.force_region_id
)
Report.info("Capsule start z position = {}".format(Capsule.z_start_position))
# Force Region Event Handler
def on_calculate_net_force(args):
"""
Called when there is a collision in the level
Args:
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
assert RegionObject.force_region_id.Equal(args[0])
vect = args[2]
mag = args[3]
if not RegionObject.force_region_entered:
RegionObject.force_region_entered = True
RegionObject.force_vector = vect
RegionObject.force_magnitude_on_capsule = mag
Report.info("Force Region entered")
# Assign the handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_calculate_net_force)
# Give Capsule time to travel. Exit when Capsule is done moving or time runs out.
def test_completed():
Capsule.z_end_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Capsule.id)
Capsule.gained_height = Capsule.z_end_position > (Capsule.z_start_position + 0.5) # 0.5 for buffer
# Validate if Capsule gained height and entered force region
if Capsule.gained_height and RegionObject.force_region_entered:
Report.success(Tests.capsule_gained_height)
return True
return False
# Wait for test to complete
test_is_completed = helper.wait_for_condition(test_completed, TIME_OUT)
if not test_is_completed:
Report.info("The test timed out, check log for possible solutions or adjust the time out time")
Report.failure(Tests.tests_completed)
else:
# Did Force Region succeed. True if vector z is close to 1 and force region magnitude is close to magnitude applied on Capsule
force_region_result = (
ifVectorClose(RegionObject.force_vector.z, UPWARD_Z_VECTOR) and RegionObject.force_region_in_range()
)
Report.result(Tests.force_region_success, force_region_result)
Report.success(Tests.tests_completed)
# Report test info to logSS
Report.info("******* FINAL ENTITY INFORMATION *********")
Report.info("Capsule Entered force region = {}".format(RegionObject.force_region_entered))
Report.info("Start Z Position = {} End Z Position = {}".format(Capsule.z_start_position, Capsule.z_end_position))
Report.info("Capsule Gained height = {}".format(Capsule.gained_height))
Report.info(
"Vector = {} Magnitude = {}".format(RegionObject.force_vector.z, RegionObject.force_magnitude_on_capsule)
)
# Exit game mode and close the editor
Report.result(Tests.tests_completed, test_is_completed)
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ImpulsesCapsuleShapedRigidBody)
@@ -0,0 +1,171 @@
# coding=utf-8
"""
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 : C5959765
# Test Case Title : Check that rigid body (asset) gets impulse from force region
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_asset = ("Entity asset found", "Asset not found")
find_force_region = ("Force Region found", "Force Region not found")
asset_gained_height = ("Asset went up", "Asset didn't go up")
force_region_success = ("Force Region impulsed asset", "Force Region didn't impulse asset")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
tests_completed = ("Tests completed", "Tests did not complete")
# fmt: on
def ForceRegion_ImpulsesPxMeshShapedRigidBody():
"""
# This run() function will open a a level and validate that a asset gets impulsed by a force region.
# It does this by:
# 1) Open level
# 2) Enters Game mode
# 3) Finds the entities in the scene
# 4) Set values for Asset and force region
# 5) Listens for asset to enter the force region
# 6) Gets the vector and magnitude when asset is in force region
# 7) Lets the asset travel up
# 8) Validate if Asset gained height and entered force region
# 9) Checks if test completed
# 10) Exits game mode and editor
# Level setup: Sedan asset above force region
# First Asset: Name = "Sedan" This entity should drop vertically, collide with force region, and be shot up
# First force region: Name = "Force Region" Should shoot Sedan entity up upon entry
"""
# Setup path
import os, 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
class Asset:
id = None
gained_height = False # Did the Asset gain height
z_end_position = 0
z_start_position = 0
# Listen for Force Region events
class RegionData:
def __init__(self, ID):
self.force_region_id = ID
self.force_region_entered = False
self.force_vector = None
self.force_magnitude_on_asset = 0 # Magnitude applied on Asset
self.force_region_magnitude = 0 # Magnitude value for the force region set in the editor
self.force_region_magnitude_range = (
0.01
) # Delta value allowed between magnitude force_magnitude_on_asset and force_region_magnitude
def force_region_in_range(self):
return abs(self.force_magnitude_on_asset - self.force_region_magnitude) < 0.01 # 0.01 for buffer room
def ifVectorAxisClose(vec1, vec2):
return abs(vec1 - vec2) < 0.01 # 0.01 For buffer room for dif in the two vectors.
helper.init_idle()
# *****Variables*****
TIME_OUT = 4.0 # Seconds
UPWARD_Z_VECTOR = 1.0
# 1) Open level
helper.open_level("Physics", "ForceRegion_ImpulsesPxMeshShapedRigidBody")
# 2) Enters Game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Finds the entities in the scene
Asset.id = general.find_game_entity("Sedan")
Report.critical_result(Tests.find_asset, Asset.id.IsValid())
RegionObject = RegionData(general.find_game_entity("Force Region"))
Report.critical_result(Tests.find_force_region, RegionObject.force_region_id.IsValid())
# 4) Set values for Asset and force region
Asset.z_start_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Asset.id)
RegionObject.force_region_magnitude = azlmbr.physics.ForcePointRequestBus(
azlmbr.bus.Event, "GetMagnitude", RegionObject.force_region_id
)
Report.info("Asset start z position = {}".format(Asset.z_start_position))
# 5) Listens for asset to enter the force region
def on_calculate_net_force(args):
"""
Called when there is a collision in the level
Args:
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
# 6) Gets the vector and magnitude when asset is in force region
vect = args[2]
mag = args[3]
if RegionObject.force_region_id.Equal(args[0]) and not RegionObject.force_region_entered:
RegionObject.force_region_entered = True
RegionObject.force_vector = vect
RegionObject.force_magnitude_on_asset = mag
Report.info("Force Region entered")
# Assign the handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_calculate_net_force)
def test_completed():
# test_completed() will return a bool saying if all the Necessary actions in the test have been completed.
# Necessary Actions: 1) Asset entered Force Region 2) Asset end_height > start_height
Asset.z_end_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Asset.id)
Asset.gained_height = Asset.z_end_position > (Asset.z_start_position + 0.5) # 0.5 for buffer
# 8) Validate if Asset gained height and entered force region
if Asset.gained_height and RegionObject.force_region_entered:
Report.success(Tests.asset_gained_height)
return True
return False
# 7) Lets the asset travel up
test_is_completed = helper.wait_for_condition(test_completed, TIME_OUT)
# 9) Checks if test completed
if not test_is_completed:
Report.info("The test timed out, check log for possible solutions or adjust the time out time")
Report.failure(Tests.tests_completed)
else:
# Did Force Region succeed. True if vector z is close to 1 and force region magnitude is close to magnitude applied on Asset
force_region_result = (
ifVectorAxisClose(RegionObject.force_vector.z, UPWARD_Z_VECTOR) and RegionObject.force_region_in_range()
)
Report.result(Tests.force_region_success, force_region_result)
Report.success(Tests.tests_completed)
# Report test info to log
Report.info("******* FINAL ENTITY INFORMATION *********")
Report.info("Asset Entered force region = {}".format(RegionObject.force_region_entered))
Report.info("Start Z Position = {} End Z Position = {}".format(Asset.z_start_position, Asset.z_end_position))
Report.info("Asset Gained height = {}".format(Asset.gained_height))
Report.info("Vector = {} Magnitude = {}".format(RegionObject.force_vector.z, RegionObject.force_magnitude_on_asset))
# 10) Exit game mode and close the editor
Report.result(Tests.tests_completed, test_is_completed)
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ImpulsesPxMeshShapedRigidBody)
@@ -0,0 +1,268 @@
"""
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 : C5932042
# Test Case Title : Check that force region exerts linear damping force on rigid bodies
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
sphere_validated = ("Sphere entity validated", "Sphere entity NOT validated")
force_region_validated = ("Force Region validated", "Force Region NOT validated")
trigger_validated = ("Trigger entity validated", "Trigger entity NOT validated")
sphere_pos_found = ("Sphere position found", "Sphere position NOT found")
force_region_pos_found = ("Force Region position found", "Force Region position NOT found")
trigger_pos_found = ("Trigger position found", "Trigger position NOT found")
level_setup = ("Level looks set up right", "Level NOT set up right")
damping_force_entered = ("Sphere entered linear damping region", "Linear damping region never entered")
damping_force_expected = ("Damping force was as expected", "Damping force differed from expected")
sphere_slowed = ("Sphere slowed down", "Sphere DID NOT slow down")
sphere_stopped = ("Sphere entity stopped", "Sphere entity DID NOT stop")
force_region_no_move = ("Force Region did not move", "Fore Region DID move")
trigger_no_move = ("Trigger did not move", "Trigger DID move")
timed_out = ("The test did not time out", "The test TIMED OUT")
trigger_not_triggered = ("The Trigger was not triggered", "The Trigger WAS triggered")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_LinearDampingForceOnRigidBodies():
# type: () -> None
"""
Summary:
Runs an automated test to ensure linear damping is exerted on rigid body objects from force regions.
Level Description:
A sphere (entity: Sphere) in positioned above a large cube force region (entity: force_region_entity) who is
assigned a linear damping force with damping of 10.0. The Sphere has gravity enabled, and is positioned high
enough for gravity to accelerate it faster than the maximum velocity inside the linear damping force region.
Expected Behavior:
When game mode is entered, gravity should accelerate the Sphere downward. The velocity of the Sphere should peak
right before it enters force_region_entity. Upon entering the force region, the Sphere should noticeably slow
down. The slower velocity should have a substantially larger (less negative) velocity.
Test Steps:
0) Define useful classes and constants
1) Loads the level / Enters game mode
2) Retrieve and validate entities
3) Ensures that the test object (Sphere) is located
3.5) Set up event handlers
4) Execute test until exit condition met
5) Logs results
5.5) Dump all collected data to log
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
"""
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 azmath
# Entity base class: Handles basic entity data
class EntityBase:
def __init__(self, name):
self.name = name
self.id = None
self.initial_pos = None
self.current_pos = None
# Specific Sphere class
class Sphere(EntityBase):
def __init__(self, name):
EntityBase.__init__(self, name)
self.initial_velocity = None
self.initial_velocity_magnitude = None
self.current_velocity = None
self.slowed = False
self.stopped = False
def check_for_stop(self):
if not self.stopped:
self.current_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
self.slowed = self.initial_velocity_magnitude > self.current_velocity.GetLength() + (
0.5 * self.initial_velocity_magnitude
)
self.stopped = self.current_velocity.IsZero()
return self.stopped
# Specific Force Region class
class ForceRegion(EntityBase):
def __init__(self, name):
EntityBase.__init__(self, name)
self.entered = False
self.object_entered = None
self.expected_force_direction = None
self.actual_force_vector = None
self.actual_force_magnitude = None
self.handler = None
# Specific Trigger class
class Trigger(EntityBase):
def __init__(self, name):
EntityBase.__init__(self, name)
self.triggered = False
self.triggering_obj = None
self.handler = None
# Constants
CLOSE_ENOUGH = 0.001
TIME_OUT = 3.0
INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0)
# 1) Open level / Enter game mode
helper.init_idle()
helper.open_level("Physics", "ForceRegion_LinearDampingForceOnRigidBodies")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve and validate entities
sphere = Sphere("Sphere")
sphere.id = general.find_game_entity(sphere.name)
force_region = ForceRegion("ForceRegion")
force_region.id = general.find_game_entity(force_region.name)
trigger = Trigger("Trigger")
trigger.id = general.find_game_entity(trigger.name)
Report.critical_result(Tests.sphere_validated, sphere.id.IsValid())
Report.critical_result(Tests.force_region_validated, force_region.id.IsValid())
Report.critical_result(Tests.trigger_validated, trigger.id.IsValid())
# 3) Log Entities' positions and initial data
sphere.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere.id)
force_region.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", force_region.id)
trigger.initial_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", trigger.id)
Report.critical_result(Tests.sphere_pos_found, sphere.initial_pos is not None and not sphere.initial_pos.IsZero())
Report.critical_result(
Tests.force_region_pos_found, force_region.initial_pos is not None and not force_region.initial_pos.IsZero()
)
Report.critical_result(
Tests.trigger_pos_found, trigger.initial_pos is not None and not trigger.initial_pos.IsZero()
)
sphere.initial_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", sphere.id)
level_correct = (
(abs(sphere.initial_pos.y - force_region.initial_pos.y) < CLOSE_ENOUGH)
and (abs(sphere.initial_pos.y - trigger.initial_pos.y) < CLOSE_ENOUGH)
and (abs(sphere.initial_pos.x - force_region.initial_pos.x) < CLOSE_ENOUGH)
and (abs(sphere.initial_pos.x - trigger.initial_pos.x) < CLOSE_ENOUGH)
and (sphere.initial_pos.z > force_region.initial_pos.z > trigger.initial_pos.z)
and sphere.initial_velocity.IsClose(INITIAL_VELOCITY, CLOSE_ENOUGH)
)
Report.critical_result(Tests.level_setup, level_correct)
sphere.current_pos = sphere.initial_pos
force_region.current_pos = force_region.initial_pos
trigger.current_pos = trigger.initial_pos
force_region.expected_force_direction = sphere.initial_velocity.MultiplyFloat(-1.0)
force_region.expected_force_direction.Normalize()
sphere.current_velocity = sphere.initial_velocity
sphere.initial_velocity_magnitude = sphere.initial_velocity.GetLength()
# 3.5) Set up variables and handler for observing force region interaction
def done_collecting_results():
# Update current positions
sphere.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere.id)
force_region.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", force_region.id)
trigger.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", trigger.id)
return force_region.entered and sphere.check_for_stop()
# Force Region Event Handler
def on_calc_net_force(args):
if args[0].Equal(force_region.id):
if args[1].Equal(sphere.id):
if not force_region.entered:
force_region.entered = True
force_region.object_entered = sphere
force_region.actual_force_vector = args[2]
force_region.actual_force_magnitude = args[3]
Report.info("Entity: {} entered entity: {}'s volume".format(sphere.name, force_region.name))
def on_trigger_entered(args):
if args[0].Equal(sphere.id):
trigger.triggered = True
trigger.triggering_obj = sphere
# Assign event handlers
force_region.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_region.handler.connect(None)
force_region.handler.add_callback("OnCalculateNetForce", on_calc_net_force)
trigger.handler = azlmbr.physics.TriggerNotificationBusHandler()
trigger.handler.connect(trigger.id)
trigger.handler.add_callback("OnTriggerEnter", on_trigger_entered)
# 4) Execute test until exit condition is met
Report.critical_result(Tests.timed_out, helper.wait_for_condition(done_collecting_results, TIME_OUT))
# 5) Log results
Report.result(Tests.damping_force_entered, force_region.entered)
Report.result(
Tests.damping_force_expected,
force_region.actual_force_vector.IsClose(force_region.expected_force_direction, CLOSE_ENOUGH),
)
Report.result(Tests.sphere_slowed, sphere.slowed)
Report.result(Tests.sphere_stopped, sphere.stopped)
Report.result(Tests.trigger_not_triggered, not trigger.triggered)
Report.result(Tests.force_region_no_move, force_region.initial_pos.IsClose(force_region.current_pos, CLOSE_ENOUGH))
Report.result(Tests.trigger_no_move, trigger.initial_pos.IsClose(trigger.current_pos, CLOSE_ENOUGH))
# 5.5) Collected Data Dump
Report.info(" ********** Collected Data ***************")
Report.info("{}:".format(sphere.name))
Report.info_vector3(sphere.initial_pos, " Initial position:")
Report.info_vector3(sphere.current_pos, " Final position:")
Report.info_vector3(sphere.initial_velocity, " Initial velocity:")
Report.info_vector3(sphere.current_velocity, " Final velocity:")
Report.info(" Slowed: {}".format(sphere.slowed))
Report.info(" Stopped: {}".format(sphere.stopped))
Report.info("***********************************")
Report.info("{}:".format(force_region.name))
Report.info_vector3(force_region.initial_pos, " Initial position:")
Report.info_vector3(force_region.current_pos, " Final position:")
Report.info_vector3(force_region.expected_force_direction, " Expected Force Direction:")
Report.info_vector3(
force_region.actual_force_vector, " Actual Force Direction:", force_region.actual_force_magnitude
)
Report.info(" Entered: {}".format(force_region.entered))
Report.info(" Object Entered: {}".format(force_region.object_entered.name))
Report.info("***********************************")
Report.info("{}:".format(trigger.name))
Report.info_vector3(trigger.initial_pos, " Initial position:")
Report.info_vector3(trigger.current_pos, " Final position:")
Report.info(" Triggered: {}".format(trigger.triggered))
Report.info(" Triggering Object: {}".format(trigger.triggering_obj))
Report.info("***********************************")
# 6) Exit Game mode
helper.exit_game_mode(Tests.exit_game_mode)
Report.info("*** FINISHED TEST ***")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_LinearDampingForceOnRigidBodies)
@@ -0,0 +1,160 @@
"""
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 : C5932041
# Test Case Title : Check that force region exerts local space force on rigid bodies
# Sphere drops and is acted upon in an upward and positive x-ward direction by a force
# with a magnitude close to the assigned force region magnitude when it reaches the force region.
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere entity found", "Sphere entity not found")
find_box = ("Box entity found", "Box entity not found")
sphere_pos_found = ("Sphere position found", "Sphere position not found")
sphere_velocity_found = ("Sphere has downward velocity", "Sphere does not have downward velocity")
box_pos_found = ("Box position found", "Box position not found")
force_region_entered = ("Force region entered", "Force region never entered")
force_x_component_detected = ("Force x-component detected on the Sphere", "Force x-component was not detected on the Sphere")
force_z_component_detected = ("Force z-component detected on the Sphere", "Force z-component was not detected on the Sphere")
force_y_component_not_detected = ("Force y-component not detected on the Sphere", "Force y-component was detected on the Sphere")
force_magnitude_detected = ("Force magnitude detected on the Sphere", "Force magnitude was not detected on the Sphere")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_LocalSpaceForceOnRigidBodies():
"""
Summary:
Runs an automated test to ensure that when a rigid body enters a force region a local space force is exerted.
Level Description:
Box (entity) - suspended above terrain at 45 degree angle with Direction Z = 1.0, magnitude = 1000,
and gravity disabled; contains box mesh, PhysX Collider, and PhysX Force Region
Sphere (entity) - suspended above Box with slight x-axis offset, initial velocity in negative z direction,
gravity disabled; contains sphere mesh, PhysX Rigid Body, PhysX Collider
Expected Behavior:
When game mode is entered, the Sphere entity will travel torward the terrain.
It will reach the force region of the Box entity and be imbued with a net force in the x and z directions.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve entities
4) Log Sphere velocity and positions for Sphere and Box
5) Set up handler and variables
6) Wait for force region entry or time out
7) Look for positive x, zero y, positive z force with a valid magnitude, and report findings
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.legacy.general as general
import azlmbr.bus
# Constants
TIMEOUT = 2.0
TOLERANCE = 1
MAGNITUDE = 1000 # Magnitude assigned to the force region
FORCE_Y_TOLERANCE = sys.float_info.epsilon
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_LocalSpaceForceOnRigidBodies")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
sphere_id = general.find_game_entity("Sphere")
box_id = general.find_game_entity("Box")
Report.critical_result(Tests.find_sphere, sphere_id.isValid())
Report.critical_result(Tests.find_box, box_id.isValid())
# 4) Log Sphere velocity and positions for Sphere and Box
sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
box_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
# Validate and print positions and sphere velocity
sphere_pos_found = sphere_pos is not None and sphere_pos.x != 0 and sphere_pos.y != 0 and sphere_pos.z != 0
Report.critical_result(Tests.sphere_pos_found, sphere_pos_found)
Report.info_vector3(sphere_pos, "Sphere Position:")
sphere_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", sphere_id)
Report.critical_result(Tests.sphere_velocity_found, sphere_velocity.z < 0)
Report.info_vector3(sphere_velocity, "Sphere Initial Velocity:")
box_pos_found = box_pos is not None and box_pos.x != 0 and box_pos.y != 0 and box_pos.z != 0
Report.critical_result(Tests.box_pos_found, box_pos_found)
Report.info_vector3(box_pos, "Box Position:")
# 5) Set up handler and variables
class RegionData:
force_region_entered = False
force_vector = None
force_magnitude = 0
# Force Region Event Handler
def on_force_region_entered(args):
region_id = args[0]
object_id = args[1]
force_vector = args[2]
force_magnitude = args[3]
if region_id.Equal(box_id) and object_id.Equal(sphere_id):
if not RegionData.force_region_entered:
RegionData.force_region_entered = True
RegionData.force_vector = force_vector
RegionData.force_magnitude = force_magnitude
Report.info("Force Region entered")
# Assign the handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_force_region_entered)
# 6) Wait for force region entry or time out
helper.wait_for_condition(lambda: RegionData.force_region_entered, TIMEOUT)
# 7) Look for positive x, positive z force with a valid magnitude, and report findings
force_x_component_detected = RegionData.force_vector.x > 0
force_z_component_detected = RegionData.force_vector.z > 0
force_y_component_detected = abs(RegionData.force_vector.y) > FORCE_Y_TOLERANCE
force_magnitude_detected = abs(RegionData.force_magnitude - MAGNITUDE) < TOLERANCE
Report.result(Tests.force_region_entered, RegionData.force_region_entered)
Report.info_vector3(RegionData.force_vector, "Force vector detected", RegionData.force_magnitude)
Report.result(Tests.force_x_component_detected, force_x_component_detected)
Report.result(Tests.force_z_component_detected, force_z_component_detected)
Report.result(Tests.force_y_component_not_detected, not force_y_component_detected)
Report.result(Tests.force_magnitude_detected, force_magnitude_detected)
# 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(ForceRegion_LocalSpaceForceOnRigidBodies)
@@ -0,0 +1,162 @@
"""
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 : C5968760
# Test Case Title : Check moving force region changes net force
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("SphereRigidBody found", "SphereRigidBody not found")
find_force_region = ("ForceRegionBox is found", "ForceRegionBox is not found")
sphere_dropped = ("Sphere dropped down", "Sphere did not drop down")
sphere_bounced = ("Sphere bounced to its left", "Sphere did not bounce to its left")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_MovingForceRegionChangesNetForce():
"""
Summary:
Check moving force region changes net force.
Level Description:
The SphereRigidBody entity is placed above the ForceRegionBox entity.
ForceRegionBox (entity) - Entity with PhysX Force Region, Mesh, PhysX Collider
SphereRigidBody (entity) - Entity with PhysX Rigid body, Mesh and collider components
Expected Behavior:
We are checking if the ball falls down initially and then moving the force region to the right to verify if the
ball bounces off to the left when it collides with the force region.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Get the initial position of the Sphere (rigid body)
5) Move the object to right (X - direction) and rotate in Y - direction
6) Check if the ball is falling down
7) Add force region notification handler
8) Wait till the ball enters the force region
9) Check if the ball has bounced and moved left
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.legacy.general as general
import azlmbr.bus
# Constants
TIMEOUT = 3.0 # wait a maximum of 3 seconds
SPHERE_RADIUS = 0.5
TRANSLATION_OFFSET = 0.2
ROTATION_OFFSET = -0.005
class Sphere:
id = None
initial_position = None
current_position = None
z_at_collision = None
in_force_region = False
bounced = False
class ForceRegion:
id = None
translation_position = None
rotation_position = None
def sphere_bounced():
Sphere.current_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
bounced_up = Sphere.current_position.z > Sphere.z_at_collision + SPHERE_RADIUS
bounced_left = Sphere.current_position.x < Sphere.initial_position.x
Sphere.bounced = bounced_left and bounced_up
return Sphere.bounced
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_MovingForceRegionChangesNetForce")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
Sphere.id = general.find_game_entity("SphereRigidBody")
Report.critical_result(Tests.find_sphere, Sphere.id.IsValid())
ForceRegion.id = general.find_game_entity("ForceRegionBox")
Report.critical_result(Tests.find_force_region, ForceRegion.id.IsValid())
# 4) Get the initial position of the Sphere (rigid body)
Sphere.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
# 5) Move the object to right (X - direction) and rotate in Y - direction
ForceRegion.translation_position = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTranslation", ForceRegion.id
)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "SetWorldX", ForceRegion.id, (ForceRegion.translation_position.x + TRANSLATION_OFFSET)
)
# Rotation in y direction anti clockwise
ForceRegion.rotation_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", ForceRegion.id)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "RotateAroundLocalY", ForceRegion.id, (ForceRegion.rotation_position.y + ROTATION_OFFSET)
)
Report.info("The force region has been repositioned")
# 6) Check if the ball is falling down
Sphere.current_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
sphere_dropped = Sphere.current_position.z < (Sphere.initial_position.z + SPHERE_RADIUS)
Report.critical_result(Tests.sphere_dropped, sphere_dropped)
# 7) Add force region notification handler
def on_force_region_entered(args):
region_id = args[0]
object_id = args[1]
if region_id.Equal(ForceRegion.id) and object_id.Equal(Sphere.id):
if not Sphere.in_force_region:
Sphere.in_force_region = True
Report.info("Force Region entered")
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_force_region_entered)
# 8) Wait till the ball enters the force region
helper.wait_for_condition(lambda: Sphere.in_force_region, TIMEOUT)
# sphere z position when it entered force region
Sphere.z_at_collision = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id).z
# 9) Check if the ball has bounced and moved left
# wait frames till the ball bounces
helper.wait_for_condition(sphere_bounced, TIMEOUT)
Report.result(Tests.sphere_bounced, Sphere.bounced)
# 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(ForceRegion_MovingForceRegionChangesNetForce)
@@ -0,0 +1,177 @@
"""
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 : C5968759
# Test Case Title : Check nested force regions exert forces simultaneously on rigid body
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_vertical_sphere = ("Vertical sphere found", "Vertical sphere not found")
find_angled_sphere = ("Angled sphere found", "Angled sphere not found")
find_point_force_region = ("Point force region found", "Force Region not found")
find_angled_force_region = ("Angled force region found", "Angled force region not found")
vertical_entered_force_region = ("Vertical Sphere actions completed", "Vertical Sphere actions not completed")
timed_out = ("Test did not time out", "Test TIMED OUT")
angled_sphere_enter_force_region = ("Angled Sphere Entered Force Region", "Angled Sphere didn't enter Force Region")
vertical_sphere_fell_vertically = ("Vertical Sphere fell vertically", "Vertical Sphere didn't fall vertically")
angled_sphere_fell_at_angle = ("Angled Sphere fell at an angle", "Angled Sphere didn't fall at an angle")
vertical_sphere_slowed = ("Vertical Sphere slowed", "Vertical Sphere not slowed")
angled_sphere_slowed = ("Angled Sphere slowed", "Angled Sphere not slowed")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
import os, sys
def ForceRegion_MultipleComponentsCombineForces():
"""
Run() will open a a level and validate that the spheres are affected by the force regions as expected.
Expected Results: Both spheres fall into the force regions and are slowed. One of the spheres also falls at an angle
It does this by:
--> Opens level and enter game mode
--> Finds the entities in the scene
--> Listens for spheres to enter the force regions
--> Set Spheres start position and velocity
--> Listen for spheres to exit force regions
--> Set Spheres end position and velocity
--> Validate the results
--> Exits game mode and editor
Level Description: Two spheres floating above 2 force regions.
Sphere: 1 Name = "Sphere_vertical_drop" This sphere should fall vertically
Sphere: 2 Name = "Sphere_angled_drop" This sphere should fall at an angle
First force region: Name = "Force Region Point" Applies point force along the X axis to only the second sphere
Second force region: Name = "Force Region Simple Drag" Applies a drag force on both spheres
Setup path
"""
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.physics
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Constants
TIME_OUT = 6.0 # Second to wait before timing out
POSITION_TOLERANCE = 0.1
def is_close_XY_position(vec1, vec2):
return abs(vec1.x - vec2.x) < POSITION_TOLERANCE and abs(vec1.y - vec2.y) < POSITION_TOLERANCE
# Holds details about the sphere
class Sphere:
def __init__(self, sphere_id, sphere_name):
self.name = sphere_name
self.id = sphere_id
self.start_position = None
self.end_position = None
self.start_velocity = None
self.end_velocity = None
self.entered_force_region = False
self.exited_force_region = 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)
# 1) Opens level with spheres above a force region
helper.init_idle()
helper.open_level("Physics", "ForceRegion_MultipleComponentsCombineForces")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Finds the entities in the scene
sphere_vertical = Sphere(general.find_game_entity("Sphere_vertical_drop"), "Sphere Vertical")
Report.critical_result(Tests.find_vertical_sphere, sphere_vertical.id.IsValid())
sphere_angled = Sphere(general.find_game_entity("Sphere_angled_drop"), "Sphere Angled")
Report.critical_result(Tests.find_angled_sphere, sphere_angled.id.IsValid())
point_force_region_id = general.find_game_entity("Force Region Point")
Report.critical_result(Tests.find_point_force_region, point_force_region_id.IsValid())
simple_drag_force_region_id = general.find_game_entity("Force Region Simple Drag")
Report.critical_result(Tests.find_angled_force_region, simple_drag_force_region_id.IsValid())
# ******** Handler Functions ********
# Called if Sphere enters force region
def on_trigger_begin(args):
other_id = args[0]
# 4) Gets start position and velocity of spheres
if other_id.Equal(sphere_vertical.id) and sphere_vertical.entered_force_region is False:
Report.info("Trigger Entered")
sphere_vertical.entered_force_region = True
sphere_vertical.start_position = sphere_vertical.get_position()
sphere_vertical.start_velocity = sphere_vertical.get_velocity()
Report.result(Tests.vertical_entered_force_region, sphere_vertical.entered_force_region)
elif other_id.Equal(sphere_angled.id) and sphere_angled.entered_force_region is False:
sphere_angled.entered_force_region = True
sphere_angled.start_position = sphere_angled.get_position()
sphere_angled.start_velocity = sphere_angled.get_velocity()
Report.result(Tests.angled_sphere_enter_force_region, sphere_angled.entered_force_region)
def on_trigger_exit(args):
# 4) Gets end position and velocity of spheres
other_id = args[0]
if other_id.Equal(sphere_vertical.id):
Report.info("Trigger exited")
sphere_vertical.end_position = sphere_vertical.get_position()
sphere_vertical.end_velocity = sphere_vertical.get_velocity()
sphere_vertical.exited_force_region = True
elif other_id.Equal(sphere_angled.id):
Report.info("Trigger exited")
sphere_angled.end_position = sphere_angled.get_position()
sphere_angled.end_velocity = sphere_angled.get_velocity()
sphere_angled.exited_force_region = True
# 3) Listens for spheres to enter the force regions
# Create a handler for each force region
point_force_region_handler = azlmbr.physics.TriggerNotificationBusHandler()
point_force_region_handler.connect(point_force_region_id)
point_force_region_handler.add_callback("OnTriggerEnter", on_trigger_begin)
point_force_region_handler.add_callback("OnTriggerExit", on_trigger_exit)
simple_drag_force_region_handler = azlmbr.physics.TriggerNotificationBusHandler()
simple_drag_force_region_handler.connect(simple_drag_force_region_id)
simple_drag_force_region_handler.add_callback("OnTriggerEnter", on_trigger_begin)
simple_drag_force_region_handler.add_callback("OnTriggerExit", on_trigger_exit)
Report.result(Tests.timed_out, helper.wait_for_condition(lambda: sphere_angled.exited_force_region and
sphere_vertical.exited_force_region, TIME_OUT))
sphere_vertical_slowed_by_force_region = sphere_vertical.end_velocity.z > sphere_vertical.start_velocity.z
sphere_angled_slowed_by_force_region = sphere_angled.end_velocity.z > sphere_angled.start_velocity.z
sphere_angled_fell_at_expected_angle = sphere_angled.end_position.x > sphere_angled.start_position.x + POSITION_TOLERANCE
sphere_vertical_fell_at_expected_angle = is_close_XY_position(sphere_vertical.end_position, sphere_vertical.start_position)
Report.result(Tests.angled_sphere_fell_at_angle, sphere_angled_fell_at_expected_angle)
Report.result(Tests.vertical_sphere_fell_vertically, sphere_vertical_fell_at_expected_angle)
Report.result(Tests.angled_sphere_slowed, sphere_angled_slowed_by_force_region)
Report.result(Tests.vertical_sphere_slowed, sphere_vertical_slowed_by_force_region)
# 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(ForceRegion_MultipleComponentsCombineForces)
@@ -0,0 +1,233 @@
"""
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 : C5959810
# Test Case Title : Check that multiple forces in single force region create correct net force
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
sphere_exists = ("Sphere has been found", "Sphere has not been found")
force_region_exists = ("Force Region has been found", "Force Region has not been found")
sphere_position_found = ("Sphere position found", "Sphere position not found")
force_region_position_found = ("Force Region position found", "Force Region position not found")
orientation_before_collision = ("Sphere is above Force Region", "Sphere is not above Force Region")
sphere_velocity_found = ("Sphere velocity found", "Sphere velocity not found")
sphere_velocity_before_collision = ("Sphere has valid initial velocity", "Sphere initial velocity not valid")
collision = ("Collision has occurred", "No collision has occurred")
force_applied = ("Forces were combined correctly", "Forces were not applied correctly")
new_velocity_applied = ("Sphere velocity has been updated", "Sphere velocity was never updated")
orientation_after_collision = ("Sphere is above and to the left", "Entity orientation is not valid")
sphere_velocity_post_collision = ("Sphere velocity valid post-collision", "Sphere velocity no longer valid")
force_region_has_not_moved = ("Force Region has not moved", "Force Region has somehow moved")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_MultipleForcesInSameComponentCombineForces():
# type: () -> None
"""
Summary: Runs an automated test to ensure that separate forces in a force region add correctly
Level Description:
Sphere - Placed directly above the force region with an initial velocity in the -z direction;
has PhysX Rigid Body, sphere shaped PhysX Collider, Sphere Shape
Force Region - Placed directly under the sphere, has a point, and world space force in the
z and negative x direction respectively; has PhysX Force Region, box shaped PhysX Collider
Expected Behavior: Sphere collides with force region and is sent in the negative x and positive z direction
Test Steps:
1) Load Level
2) Enter Game Mode
3) Find Entities
4) Validate initial positions and velocity
5) Set up handler
6) Wait for Sphere collision with Force Region
7) Validate and Log Results
8) Exit Game Mode
9) 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
# Constants
TIMEOUT = 1
FLOAT_THRESHOLD = sys.float_info.epsilon
# Helper Functions
class Collision:
happened = False
force_vector = None
force_magnitude = None
velocity_now_updated = False
class Entity:
def __init__(self, name):
self.id = general.find_game_entity(name)
self.name = name
self.initial_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
self.final_velocity = None
self.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
self.final_position = None
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 report_sphere_values(self):
Report.info_vector3(self.initial_position, "{} initial position: ".format(self.name))
Report.info_vector3(self.initial_velocity, "{} initial velocity: ".format(self.name))
Report.info_vector3(self.final_position, "{} final position: ".format(self.name))
Report.info_vector3(self.final_velocity, "{} final velocity: ".format(self.name))
def report_force_region_values(self):
Report.info_vector3(self.initial_position, "{} initial position: ".format(self.name))
Report.info_vector3(self.final_position, "{} final position: ".format(self.name))
def validate_positions(collision_happened, sphere_position, force_region_position):
if collision_happened:
result = sphere_position.x < force_region_position.x
else:
result = abs(sphere_position.x - force_region_position.x) < FLOAT_THRESHOLD
return (
result
and sphere_position.z > force_region_position.z
and abs(sphere_position.y - force_region_position.y) < FLOAT_THRESHOLD
)
def validate_sphere_velocity(collision_happened, sphere_velocity_vector):
if collision_happened:
x_result = sphere_velocity_vector.x < 0
z_result = sphere_velocity_vector.z > 0
else:
x_result = abs(sphere_velocity_vector.x) < FLOAT_THRESHOLD
z_result = sphere_velocity_vector.z < 0
return x_result and z_result and abs(sphere_velocity_vector.y) < FLOAT_THRESHOLD
def vector_valid(vector, can_be_zero):
if can_be_zero:
return vector != None
else:
return vector != None and not vector.IsZero()
def on_collision_begin(args):
assert force_region.id.Equal(args[0])
if sphere.id.equal(args[1]):
Collision.happened = True
Report.info("Collision has begun")
if vector_valid(args[2], False):
Collision.force_vector = args[2]
Collision.force_magnitude = args[3]
def force_valid(vector, magnitude):
return magnitude > 0 and vector.x < 0 and vector.z > 0 and abs(vector.y) < FLOAT_THRESHOLD
def velocity_update_check():
current_velocity_vector = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", sphere.id)
velocity_updated = (
current_velocity_vector.x < sphere.initial_velocity.x
and current_velocity_vector.z > sphere.initial_velocity.z
)
if velocity_updated:
Collision.velocity_now_updated = True
return velocity_updated
# Main Script
helper.init_idle()
# 1) Load Level
helper.open_level("physics", "ForceRegion_MultipleForcesInSameComponentCombineForces")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find Entities
sphere = Entity("Sphere")
force_region = Entity("Force_Region")
Report.critical_result(Tests.sphere_exists, sphere.id.isValid())
Report.critical_result(Tests.force_region_exists, force_region.id.isValid())
# 4) Validate initial positions and velocity
# Position validation
Report.critical_result(Tests.sphere_position_found, vector_valid(sphere.initial_position, False))
Report.critical_result(Tests.force_region_position_found, vector_valid(force_region.initial_position, False))
# Velocity validation
Report.critical_result(Tests.sphere_velocity_found, vector_valid(sphere.initial_velocity, False))
# Value validation
Report.critical_result(
Tests.orientation_before_collision,
validate_positions(Collision.happened, sphere.initial_position, force_region.initial_position),
)
Report.critical_result(
Tests.sphere_velocity_before_collision, validate_sphere_velocity(Collision.happened, sphere.initial_velocity)
)
# 5) Set up handler
handler = azlmbr.physics.ForceRegionNotificationBusHandler()
handler.connect(None)
handler.add_callback("OnCalculateNetForce", on_collision_begin)
# 6) Wait for Sphere collision with Force Region and Velocity application
helper.wait_for_condition(lambda: Collision.happened, TIMEOUT)
helper.wait_for_condition(velocity_update_check, TIMEOUT)
# 7) Validate and Log Results
sphere.get_final_position_and_velocity()
force_region.get_final_position_and_velocity()
# Value validation
Report.result(Tests.new_velocity_applied, Collision.velocity_now_updated)
Report.result(Tests.collision, Collision.happened)
Report.result(Tests.force_applied, force_valid(Collision.force_vector, Collision.force_magnitude))
Report.result(
Tests.orientation_after_collision,
validate_positions(Collision.happened, sphere.final_position, force_region.final_position),
)
Report.result(
Tests.sphere_velocity_post_collision, validate_sphere_velocity(Collision.happened, sphere.final_velocity)
)
Report.result(
Tests.force_region_has_not_moved,
force_region.final_position.Subtract(force_region.initial_position).IsZero(FLOAT_THRESHOLD),
)
# Value logging
sphere.report_sphere_values()
force_region.report_force_region_values()
Report.info_vector3(Collision.force_vector, "Applied Force: ", Collision.force_magnitude)
# 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(ForceRegion_MultipleForcesInSameComponentCombineForces)
@@ -0,0 +1,168 @@
"""
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 : C15845879
# Test Case Title : Check that linear damping with high values do not make the object to quiver
# 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")
sphere_found = ("Found sphere", "Did not find sphere")
force_region_found = ("Found force region", "Did not find force region")
check_relative_position = ("Sphere is above force region", "Sphere isn't above force region")
sphere_moving_down = ("Sphere heading to force region", "Sphere has invalid initial velocity")
sphere_entered_force_region = ("Sphere has entered force region", "Sphere never entered force region")
sphere_stopped_moving = ("Sphere final velocity is zero", "Sphere final velocity invalid")
sphere_still_above_force_region = ("Sphere still above force region", "Sphere not above force region")
no_quiver = ("Sphere is not quivering", "Sphere quivering in force region")
# fmt: on
def ForceRegion_NoQuiverOnHighLinearDampingForce():
"""
Summary: Check that linear damping with high values do not make the object to quiver
Level Description:
sphere - Starts above the force_region entity with initial velocity in the negative z direction and
gravity disabbled; has physx collider in sphere shape, physx rigid body, and sphere shape
force_region - Sits below sphere entity, has linear damping force set at 100 and region has scaling
(5,5,5); has physx collider in box shape and physx force region
Expected Behavior: Sphere falls into force region and is stuck by the damping force. It specifically should
not quiver up and down.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Setup handler and wait for sphere to enter force region
5) Validate the Sphere remains in Force Region
6) Check to see if the sphere is quivering
7) Exit Game Mode
8) 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
# Constants
FLOAT_THRESHOLD = sys.float_info.epsilon
TIMEOUT = 1
VELOCITY_THRESHOLD = 0.01
QUIVER_THRESHOLD = 0.01
SLOWDOWN_FRAMES = 30
SPHERE_STOP_OFFSET = 3.5
# Helper Functions
class Entity:
def __init__(self, name):
self.id = general.find_game_entity(name)
self.name = name
self.force_region_id = None
self.entered_force_region = False
self.quiver_reference = None
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
@property
def position(self):
# type () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
@property
def velocity(self):
# type () -> Vector3
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
)
def set_handler(self):
self.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
self.handler.connect(None)
self.handler.add_callback("OnCalculateNetForce", self.on_calculate_net_force)
def on_calculate_net_force(self, args):
# type (list) -> None
# Flips the collision happened boolean for the sphere object and prints the force values.
if self.force_region_id.Equal(args[0]) and self.id.Equal(args[1]) and not self.entered_force_region:
self.entered_force_region = True
def sphere_not_quivering():
# type () -> bool
# Returns False if sphere "quivers" from its initial position, True if it stays close to it's original position
return abs(sphere.position.z - sphere.quiver_reference) > QUIVER_THRESHOLD
def sphere_above_force_region(sphere_position, force_region_position):
# type () -> bool
return (
abs(sphere_position.x - force_region_position.x) < FLOAT_THRESHOLD
and abs(sphere_position.y - force_region_position.y) < FLOAT_THRESHOLD
and sphere_position.z > force_region_position.z
)
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "ForceRegion_NoQuiverOnHighLinearDampingForce")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
sphere = Entity("sphere")
force_region = Entity("force_region")
sphere.force_region_id = force_region.id
Report.critical_result(Tests.sphere_moving_down, not sphere.is_moving_up)
Report.critical_result(
Tests.check_relative_position, sphere_above_force_region(sphere.position, force_region.position)
)
# 4) Setup handler and wait for sphere to enter force region
sphere.set_handler()
Report.critical_result(
Tests.sphere_entered_force_region, helper.wait_for_condition(lambda: sphere.entered_force_region, TIMEOUT)
)
# 5) Validate the Sphere remains in Force Region
# Must wait for the sphere to slow down
Report.result(Tests.sphere_stopped_moving, helper.wait_for_condition(lambda: sphere.velocity.IsZero(VELOCITY_THRESHOLD), TIMEOUT))
# Force region has scaling (5,5,5). Thus the upper edge of the force region is 2.5m above the transform. With proper offset we can
# see that sphere is stuck on top of the force region and did not bounce off.
Report.result(Tests.sphere_still_above_force_region, (sphere.position.z - force_region.position.z) < SPHERE_STOP_OFFSET)
# 6) Check to see if the sphere is quivering
sphere.quiver_reference = sphere.position.z
Report.result(Tests.no_quiver, not helper.wait_for_condition(sphere_not_quivering, TIMEOUT))
# 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(ForceRegion_NoQuiverOnHighLinearDampingForce)
@@ -0,0 +1,198 @@
"""
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 : C6090547
# Test Case Title : Check that force regions in parent and child entities work together.
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere found", "Sphere not found")
find_parent_force_region = ("Parent Force Region found", "Parent Force Region not found")
find_child_force_region = ("Child Force Region found", "Child Force Region not found")
find_trigger_box = ("Trigger Box found", "Trigger Box not found")
sphere_gravity_disabled = ("Sphere gravity disabled", "Sphere gravity not disabled")
parent_force_region_direction = ("Parent Force Region is in positive x direction", "Parent Force Region is not in positive x direction")
child_force_region_direction = ("Child Force Region is in positive y direction", "Child Force Region is not in positive y direction")
parent_force_on_sphere = ("Parent Force Region applied total force on sphere", "Parent Force Region did not apply total force on sphere")
child_force_on_sphere = ("Child Force Region applied total force on sphere", "Child Force Region did not apply total force on sphere")
sphere_enters_trigger = ("Sphere entered Trigger", "Sphere did not enter Trigger before Timeout")
sphere_exits_trigger = ("Sphere exited Trigger", "Sphere did not exit Trigger before Timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ParentChildForcesCombineForces():
"""
Summary:
Runs an automated test to ensure that force regions in parent and child entities work together.
Level Description:
Parent Force Region (entity) - contains PhysX Force Region with world space force which has positive X force
Magnitude with Direction (1, 0, 0) and PhysX Collider (box shape).
Child Force Region (entity) - contains PhysX Force Region with world space force which has positive Y force
Magnitude with Direction (0, 1, 0) and PhysX Collider (box shape).
Sphere (entity) - contains a Sphere mesh, PhysX Collider (sphere shape) and PhysX Rigid Body.
Sphere located at the low (x, y) corner of where the force regions overlap.
Trigger Box (entity) - contains PhysX Collider (box shape)
trigger box placed in the (1, 1, 0) direction from the sphere at the opposite end of
the force region overlap.
Parent and Child force regions are placed above the terrain as two overlapping sheets.
Expected Behavior:
When game mode is entered, the Sphere should accelerate evenly in the positive (x, y) direction and it should move
as much in x as it does in y. Sphere should pass through Trigger Box.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Make sure gravity is off from the start
5) Make sure the parent entity is set as the parent of the child entity in level
6) Make sure parent and child force regions are in correct directions
7) Check parent and child force regions each exert its force on sphere
8) Verify sphere passes through trigger box
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
import azlmbr.legacy.general as general
import azlmbr.bus
import azlmbr.math as lymath
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Constants
TIMEOUT = 3.0
X_DIRECTION = lymath.Vector3(1.0, 0.0, 0.0)
Y_DIRECTION = lymath.Vector3(0.0, 1.0, 0.0)
EXPECTED_MAGNITUDE = 100.0
TOLERANCE = 0.1
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_ParentChildForcesCombineForces")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
sphere_id = general.find_game_entity("Sphere")
Report.critical_result(Tests.find_sphere, sphere_id.IsValid())
parent_id = general.find_game_entity("Parent Force Region")
Report.critical_result(Tests.find_parent_force_region, parent_id.IsValid())
child_id = general.find_game_entity("Child Force Region")
Report.critical_result(Tests.find_child_force_region, child_id.IsValid())
trigger_box_id = general.find_game_entity("Trigger Box")
Report.critical_result(Tests.find_trigger_box, trigger_box_id.IsValid())
# 4) Make sure gravity is off from the start
is_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", sphere_id)
Report.critical_result(Tests.sphere_gravity_disabled, not is_gravity_enabled)
# 5) Make sure the parent entity is set as the parent of the child entity in level
id = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetParentId", child_id)
if id.Equal(parent_id):
Report.info("parent and child force regions are in correct position")
# 6) Make sure parent and child force regions are in correct directions
dir_parent = azlmbr.physics.ForceWorldSpaceRequestBus(azlmbr.bus.Event, "GetDirection", parent_id)
dir_child = azlmbr.physics.ForceWorldSpaceRequestBus(azlmbr.bus.Event, "GetDirection", child_id)
Report.info_vector3(dir_parent, "Parent force region direction : ")
Report.info_vector3(dir_child, "Child force region direction : ")
Report.critical_result(Tests.parent_force_region_direction, dir_parent.IsClose(X_DIRECTION, TOLERANCE))
Report.critical_result(Tests.child_force_region_direction, dir_child.IsClose(Y_DIRECTION, TOLERANCE))
# 7) Check parent and child force regions each exert its force on sphere
class NetForceMagnitude:
parent_force_region_magnitude = 0
child_force_region_magnitude = 0
def on_calc_net_force(args):
"""
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
force_region_id = args[0]
entering_entity = args[1]
if entering_entity.Equal(sphere_id):
if force_region_id.Equal(parent_id):
NetForceMagnitude.parent_force_region_magnitude = args[3]
elif force_region_id.Equal(child_id):
NetForceMagnitude.child_force_region_magnitude = args[3]
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
helper.wait_for_condition(lambda : NetForceMagnitude.parent_force_region_magnitude != 0 and NetForceMagnitude.child_force_region_magnitude != 0, 1.0)
Report.info("Parent Force Region Magnitude on Sphere : {}".format(NetForceMagnitude.parent_force_region_magnitude))
Report.info("Child Force Region Magnitude on Sphere : {}".format(NetForceMagnitude.child_force_region_magnitude))
Report.critical_result(
Tests.parent_force_on_sphere,
abs(EXPECTED_MAGNITUDE - NetForceMagnitude.parent_force_region_magnitude) < TOLERANCE,
)
Report.critical_result(
Tests.child_force_on_sphere,
abs(EXPECTED_MAGNITUDE - NetForceMagnitude.child_force_region_magnitude) < TOLERANCE,
)
# 8) Verify sphere passes through trigger box
class Trigger:
on_entered = False
on_exited = False
def on_trigger_enter(args):
other_id = args[0]
if other_id.Equal(sphere_id):
Trigger.on_entered = True
def on_trigger_exit(args):
other_id = args[0]
if other_id.Equal(sphere_id):
Trigger.on_exited = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(trigger_box_id)
handler.add_callback("OnTriggerEnter", on_trigger_enter)
handler.add_callback("OnTriggerExit", on_trigger_exit)
# Check sphere enters trigger box
helper.wait_for_condition(lambda: Trigger.on_entered, TIMEOUT)
Report.critical_result(Tests.sphere_enters_trigger, Trigger.on_entered)
# Check sphere exits trigger box
helper.wait_for_condition(lambda: Trigger.on_exited, TIMEOUT)
Report.result(Tests.sphere_exits_trigger, Trigger.on_exited)
# 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(ForceRegion_ParentChildForcesCombineForces)
@@ -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 : C5932044
# Test Case Title : Check that force region exerts point force on rigid bodies
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode" )
find_ball = ("Ball entity found", "Ball entity not found" )
find_box = ("Box entity found", "Box entity not found" )
gravity_works = ("Ball fell", "Ball did not fall" )
ball_entered_force_region = ("Ball entered force region", "Ball did not enter force region before timeout" )
ball_exited_force_region = ("Ball exited force region", "Ball did not exit force region before timeout" )
net_force_magnitude = ("The net force magnitude on ball is close to expected value", "The net force magnitude on ball is not close to expected value")
ball_moved_up = ("Ball moved up", "Ball did not move up" )
ball_moved_right = ("Ball moved right", "Ball did not move right" )
ball_not_moved_y = ("Ball did not move in the y direction", "Ball moved in the y direction" )
exit_game_mode = ("Exited game mode", "Couldn't exit game mode" )
# fmt: on
def ForceRegion_PointForceOnRigidBodies():
"""
Summary:
Runs an automated test to ensure that that a force region exerts point force on rigid bodies
Level Description:
RigidBody (entity) - a sphere suspended above and to the right the a force region with gravity enabled;
contains a sphere mesh, PhysX Collider (sphere shape), and PhysX RigidBody
ForceRegion (entity) - contains box mesh, PhysX Collider (Box shape), and PhysX RigidBody
Expected Behavior:
When game mode is entered, the ball entity will experience gravity and fall toward the upper right edge (+z, +x) of
the force region. The force region applies a point force to the ball, sending it upwards (+z) and to the right (+x)
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve entities
4) Get the starting x & z position of the ball
5) Check that the ball falls (gravity check)
6) Check that the ball enters the trigger area
7) Get the magnitude of the collision
8) Check that the ball exits the trigger area
9) Verify that the magnitude of the collision is as expected
10) Check that the ball is moving up and to the right
11) Exit game mode
12) Close the editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import os
import sys
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
class Ball:
start_position_x = None
start_position_z = None
fell = False
# Constants
TIMEOUT = 2.0
MAGNITUDE_TOLERANCE = 0.2 # Magnitudes must be within this amount in order to be valid
NO_MOTION_Y_TOLERANCE = sys.float_info.epsilon # Motion in the y axis must be below this in order to be valid
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_PointForceOnRigidBodies")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
ball_id = general.find_game_entity("RigidBody")
Report.critical_result(Tests.find_ball, ball_id.IsValid())
box_id = general.find_game_entity("ForceRegion")
Report.critical_result(Tests.find_box, box_id.IsValid())
# 4) Get the starting x & z position of the ball
Ball.start_position_x = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldX", ball_id)
Ball.start_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
Report.info("Ball start X: {}".format(Ball.start_position_x))
Report.info("Ball start Z: {}".format(Ball.start_position_z))
# 5) Check that the ball falls (gravity check)
def ball_falls():
if not Ball.fell:
ball_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
if (ball_position_z - Ball.start_position_z) < 0.0:
Report.info("Ball position is now lower than the starting position")
Ball.fell = True
return Ball.fell
helper.wait_for_condition(ball_falls, TIMEOUT)
Report.result(Tests.gravity_works, Ball.fell)
# 6) Check that the ball enters the trigger area
class ForceRegionTrigger:
entered = False
exited = False
def on_enter(args):
other_id = args[0]
if other_id.Equal(ball_id):
Report.info("Trigger entered")
ForceRegionTrigger.entered = True
def on_exit(args):
other_id = args[0]
if other_id.Equal(ball_id):
Report.info("Trigger exited")
ForceRegionTrigger.exited = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(box_id)
handler.add_callback("OnTriggerEnter", on_enter)
handler.add_callback("OnTriggerExit", on_exit)
helper.wait_for_condition(lambda: ForceRegionTrigger.entered, TIMEOUT)
Report.result(Tests.ball_entered_force_region, ForceRegionTrigger.entered)
# 7) Get the magnitude of the collision
class NetForceMagnitude:
value = 0
def on_calc_net_force(args):
"""
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
force_magnitude = args[3]
NetForceMagnitude.value = force_magnitude
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
# 8) Check that the ball exits the trigger area
helper.wait_for_condition(lambda: ForceRegionTrigger.exited, TIMEOUT)
Report.result(Tests.ball_exited_force_region, ForceRegionTrigger.exited)
# 9) Verify that the magnitude of the collision is as expected
def is_close_float(a, b, tolerance):
return abs(b - a) < tolerance
force_region_magnitude = azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "GetMagnitude", box_id)
Report.info(
"NetForce magnitude is {}, Force Region magnitude is {}".format(NetForceMagnitude.value, force_region_magnitude)
)
Report.result(
Tests.net_force_magnitude, is_close_float(NetForceMagnitude.value, force_region_magnitude, MAGNITUDE_TOLERANCE)
)
# 10) Check that the ball is moving up and to the right
linear_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", ball_id)
Report.info_vector3(linear_velocity, "Ball linear velocity")
Report.result(Tests.ball_moved_up, linear_velocity.z > 0)
Report.result(Tests.ball_moved_right, linear_velocity.x > 0)
Report.result(Tests.ball_not_moved_y, abs(linear_velocity.y) < NO_MOTION_Y_TOLERANCE)
# 11) 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(ForceRegion_PointForceOnRigidBodies)
@@ -0,0 +1,404 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# Test case ID : C5959808
# Test Case Title : Verify Force Region Position Offset
# fmt:off
class Tests:
# General tests
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
test_completed = ("The test successfully completed", "The test timed out")
# ***** Entities found *****
# Force Regions
force_region_x_found = ("Force Region for X axis test was found", "Force Region for X axis test was NOT found")
force_region_y_found = ("Force Region for Y axis test was found", "Force Region for Y axis test was NOT found")
force_region_z_found = ("Force Region for Z axis test was found", "Force Region for Z axis test was NOT found")
# Force Region Pass Boxes
force_region_pass_box_x_found = ("Force Region Pass Box for X axis test was found", "Force Region Pass Box for X axis test was NOT found")
force_region_pass_box_y_found = ("Force Region Pass Box for Y axis test was found", "Force Region Pass Box for Y axis test was NOT found")
force_region_pass_box_z_found = ("Force Region Pass Box for Z axis test was found", "Force Region Pass Box for Z axis test was NOT found")
# External Pass Boxes
external_pass_box_x_found = ("External Pass Box for X axis test was found", "External Pass Box for X axis test was NOT found")
external_pass_box_y_found = ("External Pass Box for Y axis test was found", "External Pass Box for Y axis test was NOT found")
external_pass_box_z_found = ("External Pass Box for Z axis test was found", "External Pass Box for Z axis test was NOT found")
# Force Region Fail Boxes
force_region_fail_box_x_found = ("Force Region Fail Box for X axis test was found", "Force Region Fail Box for X axis test was NOT found")
force_region_fail_box_y_found = ("Force Region Fail Box for Y axis test was found", "Force Region Fail Box for Y axis test was NOT found")
force_region_fail_box_z_found = ("Force Region Fail Box for Z axis test was found", "Force Region Fail Box for Z axis test was NOT found")
# External Fail Boxes
external_fail_box_x_found = ("External Fail Box for X axis test was found", "External Fail Box for X axis test was NOT found")
external_fail_box_y_found = ("External Fail Box for Y axis test was found", "External Fail Box for Y axis test was NOT found")
external_fail_box_z_found = ("External Fail Box for Z axis test was found", "External Fail Box for Z axis test was NOT found")
# Pass spheres
sphere_pass_x_found = ("Pass Sphere for X axis test was found", "Pass Sphere for X axis test was NOT found")
sphere_pass_y_found = ("Pass Sphere for Y axis test was found", "Pass Sphere for Y axis test was NOT found")
sphere_pass_z_found = ("Pass Sphere for Z axis test was found", "Pass Sphere for Z axis test was NOT found")
# Bounce Spheres
sphere_bounce_x_found = ("Bounce Sphere for X axis test was found", "Bounce Sphere for X axis test was NOT found")
sphere_bounce_y_found = ("Bounce Sphere for Y axis test was found", "Bounce Sphere for Y axis test was NOT found")
sphere_bounce_z_found = ("Bounce Sphere for Z axis test was found", "Bounce Sphere for Z axis test was NOT found")
# ****** Entities' results ******
# Force Regions
force_region_x_mag_result = ("Force Region for X axis magnitude exerted was as expected", "Force Region for X axis magnitude exerted was NOT as expected")
force_region_y_mag_result = ("Force Region for Y axis magnitude exerted was as expected", "Force Region for Y axis magnitude exerted was NOT as expected")
force_region_z_mag_result = ("Force Region for Z axis magnitude exerted was as expected", "Force Region for Z axis magnitude exerted was NOT as expected")
force_region_x_norm_result = ("Force Region for X axis normal exerted was as expected", "Force Region for X axis normal exerted was NOT as expected")
force_region_y_norm_result = ("Force Region for Y axis normal exerted was as expected", "Force Region for Y axis normal exerted was NOT as expected")
force_region_z_norm_result = ("Force Region for Z axis normal exerted was as expected", "Force Region for Z axis normal exerted was NOT as expected")
# Force Region Pass Boxes
force_region_pass_box_x_result = ("Force Region Pass Box for X axis collided with exactly one sphere", "Force Region Pass Box for X axis DID NOT collide with exactly one sphere")
force_region_pass_box_y_result = ("Force Region Pass Box for Y axis collided with exactly one sphere", "Force Region Pass Box for Y axis DID NOT collide with exactly one sphere")
force_region_pass_box_z_result = ("Force Region Pass Box for Z axis collided with exactly one sphere", "Force Region Pass Box for Z axis DID NOT collide with exactly one sphere")
# External Pass Boxes
external_pass_box_x_result = ("External Pass Box for X axis collided with exactly one sphere", "External Pass Box for X axis DID NOT collide with exactly one sphere")
external_pass_box_y_result = ("External Pass Box for Y axis collided with exactly one sphere", "External Pass Box for Y axis DID NOT collide with exactly one sphere")
external_pass_box_z_result = ("External Pass Box for Z axis collided with exactly one sphere", "External Pass Box for Z axis DID NOT collide with exactly one sphere")
# Force Region Fail Boxes
force_region_fail_box_x_result = ("Force Region Fail Box for X axis collided with no spheres", "Force Region Fail Box for X axis DID collide with a sphere")
force_region_fail_box_y_result = ("Force Region Fail Box for Y axis collided with no spheres", "Force Region Fail Box for Y axis DID collide with a sphere")
force_region_fail_box_z_result = ("Force Region Fail Box for Z axis collided with no spheres", "Force Region Fail Box for Z axis DID collide with a sphere")
# External Fail Boxes
external_fail_box_x_result = ("External Fail Box for X axis collided with no spheres", "External Fail Box for X axis DID collide with a sphere")
external_fail_box_y_result = ("External Fail Box for Y axis collided with no spheres", "External Fail Box for Y axis DID collide with a sphere")
external_fail_box_z_result = ("External Fail Box for Z axis collided with no spheres", "External Fail Box for Z axis DID collide with a sphere")
# Pass spheres
sphere_pass_x_result = ("Pass Sphere for X axis collided with expected Box", "Pass Sphere for X axis DID NOT collide with expected Box")
sphere_pass_y_result = ("Pass Sphere for Y axis collided with expected Box", "Pass Sphere for Y axis DID NOT collide with expected Box")
sphere_pass_z_result = ("Pass Sphere for Z axis collided with expected Box", "Pass Sphere for Z axis DID NOT collide with expected Box")
# Bounce Spheres
sphere_bounce_x_result = ("Bounce Sphere for X axis collided with expected Box", "Bounce Sphere for X axis DID NOT collide with expected Box")
sphere_bounce_y_result = ("Bounce Sphere for Y axis collided with expected Box", "Bounce Sphere for Y axis DID NOT collide with expected Box")
sphere_bounce_z_result = ("Bounce Sphere for Z axis collided with expected Box", "Bounce Sphere for Z axis DID NOT collide with expected Box")
# fmt:on
@staticmethod
# Test tuple accessor via string
def get_test(test_name):
if test_name in Tests.__dict__:
return Tests.__dict__[test_name]
else:
return None
def ForceRegion_PositionOffset():
"""
Summary:
Force Region positional offset is tested for each of the 3 axises (X, Y, and Z). Each axis's test has one
ForceRegion, two spheres and four boxes. By monitoring which box each sphere collides with we can validate the
integrity of the ForceRegions positional offset.
Level Description:
Each axis's test has the following entities:
one force region - set for point force and with it's collider set offset (on the axis in test).
two spheres - one positioned near the transform of the force region, one positioned near the [offset] collider for
the force region
four boxes - One box is positioned inside the force region's transform, one inside the force region's [offset]
collider. The other two boxes are positioned behind the two spheres (relative to the direction they will be
initially traveling)
Expected Behavior:
All three axises' tests run in parallel. when the tests begin, the spheres should move toward their expected
force regions. The spheres positioned to collide with their region's [offset] collider should be forced backwards
before entering the force region and collide with the box behind it. The spheres positioned by their force region's
transforms should pass straight into the transform and collide with the box inside the transform.
The boxes inside the Force Regions' [offset] colliders and the boxes behind the spheres set to move into the Force
Regions' transforms should not register any collisions.
Steps:
1) Open level and enter game mode
2) Set up tests and variables
3) Wait for test results (or time out)
(Report results)
4) Exit game mode and close the editor
:return: None
"""
import os
import sys
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import azlmbr.legacy.general as general
import azlmbr.math as azmath
import azlmbr.bus
import azlmbr
# Constants
CLOSE_ENOUGH = 0.01 # Close enough threshold for comparing floats
TIME_OUT = 2.0 # Time out (in seconds) until test is aborted
FORCE_MAGNITUDE = 1000.0 # Point force magnitude for Force Regions
SPEED = 3.0 # Initial speed (in m/s) of the moving spheres.
# Full list for all spheres. Used for EntityId look up in event handlers
all_spheres = []
# Entity base class handles very general entity initialization
# Should be treated as a "virtual" class and all implementing child
# classes should implement a "self.result()" function referenced in EntityBase::report(self)
class EntityBase:
def __init__(self, name):
# type: (str) -> None
self.name = name
self.print_list = []
self.id = general.find_game_entity(name)
found_test = Tests.get_test(name + "_found")
Report.critical_result(found_test, self.id.IsValid())
# Reports this entity's result. Implicitly calls "get" on result.
# Subclasses implement their own definition of a successful result
def report(self):
# type: () -> None
result_test = Tests.get_test(self.name + "_result")
Report.result(result_test, self.result())
# Prints the print queue (with decorated header) if not empty
def print_log(self):
# type: () -> None
if self.print_list:
Report.info("*********** {} **********".format(self))
for line in self.print_list:
Report.info(line)
Report.info("")
# Quick string cast, returns entity name
def __str__(self):
# type: () -> str
return self.name
# ForceRegion handles all the data and behavior associated with a ForceRegion (for this test)
# They simply wait for a Sphere to collide with them. On collision they store the calculated force
# magnitude for verification.
class ForceRegion(EntityBase):
def __init__(self, name, magnitude):
# type: (str, float) -> None
EntityBase.__init__(self, name)
self.expected_magnitude = magnitude
self.actual_magnitude = None
self.expected_normal = None
self.actual_normal = None
# Set point force Magnitude
azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "SetMagnitude", self.id, magnitude)
# Set up handler
self.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
self.handler.connect(None)
self.handler.add_callback("OnCalculateNetForce", self.on_calc_force)
# Callback function for OnCalculateNetForce event
def on_calc_force(self, args):
# type: ([EntityId, EntityId, azmath.Vector3, float]) -> None
if self.id.Equal(args[0]) and self.actual_magnitude is None:
for sphere in all_spheres:
if sphere.id.Equal(args[1]):
# Log event in print queue (for me and for the sphere)
self.print_list.append("Exerting force on {}:".format(sphere))
sphere.print_list.append("Force exerted by {}".format(self))
# Save calculated data to be compared later
self.actual_normal = args[2]
self.actual_magnitude = args[3]
self.expected_normal = sphere.initial_velocity.GetNormalizedSafe().Unary()
# Add expected/actual to print queue
self.print_list.append("Force Vector: ")
self.print_list.append(
" Expected: ({:.2f}, {:.2f}, {:.2f})".format(
self.expected_normal.x, self.expected_normal.y, self.expected_normal.z
)
)
self.print_list.append(
" Actual: ({:.2f}, {:.2f}, {:.2f})".format(
self.actual_normal.x, self.actual_normal.y, self.actual_normal.z
)
)
self.print_list.append("Force Magnitude: ")
self.print_list.append(" Expected: {}".format(self.expected_magnitude))
self.print_list.append(" Actual: {:.2f}".format(self.actual_magnitude))
# EntityBase::report() overload.
# Force regions have 2 test tuples to report on
def report(self):
magnitude_test = Tests.get_test(self.name + "_mag_result")
normal_test = Tests.get_test(self.name + "_norm_result")
Report.result(magnitude_test, self.magnitude_result())
Report.result(normal_test, self.normal_result())
# Test result calculations
# Used in EntityBase for reporting results
def result(self):
# type: () -> bool
return self.magnitude_result() and self.normal_result()
def magnitude_result(self):
# type: () -> bool
return (
self.actual_magnitude is not None
and abs(self.actual_magnitude - self.expected_magnitude) < CLOSE_ENOUGH
)
def normal_result(self):
# type: () -> bool
return (
self.actual_normal is not None
and self.expected_normal is not None
and self.expected_normal.IsClose(self.actual_normal, CLOSE_ENOUGH)
)
# Spheres are the objects that test the force regions. They store an expected collision entity and an
# actual collision entity
class Sphere(EntityBase):
def __init__(self, name, initial_velocity, expected_collision):
# type: (str, azmath.Vector3, EntityBase, bool) -> None
EntityBase.__init__(self, name)
self.initial_velocity = initial_velocity
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", self.id, initial_velocity)
self.print_list.append(
"Initial velocity: ({:.2f}, {:.2f}, {:.2f})".format(
initial_velocity.x, initial_velocity.y, initial_velocity.z
)
)
self.expected_collision = expected_collision
self.print_list.append("Expected Collision: {}".format(expected_collision))
self.actual_collision = None
self.active = True
self.force_normal = None
# Registers a collision with this sphere. Saves a reference to the colliding entity for processing later.
# Deactivate self after collision is registered.
def collide(self, collision_entity):
# type: (EntityBase) -> None
# Log the event
self.print_list.append("Collided with {}".format(collision_entity))
self.actual_collision = collision_entity
# Deactivate self
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity", self.id)
self.active = False
# Calculates result
# Used in EntityBase for reporting results
def result(self):
if self.actual_collision is None:
return False
else:
return self.expected_collision.id.Equal(self.actual_collision.id)
# Box entities wait for a collision with a sphere as a means of validation the force region's offset
# worked according to plan.
class Box(EntityBase):
def __init__(self, name, expected_sphere_collisions):
# type: (str, int) -> None
EntityBase.__init__(self, name)
self.spheres_collided = 0
self.expected_sphere_collisions = expected_sphere_collisions
# Set up handler
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# Callback function for OnCollisionBegin event
def on_collision_begin(self, args):
for sphere in all_spheres:
if sphere.id.Equal(args[0]):
# Log event
self.print_list.append("Collided with {}".format(sphere))
# Register collision with sphere
sphere.collide(self)
self.spheres_collided += 1 # Count collisions for validation later
break
# Calculates test result
# Used in EntityBase for reporting results
def result(self):
return self.spheres_collided == self.expected_sphere_collisions
# Manages the entities required to run the test for one axis (X, Y, or Z)
class AxisTest:
def __init__(self, axis, init_velocity):
# type: (str, azmath.Vector3) -> None
self.name = axis + " axis test"
self.force_region = ForceRegion("force_region_" + axis, FORCE_MAGNITUDE)
self.spheres = [
Sphere("sphere_pass_" + axis, init_velocity, Box("force_region_pass_box_" + axis, 1)),
Sphere("sphere_bounce_" + axis, init_velocity, Box("external_pass_box_" + axis, 1)),
]
self.boxes = [
Box("external_fail_box_" + axis, 0),
Box("force_region_fail_box_" + axis, 0)
] + [
# Gets the Boxes passed to spheres on init
sphere.expected_collision for sphere in self.spheres
]
# Full list for all entities this test is responsible for
self.all_entities = self.boxes + self.spheres + [self.force_region]
# Add spheres to global "lookup" list
all_spheres.extend(self.spheres)
# Checks for all entities' test passing conditions
def passed(self):
return all([e.result() for e in self.all_entities])
# Returns true when this test has completed (i.e. when the spheres have collided and are deactivated)
def completed(self):
return all([not sphere.active for sphere in self.spheres])
# Reports results for all entities in this test
def report(self):
Report.info("::::::::::::::::::::::::::::: {} Results :::::::::::::::::::::::::::::".format(self.name))
for entity in self.all_entities:
entity.report()
# Prints the logs for all entities in this test
def print_log(self):
Report.info("::::::::::::::::::::::::::::: {} Log :::::::::::::::::::::::::::::".format(self.name))
for entity in self.all_entities:
entity.print_log()
# *********** Execution Code ***********
# 1) Open level
helper.init_idle()
helper.open_level("Physics", "ForceRegion_PositionOffset")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Variable set up
# Initial velocities for the three different directions spheres will be moving
x_vel = azmath.Vector3(SPEED, 0.0, 0.0)
y_vel = azmath.Vector3(0.0, SPEED, 0.0)
z_vel = azmath.Vector3(0.0, 0.0, SPEED)
# The three tests, one for each axis
axis_tests = [
AxisTest("x", z_vel), # Spheres move in Z direction when testing X axis offset
AxisTest("y", x_vel), # Spheres move in X direction when testing Y axis offset
AxisTest("z", y_vel), # Spheres move in Y direction when testing Z axis offset
]
# 3) Wait for test results or time out
Report.result(
Tests.test_completed, helper.wait_for_condition(
lambda: all([test.completed() for test in axis_tests]), TIME_OUT
)
)
# Report results
for test in axis_tests:
test.report()
# Print entity print queues for each failed test
for test in axis_tests:
if not test.passed():
test.print_log()
# 4) Exit game mode and close editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_PositionOffset)
@@ -0,0 +1,138 @@
"""
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 : C5959761
# Test Case Title : Check that force region (physics asset) exerts point force
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_ball = ("Ball entity found", "Ball entity not found")
find_sedan = ("Sedan entity found", "Sedan entity not found")
ball_fell = ("The ball fell", "The ball did not fall")
ball_enters_force_region = ("Ball entered force region", "Ball did not enter force region")
ball_exits_force_region = ("Ball exited force region", "Ball did not exit force region")
ball_moved_up = ("Ball moved up", "Ball did not move up")
ball_moved_forward = ("Ball moved forward", "Ball did not move forward")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_PxMeshShapedForce():
"""
Summary:
Runs an automated test to ensure that PhysX force regions with physics assets exert point force on rigid bodies
Level Description:
A ball is suspended over a force region with a physics asset mesh (sedan). The ball is offset 1 unit towards the
hood of the sedan (In the Y direction)
Ball (entity) - Sphere shaped PhysX Collider; PhysX Rigid body with gravity enabled
Sedan (entity) - Sedan shaped PhysX Collider; PhysX Force Region with a point force (magnitude 1000.0)
Expected Behavior:
The ball should fall once game mode is entered and bounce off the force region down and to the right.
Test Steps:
1) Open level
2) Enter game mode
3) Validate entities
4) Wait for ball to fall
5) Wait for ball to enter force region
6) Wait for ball to exit force region
7) Validate velocity vector
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
import azlmbr
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
TIMEOUT = 2.0
class Ball:
def __init__(self, name):
self.id = general.find_game_entity(name)
self.entered_force_region = False
self.exited_force_region = False
def get_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
def is_falling(self):
return self.get_velocity().z < 0.0
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_PxMeshShapedForce")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate entities
ball = Ball("Ball")
Report.critical_result(Tests.find_ball, ball.id.IsValid())
sedan_id = general.find_game_entity("Sedan")
Report.critical_result(Tests.find_sedan, sedan_id.IsValid())
# 4) Wait for ball to fall
def on_trigger_enter(args):
other_id = args[0]
if other_id.Equal(ball.id):
ball.entered_force_region = True
def on_trigger_exit(args):
other_id = args[0]
if other_id.Equal(ball.id):
ball.exited_force_region = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(sedan_id)
handler.add_callback("OnTriggerEnter", on_trigger_enter)
handler.add_callback("OnTriggerExit", on_trigger_exit)
Report.critical_result(Tests.ball_fell, helper.wait_for_condition(ball.is_falling, TIMEOUT))
# 5) Wait for ball to enter force region
helper.wait_for_condition(lambda: ball.entered_force_region, TIMEOUT)
Report.critical_result(Tests.ball_enters_force_region, ball.entered_force_region)
# 6) Wait for ball to exit force region
helper.wait_for_condition(lambda: ball.exited_force_region, TIMEOUT)
Report.critical_result(Tests.ball_exits_force_region, ball.exited_force_region)
# 7) Validate velocity vector
velocity = ball.get_velocity()
Report.result(Tests.ball_moved_up, velocity.z > 0.0)
Report.result(Tests.ball_moved_forward, velocity.y > 0.0)
# 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(ForceRegion_PxMeshShapedForce)
@@ -0,0 +1,404 @@
"""
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 : C5959809
# Test Case Title : Verify Force Region Rotational Offset
# fmt:off
class Tests:
# General tests
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
test_completed = ("The test successfully completed", "The test timed out")
# ***** Entities found *****
# Force Regions
force_region_x_found = ("Force Region for X axis test was found", "Force Region for X axis test was NOT found")
force_region_y_found = ("Force Region for Y axis test was found", "Force Region for Y axis test was NOT found")
force_region_z_found = ("Force Region for Z axis test was found", "Force Region for Z axis test was NOT found")
# Force Region Pass Boxes
force_region_pass_box_x_found = ("Force Region Pass Box for X axis test was found", "Force Region Pass Box for X axis test was NOT found")
force_region_pass_box_y_found = ("Force Region Pass Box for Y axis test was found", "Force Region Pass Box for Y axis test was NOT found")
force_region_pass_box_z_found = ("Force Region Pass Box for Z axis test was found", "Force Region Pass Box for Z axis test was NOT found")
# External Pass Boxes
external_pass_box_x_found = ("External Pass Box for X axis test was found", "External Pass Box for X axis test was NOT found")
external_pass_box_y_found = ("External Pass Box for Y axis test was found", "External Pass Box for Y axis test was NOT found")
external_pass_box_z_found = ("External Pass Box for Z axis test was found", "External Pass Box for Z axis test was NOT found")
# Force Region Fail Boxes
force_region_fail_box_x_found = ("Force Region Fail Box for X axis test was found", "Force Region Fail Box for X axis test was NOT found")
force_region_fail_box_y_found = ("Force Region Fail Box for Y axis test was found", "Force Region Fail Box for Y axis test was NOT found")
force_region_fail_box_z_found = ("Force Region Fail Box for Z axis test was found", "Force Region Fail Box for Z axis test was NOT found")
# External Fail Boxes
external_fail_box_x_found = ("External Fail Box for X axis test was found", "External Fail Box for X axis test was NOT found")
external_fail_box_y_found = ("External Fail Box for Y axis test was found", "External Fail Box for Y axis test was NOT found")
external_fail_box_z_found = ("External Fail Box for Z axis test was found", "External Fail Box for Z axis test was NOT found")
# Pass spheres
sphere_pass_x_found = ("Pass Sphere for X axis test was found", "Pass Sphere for X axis test was NOT found")
sphere_pass_y_found = ("Pass Sphere for Y axis test was found", "Pass Sphere for Y axis test was NOT found")
sphere_pass_z_found = ("Pass Sphere for Z axis test was found", "Pass Sphere for Z axis test was NOT found")
# Bounce Spheres
sphere_bounce_x_found = ("Bounce Sphere for X axis test was found", "Bounce Sphere for X axis test was NOT found")
sphere_bounce_y_found = ("Bounce Sphere for Y axis test was found", "Bounce Sphere for Y axis test was NOT found")
sphere_bounce_z_found = ("Bounce Sphere for Z axis test was found", "Bounce Sphere for Z axis test was NOT found")
# ****** Entities' results ******
# Force Regions
force_region_x_mag_result = ("Force Region for X axis magnitude exerted was as expected", "Force Region for X axis magnitude exerted was NOT as expected")
force_region_y_mag_result = ("Force Region for Y axis magnitude exerted was as expected", "Force Region for Y axis magnitude exerted was NOT as expected")
force_region_z_mag_result = ("Force Region for Z axis magnitude exerted was as expected", "Force Region for Z axis magnitude exerted was NOT as expected")
force_region_x_norm_result = ("Force Region for X axis normal exerted was as expected", "Force Region for X axis normal exerted was NOT as expected")
force_region_y_norm_result = ("Force Region for Y axis normal exerted was as expected", "Force Region for Y axis normal exerted was NOT as expected")
force_region_z_norm_result = ("Force Region for Z axis normal exerted was as expected", "Force Region for Z axis normal exerted was NOT as expected")
# Force Region Pass Boxes
force_region_pass_box_x_result = ("Force Region Pass Box for X axis collided with exactly one sphere", "Force Region Pass Box for X axis DID NOT collide with exactly one sphere")
force_region_pass_box_y_result = ("Force Region Pass Box for Y axis collided with exactly one sphere", "Force Region Pass Box for Y axis DID NOT collide with exactly one sphere")
force_region_pass_box_z_result = ("Force Region Pass Box for Z axis collided with exactly one sphere", "Force Region Pass Box for Z axis DID NOT collide with exactly one sphere")
# External Pass Boxes
external_pass_box_x_result = ("External Pass Box for X axis collided with exactly one sphere", "External Pass Box for X axis DID NOT collide with exactly one sphere")
external_pass_box_y_result = ("External Pass Box for Y axis collided with exactly one sphere", "External Pass Box for Y axis DID NOT collide with exactly one sphere")
external_pass_box_z_result = ("External Pass Box for Z axis collided with exactly one sphere", "External Pass Box for Z axis DID NOT collide with exactly one sphere")
# Force Region Fail Boxes
force_region_fail_box_x_result = ("Force Region Fail Box for X axis collided with no spheres", "Force Region Fail Box for X axis DID collide with a sphere")
force_region_fail_box_y_result = ("Force Region Fail Box for Y axis collided with no spheres", "Force Region Fail Box for Y axis DID collide with a sphere")
force_region_fail_box_z_result = ("Force Region Fail Box for Z axis collided with no spheres", "Force Region Fail Box for Z axis DID collide with a sphere")
# External Fail Boxes
external_fail_box_x_result = ("External Fail Box for X axis collided with no spheres", "External Fail Box for X axis DID collide with a sphere")
external_fail_box_y_result = ("External Fail Box for Y axis collided with no spheres", "External Fail Box for Y axis DID collide with a sphere")
external_fail_box_z_result = ("External Fail Box for Z axis collided with no spheres", "External Fail Box for Z axis DID collide with a sphere")
# Pass spheres
sphere_pass_x_result = ("Pass Sphere for X axis collided with expected Box", "Pass Sphere for X axis DID NOT collide with expected Box")
sphere_pass_y_result = ("Pass Sphere for Y axis collided with expected Box", "Pass Sphere for Y axis DID NOT collide with expected Box")
sphere_pass_z_result = ("Pass Sphere for Z axis collided with expected Box", "Pass Sphere for Z axis DID NOT collide with expected Box")
# Bounce Spheres
sphere_bounce_x_result = ("Bounce Sphere for X axis collided with expected Box", "Bounce Sphere for X axis DID NOT collide with expected Box")
sphere_bounce_y_result = ("Bounce Sphere for Y axis collided with expected Box", "Bounce Sphere for Y axis DID NOT collide with expected Box")
sphere_bounce_z_result = ("Bounce Sphere for Z axis collided with expected Box", "Bounce Sphere for Z axis DID NOT collide with expected Box")
# fmt:on
@staticmethod
# Test tuple accessor via string
def get_test(test_name):
if test_name in Tests.__dict__:
return Tests.__dict__[test_name]
else:
return None
def ForceRegion_RotationalOffset():
"""
Summary:
Force Region rotational offset is tested for each of the 3 axises (X, Y, and Z). Each axis's test has one
ForceRegion, two spheres and four boxes. By monitoring which box each sphere collides with we can validate the
integrity of the ForceRegions rotational offset.
Level Description:
Each axis's test has the following entities:
one force region - set for point force and with it's collider rotationally offset (on the axis in test).
two spheres - one positioned near the transform of the force region, one positioned near the [offset] collider for
the force region
four boxes - One box is positioned inside the force region's transform, one inside the force region's [offset]
collider. The other two boxes are positioned behind the two spheres (relative to the direction they will be
initially traveling)
Expected Behavior:
All three axises' tests run in parallel. when the tests begin, the spheres should move toward their expected
force regions. The spheres positioned to collide with their region's [offset] collider should be forced backwards
before entering the force region and collide with the box behind it. The spheres positioned by their force region's
transforms should pass straight into the transform and collide with the box inside the transform.
The boxes inside the Force Regions' [offset] colliders and the boxes behind the spheres set to move into the Force
Regions' transforms should not register any collisions.
Steps:
1) Open level and enter game mode
2) Set up tests and variables
3) Wait for test results (or time out)
(Report results)
4) Exit game mode and close the editor
:return: None
"""
import os
import sys
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import azlmbr.legacy.general as general
import azlmbr.math as azmath
import azlmbr.bus
import azlmbr
# Constants
CLOSE_ENOUGH = 0.01 # Close enough threshold for comparing floats
TIME_OUT = 2.0 # Time out (in seconds) until test is aborted
FORCE_MAGNITUDE = 1000.0 # Point force magnitude for Force Regions
SPEED = 3.0 # Initial speed (in m/s) of the moving spheres.
# Full list for all spheres. Used for EntityId look up in event handlers
all_spheres = []
# Entity base class handles very general entity initialization
# Should be treated as a "virtual" class and all implementing child
# classes should implement a "self.result()" function referenced in EntityBase::report(self)
class EntityBase:
def __init__(self, name):
# type: (str) -> None
self.name = name
self.print_list = []
self.id = general.find_game_entity(name)
found_test = Tests.get_test(name + "_found")
Report.critical_result(found_test, self.id.IsValid())
# Reports this entity's result. Implicitly calls "get" on result.
# Subclasses implement their own definition of a successful result
def report(self):
# type: () -> None
result_test = Tests.get_test(self.name + "_result")
Report.result(result_test, self.result())
# Prints the print queue (with decorated header) if not empty
def print_log(self):
# type: () -> None
if self.print_list:
Report.info("*********** {} **********".format(self))
for line in self.print_list:
Report.info(line)
Report.info("")
# Quick string cast, returns entity name
def __str__(self):
# type: () -> str
return self.name
# ForceRegion handles all the data and behavior associated with a ForceRegion (for this test)
# They simply wait for a Sphere to collide with them. On collision they store the calculated force
# magnitude for verification.
class ForceRegion(EntityBase):
def __init__(self, name, magnitude):
# type: (str, float) -> None
EntityBase.__init__(self, name)
self.expected_magnitude = magnitude
self.actual_magnitude = None
self.expected_normal = None
self.actual_normal = None
# Set point force Magnitude
azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "SetMagnitude", self.id, magnitude)
# Set up handler
self.handler = azlmbr.physics.ForceRegionNotificationBusHandler()
self.handler.connect(None)
self.handler.add_callback("OnCalculateNetForce", self.on_calc_force)
# Callback function for OnCalculateNetForce event
def on_calc_force(self, args):
# type: ([EntityId, EntityId, azmath.Vector3, float]) -> None
if self.id.Equal(args[0]) and self.actual_magnitude is None:
for sphere in all_spheres:
if sphere.id.Equal(args[1]):
# Log event in print queue (for me and for the sphere)
self.print_list.append("Exerting force on {}:".format(sphere))
sphere.print_list.append("Force exerted by {}".format(self))
# Save calculated data to be compared later
self.actual_normal = args[2]
self.actual_magnitude = args[3]
pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere.id)
self.expected_normal = sphere_pos.Subtract(pos).GetNormalizedSafe()
# Add expected/actual to print queue
self.print_list.append("Force Vector: ")
self.print_list.append(
" Expected: ({:.2f}, {:.2f}, {:.2f})".format(
self.expected_normal.x, self.expected_normal.y, self.expected_normal.z
)
)
self.print_list.append(
" Actual: ({:.2f}, {:.2f}, {:.2f})".format(
self.actual_normal.x, self.actual_normal.y, self.actual_normal.z
)
)
self.print_list.append("Force Magnitude: ")
self.print_list.append(" Expected: {}".format(self.expected_magnitude))
self.print_list.append(" Actual: {:.2f}".format(self.actual_magnitude))
# EntityBase::report() overload.
# Force regions have 2 test tuples to report on
def report(self):
magnitude_test = Tests.get_test(self.name + "_mag_result")
normal_test = Tests.get_test(self.name + "_norm_result")
Report.result(magnitude_test, self.magnitude_result())
Report.result(normal_test, self.normal_result())
# Test result calculations
# Used in EntityBase for reporting results
def result(self):
# type: () -> bool
return self.magnitude_result() and self.normal_result()
def magnitude_result(self):
# type: () -> bool
return (
self.actual_magnitude is not None
and abs(self.actual_magnitude - self.expected_magnitude) < CLOSE_ENOUGH
)
def normal_result(self):
# type: () -> bool
return (
self.actual_normal is not None
and self.expected_normal is not None
and self.expected_normal.IsClose(self.actual_normal, CLOSE_ENOUGH)
)
# Spheres are the objects that test the force regions. They store an expected collision entity and an
# actual collision entity
class Sphere(EntityBase):
def __init__(self, name, initial_velocity, expected_collision):
# type: (str, azmath.Vector3, EntityBase) -> None
EntityBase.__init__(self, name)
self.initial_velocity = initial_velocity
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", self.id, initial_velocity)
self.print_list.append(
"Initial velocity: ({:.2f}, {:.2f}, {:.2f})".format(
initial_velocity.x, initial_velocity.y, initial_velocity.z
)
)
self.expected_collision = expected_collision
self.print_list.append("Expected Collision: {}".format(expected_collision))
self.actual_collision = None
self.active = True
self.force_normal = None
# Registers a collision with this sphere. Saves a reference to the colliding entity for processing later.
# Deactivate self after collision is registered.
def collide(self, collision_entity):
# type: (EntityBase) -> None
# Log the event
self.print_list.append("Collided with {}".format(collision_entity))
self.actual_collision = collision_entity
# Deactivate self
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DeactivateGameEntity",
self.id)
self.active = False
# Calculates result
# Used in EntityBase for reporting results
def result(self):
if self.actual_collision is None:
return False
else:
return self.expected_collision.id.Equal(self.actual_collision.id)
# Box entities wait for a collision with a sphere as a means of validation the force region's offset
# worked according to plan.
class Box(EntityBase):
def __init__(self, name, expected_sphere_collisions):
# type: (str, int) -> None
EntityBase.__init__(self, name)
self.spheres_collided = 0
self.expected_sphere_collisions = expected_sphere_collisions
# Set up handler
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# Callback function for OnCollisionBegin event
def on_collision_begin(self, args):
for sphere in all_spheres:
if sphere.id.Equal(args[0]):
# Log event
self.print_list.append("Collided with {}".format(sphere))
# Register collision with sphere
sphere.collide(self)
self.spheres_collided += 1 # Count collisions for validation later
break
# Calculates test result
# Used in EntityBase for reporting results
def result(self):
return self.spheres_collided == self.expected_sphere_collisions
# Manages the entities required to run the test for one axis (X, Y, or Z)
class AxisTest:
def __init__(self, axis, init_velocity):
# type: (str, azmath.Vector3) -> None
self.name = axis + " axis test"
self.force_region = ForceRegion("force_region_" + axis, FORCE_MAGNITUDE)
self.spheres = [
Sphere("sphere_pass_" + axis, init_velocity, Box("force_region_pass_box_" + axis, 1)),
Sphere("sphere_bounce_" + axis, init_velocity, Box("external_pass_box_" + axis, 1)),
]
self.boxes = [
Box("external_fail_box_" + axis, 0),
Box("force_region_fail_box_" + axis, 0)
] + [
sphere.expected_collision for sphere in self.spheres
# Gets the Boxes passed to spheres on init
]
# Full list for all entities this test is responsible for
self.all_entities = self.boxes + self.spheres + [self.force_region]
# Add spheres to global "lookup" list
all_spheres.extend(self.spheres)
# Checks for all entities' test passing conditions
def passed(self):
return all([e.result() for e in self.all_entities])
# Returns true when this test has completed (i.e. when the spheres have collided and are deactivated)
def completed(self):
return all([not sphere.active for sphere in self.spheres])
# Reports results for all entities in this test
def report(self):
Report.info("::::::::::::::::::::::::::::: {} Results :::::::::::::::::::::::::::::".format(self.name))
for entity in self.all_entities:
entity.report()
# Prints the logs for all entities in this test
def print_log(self):
Report.info("::::::::::::::::::::::::::::: {} Log :::::::::::::::::::::::::::::".format(self.name))
for entity in self.all_entities:
entity.print_log()
# *********** Execution Code ***********
# 1) Open level
helper.init_idle()
helper.open_level("Physics", "ForceRegion_RotationalOffset")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Variable set up
# Initial velocities for the three different directions spheres will be moving
x_vel = azmath.Vector3(SPEED, 0.0, 0.0)
y_vel = azmath.Vector3(0.0, SPEED, 0.0)
z_vel = azmath.Vector3(0.0, 0.0, SPEED)
# The three tests, one for each axis
axis_tests = [AxisTest("x", x_vel), AxisTest("y", y_vel), AxisTest("z", z_vel)]
# 3) Wait for test results or time out
Report.result(
Tests.test_completed, helper.wait_for_condition(
lambda: all([test.completed() for test in axis_tests]), TIME_OUT
)
)
# Report results
for test in axis_tests:
test.report()
# Print entity print queues for each failed test
for test in axis_tests:
if not test.passed():
test.print_log()
# 4) Exit game mode and close editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_RotationalOffset)
@@ -0,0 +1,141 @@
# coding=utf-8
"""
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 : C5932043
# Test Case Title : Check that force region exerts simple drag force on rigid bodies
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere found", "Sphere not found")
find_force_region = ("Force Region found", "Force Region not found")
# entity_actions_success refers to if the entity completed all expected actions in the test. For this test, this
# will be, did the Sphere enter and exit the force region
entity_actions_success = ("Entity actions completed", "Entity actions not completed")
sphere_lost_height = ("Sphere went down", "Sphere didn't go down")
force_region_slows = ("Force Region slowed Sphere", "Force Region didn't slow Sphere")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SimpleDragForceOnRigidBodies():
# This run() function will open a a level and validate that the force region slows down a spheres fall.
# It does this by:
# 1) Opens level with sphere above a force region
# 2) Enters Game mode
# 3) Finds the entities in the scene
# 4) Listens for sphere to enter the force region
# 5) Gets z velocity and position of sphere
# 6) Listens for sphere to exit force region
# 7) Gets new velocity and position of sphere
# 8) Validate the results
# 9) Exits game mode and editor
# Setup path
import os, 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
# Holds details about the sphere
class Sphere:
id = None
start_velocity_z = 0.0
end_velocity_z = 0.0
sphere_start_z_position = 0.0
sphere_end_z_position = 0.0
entered_force_region = False
exited_force_region = False
TIME_OUT = 4.0 # Time given to test to complete.
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_SimpleDragForceOnRigidBodies")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Get Entities
Sphere.id = general.find_game_entity("Sphere")
Report.result(Tests.find_sphere, Sphere.id.IsValid())
force_region_id = general.find_game_entity("Force Region")
Report.result(Tests.find_force_region, force_region_id.IsValid())
# Called if Sphere enters force region
def on_trigger_begin(args):
other_id = args[0]
if other_id.Equal(Sphere.id):
Report.info("Entered force region")
Sphere.entered_force_region = True
# 5) Gets z velocity and position of sphere
Sphere.start_velocity_z = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", Sphere.id).z
Sphere.sphere_start_z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Sphere.id)
Report.info(
"Sphere Start Z position = {} Z Start Velocity = {}".format(
Sphere.sphere_start_z_position, Sphere.start_velocity_z
)
)
# Called when sphere exits force region
def on_trigger_end(args):
other_id = args[0]
if other_id.Equal(Sphere.id):
Report.info("Exited force region")
Sphere.exited_force_region = True
# 7) Gets new velocity and position of sphere
Sphere.end_velocity_z = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", Sphere.id).z
Sphere.sphere_end_z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Sphere.id)
Report.info(
"Sphere End Z position = {} Sphere End Z Velocity = {}".format(
Sphere.sphere_end_z_position, Sphere.end_velocity_z
)
)
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(force_region_id)
# 4) Listens for sphere to enter the force region
handler.add_callback("OnTriggerEnter", on_trigger_begin)
# 6) Listens for sphere to exit force region
handler.add_callback("OnTriggerExit", on_trigger_end)
# Wait until all entities in scene are done performing their actions or test times out.
def done_with_entity_actions():
return Sphere.entered_force_region and Sphere.exited_force_region
test_completed = helper.wait_for_condition(done_with_entity_actions, TIME_OUT)
Report.result(Tests.entity_actions_success, test_completed)
# 8) Validate the results
if test_completed:
# Did Sphere fall
sphere_descended = Sphere.sphere_end_z_position + 0.5 < Sphere.sphere_start_z_position # 0.5 for buffer
Report.result(Tests.sphere_lost_height, sphere_descended)
# Did Force Region slow down sphere's falling
# Note: The faster a sphere falls, the greater its negative/downward velocity will be. Adding 1.0 for buffer.
force_region_result = round(Sphere.end_velocity_z, 2) > round(Sphere.start_velocity_z, 2) + 1.0
Report.result(Tests.force_region_slows, force_region_result)
# 9) Exits game mode and editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_SimpleDragForceOnRigidBodies)
@@ -0,0 +1,137 @@
"""
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 : C6090546
# Test Case Title : Check that a force region slice can be saved and instantiated
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("SphereRigidBody found", "SphereRigidBody not found")
find_force_region = ("ForceRegionSliceEntity found", "ForceRegionSliceEntity not found")
sphere_dropped = ("Sphere dropped down", "Sphere did not drop down")
sphere_bounced = ("Sphere bounced up vertically", "Sphere did not bounce up vertically")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SliceFileInstantiates():
"""
Summary:
Check that a force region slice can be saved and instantiated
Level Description:
The SphereRigidBody entity is placed above the ForceRegionBox entity
ForceRegionSliceEntity (entity) - Slice Asset which is imported from .slice file of another level which has
an entity with force region component.
SphereRigidBody (entity) - Entity with PhysX Rigid body, Mesh and collider components.
The SphereRigidBody is placed above the ForceRegionEntity.
Expected Behavior:
Sphere drops and bounces vertically up from force region.
We are checking if the ball started falling down from its initial position and then verifying if it has bounced up
its initial position after entering into the force region.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Get the initial position of the Sphere (rigid body)
5) Check if the ball is falling down
6) Add trigger notification handler
7) Wait till the ball enters the force region
8) Check if the ball has bounced vertically up
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.legacy.general as general
import azlmbr.bus
# Constants
TIMEOUT = 3.0 # wait a maximum of 3 seconds
SPHERE_RADIUS = 0.5
class Sphere:
id = None
initial_position = None
current_position = None
in_force_region = False
bounced = False
class ForceRegion:
id = None
def sphere_bounced():
Sphere.current_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
Sphere.bounced = Sphere.current_position.z > (Sphere.initial_position.z + SPHERE_RADIUS)
return Sphere.bounced
def on_trigger_enter(args):
if args[0].Equal(Sphere.id):
Sphere.in_force_region = True
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_SliceFileInstantiates")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
Sphere.id = general.find_game_entity("SphereRigidBody")
Report.critical_result(Tests.find_sphere, Sphere.id.IsValid())
ForceRegion.id = general.find_game_entity("ForceRegionSliceEntity")
Report.critical_result(Tests.find_force_region, ForceRegion.id.IsValid())
# 4) Get the initial position of the Sphere (rigid body)
Sphere.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
# 5) Check if the ball is falling down
Sphere.current_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
sphere_dropped = Sphere.current_position.z < (Sphere.initial_position.z + SPHERE_RADIUS)
Report.critical_result(Tests.sphere_dropped, sphere_dropped)
# 6) Add trigger notification handler
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(ForceRegion.id)
handler.add_callback("OnTriggerEnter", on_trigger_enter)
# 7) Wait till the ball enters the force region
helper.wait_for_condition(lambda: Sphere.in_force_region, TIMEOUT)
# 8) Check if the ball has bounced vertically up
# wait till the ball bounces
helper.wait_for_condition(sphere_bounced, TIMEOUT)
Report.critical_result(Tests.sphere_bounced, Sphere.bounced)
# 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(ForceRegion_SliceFileInstantiates)
@@ -0,0 +1,142 @@
"""
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 : C12905527
# Test Case Title : Check that deviation occurring in Force Magnitude due to Values in Force direction is not large
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_force_region = ("Force region was found", "Force region was not found")
find_sphere = ("Sphere was found", "Sphere was not found")
sphere_entered_region = ("Sphere entered force region", "Sphere did not enter force region")
sphere_exited_region = ("Sphere exited force region", "Sphere did not exit force region")
force_magnitude_close = ("The net force magnitude was close to the expected value", "The net force magnitude was not close to the expected value")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SmallMagnitudeDeviationOnLargeForces():
"""
Summary:
Runs an automated test to ensure that the calculated net force magnitude is close to the configured value
Level Description:
A sphere (Sphere) is positioned above a force region (ForceRegion)
Sphere has a sphere PhysX collider and PhysX Rigid Body. Gravity is disabled, and it has an initial velocity of
2 m/s in the Z direction.
ForceRegion has a box PhysX collider and PhysX Force Region. Magnitude on the force region is set to 1,000,000.0
Expected Behavior:
The sphere enters and exits the force region. on_calc_net_force returns a value close to 1,000,000.0
Test Steps:
1) Open level
2) Enter game mode
3) Validate entities
4) Wait for the sphere to enter and exit the force region
5) Check the calculated net force against what we expect
6) Exit game mode
7) 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
EXPECTED_MAGNITUDE = 1000000.0
PERMISSIBLE_ERROR = 0.001 # +/- 0.1%
TIMEOUT = 1.0
class Sphere:
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(name)
self.entered_force_region = False
self.exited_force_region = False
self.net_force_magnitude = 0
def on_trigger_enter(args):
Report.info("triggered")
other_id = args[0]
if other_id.Equal(sphere.id):
sphere.entered_force_region = True
def on_trigger_exit(args):
other_id = args[0]
if other_id.Equal(sphere.id):
sphere.exited_force_region = True
def on_calc_net_force(args):
other_id = args[1]
force_magnitude = args[3]
if other_id.Equal(sphere.id):
sphere.net_force_magnitude = force_magnitude
helper.init_idle()
# 1) Open level
Report.info(general.get_current_level_name())
helper.open_level("Physics", "ForceRegion_SmallMagnitudeDeviationOnLargeForces")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate entities
sphere = Sphere("Sphere")
force_region_id = general.find_game_entity("ForceRegion")
Report.critical_result(Tests.find_sphere, sphere.id.IsValid())
Report.critical_result(Tests.find_force_region, force_region_id.IsValid())
# Create handlers
trigger_handler = azlmbr.physics.TriggerNotificationBusHandler()
trigger_handler.connect(force_region_id)
trigger_handler.add_callback("OnTriggerEnter", on_trigger_enter)
trigger_handler.add_callback("OnTriggerExit", on_trigger_exit)
net_force_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
net_force_handler.connect(None)
net_force_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
# 4) Wait for the sphere to enter and exit the force region
Report.result(Tests.sphere_entered_region, helper.wait_for_condition(lambda: sphere.entered_force_region, TIMEOUT))
Report.result(Tests.sphere_exited_region, helper.wait_for_condition(lambda: sphere.exited_force_region, TIMEOUT))
# 5) Check the calculated net force against what we expect
absolute_difference = abs(sphere.net_force_magnitude - EXPECTED_MAGNITUDE)
error = absolute_difference / EXPECTED_MAGNITUDE
net_force_was_close = error < PERMISSIBLE_ERROR
Report.result(Tests.force_magnitude_close, net_force_was_close)
if not net_force_was_close:
Report.info(
"\nExpected Magnitude: {}"
"\nActual Magnitude: {}"
"\nPermissible Error: {}"
"\nMeasured Error: {}".format(EXPECTED_MAGNITUDE, sphere.net_force_magnitude, PERMISSIBLE_ERROR, error)
)
# 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(ForceRegion_SmallMagnitudeDeviationOnLargeForces)
@@ -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 : C5959759
# Test Case Title : Check that force region (sphere) exerts point force
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_cube = ("Entity Cube found", "Cube not found")
find_force_region = ("Entity force region found", "Force region not found")
gravity_works = ("Cube falls", "Cube did not fall")
sphere_gravity_enabled = ("Gravity is enabled on the cube", "Gravity is not enabled on the cube")
force_region_trigger = ("Cube entered and exited force region", "Cube did not enter adn exit force region")
force_calculated = ("OnCalculateNetForce calculated", "OnCalculateNetForce did not get calculated")
force_x_vector = ("Force x vector is positive", "Force x vector is not positive")
force_y_vector = ("Force y vector is positive", "Force y vector is not positive")
point_force_magnitude_value = ("Magnitude is set to 1000", "Magnitude is not set to 1000")
point_force_magnitude = ("Calculated magnitude is greater than set magnitude", "Calculated magnitude is not greater than set magnitude")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SphereShapedForce():
"""
Level Setup
The level consists of a sherical force region with a point force of 1000.
A RigidBody cube with mass 1kg is positioned above the force region at an offset.
On entering game mode the cube will fall into the spherical point force region.
This should cause the cube to bounce off at considerable velocity.
We validate the point force observed is as expected
"""
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
EXPECTED_MAGNITUDE = 1000.0
NEGATIVE_VELOCITY = -0.001
TOLERANCE = 0.001
# 1) Open level and enter game mode
helper.init_idle()
helper.open_level("Physics", "ForceRegion_SphereShapedForce")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve entities
cube_id = general.find_game_entity("CubeRigidBody")
Report.critical_result(Tests.find_cube, cube_id.IsValid())
sphere_force_region = general.find_game_entity("SphereForceRegion")
Report.critical_result(Tests.find_force_region, sphere_force_region.IsValid())
# 3) Gravity works
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", cube_id)
Report.result(Tests.sphere_gravity_enabled, gravity_enabled)
def is_going_down():
vel = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", cube_id)
return vel.z < NEGATIVE_VELOCITY
helper.wait_for_condition(is_going_down, 1.0)
cube_linear_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", cube_id)
Report.info("Cube z velocity from gravity: {}".format(cube_linear_velocity.z))
Report.result(Tests.gravity_works, cube_linear_velocity.z < NEGATIVE_VELOCITY)
# 4) Listen to trigger events and OnCalculateNetForce notification
class SphereForceRegion:
enter = False
exit = False
def on_enter(args):
other_id = args[0]
if other_id.Equal(cube_id):
Report.info("Cube touched spherical force region")
SphereForceRegion.enter = True
def on_exit(args):
other_id = args[0]
if other_id.Equal(cube_id):
Report.info("Cube touched spherical force region")
SphereForceRegion.exit = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(sphere_force_region)
handler.add_callback("OnTriggerEnter", on_enter)
handler.add_callback("OnTriggerExit", on_exit)
class NetForce:
vector = None
magnitude = 0
def on_calc_net_force(args):
"""
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
entering_entity = args[1]
if entering_entity.Equal(cube_id):
vector = args[2]
magnitude = args[3]
Report.info_vector3(vector, "Net Force vector", magnitude)
NetForce.vector = vector
NetForce.magnitude = magnitude
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
def force_region_enter_and_exit():
return SphereForceRegion.enter and SphereForceRegion.exit
helper.wait_for_condition(force_region_enter_and_exit, 3.0)
# 5) Report results
Report.result(Tests.force_region_trigger, force_region_enter_and_exit())
force_region_magnitude = azlmbr.physics.ForcePointRequestBus(azlmbr.bus.Event, "GetMagnitude", sphere_force_region)
Report.info("Force region magnitude = {}".format(force_region_magnitude))
# explicit check that the value is as expected
# set at level creation
Report.result(Tests.point_force_magnitude_value, force_region_magnitude == EXPECTED_MAGNITUDE)
# prevent the test from hanging if the force vector is not set
if NetForce.vector:
Report.success(Tests.force_calculated)
Report.result(Tests.force_x_vector, NetForce.vector.x > 0)
Report.result(Tests.force_y_vector, NetForce.vector.z > 0)
else:
Report.failure(Tests.force_calculated)
outcome = abs(NetForce.magnitude - force_region_magnitude) < TOLERANCE
Report.result(Tests.point_force_magnitude, outcome)
# 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(ForceRegion_SphereShapedForce)
@@ -0,0 +1,161 @@
"""
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 : C5932045
# Test Case Title : Check that force region exerts spline follow force on rigid bodies
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere entity found", "Sphere entity not found")
find_force_region = ("Force region found", "Force region not found")
find_trigger_0 = ("Trigger0 entity found", "Trigger0 entity not found")
find_trigger_1 = ("Trigger1 entity found", "Trigger1 entity not found")
find_trigger_2 = ("Trigger2 entity found", "Trigger2 entity not found")
find_trigger_3 = ("Trigger3 entity found", "Trigger3 entity not found")
triggers_positioned_apart = ("All triggers were positioned apart", "All triggers were not positioned apart")
sphere_fell = ("The sphere fell", "The sphere did not fall")
sphere_entered_force_region = ("The sphere entered the force region", "The sphere did not enter the force region before timeout")
sphere_reached_trigger0 = ("The sphere reached Trigger0", "The sphere did not reach Trigger0 before timeout")
sphere_reached_trigger1 = ("The sphere reached Trigger1", "The sphere did not reach Trigger1 before timeout")
sphere_reached_trigger2 = ("The sphere reached Trigger2", "The sphere did not reach Trigger2 before timeout")
sphere_reached_trigger3 = ("The sphere reached Trigger3", "The sphere did not reach Trigger3 before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SplineForceOnRigidBodies():
"""
Summary:
Runs an automated test to ensure that a PhysX force region exerts spline follow force on rigid bodies
Level Description:
A sphere entity is positioned above a force region entity
The sphere has a PhysX collider (sphere) and rigidbody component with default values
The force region entity has a PhysX collider (Box) and force region with a 'spline follow' force.
It also has a spline component with 4 nodes. Each node is connected linearly in the following pattern:
[0]___
___[1]
[2]___
[3]
There are 4 trigger entities, each with a PhysX collider (sphere). They are positioned at each node.
Expected Behavior:
The sphere will fall into the force region and begin to follow the spline. It will visit each node in order.
Test Steps:
1) Open level
2) Enter game mode
3) Find entities
4) Verify triggers are apart
5) Drop the sphere
6) Wait for sphere to complete path
7) Exit game mode
8) 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 itertools
import azlmbr
import azlmbr.legacy.general as general
import azlmbr.bus as bus
TIMEOUT = 5
MIN_TRIGGER_DISTANCE = 2
class Sphere:
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(name)
def get_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
def is_falling(self):
return self.get_velocity().z < 0.0
class Trigger:
def __init__(self, name, valid_test, triggered_test):
self.name = name
self.id = general.find_game_entity(name)
self.valid_test = valid_test
self.triggered_test = triggered_test
self.triggered = False
self.create_handler()
def get_position(self):
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
def on_trigger_enter(self, args):
self.triggered = True
def create_handler(self):
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
def are_apart(position1, position2, distance):
return position1.GetDistance(position2) >= distance
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_SplineForceOnRigidBodies")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find entities
sphere = Sphere("Sphere")
Report.critical_result(Tests.find_sphere, sphere.id.IsValid())
force_region = Trigger("ForceRegion", Tests.find_force_region, Tests.sphere_entered_force_region)
trigger0 = Trigger("Trigger0", Tests.find_trigger_0, Tests.sphere_reached_trigger0)
trigger1 = Trigger("Trigger1", Tests.find_trigger_1, Tests.sphere_reached_trigger1)
trigger2 = Trigger("Trigger2", Tests.find_trigger_2, Tests.sphere_reached_trigger2)
trigger3 = Trigger("Trigger3", Tests.find_trigger_3, Tests.sphere_reached_trigger3)
all_triggers = (force_region, trigger0, trigger1, trigger2, trigger3)
for trigger in all_triggers:
Report.critical_result(trigger.valid_test, trigger.id.IsValid())
# 4) Verify triggers are apart
all_triggers_apart = True
for combination in itertools.combinations(all_triggers, 2):
if not are_apart(combination[0].get_position(), combination[1].get_position(), MIN_TRIGGER_DISTANCE):
all_triggers_apart = False
Report.critical_result(Tests.triggers_positioned_apart, all_triggers_apart)
# 5) Drop the sphere
Report.result(Tests.sphere_fell, helper.wait_for_condition(sphere.is_falling, TIMEOUT))
# 6) Wait for sphere to complete path
for trigger in all_triggers:
Report.result(trigger.triggered_test, helper.wait_for_condition(lambda: trigger.triggered, TIMEOUT))
# 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(ForceRegion_SplineForceOnRigidBodies)
@@ -0,0 +1,166 @@
"""
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 : C12868580
# Test Case Title : Check that spline follow force works if transform components of entity are altered
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere entity found", "Sphere entity not found")
find_force_region = ("Force region found", "Force region not found")
find_trigger_0 = ("Trigger0 entity found", "Trigger0 entity not found")
find_trigger_1 = ("Trigger1 entity found", "Trigger1 entity not found")
find_trigger_2 = ("Trigger2 entity found", "Trigger2 entity not found")
find_trigger_3 = ("Trigger3 entity found", "Trigger3 entity not found")
triggers_positioned_apart = ("All triggers were positioned apart", "All triggers were not positioned apart")
sphere_entered_force_region = ("The sphere entered the force region", "The sphere did not enter the force region before timeout")
sphere_reached_trigger0 = ("The sphere reached Trigger0", "The sphere did not reach Trigger0 before timeout")
sphere_reached_trigger1 = ("The sphere reached Trigger1", "The sphere did not reach Trigger1 before timeout")
sphere_reached_trigger2 = ("The sphere reached Trigger2", "The sphere did not reach Trigger2 before timeout")
sphere_reached_trigger3 = ("The sphere reached Trigger3", "The sphere did not reach Trigger3 before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_SplineRegionWithModifiedTransform():
"""
Summary:
Runs an automated test to ensure that a PhysX force region correctly exerts spline follow force on rigid bodies when
its transform component has been modified
Level Description:
A sphere entity is positioned outside (in the +x direction) a force region entity.
The sphere has a PhysX collider (sphere) and rigidbody component with gravity disabled, and an initial velocity of
-3 m/s (in the x direction)
The force region entity has a PhysX collider (Box) and force region with a 'spline follow' force.
It also has a bezier spline component with 4 nodes. Each node is connected in a meandering path through the region.
[3]~~~[2]
)
O -> [0]~~~[1]
(sphere)
The force region is transformed 45 degrees around the Z axis, and scaled by 2 units in the X, Y, and Z directions.
There are also 4 trigger entities, each with a PhysX collider (sphere). They are positioned at each node.
Expected Behavior:
The sphere will enter into the force region and begin to follow the spline. It will visit each node in order.
Test Steps:
1) Open level
2) Enter game mode
3) Find entities
4) Verify triggers are apart
5) Wait for sphere to complete path
6) Exit game mode
7) 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 itertools
import azlmbr
import azlmbr.legacy.general as general
import azlmbr.bus as bus
# region Constants
TIMEOUT = 5.0
MIN_TRIGGER_DISTANCE = 2.0
# endregion
# region Entity Classes
class Sphere:
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(name)
def get_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetLinearVelocity", self.id)
class Trigger:
def __init__(self, name, valid_test, triggered_test):
self.name = name
self.id = general.find_game_entity(name)
self.valid_test = valid_test
self.triggered_test = triggered_test
self.triggered = False
self.create_handler()
def get_position(self):
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
def on_trigger_enter(self, args):
self.triggered = True
def create_handler(self):
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
# endregion
# region Helper Functions
def are_apart(position1, position2, distance):
return position1.GetDistance(position2) >= distance
# endregion
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_SplineRegionWithModifiedTransform")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find Entities
sphere = Sphere("Sphere")
Report.critical_result(Tests.find_sphere, sphere.id.IsValid())
force_region = Trigger("ForceRegion", Tests.find_force_region, Tests.sphere_entered_force_region)
trigger0 = Trigger("Trigger0", Tests.find_trigger_0, Tests.sphere_reached_trigger0)
trigger1 = Trigger("Trigger1", Tests.find_trigger_1, Tests.sphere_reached_trigger1)
trigger2 = Trigger("Trigger2", Tests.find_trigger_2, Tests.sphere_reached_trigger2)
trigger3 = Trigger("Trigger3", Tests.find_trigger_3, Tests.sphere_reached_trigger3)
all_triggers = (force_region, trigger0, trigger1, trigger2, trigger3)
for trigger in all_triggers:
Report.critical_result(trigger.valid_test, trigger.id.IsValid())
# 4) Verify triggers are apart
all_triggers_apart = True
for trigger_a, trigger_b in itertools.combinations(all_triggers, 2):
if not are_apart(trigger_a.get_position(), trigger_b.get_position(), MIN_TRIGGER_DISTANCE):
Report.info("{} was not far enough away from {}".format(trigger_a.name, trigger_b.name))
all_triggers_apart = False
Report.critical_result(Tests.triggers_positioned_apart, all_triggers_apart)
# 5) Wait for sphere to complete path
for trigger in all_triggers:
Report.result(trigger.triggered_test, helper.wait_for_condition(lambda: trigger.triggered, TIMEOUT))
# 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(ForceRegion_SplineRegionWithModifiedTransform)
@@ -0,0 +1,82 @@
"""
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 : C12905528
Test Case Title : Check that user is warned if non-trigger collider component is used with force region
"""
# fmt: off
class Tests():
create_test_entity = ("Entity created successfully", "Failed to create Entity")
add_physx_force_region = ("PhysX Force Region component added", "Failed to add PhysX Force Region component")
add_physx_collider = ("PhysX Collider component added", "Failed to add PhysX Collider component")
warnings_found = ("Warnings found in logs", "No warnings found in logs")
# fmt: on
def ForceRegion_WithNonTriggerColliderWarning():
"""
Summary:
Create entity with PhysX Force Region component. Check that user is warned if new PhysX Collider component is
added to Entity.
Expected Behavior:
User is warned by message in the console that the PhysX Collider component was not marked as a trigger
Test Steps:
1) Load the empty level
2) Create test entity
3) Add PhysX Force Region component
4) Start the Tracer to catch any errors and warnings
5) Add PhysX Collider component to the Entity
6) Verify there is warning in the logs
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 azlmbr.legacy.general as general
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.utils import Tracer
helper.init_idle()
# 1) Load the empty level
helper.open_level("Physics", "Base")
# 2) Create test entity
test_entity = EditorEntity.create_editor_entity("TestEntity")
Report.result(Tests.create_test_entity, test_entity.id.IsValid())
# 3) Add PhysX Force Region component
test_entity.add_component("PhysX Force Region")
Report.result(Tests.add_physx_force_region, test_entity.has_component("PhysX Force Region"))
# 4) Start the Tracer to catch any errors and warnings
Report.info("Starting warning monitoring")
with Tracer() as section_tracer:
# 5) Add the PhysX Collider component
test_entity.add_component("PhysX Collider")
Report.result(Tests.add_physx_collider, test_entity.has_component("PhysX Collider"))
general.idle_wait_frames(1)
Report.info("Ending warning monitoring")
# ) Verify there is warning in the logs
success_condition = section_tracer.has_warnings
# Checking if warning exist and the exact warning is caught in the expected lines in Test file
Report.result(Tests.warnings_found, success_condition)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_WithNonTriggerColliderWarning)
@@ -0,0 +1,182 @@
"""
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 : C5932040
# Test Case Title : Check that force region exerts world space force on rigid bodies
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_ball = ("Ball entity found", "Ball entity not found")
find_box = ("Box entity found", "Box entity not found")
gravity_works = ("Ball fell", "Ball did not fall")
ball_triggers_force_region = ("Ball triggered force region", "Ball did not trigger force region")
net_force_magnitude = ("The net force magnitude on the ball is close to expected value", "The net force magnitude on the ball is not close to expected value")
ball_moved_up = ("Ball moved up", "Ball did not move up before timeout occurred")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def is_close_float(a, b, factor):
return abs(b - a) < factor
def ForceRegion_WorldSpaceForceOnRigidBodies():
"""
Summary:
A ball is suspended over a box shaped force region.
The ball drops and is forced upward by the world space force of the force region
Level Description:
Ball (entity) - Sphere shaped Mesh; Sphere shaped PhysX Collider; PhysX Rigid Body
ForceRegion (entity) - Cube shaped Mesh; Cube shaped PhysX Collider; PhysX Force Region with world space force
Expected Behavior:
The level opens and enters game mode. At this time the ball will fall towards the force region.
When it collides, it will be launched upwards. Then the game mode will exit and the editor will close.
Test Steps:
1) Open level
2) Enter game mode
3) Find the entities
4) Get starting position of the ball
5) Check that gravity works and ball falls
6) Check that the ball enters the trigger area of force region
7) Get the magnitude of the collision
8) Check that the ball moved up
9) Verify that the magnitude of the collision is as expected
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.legacy.general as general
import azlmbr.bus
class Ball:
start_position_z = None
fell = False
# Constants
TIMEOUT = 2.0
BALL_MIN_MOVED_UP = 5 # Minimum amount to indicate Z movement
MAGNITUDE_TOLERANCE = 0.26 # Force region magnitude tolerance
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_WorldSpaceForceOnRigidBodies")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
ball_id = general.find_game_entity("Ball")
Report.critical_result(Tests.find_ball, ball_id.IsValid())
box_id = general.find_game_entity("ForceRegion")
Report.critical_result(Tests.find_box, box_id.IsValid())
# 4) Get the starting z position of the ball
Ball.start_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
Report.info("Starting Height of the ball: {}".format(Ball.start_position_z))
# 5) Check that gravity works and the ball falls
def ball_falls():
if not Ball.fell:
ball_after_frame_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
if (ball_after_frame_z - Ball.start_position_z) < 0.0:
Report.info("Ball position is now lower than the starting position")
Ball.fell = True
return Ball.fell
helper.wait_for_condition(ball_falls, TIMEOUT)
Report.result(Tests.gravity_works, Ball.fell)
# 6) Check that the ball enters the trigger area
class ForceRegionTrigger:
entered = False
exited = False
def on_enter(args):
other_id = args[0]
if other_id.Equal(ball_id):
Report.info("Trigger entered")
ForceRegionTrigger.entered = True
def on_exit(args):
other_id = args[0]
if other_id.Equal(ball_id):
Report.info("Trigger exited")
ForceRegionTrigger.exited = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(box_id)
handler.add_callback("OnTriggerEnter", on_enter)
handler.add_callback("OnTriggerExit", on_exit)
helper.wait_for_condition(lambda: ForceRegionTrigger.entered, TIMEOUT)
Report.result(Tests.ball_triggers_force_region, ForceRegionTrigger.entered)
# 7) Get the magnitude of the collision
class NetForceMagnitude:
value = 0
def on_calc_net_force(args):
"""
args[0] - force region entity
args[1] - entity entering
args[2] - vector
args[3] - magnitude
"""
force_magnitude = args[3]
NetForceMagnitude.value = force_magnitude
force_notification_handler = azlmbr.physics.ForceRegionNotificationBusHandler()
force_notification_handler.connect(None)
force_notification_handler.add_callback("OnCalculateNetForce", on_calc_net_force)
def ball_moved_up():
ball_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
return ball_z > Ball.start_position_z + BALL_MIN_MOVED_UP
# 8) Check that the ball moved up
if helper.wait_for_condition(lambda: ball_moved_up and ForceRegionTrigger.exited, TIMEOUT):
Report.success(Tests.ball_moved_up)
else:
Report.failure(Tests.ball_moved_up)
# 9) Verify that the magnitude of the collision is as expected
force_region_magnitude = azlmbr.physics.ForceWorldSpaceRequestBus(azlmbr.bus.Event, "GetMagnitude", box_id)
Report.info(
"NetForce magnitude is {}, Force Region magnitude is {}".format(NetForceMagnitude.value, force_region_magnitude)
)
Report.result(
Tests.net_force_magnitude, is_close_float(NetForceMagnitude.value, force_region_magnitude, MAGNITUDE_TOLERANCE)
)
# 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(ForceRegion_WorldSpaceForceOnRigidBodies)
@@ -0,0 +1,241 @@
"""
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 : C6090552
Test Case Title : Check that force region exerts linear damping force on rigid bodies (negative test)
"""
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
ball_exists = ("The Ball entity was found", "The Ball entity was not found")
forceregion_exists = ("The ForceRegion entity was found", "The ForceRegion entity was not found")
terrain_exists = ("The Terrain entity was found", "The Terrain entity was not found")
ball_over_force_region = ("The ball is over the force region", "The ball is not over the force region")
gravity_enabled_on_ball = ("The ball has gravity enabled", "The ball gravity failed to enable")
ball_entered_force_region = ("The ball entered the force region", "The ball did not enter the force region before timeout")
ball_fell_through_region = ("The ball fell through the force region", "The ball did not exit force region below enter location")
enter_exit_distance_close = ("The ball has passed through the entire force region", "Ball did not move the height of the force region")
ball_hit_ground = ("The ball collided with the PhysX Terrain", "The ball did not collide with the PhysX Terrain before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroLinearDampingDoesNothing():
"""
Summary:
A ball is suspended over a force region with a linear damping value of zero and a PhysX Terrain.
Level Description:
Ball - (entity): Mesh: Sphere shaped
PhysX Rigid Body: Gravity is enabled; Default settings
PhysX Collider: Sphere shape; offset (0.0, 0.0, 0.5); draw collider checked
ForceRegion - (entity): Box Shape: Game view checked; Shape dimensions (3.0, 3.0, 3.0)
PhysX Force Region: Linear damping force: 0.0
PhysX Collider: Box shape; Box dimensions (3.0, 3.0, 3.0); Trigger checked;
Draw collider checked
Terrain - (entity): PhysX Terrain: Default settings
Expected Behavior:
When game mode is entered, the ball will fall down toward the force region. It will enter the region and fall
straight through as if the region did not exist because the linear damping is set to zero. It will then
collide with the PhysX Terrain.
Test Steps
1) Open level
2) Enter game mode
3) Find/setup entities and handlers
4) Check that the ball is over the force region
5) Wait for ball to hit the ground
6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
7) 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
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TIMEOUT_SECONDS = 3.0
X_Y_Z_TOLERANCE = 1.5
REGION_HEIGHT = 3.0
TERRAIN_HEIGHT = 32.0
def is_close(value1, value2, tolerance):
"""
() -> bool
Absolute value of difference of values being less than or equal to the tolerance
"""
return abs(value1 - value2) <= tolerance
def is_at_least(value1, value2, minimum):
"""
() -> bool
Subtraction of value2 from value1 being greater than or equal to the minimum
"""
return (value1 - value2) >= minimum
class Entity(object):
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name.lower() + "_exists"], self.id.IsValid())
def get_location(self):
# () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Ball(Entity):
def __init__(self):
super(Ball, self).__init__("Ball")
self.location = self.get_location()
self.enter_region_location = None
self.exit_region_location = None
self.enable_gravity()
self.check_gravity_is_on()
def enable_gravity(self):
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id)
def check_gravity_is_on(self):
gravity_status = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
Report.critical_result(Tests.__dict__["gravity_enabled_on_" + self.name.lower()], gravity_status)
def check_alignment(self, region):
# () -> bool
Report.info_vector3(self.location, "Location of Ball: ")
Report.info_vector3(region.location, "Location of ForceRegion: ")
aligned = True
if not is_close(self.location.x, region.location.x, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the X axis.")
if not is_close(self.location.y, region.location.y, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the Y axis.")
if (self.location.z - region.location.z) < X_Y_Z_TOLERANCE:
aligned = False
Report.info("The ball is not high enough to enter the force region.")
if self.location.z < TERRAIN_HEIGHT:
aligned = False
Report.info("The ball is below the terrain.")
return aligned
def did_ball_fall_in_region_as_expected(self):
# () -> bool
if not self.exit_region_location:
Report.info("The ball did not exit the force region in time.")
return False
return (self.enter_region_location.z - self.exit_region_location.z) > X_Y_Z_TOLERANCE
def check_enter_vs_exit_locations(self):
"""
Check that the ball enter location is above the exit location (ball successfully fell through the force
region) and check that the distance between the enter/exit locations is at least the height of the region
NOTE: The ball will "enter" the force region when the bottom edge of its collision box enters the region and
will "exit" when it is completely outside the force region (top edge leaves region)
"""
Report.info_vector3(self.enter_region_location, "Ball entering location: ")
Report.info_vector3(self.exit_region_location, "Ball exiting location: ")
Report.result(Tests.ball_fell_through_region, self.did_ball_fall_in_region_as_expected())
Report.result(
Tests.enter_exit_distance_close,
is_at_least(self.enter_region_location.z, self.exit_region_location.z, REGION_HEIGHT),
)
class ForceRegion(Entity):
def __init__(self, ball):
super(ForceRegion, self).__init__("ForceRegion")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.location = self.get_location()
self.ball_entered_trigger_area = False
self.trigger_handler = azlmbr.physics.TriggerNotificationBusHandler()
self.connect_trigger_handler()
def on_trigger_enter(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has entered the force region trigger area.")
self.ball_entered_trigger_area = True
self.ball.enter_region_location = self.ball.get_location()
def on_trigger_exit(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has exited the force region trigger area.")
self.ball.exit_region_location = self.ball.get_location()
def connect_trigger_handler(self):
self.trigger_handler.connect(self.id)
self.trigger_handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
self.trigger_handler.add_callback("OnTriggerExit", self.on_trigger_exit)
class Terrain(Entity):
def __init__(self, ball):
super(Terrain, self).__init__("Terrain")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.ball_collided = False
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
self.connect_collision_handler()
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball struck PhysX Terrain")
self.ball_collided = True
def connect_collision_handler(self):
self.collision_handler.connect(self.id)
self.collision_handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroLinearDampingDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find/setup entities and handlers
ball = Ball()
region = ForceRegion(ball)
terrain = Terrain(ball)
# 4) Check that the ball is over the force region
Report.critical_result(Tests.ball_over_force_region, ball.check_alignment(region))
# 5) Wait for ball to hit the ground
helper.wait_for_condition(lambda: terrain.ball_collided, TIMEOUT_SECONDS)
# 6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
Report.result(Tests.ball_entered_force_region, region.ball_entered_trigger_area)
ball.check_enter_vs_exit_locations()
Report.result(Tests.ball_hit_ground, terrain.ball_collided)
# 7) Exit game mode and close the editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ZeroLinearDampingDoesNothing)
@@ -0,0 +1,242 @@
"""
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 : C6090551
Test Case Title : Check that force region exerts local space force on rigid bodies (negative test)
"""
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
ball_exists = ("The Ball entity was found", "The Ball entity was not found")
forceregion_exists = ("The ForceRegion entity was found", "The ForceRegion entity was not found")
terrain_exists = ("The Terrain entity was found", "The Terrain entity was not found")
ball_over_force_region = ("The ball is over the force region", "The ball is not over the force region")
gravity_enabled_on_ball = ("The ball has gravity enabled", "The ball gravity failed to enable")
ball_entered_force_region = ("The ball entered the force region", "The ball did not enter the force region before timeout")
ball_fell_through_region = ("The ball fell through the force region", "The ball did not exit force region below enter location")
enter_exit_distance_close = ("The ball has passed through the entire force region", "Ball did not move the height of the force region")
ball_hit_ground = ("The ball collided with the PhysX Terrain", "The ball did not collide with the PhysX Terrain before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroLocalSpaceForceDoesNothing():
"""
Summary:
A ball is suspended over a box shaped force region and a PhysX Terrain. The force is a local space force
pointed in the positive Z direction with a magnitude of 10.
Level Description:
Ball - (entity): Mesh: Sphere shaped
PhysX Rigid Body: Gravity is enabled; Default settings
PhysX Collider: Sphere shape; offset (0.0, 0.0, 0.5); draw collider checked
ForceRegion - (entity): Box Shape: Game view checked; Shape dimensions (3.0, 3.0, 3.0)
PhysX Force Region: Local Space force; direction (0.0, 0.0, 1.0); magnitude 10.0
PhysX Collider: Box shape; Box dimensions (3.0, 3.0, 3.0); Trigger checked;
Draw collider checked
Terrain - (entity): PhysX Terrain: Default settings
Expected Behavior:
When game mode is entered, the ball entity will fall due to gravity and enter the force region.
The region has a very weak force and therefore the ball will fall through the force region,
where it will then collide with the PhysX Terrain.
Test Steps:
1) Open level
2) Enter game mode
3) Find/setup entities and handlers
4) Check that the ball is over the force region
5) Wait for ball to hit the ground
6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
7) 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
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TIMEOUT_SECONDS = 3.0
X_Y_Z_TOLERANCE = 1.5
REGION_HEIGHT = 3.0
TERRAIN_HEIGHT = 32.0
def is_close(value1, value2, tolerance):
"""
() -> bool
Absolute value of difference of values being less than or equal to the tolerance
"""
return abs(value1 - value2) <= tolerance
def is_at_least(value1, value2, minimum):
"""
() -> bool
Subtraction of value2 from value1 being greater than or equal to the minimum
"""
return (value1 - value2) >= minimum
class Entity(object):
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name.lower() + "_exists"], self.id.IsValid())
def get_location(self):
# () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Ball(Entity):
def __init__(self):
super(Ball, self).__init__("Ball")
self.location = self.get_location()
self.enter_region_location = None
self.exit_region_location = None
self.enable_gravity()
self.check_gravity_is_on()
def enable_gravity(self):
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id)
def check_gravity_is_on(self):
gravity_status = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
Report.critical_result(Tests.__dict__["gravity_enabled_on_" + self.name.lower()], gravity_status)
def check_alignment(self, region):
# () -> bool
Report.info_vector3(self.location, "Location of Ball: ")
Report.info_vector3(region.location, "Location of ForceRegion: ")
aligned = True
if not is_close(self.location.x, region.location.x, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the X axis.")
if not is_close(self.location.y, region.location.y, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the Y axis.")
if (self.location.z - region.location.z) < X_Y_Z_TOLERANCE:
aligned = False
Report.info("The ball is not high enough to enter the force region.")
if self.location.z < TERRAIN_HEIGHT:
aligned = False
Report.info("The ball is below the terrain.")
return aligned
def did_ball_fall_in_region_as_expected(self):
# () -> bool
if not self.exit_region_location:
Report.info("The ball did not exit the force region in time.")
return False
return (self.enter_region_location.z - self.exit_region_location.z) > X_Y_Z_TOLERANCE
def check_enter_vs_exit_locations(self):
"""
Check that the ball enter location is above the exit location (ball successfully fell through the force
region) and check that the distance between the enter/exit locations is at least the height of the region
NOTE: The ball will "enter" the force region when the bottom edge of its collision box enters the region and
will "exit" when it is completely outside the force region (top edge leaves region)
"""
Report.info_vector3(self.enter_region_location, "Ball entering location: ")
Report.info_vector3(self.exit_region_location, "Ball exiting location: ")
Report.result(Tests.ball_fell_through_region, self.did_ball_fall_in_region_as_expected())
Report.result(
Tests.enter_exit_distance_close,
is_at_least(self.enter_region_location.z, self.exit_region_location.z, REGION_HEIGHT),
)
class ForceRegion(Entity):
def __init__(self, ball):
super(ForceRegion, self).__init__("ForceRegion")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.location = self.get_location()
self.ball_entered_trigger_area = False
self.trigger_handler = azlmbr.physics.TriggerNotificationBusHandler()
self.connect_trigger_handler()
def on_trigger_enter(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has entered the force region trigger area.")
self.ball_entered_trigger_area = True
self.ball.enter_region_location = self.ball.get_location()
def on_trigger_exit(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has exited the force region trigger area.")
self.ball.exit_region_location = self.ball.get_location()
def connect_trigger_handler(self):
self.trigger_handler.connect(self.id)
self.trigger_handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
self.trigger_handler.add_callback("OnTriggerExit", self.on_trigger_exit)
class Terrain(Entity):
def __init__(self, ball):
super(Terrain, self).__init__("Terrain")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.ball_collided = False
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
self.connect_collision_handler()
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball struck PhysX Terrain")
self.ball_collided = True
def connect_collision_handler(self):
self.collision_handler.connect(self.id)
self.collision_handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroLocalSpaceForceDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find/setup entities and handlers
ball = Ball()
region = ForceRegion(ball)
terrain = Terrain(ball)
# 4) Check that the ball is over the force region
Report.critical_result(Tests.ball_over_force_region, ball.check_alignment(region))
# 5) Wait for ball to hit the ground
helper.wait_for_condition(lambda: terrain.ball_collided, TIMEOUT_SECONDS)
# 6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
Report.result(Tests.ball_entered_force_region, region.ball_entered_trigger_area)
ball.check_enter_vs_exit_locations()
Report.result(Tests.ball_hit_ground, terrain.ball_collided)
# 7) Exit game mode and close the editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ZeroLocalSpaceForceDoesNothing)
@@ -0,0 +1,242 @@
"""
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 : C6090554
Test Case Title : Check that force region exerts point force on rigid bodies (negative test)
"""
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
ball_exists = ("The Ball entity was found", "The Ball entity was not found")
forceregion_exists = ("The ForceRegion entity was found", "The ForceRegion entity was not found")
terrain_exists = ("The Terrain entity was found", "The Terrain entity was not found")
ball_over_force_region = ("The ball is over the force region", "The ball is not over the force region")
gravity_enabled_on_ball = ("The ball has gravity enabled", "The ball gravity failed to enable")
ball_entered_force_region = ("The ball entered the force region", "The ball did not enter the force region before timeout")
ball_fell_through_region = ("The ball fell through the force region", "The ball did not exit force region below enter location")
enter_exit_distance_close = ("The ball has passed through the entire force region", "Ball did not move the height of the force region")
ball_hit_ground = ("The ball collided with the PhysX Terrain", "The ball did not collide with the PhysX Terrain before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroPointForceDoesNothing():
"""
Summary:
A ball is suspended over a box shaped force region and a PhysX Terrain. The force is a point force
pointed outward from center with a magnitude of 10.
Level Description:
Ball - (entity): Mesh: Sphere shaped
PhysX Rigid Body: Gravity is enabled; Default settings
PhysX Collider: Sphere shape; offset (0.0, 0.0, 0.5); draw collider checked
ForceRegion - (entity): Box Shape: Game view checked; Shape dimensions (3.0, 3.0, 3.0)
PhysX Force Region: Point force; magnitude 10.0
PhysX Collider: Box shape; Box dimensions (3.0, 3.0, 3.0); Trigger checked;
Draw collider checked
Terrain - (entity): PhysX Terrain: Default settings
Expected Behavior:
When game mode is entered, the ball entity will fall due to gravity and enter the force region.
The region has a very weak force and therefore the ball will fall through the force region,
where it will then collide with the PhysX Terrain.
Test Steps:
1) Open level
2) Enter game mode
3) Find/setup entities and handlers
4) Check that the ball is over the force region
5) Wait for ball to hit the ground
6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
7) 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
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TIMEOUT_SECONDS = 3.0
X_Y_Z_TOLERANCE = 1.5
REGION_HEIGHT = 3.0
TERRAIN_HEIGHT = 32.0
def is_close(value1, value2, tolerance):
"""
() -> bool
Absolute value of difference of values being less than or equal to the tolerance
"""
return abs(value1 - value2) <= tolerance
def is_at_least(value1, value2, minimum):
"""
() -> bool
Subtraction of value2 from value1 being greater than or equal to the minimum
"""
return (value1 - value2) >= minimum
class Entity(object):
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name.lower() + "_exists"], self.id.IsValid())
def get_location(self):
# () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Ball(Entity):
def __init__(self):
super(Ball, self).__init__("Ball")
self.location = self.get_location()
self.enter_region_location = None
self.exit_region_location = None
self.enable_gravity()
self.check_gravity_is_on()
def enable_gravity(self):
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id)
def check_gravity_is_on(self):
gravity_status = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
Report.critical_result(Tests.__dict__["gravity_enabled_on_" + self.name.lower()], gravity_status)
def check_alignment(self, region):
# () -> bool
Report.info_vector3(self.location, "Location of Ball: ")
Report.info_vector3(region.location, "Location of ForceRegion: ")
aligned = True
if not is_close(self.location.x, region.location.x, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the X axis.")
if not is_close(self.location.y, region.location.y, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the Y axis.")
if (self.location.z - region.location.z) < X_Y_Z_TOLERANCE:
aligned = False
Report.info("The ball is not high enough to enter the force region.")
if self.location.z < TERRAIN_HEIGHT:
aligned = False
Report.info("The ball is below the terrain.")
return aligned
def did_ball_fall_in_region_as_expected(self):
# () -> bool
if not self.exit_region_location:
Report.info("The ball did not exit the force region in time.")
return False
return (self.enter_region_location.z - self.exit_region_location.z) > X_Y_Z_TOLERANCE
def check_enter_vs_exit_locations(self):
"""
Check that the ball enter location is above the exit location (ball successfully fell through the force
region) and check that the distance between the enter/exit locations is at least the height of the region
NOTE: The ball will "enter" the force region when the bottom edge of its collision box enters the region and
will "exit" when it is completely outside the force region (top edge leaves region)
"""
Report.info_vector3(self.enter_region_location, "Ball entering location: ")
Report.info_vector3(self.exit_region_location, "Ball exiting location: ")
Report.result(Tests.ball_fell_through_region, self.did_ball_fall_in_region_as_expected())
Report.result(
Tests.enter_exit_distance_close,
is_at_least(self.enter_region_location.z, self.exit_region_location.z, REGION_HEIGHT),
)
class ForceRegion(Entity):
def __init__(self, ball):
super(ForceRegion, self).__init__("ForceRegion")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.location = self.get_location()
self.ball_entered_trigger_area = False
self.trigger_handler = azlmbr.physics.TriggerNotificationBusHandler()
self.connect_trigger_handler()
def on_trigger_enter(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has entered the force region trigger area.")
self.ball_entered_trigger_area = True
self.ball.enter_region_location = self.ball.get_location()
def on_trigger_exit(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has exited the force region trigger area.")
self.ball.exit_region_location = self.ball.get_location()
def connect_trigger_handler(self):
self.trigger_handler.connect(self.id)
self.trigger_handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
self.trigger_handler.add_callback("OnTriggerExit", self.on_trigger_exit)
class Terrain(Entity):
def __init__(self, ball):
super(Terrain, self).__init__("Terrain")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.ball_collided = False
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
self.connect_collision_handler()
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball struck PhysX Terrain")
self.ball_collided = True
def connect_collision_handler(self):
self.collision_handler.connect(self.id)
self.collision_handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroPointForceDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find/setup entities and handlers
ball = Ball()
region = ForceRegion(ball)
terrain = Terrain(ball)
# 4) Check that the ball is over the force region
Report.critical_result(Tests.ball_over_force_region, ball.check_alignment(region))
# 5) Wait for ball to hit the ground
helper.wait_for_condition(lambda: terrain.ball_collided, TIMEOUT_SECONDS)
# 6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
Report.result(Tests.ball_entered_force_region, region.ball_entered_trigger_area)
ball.check_enter_vs_exit_locations()
Report.result(Tests.ball_hit_ground, terrain.ball_collided)
# 7) Exit game mode and close the editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ZeroPointForceDoesNothing)
@@ -0,0 +1,173 @@
"""
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 : C6090553
# Test Case Title : Check that force region exerts simple drag force on rigid bodies (negative test)
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_ball = ("Ball found", "Ball not found")
find_force_region = ("Force Region found", "Force Region not found")
ball_gravity_disabled = ("Ball gravity disabled", "Ball gravity not disabled")
ball_fell = ("The ball fell", "The ball did not fall")
ball_enters_force_region = ("Ball entered force region", "Ball did not enter force region")
ball_exits_force_region = ("Ball exited force region", "Ball did not exit force region")
force_region_slows_ball = ("Force Region did not slow ball", "Force Region slows ball")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroSimpleDragForceDoesNothing():
"""
Summary:
Runs an automated test to ensure that force region exerts simple drag force on rigid bodies(negative test).
Level Description:
Ball (entity) - contains a sphere mesh, PhysX Collider (sphere shape) and PhysX RigidBody. Ball is
placed above force region
Force Region (entity) - contains Physx Force Region with Simple Drag force with Region Density as 0 and
PhysX Collider (box shape)
Expected Behavior:
When game mode is entered, Sphere falls through force region as though force region doesn't exist because
region density for the simple drag force is zero and has no effect on the dropping sphere.
Test Steps:
1) Open level
2) Enter Game mode
3) Validate the entities in the scene
4) Check ball gravity is disabled or not
5) Get initial velocity of ball
6) Wait for ball to enter force region
7) Gets z velocity and position of ball
8) Wait for ball to exit force region
9) Gets new velocity and position of ball
10) Check that the ball does not slow due to the force region
11) Exits game mode and 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
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Holds details about the ball
class Ball:
id = None
initial_velocity_z = 0.0
start_velocity_z = 0.0
end_velocity_z = 0.0
ball_start_z_position = 0.0
ball_end_z_position = 0.0
entered_force_region = False
exited_force_region = False
ball_fell_down = False
ball_slows = False
def on_trigger_enter(args):
other_id = args[0]
if other_id.Equal(Ball.id):
Ball.entered_force_region = True
def on_trigger_exit(args):
other_id = args[0]
if other_id.Equal(Ball.id):
Ball.exited_force_region = True
# Constants
TIMEOUT = 2.0
CLOSE_ENOUGH_THRESHOLD = 0.01
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroSimpleDragForceDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate the entities in the scene
Ball.id = general.find_game_entity("Sphere")
Report.critical_result(Tests.find_ball, Ball.id.IsValid())
force_region_id = general.find_game_entity("Force Region")
Report.critical_result(Tests.find_force_region, force_region_id.IsValid())
# 4) Check ball gravity is disabled or not
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", Ball.id)
Report.critical_result(Tests.ball_gravity_disabled, not gravity_enabled)
# 5) Get initial velocity of ball
# Ball linear velocity is set at (0, 0, -5) in the level
Ball.initial_velocity_z = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", Ball.id).z
Report.info("Ball initial velocity = {}".format(Ball.initial_velocity_z))
# 6) Wait for ball to enter force region
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(force_region_id)
handler.add_callback("OnTriggerEnter", on_trigger_enter)
handler.add_callback("OnTriggerExit", on_trigger_exit)
helper.wait_for_condition(lambda: Ball.entered_force_region, TIMEOUT)
Report.critical_result(Tests.ball_enters_force_region, Ball.entered_force_region)
# 7) Gets z velocity and position of ball
Ball.start_velocity_z = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", Ball.id).z
Ball.ball_start_z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Ball.id)
Report.info(
"Ball Start Z position = {} Ball Start Z Velocity = {}".format(
Ball.ball_start_z_position, Ball.start_velocity_z
)
)
# 8) Wait for ball to exit force region
helper.wait_for_condition(lambda: Ball.exited_force_region, TIMEOUT)
Report.critical_result(Tests.ball_exits_force_region, Ball.exited_force_region)
# 9) Gets new velocity and position of ball
Ball.end_velocity_z = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", Ball.id).z
Ball.ball_end_z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Ball.id)
Report.info(
"Ball End Z position = {} Ball End Z Velocity = {}".format(Ball.ball_end_z_position, Ball.end_velocity_z)
)
# 10) Check that the ball does not slow due to the force region
# Check ball fell down or not
if (Ball.ball_end_z_position - CLOSE_ENOUGH_THRESHOLD) < Ball.ball_start_z_position:
Ball.ball_fell_down = True
Report.critical_result(Tests.ball_fell, Ball.ball_fell_down)
# Ball initial velocity is -5.0. Check that the ball does not slow down in force region
if ((Ball.end_velocity_z - Ball.initial_velocity_z) < CLOSE_ENOUGH_THRESHOLD) and (
(Ball.start_velocity_z - Ball.initial_velocity_z) < CLOSE_ENOUGH_THRESHOLD
):
Ball.ball_slows = True
Report.critical_result(Tests.force_region_slows_ball, Ball.ball_slows)
# 11) Exits game mode and editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ZeroSimpleDragForceDoesNothing)
@@ -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 : C6090555
# Test Case Title : Check that force region exerts spline follow force on rigid bodies(negative test)
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_sphere = ("Sphere entity found", "Sphere entity not found")
find_force_region = ("Force region found", "Force region not found")
find_triggers = ("All triggers are found", "All triggers are not found")
sphere_fell = ("The sphere fell", "The sphere did not fall")
sphere_entered_force_region = ("The sphere entered the force region", "The sphere did not enter the force region before timeout")
sphere_exited_force_region = ("The sphere exited the force region", "The sphere did not exit the force region before timeout")
sphere_drops_force_region = ("Sphere drops through the force region", "Sphere did not drop through the force region due to spline force")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroSplineForceDoesNothing():
"""
Summary:
Runs an automated test to ensure that a PhysX force region exerts spline follow force on rigid bodies(negative test)
Level Description:
A sphere entity is positioned above a force region entity
The sphere has a PhysX collider (sphere) and rigidbody component with default values
The force region entity has a PhysX collider (Box) and force region with a 'spline follow' force.
It also has a spline component with 4 nodes. Each node is connected linearly in the following pattern:
___[0]
[1] ___
___ [2]
[3]
There are 4 trigger entities, each with a PhysX collider (sphere). They are positioned at each node.
Expected Behavior:
The Sphere drops through the force region as though spline follow force does not exist.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and Validate entities
4) Get position of spline and sphere
5) Wait till the sphere drops
6) Get z position of sphere when it enters and exits from trigger area
7) Verify sphere drops through force region without spline force effect
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
# Constants
TIMEOUT = 3
CLOSE_ENOUGH = 0.001
class Sphere:
id = None
start_position_z = None
fell = False
entered_force_region = False
exited_force_region = False
def on_trigger_enter(args):
other_id = args[0]
if other_id.Equal(Sphere.id):
Sphere.entered_force_region = True
def on_trigger_exit(args):
other_id = args[0]
if other_id.Equal(Sphere.id):
Sphere.exited_force_region = True
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroSplineForceDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and Validate entities
Sphere.id = general.find_game_entity("Sphere")
Report.critical_result(Tests.find_sphere, Sphere.id.IsValid())
force_region_id = general.find_game_entity("Force Region")
Report.critical_result(Tests.find_force_region, force_region_id.IsValid())
all_triggers = ("Trigger0", "Trigger1", "Trigger2", "Trigger3")
all_triggers_found = True
for trigger in all_triggers:
trigger_id = general.find_game_entity(trigger)
if not trigger_id.IsValid():
all_triggers_found = False
Report.critical_result(Tests.find_triggers, all_triggers_found)
# 4) Get z position of spline and sphere
# All triggers are arranged at each node in spline. Getting z position of Trigger3 is same as z position of spline
spline_z_position = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldZ", general.find_game_entity("Trigger3")
)
Report.info("Spline z position is : {}".format(spline_z_position))
Sphere.start_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Sphere.id)
Report.info("Sphere z position is : {}".format(Sphere.start_position_z))
# 5) Wait till the sphere drops
def sphere_fell():
if not Sphere.fell:
sphere_position_z = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Sphere.id)
if sphere_position_z < (Sphere.start_position_z - CLOSE_ENOUGH):
Report.info("Sphere position is lower than the starting position now")
Sphere.fell = True
return Sphere.fell
helper.wait_for_condition(sphere_fell, TIMEOUT)
Report.critical_result(Tests.sphere_fell, Sphere.fell)
# 6) Get position of sphere when it enters and exits from trigger area
# Wait for ball to enter force region
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(force_region_id)
handler.add_callback("OnTriggerEnter", on_trigger_enter)
helper.wait_for_condition(lambda: Sphere.entered_force_region, TIMEOUT)
Report.critical_result(Tests.sphere_entered_force_region, Sphere.entered_force_region)
sphere_start_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
Report.info_vector3(sphere_start_position, "Sphere start position in Force Region")
# Wait for ball to exit force region
handler.add_callback("OnTriggerExit", on_trigger_exit)
helper.wait_for_condition(lambda: Sphere.exited_force_region, TIMEOUT)
Report.critical_result(Tests.sphere_exited_force_region, Sphere.exited_force_region)
sphere_end_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Sphere.id)
Report.info_vector3(sphere_end_position, "Sphere end position in Force Region")
# 7) Verify sphere drops through force region without spline force effect
if (
((sphere_start_position.x - sphere_end_position.x) < CLOSE_ENOUGH) and
((sphere_start_position.y - sphere_end_position.y) < CLOSE_ENOUGH) and
(sphere_end_position.z < (spline_z_position - CLOSE_ENOUGH))
):
Sphere.sphere_drops = True
Report.critical_result(Tests.sphere_drops_force_region, Sphere.sphere_drops)
# 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(ForceRegion_ZeroSplineForceDoesNothing)
@@ -0,0 +1,242 @@
"""
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 : C6090550
Test Case Title : Check that force region exerts world space force on rigid bodies (negative test)
"""
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
ball_exists = ("The Ball entity was found", "The Ball entity was not found")
forceregion_exists = ("The ForceRegion entity was found", "The ForceRegion entity was not found")
terrain_exists = ("The Terrain entity was found", "The Terrain entity was not found")
ball_over_force_region = ("The ball is over the force region", "The ball is not over the force region")
gravity_enabled_on_ball = ("The ball has gravity enabled", "The ball gravity failed to enable")
ball_entered_force_region = ("The ball entered the force region", "The ball did not enter the force region before timeout")
ball_fell_through_region = ("The ball fell through the force region", "The ball did not exit force region below enter location")
enter_exit_distance_close = ("The ball has passed through the entire force region", "Ball did not move the height of the force region")
ball_hit_ground = ("The ball collided with the PhysX Terrain", "The ball did not collide with the PhysX Terrain before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def ForceRegion_ZeroWorldSpaceForceDoesNothing():
"""
Summary:
A ball is suspended above a cube shaped force region and a PhysX Terrain. The force is a world space force
pointed in the positive Z direction with a magnitude of 10.
Level Description:
Ball - (entity): Mesh: Sphere shaped
PhysX Rigid Body: Gravity is enabled; Default settings
PhysX Collider: Sphere shape; offset (0.0, 0.0, 0.5); draw collider checked
ForceRegion - (entity): Box Shape: Game view checked; Shape dimensions (3.0, 3.0, 3.0)
PhysX Force Region: World Space force; direction (0.0, 0.0, 1.0); magnitude 10.0
PhysX Collider: Box shape; Box dimensions (3.0, 3.0, 3.0); Trigger checked;
Draw collider checked
Terrain - (entity): PhysX Terrain: Default settings
Expected Behavior:
When game mode is entered, the ball entity will fall due to gravity and enter the force region.
The region has a very weak force and therefore the ball will fall through the force region,
where it will then collide with the PhysX Terrain.
Test Steps:
1) Open level
2) Enter game mode
3) Find/setup entities and handlers
4) Check that the ball is over the force region
5) Wait for ball to hit the ground
6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
7) 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
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TIMEOUT_SECONDS = 3.0
X_Y_Z_TOLERANCE = 1.5
REGION_HEIGHT = 3.0
TERRAIN_HEIGHT = 32.0
def is_close(value1, value2, tolerance):
"""
() -> bool
Absolute value of difference of values being less than or equal to the tolerance
"""
return abs(value1 - value2) <= tolerance
def is_at_least(value1, value2, minimum):
"""
() -> bool
Subtraction of value2 from value1 being greater than or equal to the minimum
"""
return (value1 - value2) >= minimum
class Entity(object):
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name.lower() + "_exists"], self.id.IsValid())
def get_location(self):
# () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Ball(Entity):
def __init__(self):
super(Ball, self).__init__("Ball")
self.location = self.get_location()
self.enter_region_location = None
self.exit_region_location = None
self.enable_gravity()
self.check_gravity_is_on()
def enable_gravity(self):
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", self.id)
def check_gravity_is_on(self):
gravity_status = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
Report.critical_result(Tests.__dict__["gravity_enabled_on_" + self.name.lower()], gravity_status)
def check_alignment(self, region):
# () -> bool
Report.info_vector3(self.location, "Location of Ball: ")
Report.info_vector3(region.location, "Location of ForceRegion: ")
aligned = True
if not is_close(self.location.x, region.location.x, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the X axis.")
if not is_close(self.location.y, region.location.y, X_Y_Z_TOLERANCE):
aligned = False
Report.info("The ball is not aligned in the Y axis.")
if (self.location.z - region.location.z) < X_Y_Z_TOLERANCE:
aligned = False
Report.info("The ball is not high enough to enter the force region.")
if self.location.z < TERRAIN_HEIGHT:
aligned = False
Report.info("The ball is below the terrain.")
return aligned
def did_ball_fall_in_region_as_expected(self):
# () -> bool
if not self.exit_region_location:
Report.info("The ball did not exit the force region in time.")
return False
return (self.enter_region_location.z - self.exit_region_location.z) > X_Y_Z_TOLERANCE
def check_enter_vs_exit_locations(self):
"""
Check that the ball enter location is above the exit location (ball successfully fell through the force
region) and check that the distance between the enter/exit locations is at least the height of the region
NOTE: The ball will "enter" the force region when the bottom edge of its collision box enters the region and
will "exit" when it is completely outside the force region (top edge leaves region)
"""
Report.info_vector3(self.enter_region_location, "Ball entering location: ")
Report.info_vector3(self.exit_region_location, "Ball exiting location: ")
Report.result(Tests.ball_fell_through_region, self.did_ball_fall_in_region_as_expected())
Report.result(
Tests.enter_exit_distance_close,
is_at_least(self.enter_region_location.z, self.exit_region_location.z, REGION_HEIGHT),
)
class ForceRegion(Entity):
def __init__(self, ball):
super(ForceRegion, self).__init__("ForceRegion")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.location = self.get_location()
self.ball_entered_trigger_area = False
self.trigger_handler = azlmbr.physics.TriggerNotificationBusHandler()
self.connect_trigger_handler()
def on_trigger_enter(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has entered the force region trigger area.")
self.ball_entered_trigger_area = True
self.ball.enter_region_location = self.ball.get_location()
def on_trigger_exit(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball has exited the force region trigger area.")
self.ball.exit_region_location = self.ball.get_location()
def connect_trigger_handler(self):
self.trigger_handler.connect(self.id)
self.trigger_handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
self.trigger_handler.add_callback("OnTriggerExit", self.on_trigger_exit)
class Terrain(Entity):
def __init__(self, ball):
super(Terrain, self).__init__("Terrain")
self.ball = ball # This creates a reference point to access the ball without looking in global scope
self.ball_collided = False
self.collision_handler = azlmbr.physics.CollisionNotificationBusHandler()
self.connect_collision_handler()
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.ball.id):
Report.info("Ball struck PhysX Terrain")
self.ball_collided = True
def connect_collision_handler(self):
self.collision_handler.connect(self.id)
self.collision_handler.add_callback("OnCollisionBegin", self.on_collision_begin)
# 1) Open level
helper.open_level("Physics", "ForceRegion_ZeroWorldSpaceForceDoesNothing")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find/setup entities and handlers
ball = Ball()
region = ForceRegion(ball)
terrain = Terrain(ball)
# 4) Check that the ball is over the force region
Report.critical_result(Tests.ball_over_force_region, ball.check_alignment(region))
# 5) Wait for ball to hit the ground
helper.wait_for_condition(lambda: terrain.ball_collided, TIMEOUT_SECONDS)
# 6) Report if the ball entered the force region, fell through it, and collided with terrain in the alloted time
Report.result(Tests.ball_entered_force_region, region.ball_entered_trigger_area)
ball.check_enter_vs_exit_locations()
Report.result(Tests.ball_hit_ground, terrain.ball_collided)
# 7) Exit game mode and close the editor
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(ForceRegion_ZeroWorldSpaceForceDoesNothing)
@@ -0,0 +1,60 @@
"""
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
"""
from editor_python_test_tools.utils import Report
import azlmbr.legacy.general as general
import azlmbr.bus
def vector3SmallerThanScalar(vec3Value, scalarValue):
return (vec3Value.x < scalarValue and
vec3Value.y < scalarValue and
vec3Value.z < scalarValue)
def vector3LargerThanScalar(vec3Value, scalarValue):
return (vec3Value.x > scalarValue and
vec3Value.y > scalarValue and
vec3Value.z > scalarValue)
def getRelativeVector(vecA, vecB):
relativeVec = vecA
relativeVec.x = relativeVec.x - vecB.x
relativeVec.y = relativeVec.y - vecB.y
relativeVec.z = relativeVec.z - vecB.z
return relativeVec
# Entity class for joints tests
class JointEntity:
def criticalEntityFound(self): # For overriding in sub-classes so that can report if entities are found using their own dictionary of entities
pass
def __init__(self, name):
self.id = general.find_game_entity(name)
self.name = name
self.criticalEntityFound()
@property
def position(self):
# type () -> Vector3
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
# Entity class that sets a flag when an instance receives collision events.
class JointEntityCollisionAware(JointEntity):
def on_collision_begin(self, args):
if not self.collided:
self.collided = True
def __init__(self, name):
self.id = general.find_game_entity(name)
self.name = name
self.criticalEntityFound()
self.collided = 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)
@@ -0,0 +1,113 @@
"""
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 : C18243588
# Test Case Title : Check that ball joint constrains 2 bodies within cone limits
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved in X, Y and Z directions", "Follower did not move in X, Y and Z directions")
check_follower_below_lead = ("Follower remains below lead", "Follower moved above lead")
# fmt: on
def Joints_Ball2BodiesConstrained():
"""
Summary: Check that ball joint constrains 2 bodies within cone limits
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with ball joint. Starts with initial velocity of (5, 2, 0).
Expected Behavior:
The follower entity moved in the positive X, Y and Z directions.
The position of the lead entity does not change much.
The follower entity did not move above the lead entity.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.1 # Negligible float value for comparing with translation vectors. Values smaller than this are considered zero.
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_Ball2BodiesConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerPositionDelta = follower.position.Subtract(followerInitialPosition)
followerMovedInXAndZOnly = JointsHelper.vector3LargerThanScalar(followerPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_follower_position, followerMovedInXAndZOnly)
followerBelowLead = follower.position.z < lead.position.z
Report.critical_result(Tests.check_follower_below_lead, followerBelowLead)
# 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(Joints_Ball2BodiesConstrained)
@@ -0,0 +1,107 @@
"""
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 : C18243592
# Test Case Title : Check that ball joint is breakable
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved in X direction only", "Follower did not just move in X direction, but also in Z direction")
# fmt: on
def Joints_BallBreakable():
"""
Summary: Check that ball joint is breakable
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with ball joint. Starts with initial velocity of (5, 2, 0).
Expected Behavior:
Lead entity remains still.
Follower entity moves in the positive X direction, and not much in the Z direction since the joint broke.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_BallBreakable")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerMovedInXOnly = ((follower.position.x - followerInitialPosition.x) > FLOAT_EPSILON and
(follower.position.z - followerInitialPosition.z) < FLOAT_EPSILON)
Report.critical_result(Tests.check_follower_position, followerMovedInXOnly)
# 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(Joints_BallBreakable)
@@ -0,0 +1,91 @@
"""
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 : C18243591
# Test Case Title : Check that ball joint allows lead-follower collision
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_collision_happened = ("Lead and follower collided", "Lead and follower did not collide")
# fmt: on
def Joints_BallLeadFollowerCollide():
"""
Summary: Check that ball joint allows lead-follower collision
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a ball joint. Starts with initial velocity of (5, 2, 0).
Expected Behavior:
Lead entity remains still.
Follower entity swings up, collides with the lead entity, and falls back down.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected (they collided)
6) Exit Game Mode
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
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 JointsHelper import JointEntityCollisionAware
# Helper Entity class - self.collided flag is set when instance receives collision event.
class Entity(JointEntityCollisionAware):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_BallLeadFollowerCollide")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
# 4) Wait for several seconds
general.idle_wait(2.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.critical_result(Tests.check_collision_happened, lead.collided and follower.collided)
# 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(Joints_BallLeadFollowerCollide)
@@ -0,0 +1,112 @@
"""
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 : C18243590
# Test Case Title : Check that ball joint allows no limit constraints
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved higher than lead", "Follower did not move higher than lead")
# fmt: on
def Joints_BallNoLimitsConstrained():
"""
Summary: Check that ball joint allows no limit constraints
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a ball joint. The ball joint is located in between the lead and follower. Starts with initial velocity of (5, 1, 0).
dampingRegion - A force region cube is placed at the position of the lead. If the follower swings to the position above and near the lead, the force region's damping holds the follower in the place.'
Expected Behavior:
Lead entity remains still.
Follower entity's Z position exceeds lead entity's Z position.
Because the ball joint is somewhere in the middle of the follower and the lead,
if the follower manages to go above the lead,
it is evident that the ball joint without limits managed to keep the follower constrained to the lead but did not impose a limit.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_BallNoLimitsConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(3.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 2.5 second:")
Report.info_vector3(follower.position, "follower position after 2.5 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerAboveLead = follower.position.z > leadInitialPosition.z
Report.critical_result(Tests.check_follower_position, followerAboveLead)
# 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(Joints_BallNoLimitsConstrained)
@@ -0,0 +1,137 @@
"""
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 : 18243589
# Test Case Title : Check that ball joint allows soft limit constraints
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower in X, Y and Z directions", "Follower did not move in X, Y, and Z directions")
check_follower_above_joint = ("Follower swings above joint", "Follower did not swing above joint")
# fmt: on
def Joints_BallSoftLimitsConstrained():
"""
Summary: Check that ball joint allows soft limit constraints
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a ball joint. Starts with initial velocity of (5, 1, 0).
Expected Behavior:
Lead entity remains still.
Follower entity moves in the positive X, Y and Z directions.
Follower entity's Z position exceeds its original Z position + 2.5, above the position where the joint is located.
Because the cone limit is 45 degrees, if the follower manages to swing above the joint position, it is evident that the limit is soft.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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 math
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_BallSoftLimitsConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
followerMovedAboveJoint = False
#calculate the start vector from follower and lead positions
normalizedStartPos = JointsHelper.getRelativeVector(lead.position, follower.position)
normalizedStartPos = normalizedStartPos.GetNormalizedSafe()
#the targeted angle to reach between the initial vector and the current follower-lead vector
TARGET_ANGLE_DEG = 45
targetAngle = math.radians(TARGET_ANGLE_DEG)
angleAchieved = 0.0
def checkAngleMet():
#calculate the current follower-lead vector
normalVec = JointsHelper.getRelativeVector(lead.position, follower.position)
normalVec = normalVec.GetNormalizedSafe()
#dot product + acos to get the angle
angleAchieved = math.acos(normalizedStartPos.Dot(normalVec))
#is it above target?
return angleAchieved > targetAngle
MAX_WAIT_TIME = 2.0 #seconds
followerMovedAboveJoint = helper.wait_for_condition(checkAngleMet, MAX_WAIT_TIME)
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position:")
Report.info_vector3(follower.position, "follower position:")
angleAchievedDeg = math.degrees(angleAchieved)
Report.info(f"Angle achieved {angleAchievedDeg:.2f} Target {TARGET_ANGLE_DEG:.2f}")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerPositionDelta = follower.position.Subtract(followerInitialPosition)
followerMovedinXYZ = JointsHelper.vector3LargerThanScalar(followerPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_follower_position, followerMovedinXYZ)
Report.critical_result(Tests.check_follower_above_joint, followerMovedAboveJoint)
# 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(Joints_BallSoftLimitsConstrained)
@@ -0,0 +1,98 @@
"""
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 : C18243580
# Test Case Title : Check that fixed joint constrains 2 bodies
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead moved in X direction", "Lead did not move in X direction")
check_follower_position = ("Follower moved in X direction", "Follower did not move in X direction")
# fmt: on
def Joints_Fixed2BodiesConstrained():
"""
Summary: Check that fixed joint constrains 2 bodies
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with fixed joint. Starts with initial velocity of (5, 0, 0) in positive X direction.
Expected Behavior: The follower entity moves in the positive X direction and the lead entity is dragged along towards the positive X direction.
The x position of the lead entity is incremented from its original.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead entity and follower entity moved in positive X direction.
6) Exit Game Mode
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
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 JointsHelper import JointEntity
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_Fixed2BodiesConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position.x
followerInitialPosition = follower.position.x
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead entity and follower entity moved in positive X direction.
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
Report.critical_result(Tests.check_lead_position, lead.position.x > leadInitialPosition)
Report.critical_result(Tests.check_follower_position, follower.position.x > followerInitialPosition)
# 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(Joints_Fixed2BodiesConstrained)
@@ -0,0 +1,97 @@
"""
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 : C18243581
# Test Case Title : Check that fixed joint is breakable
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead did not move in X direction","Lead moved in X direction")
check_follower_position = ("Follower moved in X direction", "Follower did not move in X direction")
# fmt: on
def Joints_FixedBreakable():
"""
Summary: Check that fixed joint is breakable
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with fixed joint. Starts with initial velocity of (5, 0, 0) in positive X direction.
Expected Behavior: The follower entity moves in the positive X direction but the lead entity does not move in the positive X direction by more than a distance of 0.5.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper import JointEntity
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_FixedBreakable")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position.x
followerInitialPosition = follower.position.x
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
Report.critical_result(Tests.check_lead_position, (lead.position.x - leadInitialPosition) < 0.5)
Report.critical_result(Tests.check_follower_position, (follower.position.x - followerInitialPosition) > 0.5)
# 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(Joints_FixedBreakable)
@@ -0,0 +1,92 @@
"""
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 : C18243582
# Test Case Title : Check that fixed joint allows lead-follower collision
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_collision_happened = ("Lead and follower collided", "Lead and follower did not collide")
# fmt: on
def Joints_FixedLeadFollowerCollide():
"""
Summary: Check that fixed joint allows lead-follower collision
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with fixed joint. Starts with initial velocity of (5, 0, 0) in positive X direction.
Expected Behavior:
The follower entity moves in the positive X direction and the lead entity is dragged along towards the positive X direction.
The x position of the lead entity is incremented from its original.
The lead and follower entities are kept apart at a distance of approximately 1.0 due to collision.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected.
6) Exit Game Mode
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
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 JointsHelper import JointEntityCollisionAware
# Helper Entity class - self.collided flag is set when instance receives collision event.
class Entity(JointEntityCollisionAware):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_FixedLeadFollowerCollide")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
# 4) Wait for several seconds
general.idle_wait(2.0) # wait for lead and follower to move
# 5) Check to see if lead entity and follower collided
Report.critical_result(Tests.check_collision_happened, lead.collided and follower.collided)
# 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(Joints_FixedLeadFollowerCollide)
@@ -0,0 +1,120 @@
"""
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 : C18243593
# Test Case Title : Check that fixed/hinge/ball joints allow constraints to global frame
# 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")
follower_fixed_found = ("Found follower for fixed joint", "Did not find follower for fixed joint")
follower_hinge_found = ("Found follower for hinge joint", "Did not find follower for hinge joint")
follower_ball_found = ("Found follower for ball joint", "Did not find follower for ball joint")
check_fixed_follower_position = ("Fixed joint follower remained still", "Fixed joint follower did not remain still")
check_hinge_follower_position = ("Hinge joint follower moved in X and Z directions only", "Hinge joint follower did not move in X and Z directions, or moved in Y direction")
check_ball_follower_position = ("Ball joint follower moved in X, Y and Z directions", "Ball joint follower did not move in X, Y and Z directions")
# fmt: on
def Joints_GlobalFrameConstrained():
"""
Summary: Check that fixed/hinge/ball joints allow constraints to global frame
Level Description:
follower_fixed - Constrained to fixed joint at global frame placed above the entity.
follower_hinge - Constrained to hinge joint at global frame placed above the entity.
follower_ball - Constrained to ball joint at global frame placed above the entity.
Expected Behavior:
The follower-fixed entity should remain still.
The follower_hinge and follower_ball entities move in the positive X and Z directions.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.1
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_GlobalFrameConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
followerFixed = Entity("follower_fixed")
followerHinge = Entity("follower_hinge")
followerBall = Entity("follower_ball")
Report.info_vector3(followerFixed.position, "follower_fixed initial position:")
Report.info_vector3(followerHinge.position, "follower_hinge initial position:")
Report.info_vector3(followerBall.position, "follower_ball initial position:")
followerFixedInitialPosition = followerFixed.position
followerHingeInitialPosition = followerHinge.position
followerBallInitialPosition = followerBall.position
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(followerFixed.position, "follower_fixed initial position after 1 second:")
Report.info_vector3(followerHinge.position, "follower_hinge initial position after 1 second:")
Report.info_vector3(followerBall.position, "follower_ball initial position after 1 second:")
followerFixedPositionDelta = followerFixed.position.Subtract(followerFixedInitialPosition)
fixedFollowerRemainedStill = JointsHelper.vector3SmallerThanScalar(followerFixedPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_fixed_follower_position, fixedFollowerRemainedStill)
hingeFollowerMovedInXAndZOnly = ((followerHinge.position.x - followerHingeInitialPosition.x) > FLOAT_EPSILON and
(followerHinge.position.y - followerHingeInitialPosition.y) < FLOAT_EPSILON and
(followerHinge.position.z - followerHingeInitialPosition.z) > FLOAT_EPSILON)
Report.critical_result(Tests.check_hinge_follower_position, hingeFollowerMovedInXAndZOnly)
followerBallPositinDelta = followerBall.position.Subtract(followerBallInitialPosition)
ballFollowerMovedinXYZ = JointsHelper.vector3LargerThanScalar(followerBallPositinDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_ball_follower_position, ballFollowerMovedinXYZ)
# 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(Joints_GlobalFrameConstrained)
@@ -0,0 +1,115 @@
"""
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 : C18243583
# Test Case Title : Check that hinge joint constrains 2 bodies about X-axis
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved in X and Z directions only", "Follower did not move in X and Z directions, or moved in Y direction")
check_follower_below_lead = ("Follower remains below lead", "Follower moved above lead")
# fmt: on
def Joints_Hinge2BodiesConstrained():
"""
Summary: Check that hinge joint constrains 2 bodies about X-axis
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with hinge joint. Starts with initial velocity of (5, 1, 0).
Expected Behavior:
The follower entity moved in the positive X and Z directions, but not in the Y direction.
The position of the lead entity does not change much.
The follower entity did not move above the lead entity.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.1
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_Hinge2BodiesConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerMovedInXAndZOnly = ((follower.position.x - followerInitialPosition.x) > FLOAT_EPSILON and
(follower.position.y - followerInitialPosition.y) < FLOAT_EPSILON and
(follower.position.z - followerInitialPosition.z) > FLOAT_EPSILON)
Report.critical_result(Tests.check_follower_position, followerMovedInXAndZOnly)
followerBelowLead = follower.position.z < lead.position.z
Report.critical_result(Tests.check_follower_below_lead, followerBelowLead)
# 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(Joints_Hinge2BodiesConstrained)
@@ -0,0 +1,109 @@
"""
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 : C18243587
# Test Case Title : Check that hinge joint is breakable
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved in X direction only", "Follower did not just move in X direction, but also in Z direction")
# fmt: on
def Joints_HingeBreakable():
"""
Summary: Check that hinge joint is breakable
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with hinge joint. Starts with initial velocity of (5, 1, 0).
Expected Behavior:
Lead entity remains still.
Follower entity moves in the positive X direction, and not much in the Z direction since the joint broke.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_HingeBreakable")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(1.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerMovedInXOnly = ((follower.position.x - followerInitialPosition.x) > FLOAT_EPSILON and
(follower.position.z - followerInitialPosition.z) < FLOAT_EPSILON)
Report.critical_result(Tests.check_follower_position, followerMovedInXOnly)
# 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(Joints_HingeBreakable)
@@ -0,0 +1,91 @@
"""
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 : C18243586
# Test Case Title : Check that hinge joint allows lead-follower collision
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_collision_happened = ("Lead and follower collided", "Lead and follower did not collide")
# fmt: on
def Joints_HingeLeadFollowerCollide():
"""
Summary: Check that hinge joint allows lead-follower collision
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a hinge joint. Starts with initial velocity of (5, 1, 0).
Expected Behavior:
Lead entity remains still.
Follower entity swings up, collides with the lead entity, and falls back down.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected (they collided)
6) Exit Game Mode
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
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 JointsHelper import JointEntityCollisionAware
# Helper Entity class - self.collided flag is set when instance receives collision event.
class Entity(JointEntityCollisionAware):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_HingeLeadFollowerCollide")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
# 4) Wait for several seconds
general.idle_wait(2.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.critical_result(Tests.check_collision_happened, lead.collided and follower.collided)
# 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(Joints_HingeLeadFollowerCollide)
@@ -0,0 +1,111 @@
"""
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 : C18243585
# Test Case Title : Check that hinge joint allows no limit constraints on 2 bodies
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved higher than lead, and swinged over it", "Follower did not move higher than lead, or swing over it")
# fmt: on
def Joints_HingeNoLimitsConstrained():
"""
Summary: Check that hinge joint allows no limit constraints on 2 bodies
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a hinge joint. Starts with initial velocity of (5, 1, 0).
forceRegion - Contains suction and damping force to hold follower position when it enters the region that will pass the test.
Expected Behavior:
Lead entity remains still.
Follower entity's Z position exceeds lead entity's Z position and swings past above the lead entity.
The hinge joint constraint is not limited, the follower can swing to a position higher than the lead, and over it
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_HingeNoLimitsConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
followerInitialPosition = follower.position
# 4) Wait for several seconds
general.idle_wait(4.0) # wait for lead and follower to move
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after 1 second:")
Report.info_vector3(follower.position, "follower position after 1 second:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
followerSwingedOverLead = (follower.position.x < leadInitialPosition.x and
follower.position.z > leadInitialPosition.z)
Report.critical_result(Tests.check_follower_position, followerSwingedOverLead)
# 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(Joints_HingeNoLimitsConstrained)
@@ -0,0 +1,144 @@
"""
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 : C18243584
# Test Case Title : Check that hinge joint allows soft limit constraints on 2 bodies
# 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")
lead_found = ("Found lead", "Did not find lead")
follower_found = ("Found follower", "Did not find follower")
check_lead_position = ("Lead stays still", "Lead moved")
check_follower_position = ("Follower moved higher than lead, but does not swing over it", "Follower did not move higher than lead, or swinged over it")
# fmt: on
def Joints_HingeSoftLimitsConstrained():
"""
Summary: Check that hinge joint allows soft limit constraints on 2 bodies
Level Description:
lead - Starts above follower entity
follower - Starts below lead entity. Constrained to lead entity with a hinge joint. Starts with initial velocity of (5, 1, 0).
forceRegion - This force region has a suction (negative point) force that will hold the follower if it reaches the position that will pass the test.
Expected Behavior:
Lead entity remains still.
Follower moved higher than lead, but does not swing over it.
Since the 45 degree limit is soft, the follower can swing to a position higher than the lead, but will not swing over it.
Test Steps:
1) Open Level
2) Enter Game Mode
3) Create and Validate Entities
4) Wait for several seconds
5) Check to see if lead and follower behaved as expected
6) Exit Game Mode
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 math
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 JointsHelper
from JointsHelper import JointEntity
# Constants
FLOAT_EPSILON = 0.2
# Helper Entity class
class Entity(JointEntity):
def criticalEntityFound(self): # Override function to use local Test dictionary
Report.critical_result(Tests.__dict__[self.name + "_found"], self.id.isValid())
# Main Script
helper.init_idle()
# 1) Open Level
helper.open_level("Physics", "Joints_HingeSoftLimitsConstrained")
# 2) Enter Game Mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Create and Validate Entities
lead = Entity("lead")
follower = Entity("follower")
Report.info_vector3(lead.position, "lead initial position:")
Report.info_vector3(follower.position, "follower initial position:")
leadInitialPosition = lead.position
# 4) Wait for the follower to move above the lead or Timeout
normalizedStartPos = JointsHelper.getRelativeVector(lead.position, follower.position)
normalizedStartPos = normalizedStartPos.GetNormalizedSafe()
class WaitCondition:
TARGET_ANGLE = math.radians(45)
TARGET_MAX_ANGLE = math.radians(180)
angleAchieved = 0.0
followerMovedAbove45Deg = False #this is expected to be true to pass the test
followerMovedAbove180Deg = True #this is expected to be false to pass the test
def checkConditionMet(self):
#calculate the current follower-lead vector
normalVec = JointsHelper.getRelativeVector(lead.position, follower.position)
normalVec = normalVec.GetNormalizedSafe()
#dot product + acos to get the angle
currentAngle = math.acos(normalizedStartPos.Dot(normalVec))
#if the angle is now less then last time, it is no longer rising, so end the test.
if currentAngle < self.angleAchieved:
return True
self.angleAchieved = currentAngle
self.followerMovedAbove45Deg = currentAngle > self.TARGET_ANGLE
self.followerMovedAbove180Deg = currentAngle > self.TARGET_MAX_ANGLE
return False
def isFollowerPositionCorrect(self):
return self.followerMovedAbove45Deg and not self.followerMovedAbove180Deg
waitCondition = WaitCondition()
MAX_WAIT_TIME = 5.0 #seconds
conditionMet = helper.wait_for_condition(lambda: waitCondition.checkConditionMet(), MAX_WAIT_TIME)
# 5) Check to see if lead and follower behaved as expected
Report.info_vector3(lead.position, "lead position after test:")
Report.info_vector3(follower.position, "follower position after test:")
leadPositionDelta = lead.position.Subtract(leadInitialPosition)
leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
Report.critical_result(Tests.check_lead_position, leadRemainedStill)
Report.critical_result(Tests.check_follower_position, conditionMet and waitCondition.isFollowerPositionCorrect())
# 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(Joints_HingeSoftLimitsConstrained)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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]
@@ -0,0 +1,92 @@
"""
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 : C17411467
Test Case Title : Check that Physx Ragdoll component can be added without errors/warnings
"""
# fmt: off
class Tests():
create_test_entity = ("Entity created successfully", "Failed to create Entity")
add_actor_component = ("Actor component added", "Failed to add Actor component")
add_animgraph = ("AnimGraph component added", "Failed to add AnimGraph component")
add_physx_ragdoll = ("PhysX Ragdoll added", "Failed to add PhysX Ragdoll")
no_warnings_errors = ("Tracer found no errors or warnings", "Tracer found errors or warnings")
# fmt: on
def Ragdoll_AddPhysxRagdollComponentWorks():
"""
Summary:
Load level with Entity having Actor, AnimGraph and PhysX Ragdoll components.
Verify that editor remains stable.
Expected Behavior:
Physx Ragdoll component can be added without any errors.
Test Steps:
1) Load the level
2) Create test entity
3) Add Actor and AnimGraph components
4) Start the Tracer to catch any warnings while adding the PhysX Ragdoll component
5) Add PhysX Ragdoll component
6) Verify there are no errors/warnings in the entity outliner
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
"""
from editor_python_test_tools.utils import Report
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "Base")
# 2) Create test entity
test_entity = EditorEntity.create_editor_entity("TestEntity")
Report.result(Tests.create_test_entity, test_entity.id.IsValid())
# 3) Add Actor and AnimGraph components
test_entity.add_component("Actor")
Report.result(Tests.add_actor_component, test_entity.has_component("Actor"))
test_entity.add_component("Anim Graph")
Report.result(Tests.add_animgraph, test_entity.has_component("Anim Graph"))
# 4) Start the Tracer to catch any errors while adding the PhysX Ragdoll component
with Tracer() as section_tracer:
# 5) Add the PhysX Ragdoll component
ragdoll_component = test_entity.add_component("PhysX Ragdoll")
success_check = (
ragdoll_component.id.get_entity_id() == test_entity.id
and ragdoll_component.get_component_name() == "PhysX Ragdoll"
)
# Using this alternate way to check if PhysX Ragdoll is added to entity since there is an issue with the
# usual method in case of this component. Returned False for test_entity.has_component("PhysX Ragdoll")
Report.result(Tests.add_physx_ragdoll, success_check)
# 6) Verify there are no errors in the entity outliner
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
Report.result(Tests.no_warnings_errors, success_condition)
if not success_condition:
if section_tracer.has_warnings:
Report.info(f"Warnings found: {section_tracer.warnings}")
if section_tracer.has_errors:
Report.info(f"Errors found: {section_tracer.errors}")
Report.failure(Tests.no_warnings_found)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Ragdoll_AddPhysxRagdollComponentWorks)

Some files were not shown because too many files have changed in this diff Show More