Rename to final folder name

This commit is contained in:
AMZN-AlexOteiza
2021-09-10 14:29:17 +02:00
parent ddd662f6b9
commit 5441ab4bac
160 changed files with 0 additions and 0 deletions
@@ -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 : C4976194
# Test Case Title : Verify that you can add PhysX Rigid Bodies Physics component to an Entity without any warning or Error.
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_cube = ("Entity Cube found", "Cube not found")
linear_damp = ("Cube Linear Damping equal", "Cube Linear Damping not equal")
angular_damp = ("Cube Angular Damping equal", "Cube Angular Damping not equal")
mass = ("Cube Mass equal", "Cube Mass not equal")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_AddRigidBodyComponent():
"""
Summary:
Open a Project that already has a PhysxRigidBody component in it and verify PhysxRigidBody is working in Game Mode
Level Description:
PhysxRigidBody (entity) - PhysxRigidBody entity is created in the level
Expected Behavior:
The PhysxRigidBody entity should be working in the game mode
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entity
4) Validate Linear damping, Angular Damping and Mass of PhysxRigidBody
5) Set a new values for Linear damping, Angular Damping and Mass of PhysxRigidBody and validate it
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, 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
DEFAULT_LINEAR_DAMPING = 0.05
DEFAULT_ANGULAR_DAMPING = 0.15
DEFAULT_MASS = 1.0
NEW_LINEAR_DAMPING = 1.05
NEW_ANGULAR_DAMPING = 1.15
NEW_MASS = 2.0
CLOSE_ENOUGH = 0.001
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_AddRigidBodyComponent")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate Entity
cube_id = general.find_game_entity("CubeRigidBody")
Report.critical_result(Tests.find_cube, cube_id.IsValid())
# 4) Validate Linear damping, Angular Damping and Mass of PhysxRigidBody
cube_linear_damping = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearDamping", cube_id)
Report.result(Tests.linear_damp, abs(cube_linear_damping - DEFAULT_LINEAR_DAMPING) < CLOSE_ENOUGH)
cube_angular_damping = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetAngularDamping", cube_id)
Report.result(Tests.angular_damp, abs(cube_angular_damping - DEFAULT_ANGULAR_DAMPING) < CLOSE_ENOUGH)
cube_mass = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", cube_id)
Report.result(Tests.mass, abs(cube_mass - DEFAULT_MASS) < CLOSE_ENOUGH)
# 5) Set a new values for Linear damping, Angular Damping and Mass of PhysxRigidBody and validate it
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", cube_id, NEW_LINEAR_DAMPING)
cube_linear_damping = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearDamping", cube_id)
Report.result(Tests.linear_damp, abs(cube_linear_damping - NEW_LINEAR_DAMPING) < CLOSE_ENOUGH)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetAngularDamping", cube_id, NEW_ANGULAR_DAMPING)
cube_angular_damping = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetAngularDamping", cube_id)
Report.result(Tests.angular_damp, abs(cube_angular_damping - NEW_ANGULAR_DAMPING) < CLOSE_ENOUGH)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetMass", cube_id, NEW_MASS)
cube_mass = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", cube_id)
Report.result(Tests.mass, abs(cube_mass - NEW_MASS) < CLOSE_ENOUGH)
# 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(RigidBody_AddRigidBodyComponent)
@@ -0,0 +1,287 @@
"""
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 : C4976200
# Test Case Title : Verify that with higher angular damping, the object in rotation comes to rest faster
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
low_find_bar = ("Find bar low", "Failed to find bar low")
medium_find_bar = ("Find bar medium", "Failed to find bar medium")
high_find_bar = ("Find bar high", "Failed to find bar high")
low_no_translation_change = ("The low bar had no tanslation change", "The low bar did have a translation change")
medium_no_translation_change = ("The medium bar had no tanslation change", "The medium bar did have a translation change")
high_no_translation_change = ("The high bar had no tanslation change", "The high bar did have a translation change")
low_trigger_a = ("LowBarTriggerA triggered by the low bar", "LowBarTriggerA not triggered by the low bar")
low_trigger_b = ("LowBarTriggerB not triggered by the low bar", "LowBarTriggerB triggered by the low bar")
medium_trigger_a = ("MediumTriggerA triggered by the medium bar", "MediumTriggerA not triggered by the medium bar")
medium_trigger_b = ("MediumTriggerB not triggered by the medium bar", "MediumTriggerB triggered by the medium bar")
high_trigger_a = ("HighTriggerA not triggered by the high bar", "HighTriggerA triggered by the high bar")
high_trigger_b = ("HighTriggerB not triggered by the high bar", "HighTriggerB triggered by the high bar")
low_rotation = ("The low bar rotated only on the x axis", "The low bar did not rotate only on the x axis")
medium_rotation = ("The medium bar rotated only on the x axis", "The medium bar did not only rotate on the x axis")
high_rotation = ("The high bar did not rotate", "The high bar did rotate")
comparative_rotation = ("The rotation decreased with increased angular damping", "The rotation din not decrease with increased angular damping")
timeout = ("All spheres rotation IsClose to 0 before timeout", "All spheres rotation are not IsClose to 0 before timeout")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_AngularDampingAffectsRotation():
"""
The level consists of a PhysX Terrain component that is not interacted with (per the test case)
3 PhysX colliders with shape box, PhysX rigid bodies physics, and mesh with shape box.
In the test we use the term bar - this means a box shape changed to be a rectangle, think thin plank.
The bar shape is chosen for debugging, it is easy to see rotation.
LowBar has 2 associated triggers LowBarTriggerA and LowBarTriggerB
- LowBarTriggerA should be tripped to measure the rotation of the bar
- LowBarTriggerB should not be tripped to measure that the rotation does not start increasing unexpectedly
MediumBar has 2 associated triggers
- MediumBarTriggerA should be tripped to measure the rotation of the bar
- MediumBarTriggerB should not be tripped to measure that the rotation does not start increasing unexpectedly
HighBar has 2 associated triggers
- HighBarTriggerA is above the bar and should not be tripped
- HighBarTriggerB is below the bar and should not be tripped
1) Open level
2) Enter game mode
3) Find bars, set bars attributes, and measure initial position
4) Awaken bars
5) Measure
6) Report results
"""
# Setup path
import os
import sys
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 AngleHelper
import azlmbr.legacy.general as general
import azlmbr.bus
import azlmbr.math as lymath
class Bar:
def __init__(
self, name, initial_angular_velocity, angular_damping, triggers, find_bar_test, no_translation_change_test
):
self.name = name
self.initial_angular_velocity = initial_angular_velocity
self.angular_damping = angular_damping
self.entity = None
self.initial_position = None
self.final_position = None
self.initial_rotation = None
self.final_rotation = None
self.timeout = False
self.triggers = triggers
self.find_bar_test = find_bar_test
self.no_translation_change_test = no_translation_change_test
def find_trigger(self, name):
return next(t for t in self.triggers if t.name == name)
def __str__(self):
return """
name = {}
angular_damping = {}
timeout = {}
""".format(
self.name, self.angular_damping, self.timeout
)
def report(self):
Report.info(self)
Report.info(
"Initial angular velocity {} for {}".format(self.initial_angular_velocity.GetLength(), self.name)
)
Report.info_vector3(self.initial_position, "Initial position {}".format(self.name))
if self.final_position:
Report.info_vector3(self.final_position, "Final position {}".format(self.name))
Report.info_vector3(self.initial_rotation, "Initial rotation {}".format(self.name))
if self.final_rotation:
Report.info_vector3(self.final_rotation, "Final rotation {}".format(self.name))
for trigger in self.triggers:
Report.info(trigger)
class Trigger:
def __init__(self, name):
self.name = name
self.entity = None
self.handler = None
self.triggering_entity = None
self.triggered = False
def on_trigger(self, args):
self.triggered = True
self.triggering_entity = args[0]
Report.info("{} was triggered by {}".format(self.name, self.triggering_entity_name()))
def __str__(self):
return """
name = {}
triggering entity name = {}
triggered = {}
""".format(
self.name, self.triggering_entity_name(), self.triggered
)
def triggering_entity_name(self):
if self.triggering_entity:
return azlmbr.entity.GameEntityContextRequestBus(
azlmbr.bus.Broadcast, "GetEntityName", self.triggering_entity
)
return None
INITIAL_ANGULAR_VELOCITY = lymath.Vector3(5.0, 0.0, 0.0)
ZERO_ANGULAR_VELOCITY = lymath.Vector3(0.0, 0.0, 0.0)
TOLERANCE = 0.01
TIMEOUT = 2.0
LOW_TRIGGER_A = "LowBarTriggerA"
LOW_TRIGGER_B = "LowBarTriggerB"
MEDIUM_TRIGGER_A = "MediumBarTriggerA"
MEDIUM_TRIGGER_B = "MediumBarTriggerB"
HIGH_TRIGGER_A = "HighBarTriggerA"
HIGH_TRIGGER_B = "HighBarTriggerB"
# bars
# fmt: off
low_damping = Bar(
"LowBar",
INITIAL_ANGULAR_VELOCITY,
5.0,
[Trigger(LOW_TRIGGER_A), Trigger(LOW_TRIGGER_B)],
Tests.low_find_bar,
Tests.low_no_translation_change,
)
medium_damping = Bar(
"MediumBar",
INITIAL_ANGULAR_VELOCITY,
10.0,
[Trigger(MEDIUM_TRIGGER_A), Trigger(MEDIUM_TRIGGER_B)],
Tests.medium_find_bar,
Tests.medium_no_translation_change,
)
high_damping = Bar(
"HighBar",
INITIAL_ANGULAR_VELOCITY,
100.0,
[Trigger(HIGH_TRIGGER_A), Trigger(HIGH_TRIGGER_B)],
Tests.high_find_bar,
Tests.high_no_translation_change,
)
# fmt: on
bars = [low_damping, medium_damping, high_damping]
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_AngularDampingAffectsRotation")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Find bars, set bars attributes, and measure initial position
for bar in bars:
bar.entity = general.find_game_entity(bar.name)
Report.critical_result(bar.find_bar_test, bar.entity.IsValid())
azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "SetAngularVelocity", bar.entity, bar.initial_angular_velocity
)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetAngularDamping", bar.entity, bar.angular_damping)
general.idle_wait_frames(1) # wait one frame for changes to apply
bar.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", bar.entity)
bar.initial_rotation = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", bar.entity)
# add handler for each trigger
for trigger in bar.triggers:
trigger.entity = general.find_game_entity(trigger.name)
trigger.handler = azlmbr.bus.NotificationHandler("TriggerNotificationBus")
trigger.handler.connect(trigger.entity)
trigger.handler.add_callback("OnTriggerEnter", trigger.on_trigger)
def all_bars_stopped_rotating():
bar_results = [False for bar in bars]
for i, bar in enumerate(bars):
bar_angular_velocity = azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "GetAngularVelocity", bar.entity
)
if bar_angular_velocity.IsClose(ZERO_ANGULAR_VELOCITY, TOLERANCE):
bar.final_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", bar.entity)
bar.final_rotation = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", bar.entity)
bar_results[i] = True
return all(bar_results)
# 4) Awaken bars
for bar in bars:
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "ForceAwake", bar.entity)
# 5) Measure
if not helper.wait_for_condition(all_bars_stopped_rotating, TIMEOUT):
bar.timeout = True
# 6) Report results
no_timeout = True
for bar in bars:
if bar.timeout:
Report.info(
"Timeout occurred. bar with damping = {} and initial angular velocity = {} did not stop rotating in the timeout of {}".format(
bar.angular_damping, bar.initial_angular_velocity, TIMEOUT
)
)
no_timeout = False
# fast fail if timeout occurred, comparisons will be meaningless and all info is in log to determine which bar(s) timed out
Report.critical_result(Tests.timeout, no_timeout)
# no translation
for bar in bars:
Report.result(bar.no_translation_change_test, bar.initial_position.IsClose(bar.final_position, TOLERANCE))
# rotation (comparisons are safe because our test setup and triggers ensure the bar rotates < 360 degrees)
# if a bar rotates to exactly the expected position + n(360), the triggers will fail the test
outcome = (
low_damping.final_rotation.x > low_damping.initial_rotation.x
and AngleHelper.is_angle_close_deg(low_damping.initial_rotation.y, low_damping.final_rotation.y, TOLERANCE)
and AngleHelper.is_angle_close_deg(low_damping.initial_rotation.z, low_damping.final_rotation.z, TOLERANCE)
)
Report.result(Tests.low_rotation, outcome)
outcome = (
medium_damping.final_rotation.x > medium_damping.initial_rotation.x
and AngleHelper.is_angle_close_deg(
medium_damping.initial_rotation.y, medium_damping.final_rotation.y, TOLERANCE
)
and AngleHelper.is_angle_close_deg(
medium_damping.initial_rotation.z, medium_damping.final_rotation.z, TOLERANCE
)
)
Report.result(Tests.medium_rotation, outcome)
# we do not need worry about is_angle_close here because we expect this to not change at all
# if it rotates 360, the triggers will fail the test
Report.result(Tests.high_rotation, high_damping.initial_rotation.IsClose(high_damping.final_rotation, TOLERANCE))
# comparative rotation (comparisons are safe because our test setup and triggers ensure the bar rotates < 360 degrees)
# if the bars rotate to exactly the expected position + n(360), the triggers will fail the test
Report.result(
Tests.comparative_rotation,
high_damping.final_rotation.x < medium_damping.final_rotation.x < low_damping.final_rotation.x,
)
# triggers (quantitative measure of rotation)
Report.result(Tests.low_trigger_a, low_damping.find_trigger(LOW_TRIGGER_A).triggered)
Report.result(Tests.low_trigger_b, not low_damping.find_trigger(LOW_TRIGGER_B).triggered)
Report.result(Tests.medium_trigger_a, medium_damping.find_trigger(MEDIUM_TRIGGER_A).triggered)
Report.result(Tests.medium_trigger_b, not medium_damping.find_trigger(MEDIUM_TRIGGER_B).triggered)
Report.result(Tests.high_trigger_a, not high_damping.find_trigger(HIGH_TRIGGER_A).triggered)
Report.result(Tests.high_trigger_b, not high_damping.find_trigger(HIGH_TRIGGER_B).triggered)
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(RigidBody_AngularDampingAffectsRotation)
@@ -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 : C4976209
# Test Case Title : Verify that when Compute COM is enabled, the PhysX system computes the COM of the object on its own
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_enabled_sphere = ("Enabled Sphere Found", "Enabled Sphere not found")
find_enabled_capsule = ("Enabled Capsule Found", "Enabled Capsule not found")
find_enabled_box = ("Enabled Box Found", "Enabled Box not found")
find_enabled_physics_asset = ("Enabled Physics Asset Found", "Enabled Physics Asset not found")
find_disabled_sphere = ("Disabled Sphere Found", "Disabled Sphere not found")
find_disabled_capsule = ("Disabled Capsule Found", "Disabled Capsule not found")
find_disabled_box = ("Disabled Box Found", "Disabled Box not found")
find_disabled_physics_asset = ("Disabled Physics Asset Found", "Disabled Physics Asset not found")
enabled_sphere_COM_expected = ("Enabled Sphere COM was close to expected value", "Enabled Sphere COM was not close to expected value")
enabled_capsule_COM_expected = ("Enabled Capsule COM was close to expected value", "Enabled Capsule COM was not close to expected value")
enabled_box_COM_expected = ("Enabled Box COM was close to expected value", "Enabled Box COM was not close to expected value")
enabled_physics_asset_COM_expected = ("Enabled Physics Asset COM was close to expected value", "Enabled Physics Asset COM was not close to expected value")
disabled_sphere_COM_expected = ("Disabled Sphere COM was close to expected value", "Disabled Sphere COM was not close to expected value")
disabled_capsule_COM_expected = ("Disabled Capsule COM was close to expected value", "Disabled Capsule COM was not close to expected value")
disabled_box_COM_expected = ("Disabled Box COM was close to expected value", "Disabled Box not COM was not close to expected value")
disabled_physics_asset_COM_expected = ("Disabled Physics Asset COM was close to expected value", "Disabled Physics Asset not COM was close to expected value")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_COM_ComputingWorks():
# type: () -> None
"""
Summary:
Runs an automated test to ensure when compute COM is enabled, that the COM is automatically calculated.
Level Description:
There are 4 sets of entities:
Spheres (entity: SphereEnabled), (entity: SphereDisabled)
Boxes (entity: BoxEnabled), (entity: BoxDisabled)
Capsules (entity: CapsuleEnabled), (entity: CapsuleDisabled)
Physics Assets with sedan mesh (entity: PhysicsAssetEnabled), (entity: PhysicsAssetDisabled)
Each set has two entities, both of which are identical to one another - save for the Compute COM setting on their
rigid body. One has it set to enabled, and the other has it set to disabled.
Each entity has a rigid body and two PhysX colliders. The first collider has no offset. The second collider is
positioned +2 units in the X direction.
Expected Behavior:
The entities with 'Compute COM' disabled will have local COM offsets at the objects origin ((0.0, 0.0, 0.0) Locally)
The entities with 'Compute COM' enabled will have local COM offsets at the average position between the two
colliders.
For every entity that isn't a physics asset, that's +1 unit to the right (1/2 the offset value of the second
collider). The physics asset has a mesh whose origin is not at the natural center of mass of the object,
so we check its COM offset as a special case.
Test Steps:
1) Open level
2) Enter game mode
3) Validate entities
4) Check center of mass for each entity
5) Special Case: Check center of mass for each physics asset
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)
"""
# Setup path
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
CONTROL_COM = lymath.Vector3(0.0, 0.0, 0.0)
EXPECTED_COM = lymath.Vector3(1.0, 0.0, 0.0)
PHYSICS_ASSET_EXPECTED_COM = lymath.Vector3(1.0, -0.08, 0.43) # Derived from the sedan mesh specifically
CLOSE_THRESHOLD = 0.01
class Shape:
def __init__(self, name, valid_test, com_valid_test):
self.id = general.find_game_entity(name)
self.center_of_mass = self.get_com()
self.position = self.get_position()
self.com_local = self.center_of_mass.Subtract(self.position)
self.valid_test = valid_test
self.com_valid_test = com_valid_test
def get_com(self):
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetCenterOfMassWorld", self.id)
def get_position(self):
return azlmbr.components.TransformBus(bus.Event, "GetWorldTranslation", self.id)
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_COM_ComputingWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Validate entities
sphere_enabled = Shape("SphereEnabled", Tests.find_enabled_sphere, Tests.enabled_sphere_COM_expected)
sphere_disabled = Shape("SphereDisabled", Tests.find_disabled_sphere, Tests.disabled_sphere_COM_expected)
capsule_enabled = Shape("CapsuleEnabled", Tests.find_enabled_capsule, Tests.enabled_capsule_COM_expected)
capsule_disabled = Shape("CapsuleDisabled", Tests.find_disabled_capsule, Tests.disabled_capsule_COM_expected)
box_enabled = Shape("BoxEnabled", Tests.find_enabled_box, Tests.enabled_box_COM_expected)
box_disabled = Shape("BoxDisabled", Tests.find_disabled_box, Tests.disabled_box_COM_expected)
physics_asset_enabled = Shape(
"PhysicsAssetEnabled", Tests.find_enabled_physics_asset, Tests.enabled_physics_asset_COM_expected
)
physics_asset_disabled = Shape(
"PhysicsAssetDisabled", Tests.find_disabled_physics_asset, Tests.disabled_physics_asset_COM_expected
)
# physics asset is a special case, and thus not included
enabled_shapes = [sphere_enabled, capsule_enabled, box_enabled]
disabled_shapes = [sphere_disabled, capsule_disabled, box_disabled]
all_shapes = enabled_shapes + disabled_shapes + [physics_asset_enabled, physics_asset_disabled]
for shape in all_shapes:
Report.critical_result(shape.valid_test, shape.id.IsValid())
# 4) Check center of mass for each entity
for enabled_shape in enabled_shapes:
enabled_is_close = enabled_shape.com_local.IsClose(EXPECTED_COM, CLOSE_THRESHOLD)
Report.result(enabled_shape.com_valid_test, enabled_is_close)
for disabled_shape in disabled_shapes:
disabled_is_close = disabled_shape.com_local.IsClose(CONTROL_COM, CLOSE_THRESHOLD)
Report.result(disabled_shape.com_valid_test, disabled_is_close)
# 5) Check center of mass for each physics asset
enabled_is_close = physics_asset_enabled.com_local.IsClose(PHYSICS_ASSET_EXPECTED_COM, CLOSE_THRESHOLD)
print(f"{physics_asset_enabled.com_local.x}, {physics_asset_enabled.com_local.y}, {physics_asset_enabled.com_local.z}")
Report.result(physics_asset_enabled.com_valid_test, enabled_is_close)
disabled_is_close = physics_asset_disabled.com_local.IsClose(CONTROL_COM, CLOSE_THRESHOLD)
Report.result(physics_asset_disabled.com_valid_test, disabled_is_close)
# 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(RigidBody_COM_ComputingWorks)
@@ -0,0 +1,300 @@
"""
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 : C4976210
# Test Case Title : Verify that when Compute COM is disabled, the user gets an option to add the co-ordinates of
# the COM and the COM gets implemented at those co-ordinates.
import os
import sys
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
boxes_validated = ("All box entities were found and validated", "Couldn't find or validate at least one box")
test_objects_validated = ("All test entities were found and validated", "Not all test entities could be found and validated")
sphere_com_set = ("The Test Sphere's COM was successfully set", "The Test Sphere's COM WAS NOT successfully set")
capsule_com_set = ("The Test Capsule's COM was successfully set", "The Test Capsule's COM WAS NOT successfully set")
box_com_set = ("The Test Box's COM was successfully set", "The Test Box's COM WAS NOT successfully set")
sphere_confirm = ("The Test Sphere rolled uphill due to COM offset", "The Test Sphere rolled downhill-- COM did not work as expected")
capsule_confirm = ("The Test Capsule fell against gravity due to COM offset", "The Test Capsule fell with gravity-- COM did not work as expected")
box_confirm = ("The Test Box fell against gravity due to COM offset", "The Test Box fell with gravity-- COM did not work as expected")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_COM_ManualSettingWorks():
# type: () -> None
"""
Summary:
Tests that when an entity has "Compute COM" disabled, a user can set a custom center of mass and that
the set center of mass behaves as expected.
Level Description:
Three test objects (Box, Capsule and Sphere) are positioned on a platform that is rotated at an acute angle. Two
plates (Pass and Fail) are perpendicular to the platform so that the Fail Plate is underneath the test objects, and
the Pass Plate is above. Gravity is enabled for all test objects. Stationary objects (plates, platform... etc)
have gravity disabled and have masses set for "very large numbers" (10,000 kg) to resist the test objects'
movements. Test objects have their "compute COM" property disabled, but in the editor have a computed COM of
(0.0, 0.0, 0.0) and gravity enabled.
Expected Behavior:
When game mode starts, the script manually sets each test objects center of mass in a way where that entity
will "fall against gravity". The box and capsule should tilt against gravity into the Pass Plate, and the sphere
should "roll up hill" to the Pass Plate.
Test Steps:
0) Define data and functions
1) Open level, enter game mode
2) Find and validate entities
3) set and verify center of mass (COM)
4) Wait for results or timeout
5) Log 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
"""
# 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
import azlmbr.math as azmath
# 0) Set up data and functions used in the test
# EntityData class for storing and organizing useful test information/results
class EntityData:
def __init__(self, name):
self.name = name
self.id = None
self.init_pos = None
self.current_pos = None
self.init_rot = None
self.current_rot = None
self.result = None
self.fail_info = None
# ***** Global variables ****
# Constants
TIME_OUT = 1.5
CAPSULE_BOX_OFFSET = azmath.Vector3(3.0, 0.0, 0.0)
SPHERE_OFFSET = azmath.Vector3(0.5, 0.0, 0.0)
PASS = "pass"
FAIL = "fail"
# Test entities
test_box = EntityData("Test_Box")
test_capsule = EntityData("Test_Capsule")
test_sphere = EntityData("Test_Sphere")
test_entities = [test_box, test_capsule, test_sphere]
# Plate entities
pass_plate = EntityData("Pass_Plate")
fail_plate = EntityData("Fail_Plate")
# All non-moving entities
non_movers = [EntityData("Platform"), EntityData("Pass_Buffer"), EntityData("Fail_Buffer"), pass_plate, fail_plate]
# Full entity list
all_entities = non_movers + test_entities
# ******** Helper Functions ********
# Attempts to set COM, and manually computes expected COM to verify it was applied correctly
def set_and_validate_COM(entity, com_offset):
# type: (EntityData, Vector3) -> None
CLOSE_ENOUGH_THRESHOLD = 0.01
# Set COM offset
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetCenterOfMassOffset", entity.id, com_offset)
entity_world_com_pos = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetCenterOfMassWorld", entity.id)
# Get world rotation matrix
tm_matrix = azmath.Matrix3x3_CreateFromTransform(
azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTM", entity.id)
)
# Calculate expected world COM
world_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity.id)
calculated_com = tm_matrix.MultiplyVector3(com_offset).Add(world_pos)
# If expected COM and actual COM differ, log data collected
if not entity_world_com_pos.IsClose(calculated_com, CLOSE_ENOUGH_THRESHOLD):
Report.info("COM not applied to entity: {}".format(entity.name))
Report.info_vector3(world_pos, " Entity position:")
Report.info_vector3(com_offset, " COM offset:")
Report.info_vector3(entity_world_com_pos, " Entity world COM:")
Report.info_vector3(calculated_com, " Expected COM")
return False
return True
# ** Entity batch operation helpers **
# Attempts to validate entities' IDs and initial positions and initial rotations.
# Returns True if all entities were successfully validated.
# Print to the log if there are any problems retrieving vital information
def validate_entities(entity_list):
# type: ([EntityData]) -> int
count = 0
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 = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity.id)
entity.current_pos = entity.init_pos
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))
entity.init_rot = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", entity.id)
entity.current_rot = entity.init_rot
if entity.init_rot is None:
valid = False
Report.info("Entity: {}'s initial rotation could not be found".format(entity.name))
if valid:
count += 1
return count == len(entity_list)
# Updates entities' current position
def update_pos_and_rot(entity_list):
# type: ([EntityData]) -> None
for entity in entity_list:
entity.current_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", entity.id)
entity.current_rot = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", entity.id)
# Checks for unexpected movement in stationary objects
# Prints to the log if there is a difference between initial position and current position
def check_for_unexpected_movement(entity_list):
# type: ([EntityData]) -> None
CLOSE_ENOUGH_THRESHOLD = 0.1
for entity in entity_list:
if not entity.init_pos.IsClose(entity.current_pos, CLOSE_ENOUGH_THRESHOLD):
entity.result = FAIL
entity.fail_info = "Unexpected movement detected"
# Checks if we have enough information to end 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
if result_count == num_results:
return True
return False
# ****** single update function to pass ******
# to helper.wait_for_condition()
# Should be called every frame:
# Updates all entities positions and rotations
# Returns True if exit conditions for test are met
# see @done_collecting_results(..) for exit conditions
def is_done_updating():
# type: () -> bool
update_pos_and_rot(all_entities)
check_for_unexpected_movement(non_movers)
return done_collecting_results(test_entities, len(test_entities))
# ******** Event Handlers ********
# Fail A Box collision event handler
def on_collision_begin_fail(args):
# type: ([EntityId, ...]) -> None
collider_id = args[0]
for entity in test_entities:
if entity.id.Equal(collider_id) and entity.result is None:
entity.result = FAIL
entity.fail_info = "Collide with fail plate: COM did not apply properly"
return
# Pass Box Collision Event Handler
def on_collision_begin_pass(args):
# type: ([EntityId, ...]) -> None
collider_id = args[0]
for entity in test_entities:
if entity.id.Equal(collider_id) and entity.result is None:
Report.info("Entity: {} collided with the Pass Plate".format(entity.name))
entity.result = PASS
return
# it wasn't a test entity that collided, something went wrong
if collider_id.IsValid():
entity_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", collider_id)
Report.info("Pass Plate collided with unexpected entity: {}".format(entity_name))
# ******** Execution Code *********
# 1) Open level and start game mode
helper.init_idle()
helper.open_level("Physics", "RigidBody_COM_ManualSettingWorks")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Find and validate test entities
Report.critical_result(Tests.test_objects_validated, validate_entities(test_entities))
Report.critical_result(Tests.boxes_validated, validate_entities(non_movers))
# 3) Set and verify COM offsets
Report.critical_result(Tests.box_com_set, set_and_validate_COM(test_box, CAPSULE_BOX_OFFSET))
Report.critical_result(Tests.capsule_com_set, set_and_validate_COM(test_capsule, CAPSULE_BOX_OFFSET))
Report.critical_result(Tests.sphere_com_set, set_and_validate_COM(test_sphere, SPHERE_OFFSET))
# Assign handlers
pass_handler = azlmbr.physics.CollisionNotificationBusHandler()
pass_handler.connect(pass_plate.id)
pass_handler.add_callback("OnCollisionBegin", on_collision_begin_pass)
fail_handler = azlmbr.physics.CollisionNotificationBusHandler()
fail_handler.connect(fail_plate.id)
fail_handler.add_callback("OnCollisionBegin", on_collision_begin_fail)
# 4) wait for either time out or for the test to complete
if not helper.wait_for_condition(is_done_updating, TIME_OUT):
Report.info("The test timed out, check log for possible solutions or adjust the time out time")
# 5) Log results
# verify entities results
Report.result(Tests.box_confirm, test_box.result is not None and test_box.result == PASS)
Report.result(Tests.capsule_confirm, test_capsule.result is not None and test_capsule.result == PASS)
Report.result(Tests.sphere_confirm, test_sphere.result is not None and test_sphere.result == PASS)
# 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_vector3(entity.init_rot, " Initial rotation:")
Report.info_vector3(entity.current_rot, " Final rotation:")
Report.info(" Result: {}".format(entity.result))
if entity.fail_info is not None:
Report.info(" Fail info: {}".format(entity.fail_info))
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(RigidBody_COM_ManualSettingWorks)
@@ -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 : C13351703
# Test Case Title : Check that Center of Mass calculations should not include trigger shapes
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_entities = ("Entities are found", "Entities are not found")
com_expected = ("COM value is equal to expected value", "COM value is not equal to expected value")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_COM_NotIncludesTriggerShapes():
"""
Summary:
Check that Center of Mass calculations should not include trigger shapes.
Level Description:
RigidBody (entity) - Entity with 1 Rigid Body component and 2 PhysX Collider components
Rigid Body Component - Debug Draw Collider, Compute COM are enabled, Gravity is disabled
1st Collider - Offset(-1.0, 0.0, 0.0) - Trigger enabled
2nd Collider - Offset(1.0, 0.0, 0.0) - Trigger disabled
Expected Behavior:
We are checking if the entity is valid.
We are verifying if the center of mass is close to the collider whose trigger has been disabled.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Validate if COM is same as expected value
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 critical_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 lymath
# Constants
OFFSET = lymath.Vector3(1.0, 0.0, 0.0) # Offset of the trigger disabled sphere
CLOSE_THRESHOLD = sys.float_info.epsilon
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_COM_NotIncludesTriggerShapes")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
rigid_body_id = general.find_game_entity("RigidBody")
Report.critical_result(Tests.find_entities, rigid_body_id.IsValid())
# 4) Validate if COM is same as expected value
entity_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", rigid_body_id)
com = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetCenterOfMassWorld", rigid_body_id)
Report.result(Tests.com_expected, entity_position.Add(OFFSET).IsClose(com, CLOSE_THRESHOLD))
# 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(RigidBody_COM_NotIncludesTriggerShapes)
@@ -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 : C4976218
# Test Case Title: Verify that when compute inertia is checked, the physX engine does compute the inertia of the objects
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_upper_boxes = ("Upper Boxes found", "Upper Boxes not found")
find_lower_boxes = ("Lower Boxes found", "Lower Boxes not found")
boxes_collided = ("All Boxes Collided", "Not all Boxes Collided")
upper_box_x_did_topple = ("Upper Box X did topple", "Upper Box X did not toppled")
upper_box_y_did_topple = ("Upper Box Y did topple", "Upper Box Y did not toppled")
upper_box_z_did_topple = ("Upper Box Z did topple", "Upper Box Z did not toppled")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
# fmt: on
def RigidBody_ComputeInertiaWorks():
"""
Summary:
Level Description:
A box (entity: Upper Box) set above and askew to another box (entity: Lower Box)
Lower box and Upper box has computed inertia.
Test Steps:
--> Open level
--> Enter game mode
--> Retrieve entities
--> Check for collision between the top box and lower box for the 3 pairs (6 boxes total)
--> Check that the upper box's x, y, or z angular velocity decreases past -1.0
--> Exit game mode
--> Close editor
:return: None
"""
# Setup path
import os
import sys
import azlmbr.legacy.general as general
import azlmbr.bus as 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
class UpperBox:
def __init__(self, name):
self.name = name
self.id = general.find_game_entity(name)
self.collided_with_lower_box = False
self.handler = None
self.lower_box_list = None
def on_boxes_collision_begin(self, args):
other_id = args[0]
for boxes in self.lower_box_list:
if other_id.Equal(boxes.id):
Report.info("{} hit lower box".format(self.name))
self.collided_with_lower_box = True
def create_handler(self):
self.handler = bus.NotificationHandler("CollisionNotificationBus")
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_boxes_collision_begin)
@property
def angular_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(bus.Event, "GetAngularVelocity", self.id)
class LowerBox:
def __init__(self, name):
self.id = general.find_game_entity(name)
# Amount of time before test times out
TIME_OUT = 3.0
MINIMUM_ANGUlAR_VELOCITY = -1.0
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_ComputeInertiaWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
upper_box_x = UpperBox("Upper Box X")
upper_box_y = UpperBox("Upper Box Y")
upper_box_z = UpperBox("Upper Box Z")
lower_box_x = LowerBox("Lower Box X")
lower_box_y = LowerBox("Lower Box Y")
lower_box_z = LowerBox("Lower Box Z")
# Store boxes in two lists
upper_box_list = [upper_box_x, upper_box_y, upper_box_z]
lower_box_list = [lower_box_x, lower_box_y, lower_box_z]
upper_boxes_found = True
lower_boxes_found = True
for upper_boxes in upper_box_list:
upper_boxes.lower_box_list = lower_box_list
if upper_boxes.id is None:
upper_boxes_found = False
for lower_boxes in lower_box_list:
if lower_boxes.id is None:
lower_boxes_found = False
Report.critical_result(Tests.find_upper_boxes, upper_boxes_found)
Report.critical_result(Tests.find_lower_boxes, lower_boxes_found)
for box in upper_box_list:
box.create_handler()
def all_boxes_collided():
for boxes in upper_box_list:
if not boxes.collided_with_lower_box:
return False
return True
# 4) Wait for the boxes to collide
boxes_collided = helper.wait_for_condition(all_boxes_collided, TIME_OUT)
Report.result(Tests.boxes_collided, boxes_collided)
# 5) Check that the upper box's corresponding angular velocity is lower than minimum (higher negative number)
def validate_angular_velocities():
velocities_set = True
if upper_box_x.angular_velocity.x > MINIMUM_ANGUlAR_VELOCITY:
velocities_set = False
if upper_box_y.angular_velocity.y > MINIMUM_ANGUlAR_VELOCITY:
velocities_set = False
if upper_box_z.angular_velocity.z > MINIMUM_ANGUlAR_VELOCITY:
velocities_set = False
return velocities_set
helper.wait_for_condition(validate_angular_velocities, TIME_OUT)
# Checking to see if the X angular has a greater negative number (higher negative value) than MINIMUM
Report.info("Angular Velocity for X Box = {}".format(upper_box_x.angular_velocity.x))
Report.info("Angular Velocity for Y Box = {}".format(upper_box_y.angular_velocity.y))
Report.info("Angular Velocity for Z Box = {}".format(upper_box_z.angular_velocity.z))
Report.result(Tests.upper_box_x_did_topple, upper_box_x.angular_velocity.x < MINIMUM_ANGUlAR_VELOCITY)
Report.result(Tests.upper_box_y_did_topple, upper_box_y.angular_velocity.y < MINIMUM_ANGUlAR_VELOCITY)
Report.result(Tests.upper_box_z_did_topple, upper_box_z.angular_velocity.z < MINIMUM_ANGUlAR_VELOCITY)
# 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(RigidBody_ComputeInertiaWorks)
@@ -0,0 +1,76 @@
"""
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 : C100000
# Test Case Title : Check that Gravity works
# fmt:off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_ball = ("Entity Ball found", "Ball not found")
gravity_started_disabled = ("Gravity started disabled", "Gravity didn't start disabled")
gravity_set_enabled = ("Gravity has been enabled", "Gravity wasn't enabled")
ball_fell = ("Ball fell", "Ball didn't fall")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt:on
def RigidBody_EnablingGravityWorksPoC():
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()
helper.open_level("Physics", "EnablingGravityWorks")
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
general.idle_wait_frames(1)
ball_id = general.find_game_entity("Ball")
Report.critical_result(Tests.find_ball, ball_id.IsValid(), "Entity must be found")
# 4) Make sure gravity is off from the start
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", ball_id)
Report.critical_result(Tests.gravity_started_disabled, not gravity_enabled)
# 5) Get the Z position before enabling the physics
class Ball:
z_start = None
Ball.z_start = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
# 6) Activate gravity
Report.info("Enabling Gravity")
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "ForceAwake", ball_id)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", ball_id, True)
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", ball_id)
def ball_fell():
"""
This is an example function to use with TestHelper.wait_for_condition
It may take no parameters and it contains no wait_idle_* because that is
already handled in TestHelper.wait_for_condition
"""
z_end = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", ball_id)
return z_end < 35
# 7) Validate ball fell by ensuring z is decreasing
fell_down = helper.wait_for_condition(ball_fell, 10.0)
Report.result(Tests.ball_fell, fell_down)
helper.exit_game_mode(Tests.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(RigidBody_EnablingGravityWorksPoC)
@@ -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 : C111111
# Test Case Title : Check that Gravity works
# fmt:off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_ball = ("Entity Ball found", "Ball not found")
find_terrain = ("Entity Terrain found", "Terrain not found")
gravity_started_disabled = ("Gravity started disabled", "Gravity didn't start disabled")
gravity_set_enabled = ("Gravity has been enabled", "Gravity wasn't enabled")
ball_fell = ("Ball fell", "Ball didn't fall")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt:on
def RigidBody_EnablingGravityWorksUsingNotificationsPoC():
# Setup path
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", "EnablingGravityWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
ball_id = general.find_game_entity("Ball")
Report.result(Tests.find_ball, ball_id.IsValid())
terrain_id = general.find_game_entity("Terrain")
Report.result(Tests.find_terrain, terrain_id.IsValid())
# 4) Make sure gravity is off from the start
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", ball_id)
Report.result(Tests.gravity_started_disabled, not gravity_enabled)
# 5) Activate gravity
Report.info("Enabling Gravity")
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "ForceAwake", ball_id)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", ball_id, True)
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", ball_id)
Report.result(Tests.gravity_set_enabled, gravity_enabled)
# 6) Listen to collision events seconds so it falls down
class TouchGround:
value = False
def on_collision_begin(args):
other_id = args[0]
if other_id.Equal(terrain_id):
Report.info("Touched ground")
TouchGround.value = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(ball_id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
helper.wait_for_condition(lambda: TouchGround.value, 3.0)
Report.result(Tests.ball_fell, TouchGround.value)
# 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(RigidBody_EnablingGravityWorksUsingNotificationsPoC)
@@ -0,0 +1,179 @@
"""
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 : C4976197
# Test Case Title : Verify that when you assign an Initial Angular Velocity to an object,
# it moves with that Angular velocity when we switch to game mode
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
gravity_disabled = ("Gravity is disabled", "Gravity is not disabled")
cube_found = ("Cube was found", "Cube was not found")
trigger_found = ("Trigger was found", "Trigger was not found")
cube_pos = ("Cube position is valid", "Cube position is not valid")
trigger_pos = ("Trigger position is valid", "Trigger position is not valid")
cube_init_rotation = ("Cube rotation is valid", "Cube rotation is not valid")
cube_touched_trigger = ("Cube touched Trigger", "Cube did not touch Trigger")
cube_rotated_on_x = ("Cube rotated on X axis", "Cube did not rotate on X axis")
cube_not_rotated_on_y = ("Cube did not rotate on Y axis", "Cube rotated on Y axis")
cube_not_rotated_on_z = ("Cube did not rotate on Z axis", "Cube rotated on Z axis")
cube_velocity = ("The velocity is close to expected", "The velocity is not close to expected")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_InitialAngularVelocity():
# type: () -> None
"""
Summary:
Runs an automated test to ensure initial angular velocity of 5.0 rad/sec on X axis is exerted on a rigid body object
Level Description:
A cube positioned above terrain, with PhysX Shape Collider component with Box shape (dimensions: x=1, y=1, z=3),
and with PhysX Rigid Body component, gravity disabled, initial angular velocity: (x=5, y=0, z=0) rad/s and
angular damping set to 0.0
A trigger with PhysX Shape Collider component with Box shape (dimensions: x=1, y=1, z=1), trigger enabled,
positioned above terrain close to the Cube but not touching.
Expected Behavior:
When game mode is entered, the cube should rotate on x axis with 5 radian per second speed.
The cube is supposed to touch the trigger as it rotates.
Test Steps:
1) Loads the level
2) Enters game mode
3) Retrieves test entities (Cube and Trigger)
4) Checks if gravity is disabled
5) Checks the Cube and Trigger locations
6) Checks the Cube initial rotation
7) Captures the angular velocity of cube
8) Sets up Trigger
9) Waits for the Cube to rotate and touch the Trigger
10) Captures the Cube new rotation and reports results
11) Exits game mode
12) 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
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
class Cube:
id = None
position = None
init_rotation = None
rotation = None
angular_velocity = None
touched_trigger = False
# We found no bus to retrieve physics rigid body initial angular velocity
# So we are hardcoding 5 as its initial angular velocity
INITIAL_ANGULAR_VELOCITY = 5 # radian per second on X axis
class Trigger:
id = None
position = None
# Constants
INITIAL_ANGULAR_VELOCITY_TOLERANCE = 0.05
TIME_OUT = 2.0
ROTATION_TOLERANCE = 0.001
def is_angle_close(x, y):
r = (math.sin(x) - math.sin(y)) * (math.sin(x) - math.sin(y)) + (math.cos(x) - math.cos(y)) * (
math.cos(x) - math.cos(y)
)
diff = math.acos((2.0 - r) / 2.0)
return abs(diff) <= ROTATION_TOLERANCE
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "RigidBody_InitialAngularVelocity")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
Cube.id = general.find_game_entity("Cube")
Trigger.id = general.find_game_entity("Trigger")
Report.critical_result(Tests.cube_found, Cube.id.IsValid())
Report.critical_result(Tests.trigger_found, Trigger.id.IsValid())
# 4) Check gravity is disabled
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", Cube.id)
Report.result(Tests.gravity_disabled, not gravity_enabled)
# 5) Log Cube and Trigger positions
Cube.position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Cube.id)
valid_cube_pos = (Cube.position is not None) and (not Cube.position.IsZero())
Report.info_vector3(Cube.position, "Cube initial position:")
Report.critical_result(Tests.cube_pos, valid_cube_pos)
Trigger.position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", Trigger.id)
valid_trigger_pos = (Trigger.position is not None) and (not Trigger.position.IsZero())
Report.info_vector3(Trigger.position, "Trigger initial position:")
Report.critical_result(Tests.trigger_pos, valid_trigger_pos)
# 6) Log Cube initial rotation
Cube.init_rotation = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", Cube.id)
Report.info_vector3(Cube.init_rotation, "Cube initial rotation:")
Report.critical_result(Tests.cube_init_rotation, (Cube.init_rotation is not None))
# 7) Log Cube angular velocity
Cube.angular_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetAngularVelocity", Cube.id)
Report.info_vector3(Cube.angular_velocity, "Cube angular velocity:")
valid_velocity = abs(Cube.angular_velocity.x - Cube.INITIAL_ANGULAR_VELOCITY) < INITIAL_ANGULAR_VELOCITY_TOLERANCE
Report.result(Tests.cube_velocity, valid_velocity)
# 8) Set up Trigger
def set_touched_value(args):
entering_entity_id = args[0]
if entering_entity_id.Equal(Cube.id):
Report.info("Cube touched the Trigger")
Cube.touched_trigger = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(Trigger.id)
handler.add_callback("OnTriggerEnter", set_touched_value)
# 9) Wait a maximum of 2 seconds for the sphere to touch the trigger
helper.wait_for_condition(lambda: Cube.touched_trigger, TIME_OUT)
Report.result(Tests.cube_touched_trigger, Cube.touched_trigger)
# 10) Log Cube's new rotation and validates it
Cube.rotation = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", Cube.id)
Report.info_vector3(Cube.rotation, "Cube new rotation:")
Report.result(Tests.cube_rotated_on_x, not is_angle_close(Cube.rotation.x, Cube.init_rotation.x))
Report.result(Tests.cube_not_rotated_on_y, is_angle_close(Cube.rotation.y, Cube.init_rotation.y))
Report.result(Tests.cube_not_rotated_on_z, is_angle_close(Cube.rotation.z, Cube.init_rotation.z))
# 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(RigidBody_InitialAngularVelocity)
@@ -0,0 +1,131 @@
"""
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 : C4976195
# Test Case Title : Verify that when you assign an Initial Linear Velocity to an object,
# ... it moves with that linear velocity when we switch to game mode.
# fmt: off
class Tests:
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
entities_found = ("Entities found", "Entities not found")
entities_pos = ("Entities positions are valid", "Entities positions are not valid")
sphere_moved = ("Sphere moved correctly", "Sphere did not move correctly")
sphere_velocity = ("The magnitude of velocity is close to 5", "The magnitude of velocity is not close to 5")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
def RigidBody_InitialLinearVelocity():
# type: () -> None
"""
Summary:
Runs an automated test to ensure intial linear velocity of 5.0 on X axis is exerted on a rigid body object.
Level Description:
A sphere (entity: Sphere) positioned above terrain which is assigned with
an initial linear velocity of 5.0 in x direction. The Sphere has gravity disabled.
A trigger cube positioned on the same Y and Z but different X of the sphere.
Expected Behavior:
When game mode is entered, the sphere should move on x direction with 5 m/s speed.
The sphere is supposed to touch the trigger cube as it moves.
Test Steps:
1) Loads the level
2) Enters game mode
3) Retrieves test entities (Sphere and Trigger Cube)
4) Ensures that the sphere and the cube are located
5) Captures the velocity of sphere
5.1) Sets up trigger cube
6) Waits for the shpere to move and touch the trigger cube
7) Captures the sphere new location
8) Exits game mode
9) 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
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "RigidBody_InitialLinearVelocity")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
sphere_id = general.find_game_entity("Sphere")
trigger_cube_id = general.find_game_entity("TriggerCube")
valid_entity_ids = (sphere_id.IsValid() and trigger_cube_id.IsValid())
Report.critical_result(Tests.entities_found, valid_entity_ids)
# 4) Log sphere and cube initial position
init_sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
trigger_cube_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", trigger_cube_id)
sphere_pos_found = ((init_sphere_pos is not None) and (not init_sphere_pos.IsZero()))
trigger_cube_pos_found = ((trigger_cube_pos is not None) and (not trigger_cube_pos.IsZero()))
pos_found = sphere_pos_found and trigger_cube_pos_found
Report.critical_result(Tests.entities_pos, pos_found)
Report.info_vector3(init_sphere_pos, "Sphere initial position:")
# 5) Log Sphere's velocity
sphere_linear_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", sphere_id)
Report.info("Sphere linear velocity {}".format(sphere_linear_velocity.ToString()))
sphere_linear_velocity_magnitude = sphere_linear_velocity.GetLength()
Report.info("Magnitude = {}".format(sphere_linear_velocity_magnitude))
# We found no bus to retrieve physics rigid body initial linear velocity
# So we are hardcoding 5 as its initial linear velocity
SPHERE_INITIAL_VELOCITY = 5
outcome = abs(sphere_linear_velocity_magnitude - SPHERE_INITIAL_VELOCITY) < 0.05
Report.result(Tests.sphere_velocity, outcome)
# 5.1) Set up trigger cube
class SphereTouchedTrigger:
value = False
def set_touched_value(args):
# Called when the sphere touches the trigger cube
SphereTouchedTrigger.value = True
handler = azlmbr.physics.TriggerNotificationBusHandler()
handler.connect(trigger_cube_id)
handler.add_callback("OnTriggerEnter", set_touched_value)
# 6) Wait a maximum of 3 seconds for the sphere to touch the trigger
helper.wait_for_condition(lambda: SphereTouchedTrigger.value, 3.0)
# 7) Log Sphere's new location and validates sphere's movement
new_sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
# The sphere is supposed to move on X axis, but not on Y nor Z
valid_movement = (new_sphere_pos.x > init_sphere_pos.x and
abs(new_sphere_pos.y - init_sphere_pos.y) < 0.001 and
abs(new_sphere_pos.z - init_sphere_pos.z) < 0.001)
Report.result(Tests.sphere_moved, valid_movement)
Report.info_vector3(new_sphere_pos, "Sphere new position:")
# 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(RigidBody_InitialLinearVelocity)
@@ -0,0 +1,131 @@
"""
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 : C4976207
# Test Case Title : Verify that when Kinematic is checked, the object behaves as a Kinematic object
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_box = ("Box found", "Box not found")
find_ramp = ("Ramp found", "Ramp not found")
box_is_not_kinematic = ("Box is not kinematic", "Box is kinematic")
ramp_is_kinematic = ("Ramp is kinematic", "Ramp is not kinematic")
box_gravity_enabled = ("Gravity enabled on the box", "Gravity not enabled on the box")
ramp_gravity_enabled = ("Gravity enabled on the ramp", "Gravity not enabled on the ramp")
box_touched_ramp = ("Box touched ramp", "Box did not touch ramp")
ramp_did_not_move = ("Ramp did not move", "Ramp moved")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
# fmt: on
def RigidBody_KinematicModeWorks():
"""
Summary:
Runs an automated test to ensure kinematic property causes entity to remain in place when gravity acts upon it
and when another entity collides with it.
Level Description:
A box (entity: Box) set above a kinematic ramp (entity: Ramp). Gravity is enabled for the the box and the ramp.
Expected behavior:
The box collides with the ramp, and the ramp does not fall.
Test Steps:
1) Open level and enter game mode
2) Retrieve entities
3) Check for kinematic ramp and not kinematic box
4) Check that gravity is enabled on the entities
5) Get the initial position of the ramp
6) Check to see that the box hits the ramp
6.5) Wait for the box to touch the ramp or timeout
7) Check to see that the ramp stayed at the same position
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
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# Specific wait times in seconds
TIME_OUT = 3.0
REACTION = 0.1
# 1) Open level and enter game mode
helper.init_idle()
helper.open_level("Physics", "RigidBody_KinematicModeWorks")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve entities
box_id = general.find_game_entity("Box")
Report.result(Tests.find_box, box_id.IsValid())
ramp_id = general.find_game_entity("Ramp")
Report.result(Tests.find_ramp, ramp_id.IsValid())
# 3) Check for kinematic ramp and not kinematic box
box_kinematic = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsKinematic", box_id)
Report.result(Tests.box_is_not_kinematic, not box_kinematic)
ramp_kinematic = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsKinematic", ramp_id)
Report.result(Tests.ramp_is_kinematic, ramp_kinematic)
# 4) Check that gravity is enabled on the entities
box_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", box_id)
Report.result(Tests.box_gravity_enabled, box_gravity_enabled)
ramp_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", ramp_id)
Report.result(Tests.ramp_gravity_enabled, ramp_gravity_enabled)
# 5) Get the initial position of the ramp
ramp_pos_start = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", ramp_id)
Report.info("Ramp's initial position: {}".format(ramp_pos_start))
# 6) Check to see that the box hits the ramp
class RampTouched:
value = False
def on_collision_begin(args):
other_id = args[0]
if other_id.Equal(ramp_id):
Report.info("Box touched ramp")
RampTouched.value = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(box_id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
# 6.5) Wait for the box to touch the ramp or timeout
helper.wait_for_condition(lambda: RampTouched.value, TIME_OUT)
Report.result(Tests.box_touched_ramp, RampTouched.value)
# 7) Check to see that the ramp stayed at the same position
general.idle_wait(REACTION) # wait for collision reaction
ramp_pos_end = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", ramp_id)
Report.info("Ramp's final position: {}".format(ramp_pos_end))
Report.result(Tests.ramp_did_not_move, ramp_pos_start.Equal(ramp_pos_end))
# 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(RigidBody_KinematicModeWorks)
@@ -0,0 +1,269 @@
"""
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 : C4976199
# Test Case Title : Verify that with higher linear damping, the object in motion comes to rest faster
# fmt: off
class Tests:
enter_game_mode_low = ("Entered game mode low", "Failed to enter game mode low")
enter_game_mode_medium = ("Entered game mode medium", "Failed to enter game medium")
enter_game_mode_high = ("Entered game mode high", "Failed to enter game mode high")
find_sphere_low = ("Find sphere low", "Failed to find sphere low")
find_sphere_medium = ("Find sphere medium", "Failed to find sphere medium")
find_sphere_high = ("Find sphere high", "Failed to find sphere high")
find_triggers_low = ("Find triggers low", "Failed to find triggers low")
find_triggers_medium = ("Find triggers medium", "Failed to find triggers medium")
find_triggers_high = ("Find triggers high", "Failed to find triggers high")
x_movement = ("x direction movement decreases with increased damping", "x direction movement does not decrease with increased damping")
high_damping_no_movement = ("High damping IsClose to 0 movement", "High damping is not IsClose to 0 movement")
triggers_tripped_low = ("Low damping trips all 3 triggers", "Low damping did not trip all 3 triggers")
triggers_tripped_medium = ("Medium damping trips 2/3 triggers", "Medium damping does not trip 2/3 triggers")
triggers_tripped_high = ("High damping trips no triggers", "High damping trips triggers and should not")
timeout = ("All spheres velocity IsClose to 0 before timeout", "All spheres velocity are not IsClose to 0 before timeout")
exit_game_mode_low = ("Exited game mode low", "Couldn't exit game mode low")
exit_game_mode_medium = ("Exited game mode medium", "Couldn't exit game mode medium")
exit_game_mode_high = ("Exited game mode high", "Couldn't exit game mode high")
# fmt: on
def RigidBody_LinearDampingAffectsMotion():
"""
The level consists of a PhysX Terrain component that is not interacted with (per the test case)
and a PhysX collider with shape sphere, PhysX rigid bodies physics, and mesh with shape sphere.
3 colliders with trigger are added to ensure x movement length is decreased quantitatively not
just comparatively.
The sphere starts asleep.
We will enter game mode for each state defined
1) Open level
2) Enter game mode
3) Set sphere attributes, measure initial position, and then ForceAwake
5) Measure
6) Exit game mode
7) Run and repeat steps 2-6
8) Report results
Notes:
Initially we calculated time to stop by calling time.time() both when awakening the sphere and when velocity equaled zero.
Comparing the time to stop accross sphere settings proved flaky.
"""
# Setup path
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 lymath
class SphereInfo:
def __init__(
self,
initial_linear_velocity,
linear_damping,
enter_game_mode_test,
find_sphere_test,
exit_game_mode_test,
triggers_test,
):
self.initial_linear_velocity = initial_linear_velocity
self.linear_damping = linear_damping
self.entity = None
self.initial_position = None
self.final_position = None
self.timeout = False
self.triggers = []
self.enter_game_mode_test = enter_game_mode_test
self.find_sphere_test = find_sphere_test
self.exit_game_mode_test = exit_game_mode_test
self.triggers_test = triggers_test
def __str__(self):
return """
initial_linear_velocity = {}
linear_damping = {}
timeout = {}
""".format(
self.initial_linear_velocity.GetLength(), self.linear_damping, self.timeout
)
def report(self):
Report.info(self.__str__())
Report.info_vector3(self.initial_position, "Initial position")
Report.info_vector3(self.final_position, "Final position")
for trigger in self.triggers:
Report.info(trigger.__str__())
class Trigger:
def __init__(self, name):
self.name = name
self.entity = None
self.handler = None
self.triggering_entity = None
self.triggered = False
def on_trigger(self, args):
self.triggered = True
self.triggering_entity = args[0]
Report.info("{} was triggered by {}".format(self.name, self.triggering_entity_name()))
def __str__(self):
return """
name = {}
triggering entity name = {}
triggered = {}
""".format(
self.name, self.triggering_entity_name(), self.triggered
)
def triggering_entity_name(self):
if self.triggering_entity:
return azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", self.triggering_entity)
return None
INITIAL_LINEAR_VELOCITY = lymath.Vector3(5.0, 0.0, 0.0)
ZERO_VELOCITY = lymath.Vector3(0.0, 0.0, 0.0)
TOLERANCE = 0.001
TIMEOUT = 3.0
TRIGGER1 = "Trigger1"
TRIGGER2 = "Trigger2"
TRIGGER3 = "Trigger3"
# sphere state under test
# fmt: off
low_damping = SphereInfo(
INITIAL_LINEAR_VELOCITY,
5.0,
Tests.enter_game_mode_low,
Tests.find_sphere_low,
Tests.exit_game_mode_low,
Tests.find_triggers_low,
)
medium_damping = SphereInfo(
INITIAL_LINEAR_VELOCITY,
10.0,
Tests.enter_game_mode_medium,
Tests.find_sphere_medium,
Tests.exit_game_mode_medium,
Tests.find_triggers_medium,
)
high_damping = SphereInfo(
INITIAL_LINEAR_VELOCITY,
10000.0,
Tests.enter_game_mode_high,
Tests.find_sphere_high,
Tests.exit_game_mode_high,
Tests.find_triggers_high,
)
# fmt: on
spheres = [low_damping, medium_damping, high_damping]
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_LinearDampingAffectsMotion")
def run_test_steps(sphere):
# 2) Enter game mode
helper.enter_game_mode(sphere.enter_game_mode_test)
# 3) Retrieve entities
sphere.entity = general.find_game_entity("Sphere")
Report.critical_result(sphere.find_sphere_test, sphere.entity.IsValid())
triggers = [Trigger(TRIGGER1), Trigger(TRIGGER2), Trigger(TRIGGER3)]
for trigger in triggers:
trigger.entity = general.find_game_entity(trigger.name)
invalid_triggers = [t for t in triggers if not t.entity.IsValid()]
for trigger in invalid_triggers:
Report.info("Trigger {} is invalid".format(trigger.name))
Report.critical_result(sphere.triggers_test, len(invalid_triggers) == 0)
# 4) Set sphere attributes, measure initial position, and then ForceAwake
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearVelocity", sphere.entity, sphere.initial_linear_velocity)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", sphere.entity, sphere.linear_damping)
sphere.initial_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTM", sphere.entity).GetPosition()
# add handler for each trigger
for trigger in triggers:
trigger.handler = azlmbr.physics.TriggerNotificationBusHandler()
trigger.handler.connect(trigger.entity)
trigger.handler.add_callback("OnTriggerEnter", trigger.on_trigger)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "ForceAwake", sphere.entity)
general.idle_wait_frames(1) # wait one frame for changes to apply
# 5) Measure
def sphere_stopped():
sphere_linear_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", sphere.entity)
if sphere_linear_velocity.IsClose(ZERO_VELOCITY, TOLERANCE):
sphere.final_position = azlmbr.components.TransformBus(
azlmbr.bus.Event, "GetWorldTM", sphere.entity
).GetPosition()
for trigger in [t for t in triggers if t.triggered]:
if trigger.triggering_entity.Equal(sphere.entity):
sphere.triggers.append(trigger)
return True
return False
if not helper.wait_for_condition(sphere_stopped, TIMEOUT):
sphere.timeout = True
sphere.report()
# 6) Exit game mode
helper.exit_game_mode(sphere.exit_game_mode_test)
# 7) Run and repeat steps 2-6
for sphere in spheres:
run_test_steps(sphere)
# 8) Report results
no_timeout = True
for sphere in spheres:
if sphere.timeout:
Report.info(
"Timeout occurred. Sphere with damping = {} and Initial velocity = {} did not come to rest in the timeout of {}".format(
sphere.linear_damping, sphere.initial_linear_velocity, TIMEOUT
)
)
no_timeout = False
# fast fail if timeout occurred, comparisons will be meaningless and all info is in log to determine which sphere(s) timed out
Report.critical_result(Tests.timeout, no_timeout)
Report.result(
Tests.high_damping_no_movement, high_damping.final_position.IsClose(high_damping.initial_position, TOLERANCE)
)
# comparative movement length
Report.result(
Tests.x_movement, high_damping.final_position.x < medium_damping.final_position.x < low_damping.final_position.x
)
# quantitative movement length
all_three_tripped = (
len(low_damping.triggers) == 3
and len([t for t in low_damping.triggers if t.name == TRIGGER1]) == 1
and len([t for t in low_damping.triggers if t.name == TRIGGER2]) == 1
and len([t for t in low_damping.triggers if t.name == TRIGGER3]) == 1
)
Report.result(Tests.triggers_tripped_low, all_three_tripped)
first_two_triggers_tripped = (
len(medium_damping.triggers) == 2
and len([t for t in medium_damping.triggers if t.name == TRIGGER1]) == 1
and len([t for t in medium_damping.triggers if t.name == TRIGGER2]) == 1
)
Report.result(Tests.triggers_tripped_medium, first_two_triggers_tripped)
Report.result(Tests.triggers_tripped_high, len(high_damping.triggers) == 0)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(RigidBody_LinearDampingAffectsMotion)
@@ -0,0 +1,372 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# Test case ID : C4976201
# Test Case Title : Verify that the value assigned to the Mass of the object, gets actually assigned to the object
# fmt: off
class Tests:
# test iteration 1
enter_game_mode_1 = ("Entered game mode first time", "Failed to enter game mode first time")
ProjectileSphere_exists_1 = ("ProjectileSphere entity found first time", "ProjectileSphere entity not found first time")
TargetSphere_exists_1 = ("TargetSphere entity found first time", "TargetSphere entity not found first time")
Trigger1_exists_1 = ("Trigger1 entity found first time", "Trigger1 entity not found first time")
Trigger2_exists_1 = ("Trigger2 entity found first time", "Trigger2 entity not found first time")
Trigger3_exists_1 = ("Trigger3 entity found first time", "Trigger3 entity not found first time")
TargetSphere_mass_1 = ("Mass of TargetSphere was set to 1.0", "Mass of TargetSphere was not set to 1.0")
spheres_collided_1 = ("ProjectileSphere and TargetSphere collided first time", "Timed out before ProjectileSphere & 2 collided first time")
stopped_correctly_1 = ("TargetSphere hit Trigger1 & Trigger2 but not Trigger_3", "TargetSphere did not stop correctly")
check_y_1 = ("sphere did not move far from expected in Y direction _1", "TargetSphere moved an unexpected distance in Y direction _1")
check_z_1 = ("sphere did not move far from expected in Z direction _1", "TargetSphere moved an unexpected distance in Z direction _1")
exit_game_mode_1 = ("Exited game mode first time", "Couldn't exit game mode first time")
# test iteration 2
enter_game_mode_2 = ("Entered game mode second time", "Failed to enter game mode second time")
ProjectileSphere_exists_2 = ("ProjectileSphere entity found second time", "ProjectileSphere entity not found second time")
TargetSphere_exists_2 = ("TargetSphere entity found second time", "TargetSphere entity not found second time")
Trigger1_exists_2 = ("Trigger1 entity found second time", "Trigger1 entity not found second time")
Trigger2_exists_2 = ("Trigger2 entity found second time", "Trigger2 entity not found second time")
Trigger3_exists_2 = ("Trigger3 entity found second time", "Trigger3 entity not found second time")
TargetSphere_mass_2 = ("Mass of TargetSphere was set to 10.0", "Mass of TargetSphere was not set to 10.0")
spheres_collided_2 = ("ProjectileSphere and TargetSphere collided second time", "Timed out before ProjectileSphere & 2 collided second time")
stopped_correctly_2 = ("TargetSphere hit Trigger1 but not Trigger2 or Trigger3", "TargetSphere did not stop correctly")
check_y_2 = ("sphere did not move far from expected in Y direction _2", "TargetSphere moved an unexpected distance in Y direction _2")
check_z_2 = ("sphere did not move far from expected in Z direction _2", "TargetSphere moved an unexpected distance in Z direction _2")
exit_game_mode_2 = ("Exited game mode second time", "Couldn't exit game mode second time")
# test iteration 3
enter_game_mode_3 = ("Entered game mode third time", "Failed to enter game mode third time")
ProjectileSphere_exists_3 = ("ProjectileSphere entity found third time", "ProjectileSphere entity not found third time")
TargetSphere_exists_3 = ("TargetSphere entity found third time", "TargetSphere entity not found third time")
Trigger1_exists_3 = ("Trigger1 entity found third time", "Trigger1 entity not found third time")
Trigger2_exists_3 = ("Trigger2 entity found third time", "Trigger2 entity not found third time")
Trigger3_exists_3 = ("Trigger3 entity found third time", "Trigger3 entity not found third time")
TargetSphere_mass_3 = ("Mass of TargetSphere was set to 100.0", "Mass of TargetSphere was not set to 100.0")
spheres_collided_3 = ("ProjectileSphere and TargetSphere collided third time", "Timed out before ProjectileSphere & 2 collided third time")
stopped_correctly_3 = ("TargetSphere did not hit Trigger1, Trigger2, or Trigger3", "TargetSphere hit one or more triggers before stopping")
check_y_3 = ("sphere did not move far from expected in Y direction _3", "TargetSphere moved an unexpected distance in Y direction _3")
check_z_3 = ("sphere did not move far from expected in Z direction _3", "TargetSphere moved an unexpected distance in Z direction _3")
exit_game_mode_3 = ("Exited game mode third time", "Couldn't exit game mode third time")
# general
velocity_sizing = ("The velocities are in the correct order of magnitude", "The velocities are not correctly ordered in magnitude")
# fmt: on
def RigidBody_MassDifferentValuesWorks():
"""
Summary:
Checking that the mass set to the object is actually applied via colliding entities
Level Description:
ProjectileSphere (entity) - Sphere shaped Mesh; Sphere shaped PhysX Collider;
PhysX Rigid Body: initial linear velocity in X direction is 5m/s, initial mass 1kg,
gravity disabled, linear damping default (0.05)
TargetSphere (entity) - Sphere shaped Mesh; Sphere shaped PhysX Collider;
PhysX Rigid Body: no initial velocity, initial mass 1kg, gravity disabled, linear damping 1.0
Expected Behavior:
The ProjectileSphere entity will float towards TargetSphere entity and then collide with it.
Because they are the same mass initially, the second sphere will move after collision.
TargetSphere's mass will be increased and scenario will run again,
but TargetSphere will have a smaller velocity after collision.
TargetSphere will then increase mass again and should barely move after the final collision.
Test Steps:
1) Open level
2) Repeat steps 3-9
3) Enter game mode
4) Find and setup entities
5) Set mass of the TargetSphere
6) Check for collision
7) Wait for TargetSphere x velocity = 0
8) Check the triggers
9) Exit game mode
10) Verify the velocity of TargetSphere decreased after collision as mass increased
11) Close the editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import os
import sys
import azlmbr.legacy.general as general
import azlmbr.bus
import azlmbr
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
MOVEMENT_TIMEOUT = 7.0
COLLISION_TIMEOUT = 2.0
VELOCITY_ZERO = 0.01
Y_Z_BUFFER = 0.01
TARGET_SPHERE_NAME = "TargetSphere"
PROJECTILE_SPHERE_NAME = "ProjectileSphere"
TRIGGER_1_NAME = "Trigger1"
TRIGGER_2_NAME = "Trigger2"
TRIGGER_3_NAME = "Trigger3"
class ProjectileSphere:
def __init__(self, test_iteration):
self.name = PROJECTILE_SPHERE_NAME
self.test_iteration = test_iteration
self.timeout_reached = True
self.id = general.find_game_entity(self.name)
Report.critical_result(
Tests.__dict__["ProjectileSphere_exists_" + str(self.test_iteration)], self.id.IsValid()
)
def destroy_me(self):
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "DestroyGameEntity", self.id)
class TargetSphere:
def __init__(self, mass_to_assign, stop_before_trigger_name, expected_trigger_pattern, test_iteration):
self.id = None
self.name = TARGET_SPHERE_NAME
self.start_mass = None
self.mass_to_assign = mass_to_assign
self.collision_begin = False
self.after_collision_velocity = None
self.x_movement_timeout = True
self.stop_before_trigger_name = stop_before_trigger_name
self.expected_trigger_pattern = expected_trigger_pattern
self.collision_ended = False
self.test_iteration = test_iteration
self.test_set_mass = self.get_test("TargetSphere_mass_")
self.test_enter_game_mode = self.get_test("enter_game_mode_")
self.test_ProjectileSphere_exist = self.get_test("ProjectileSphere_exists_")
self.test_TargetSphere_exist = self.get_test("TargetSphere_exists_")
self.test_spheres_collided = self.get_test("spheres_collided_")
self.test_stop_properly = self.get_test("stopped_correctly_")
self.test_check_y = self.get_test("check_y_")
self.test_check_z = self.get_test("check_z_")
self.test_exit_game_mode = self.get_test("exit_game_mode_")
def get_test(self, test_prefix):
return Tests.__dict__[test_prefix + str(self.test_iteration)]
def find(self):
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__["TargetSphere_exists_" + str(self.test_iteration)], self.id.IsValid())
def setup_mass(self):
self.start_mass = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", self.id)
Report.info("{} starting mass: {}".format(self.name, self.start_mass))
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetMass", self.id, self.mass_to_assign)
general.idle_wait_frames(1) # wait for mass to apply
mass_after_set = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetMass", self.id)
Report.info("{} mass after setting: {}".format(self.name, mass_after_set))
Report.result(self.test_set_mass, self.mass_to_assign == mass_after_set)
def current_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.id):
Report.info("spheres collision begin")
self.collision_begin = True
def on_collision_end(self, args):
other_id = args[0]
if other_id.Equal(self.id):
Report.info("spheres collision end")
self.after_collision_velocity = self.current_velocity()
self.collision_ended = True
def add_collision_handlers(self, projectile_sphere_id):
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(projectile_sphere_id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
self.handler.add_callback("OnCollisionEnd", self.on_collision_end)
def x_velocity_zero(self):
if abs(self.current_velocity().x) < VELOCITY_ZERO:
Report.info("TargetSphere has stopped moving.")
self.x_movement_timeout = False
return True
return False
def collision_complete(self):
return self.collision_begin and self.collision_ended
def check_y_z_movement_from_collision(self):
"""
Used to check that the entity has not moved too far in either the Y or Z direction
"""
def is_within_tolerance(velocity_one_direction):
return abs(velocity_one_direction) < Y_Z_BUFFER
Report.info_vector3(self.after_collision_velocity, "Initial Velocity: ")
Report.result(self.test_check_y, is_within_tolerance(self.after_collision_velocity.y))
Report.result(self.test_check_z, is_within_tolerance(self.after_collision_velocity.z))
class Trigger:
"""
Used in the level to tell if the TargetSphere entity has moved a certain distance.
There are three triggers set up in the level.
"""
def __init__(self, name, test_iteration):
self.name = name
self.handler = None
self.triggered = False
self.test_iteration = test_iteration
self.id = general.find_game_entity(self.name)
Report.critical_result(Tests.__dict__[self.name + "_exists_" + str(self.test_iteration)], self.id.IsValid())
self.setup_handler()
def on_trigger_enter(self, args):
"""
This is passed into this object's handler.add_callback().
"""
other_id = args[0]
self.triggered = True
triggered_by_name = azlmbr.entity.GameEntityContextRequestBus(
azlmbr.bus.Broadcast, "GetEntityName", other_id
)
Report.info("{} was triggered by {}.".format(self.name, triggered_by_name))
def setup_handler(self):
"""
This is called to setup the handler for this trigger object
"""
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
class TriggerResultPattern:
"""
Used to store and determine which triggers were activated and compare to expected
"""
def __init__(self, trigger1_activated, trigger2_activated, trigger3_activated):
self.trigger1_activated = trigger1_activated
self.trigger2_activated = trigger2_activated
self.trigger3_activated = trigger3_activated
def __eq__(self, other_pattern):
"""
Used to determine if two patterns equal/match each other (i.e. Expected VS Actual)
"""
if isinstance(other_pattern, self.__class__):
return (
self.trigger1_activated == other_pattern.trigger1_activated
and self.trigger2_activated == other_pattern.trigger2_activated
and self.trigger3_activated == other_pattern.trigger3_activated
)
else:
return False
def report(self, expect_actual):
Report.info(
"""TargetSphere {} Triggers:
Trigger_1: {}
Trigger_2: {}
Trigger_3: {}
""".format(
expect_actual, self.trigger1_activated, self.trigger2_activated, self.trigger3_activated
)
)
target_sphere_1kg = TargetSphere(
mass_to_assign=1.0,
stop_before_trigger_name=TRIGGER_3_NAME,
expected_trigger_pattern=TriggerResultPattern(True, True, False),
test_iteration=1,
)
target_sphere_10kg = TargetSphere(
mass_to_assign=10.0,
stop_before_trigger_name=TRIGGER_2_NAME,
expected_trigger_pattern=TriggerResultPattern(True, False, False),
test_iteration=2,
)
target_sphere_100kg = TargetSphere(
mass_to_assign=100.0,
stop_before_trigger_name=TRIGGER_1_NAME,
expected_trigger_pattern=TriggerResultPattern(False, False, False),
test_iteration=3,
)
target_spheres = [target_sphere_1kg, target_sphere_10kg, target_sphere_100kg]
target_sphere_velocities = {}
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_MassDifferentValuesWorks")
# 2) Repeat steps 3-9
for target_sphere in target_spheres:
Report.info("***************** Begin Test Iteration {} ******************".format(target_sphere.test_iteration))
# 3) Enter game mode
helper.enter_game_mode(target_sphere.test_enter_game_mode)
# 4) Find and setup entities
projectile_sphere = ProjectileSphere(target_sphere.test_iteration)
target_sphere.find()
target_sphere.add_collision_handlers(projectile_sphere.id)
trigger_1 = Trigger(TRIGGER_1_NAME, target_sphere.test_iteration)
trigger_2 = Trigger(TRIGGER_2_NAME, target_sphere.test_iteration)
trigger_3 = Trigger(TRIGGER_3_NAME, target_sphere.test_iteration)
# 5) Set mass of the TargetSphere
target_sphere.setup_mass()
# 6) Check for collision
helper.wait_for_condition(target_sphere.collision_complete, COLLISION_TIMEOUT)
Report.critical_result(target_sphere.test_spheres_collided, target_sphere.collision_complete())
projectile_sphere.destroy_me()
Report.info_vector3(
target_sphere.after_collision_velocity, "Velocity of {} after the collision: ".format(target_sphere.name)
)
Report.info("The sphere should stop before touching {}".format(target_sphere.stop_before_trigger_name))
# 7) Wait for TargetSphere x velocity = 0
helper.wait_for_condition(target_sphere.x_velocity_zero, MOVEMENT_TIMEOUT)
if target_sphere.x_movement_timeout is True:
Report.info("TargetSphere failed to stop moving in the x direction before timeout was reached.")
# 8) Check the triggers
actual_trigger_pattern = TriggerResultPattern(trigger_1.triggered, trigger_2.triggered, trigger_3.triggered)
patterns_match = actual_trigger_pattern == target_sphere.expected_trigger_pattern
target_sphere.expected_trigger_pattern.report("Expected")
actual_trigger_pattern.report("Actual")
Report.result(target_sphere.test_stop_properly, patterns_match)
target_sphere.check_y_z_movement_from_collision()
target_sphere_velocities.update({target_sphere.test_iteration: target_sphere.after_collision_velocity.x})
# 9) Exit game mode
helper.exit_game_mode(target_sphere.test_exit_game_mode)
Report.info("~~~~~~~~~~~~~~ Test Iteration {} End ~~~~~~~~~~~~~~~~~~".format(target_sphere.test_iteration))
# 10) Verify the velocity of TargetSphere decreased after collision as mass increased
outcome = target_sphere_velocities[1] > target_sphere_velocities[2] > target_sphere_velocities[3]
Report.result(Tests.velocity_sizing, outcome)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(RigidBody_MassDifferentValuesWorks)
@@ -0,0 +1,283 @@
"""
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 : C13352089
# Test Case Title : Verify that maximum angular velocity interacts correctly with initial angular velocity
# 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")
trigger_touch_times = ("Bar5 rotated more than bar6", "Bar5 did not stop more slowly than bar6")
rotation_duration = ("Bar5 stopped faster than bar6", "Bar5 did not stop faster than bar6")
# bar 1
bar1_gravity_disabled = ("Bar1 : Gravity is disabled", "Bar1 : Gravity is not disabled")
bar1_found = ("Bar1 : Found entity", "Bar1 : Entity not found")
bar1_rotation = ("Bar1 : Rotated on X axis", "Bar1 : Unexpected rotation")
bar1_angular_velocity = ("Bar1 : Expected angular velocity", "Bar1 : Unexpected angular velocity")
# bar 2
bar2_gravity_disabled = ("Bar2 : Gravity is disabled", "Bar2 : Gravity is not disabled")
bar2_found = ("Bar2 : Found entity", "Bar2 : Entity not found")
bar2_rotation = ("Bar2 : Rotated on X axis", "Bar2 : Unexpected rotation")
bar2_angular_velocity = ("Bar2 : Expected angular velocity", "Bar2 : Unexpected angular velocity")
# bar 3
bar3_gravity_disabled = ("Bar3 : Gravity is disabled", "Bar3 : Gravity is not disabled")
bar3_found = ("Bar3 : Found entity", "Bar3 : Entity not found")
bar3_rotation = ("Bar3 : Rotated on X axis", "Bar3 : Unexpected rotation")
bar3_angular_velocity = ("Bar3 : Expected angular velocity", "Bar3 : Unexpected angular velocity")
# bar 4
bar4_gravity_disabled = ("Bar4 : Gravity is disabled", "Bar4 : Gravity is not disabled")
bar4_found = ("Bar4 : Found entity", "Bar4 : Entity not found")
bar4_rotation = ("Bar4 : Did not rotate", "Bar4 : Unexpected rotation")
bar4_angular_velocity = ("Bar4 : Expected angular velocity", "Bar4 : Unexpected angular velocity")
# bar 5
bar5_gravity_disabled = ("Bar5 : Gravity is disabled", "Bar5 : Gravity is not disabled")
bar5_found = ("Bar5 : Found entity", "Bar5 : Entity not found")
bar5_rotation = ("Bar5 : Rotated on X axis", "Bar5 : Unexpected rotation")
bar5_angular_velocity = ("Bar5 : Expected angular velocity", "Bar5 : Unexpected angular velocity")
# bar 6
bar6_gravity_disabled = ("Bar6 : Gravity is disabled", "Bar6 : Gravity is not disabled")
bar6_found = ("Bar6 : Found entity", "Bar6 : Entity not found")
bar6_rotation = ("Bar6 : Rotated on X axis", "Bar6 : Unexpected rotation")
bar6_angular_velocity = ("Bar6 : Expected angular velocity", "Bar6 : Unexpected angular velocity")
# trigger for bar 5
bar5_trigger_found = ("Trigger for bar5 : Found entity", "Trigger for bar5 : Entity not found")
# trigger for bar 6
bar6_trigger_found = ("Trigger for bar6 : Found entity", "Trigger for bar6 : Entity not found")
did_not_timeout = ("Should_wait did not time out", "Should wait timed out")
# fmt: on
def RigidBody_MaxAngularVelocityWorks():
"""
Summary:
Runs an automated test to ensure maximum angular velocity and angular damping interact correctly with
initial angular velocity
Level Description:
bars:
6 PhysX Rigid Bodies with PhysX Shape Collider (Box Shape, Dimensions: X=1, Y=1, Z=5), gravity disabled,
start asleep, positioned above terrain
bar 1: Initial Angular Velocity = 10 rad/s, Max Angular Velocity = 5 rad/s, Angular Damping = 0.0
bar 2: Initial Angular Velocity = 10 rad/s, Max Angular Velocity = 10 rad/s, Angular Damping = 0.0
bar 3: Initial Angular Velocity = 20 rad/s, Max Angular Velocity = 20 rad/s, Angular Damping = 0.0
bar 4: Initial Angular Velocity = 20 rad/s, Max Angular Velocity = 0 rad/s, Angular Damping = 0.0
bar 5: Initial Angular Velocity = 20 rad/s, Max Angular Velocity = 20 rad/s, Angular Damping = 2.0
bar 6: Initial Angular Velocity = 20 rad/s, Max Angular Velocity = 20 rad/s, Angular Damping = 5.0
triggers:
2 PhysX Shape Collider (Box Shape), gravity disabled, trigger enabled, start asleep
trigger for bar 5: positioned above terrain, in front of bar 5
trigger for bar 6: positioned above terrain, in front of bar 6
Expected Behavior:
bar 1: Should rotate at 5 rad/s on X axis
bar 2: Should rotate at 10 rad/s on X axis
bar 3: Should rotate at 20 rad/s on X axis
bar 4: Should not rotate at all
bar 5: Should start rotating at 20 rad/s on X axis and stop slowly (touching its trigger several times)
bar 6: Should start rotating at 20 rad/s on X axis but stop quickly (touching its trigger 0 or very few times)
Test Steps:
1) Loads the level
2) Enters game mode
3) Set up bars values
4) Loop for each bar
4.1) Find the bar
4.2) Validate ID
4.3) Activate the bar
4.4) Validate and log its position
4.5) Confirm gravity is disabled for the bar
4.6) Log bar's initial rotation
4.7) Log bar's angular damping
4.8) Log bar's angular velocity
4.9) Set up trigger if the bar has one
4.10) Wait for each bar to rotate or stop rotating
4.11) Log current location
4.12) Validate rotation based on expected behavior
4.13) Destroy bar (and its trigger)
5) Compare trigger touch times for bar5 and bar6
6) Exit game mode
7) Close editor
"""
import os
import sys
import math
import time
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import azlmbr.math as lymath
import azlmbr.legacy.general as general
import azlmbr.bus
# Constants
ANGULAR_VELOCITY_TOLERANCE = 0.05
TIME_OUT = 15.0
ROTATION_TOLERANCE = 0.001
def is_close(x, y, tolerance=ANGULAR_VELOCITY_TOLERANCE):
return abs(x - y) < tolerance
def is_angle_close(x, y):
r = (math.sin(x) - math.sin(y)) * (math.sin(x) - math.sin(y)) + (math.cos(x) - math.cos(y)) * (
math.cos(x) - math.cos(y)
)
diff = math.acos((2.0 - r) / 2.0)
return abs(diff) <= ROTATION_TOLERANCE
class Entity: # Parent class for bars and triggers
def __init__(self, name):
self.name = name
self.validate_ID()
self.activate_entity()
def validate_ID(self):
self.id = general.find_game_entity(self.name)
found_tuple = Tests.__dict__[self.name + "_found"]
Report.critical_result(found_tuple, self.id.IsValid())
def activate_entity(self):
Report.info("Activating Entity : " + self.name)
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.id)
general.idle_wait_frames(1)
self.rotation_start_time = time.time()
class Bar(Entity):
def __init__(self, name, init_ang_velocity_on_X, max_ang_velocity, has_trigger=False):
# 1) Validate ID, then activate Bar
Entity.__init__(self, name)
self.rotation_duration = 999999.9
self.init_angular_velocity = lymath.Vector3(init_ang_velocity_on_X, 0.0, 0.0)
self.max_angular_velocity = max_ang_velocity
self.max_valid_velocity = min(self.max_angular_velocity, self.init_angular_velocity.GetLength())
self.angular_damping = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetAngularDamping", self.id)
# ) Log initial rotation
self.init_rotation = self.get_rotation()
# ) Verify gravity is disabled for bar
self.validate_gravity_is_disabled()
# ) Validate angular velocity according to max angular velocity
self.validate_angular_velocity()
# ) Setup trigger
if has_trigger:
self.touched_trigger = 0
self.setup_trigger()
def validate_gravity_is_disabled(self):
gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
gravity_tuple = Tests.__dict__[self.name + "_gravity_disabled"]
Report.result(gravity_tuple, not gravity_enabled)
def setup_trigger(self):
self.trigger = Entity(self.name + "_trigger")
self.trigger.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.trigger.handler.connect(self.trigger.id)
self.trigger.handler.add_callback("OnTriggerEnter", self.count_touch_times)
def get_angular_velocity(self):
return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetAngularVelocity", self.id)
def get_rotation(self):
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", self.id)
def validate_angular_velocity(self):
general.idle_wait_frames(10)
self.angular_velocity = self.get_angular_velocity()
angular_velocity_tuple = Tests.__dict__[self.name + "_angular_velocity"]
if self.angular_damping == 0:
velocity_is_valid = is_close(self.angular_velocity.GetLength(), self.max_valid_velocity)
else:
velocity_is_valid = self.max_valid_velocity >= self.angular_velocity.GetLength() >= 0.0
Report.result(angular_velocity_tuple, velocity_is_valid)
def validate_rotation(self):
self.rotation = self.get_rotation()
rotated_on_x = not is_angle_close(self.init_rotation.x, self.rotation.x)
rotated_on_y = not is_angle_close(self.init_rotation.y, self.rotation.y)
rotated_on_z = not is_angle_close(self.init_rotation.z, self.rotation.z)
if self.max_valid_velocity != 0:
return rotated_on_x and not rotated_on_y and not rotated_on_z
else:
return not rotated_on_x and not rotated_on_y and not rotated_on_z
def has_waited_enough(self):
if self.angular_damping > 0:
# Bar5 or bar6 should stop rotating to consider their movement as done
if self.get_angular_velocity().IsZero():
self.rotation_duration = time.time() - self.rotation_start_time
return True
elif self.angular_damping == 0:
# Bar1, bar2, bar3 or bar4
return True
return False
def count_touch_times(self, args):
entering_entity_id = args[0]
if entering_entity_id.Equal(self.id):
Report.info(self.name + " touched " + self.trigger.name)
self.touched_trigger += 1
helper.init_idle()
# 1) Load the level
helper.open_level("Physics", "RigidBody_MaxAngularVelocityWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Init bars values
bars = [
Bar(name="bar1", init_ang_velocity_on_X=10.0, max_ang_velocity=5),
Bar(name="bar2", init_ang_velocity_on_X=10.0, max_ang_velocity=20),
Bar(name="bar3", init_ang_velocity_on_X=20.0, max_ang_velocity=20),
Bar(name="bar4", init_ang_velocity_on_X=20.0, max_ang_velocity=0,),
Bar(name="bar5", init_ang_velocity_on_X=20.0, max_ang_velocity=20, has_trigger=True,),
Bar(name="bar6", init_ang_velocity_on_X=20.0, max_ang_velocity=20, has_trigger=True,),
]
# 4.10) Wait
Report.critical_result(
Tests.did_not_timeout,
helper.wait_for_condition(lambda: all([bar.has_waited_enough() for bar in bars]), TIME_OUT),
)
# 4.12) Validate rotation
for bar in bars:
Report.result(Tests.__dict__["{}_rotation".format(bar.name)], bar.validate_rotation())
# 5) Compare number of times bar5 and bar6 touched their trigger
Report.result(Tests.trigger_touch_times, bars[4].touched_trigger > bars[5].touched_trigger)
Report.info(bars[4].rotation_duration)
Report.info(bars[5].rotation_duration)
Report.result(Tests.rotation_duration, bars[4].rotation_duration > bars[5].rotation_duration)
# 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(RigidBody_MaxAngularVelocityWorks)
@@ -0,0 +1,159 @@
"""
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 : C5340400
# Test Case Title : Verify that when Compute inertia is disabled, the user gets to set the moment of inertia
# and physX engine work accordingly
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_upper_box = ("Upper Box found", "Upper Box not found")
find_lower_box = ("Lower Box found", "Lower Box not found")
find_physx_terrain = ("PhysX Terrain found", "PhysX Terrain not found")
boxes_collided = ("Boxes collided", "Boxes did not collide")
upper_box_did_not_topple_t1 = ("Upper Box did not topple at time t1", "Upper Box toppled at time t1")
upper_box_did_not_touch_ground = ("Upper Box did not touch the terrain", "Upper Box touched the terrain")
upper_box_did_not_topple_t2 = ("Upper Box did not topple at time t2", "Upper Box toppled at time t2")
exit_game_mode = ("Exited game mode", "Failed to exit game mode")
# fmt: on
def RigidBody_MomentOfInertiaManualSetting():
"""
Summary:
Runs an automated test to ensure that assigning a high moment of inertia component causes the entity to exhibit
high intertia along the component axis and therefore resist change in motion along that axis.
Level Description:
A box (entity: Upper Box) set above and askew to another box (entity: Lower Box) which is set on a PhysX terrain
(entity: PhysX Terrain).
Gravity is enabled.
Lower box has computed inertia.
Upper box has Inertia Diagonal x=10000, y=1, z=1.
Expected behavior:
The upper box falls onto the lower box and tilts extremely slowly toward the terrain. The torque exerted on the
upper box by the opposing influences of momentum, normal force from the lower box, and/or ongoing acceleration due
to gravity will be greatly resisted by the Inertia Diagonal x=10000 resulting in a tiny change in angular velocity.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve entities
4) Check for collision between the two boxes
4.5) Wait for the boxes to collide
5) Check that the upper box's x-angular velocity is insignificant at time t1 upon collision
6) Check that the upper box does not collide with the terrain in a given time
6.5) Wait for the given time
7) Check that the upper box's x-angular velocity remains insignificant at time t2 after collision
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
"""
# Setup path
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
# Specific wait time in seconds
TIME_OUT = 3.0
# X-angular velocity should be positive and tiny
def XAngularVelocityIsValid(x_ang_vel):
TOLERANCE = 0.01
return 0 < x_ang_vel < TOLERANCE
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_MomentOfInertiaManualSetting")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve entities
upper_box_id = general.find_game_entity("Upper Box")
Report.result(Tests.find_upper_box, upper_box_id.IsValid())
lower_box_id = general.find_game_entity("Lower Box")
Report.result(Tests.find_lower_box, lower_box_id.IsValid())
physx_terrain_id = general.find_game_entity("PhysX Terrain")
Report.result(Tests.find_physx_terrain, physx_terrain_id.IsValid())
# 4) Check for collision between the two boxes
class BoxesCollided:
value = False
def on_boxes_collision_begin(args):
other_id = args[0]
if other_id.Equal(lower_box_id):
Report.info("Boxes collided")
BoxesCollided.value = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(upper_box_id)
handler.add_callback("OnCollisionBegin", on_boxes_collision_begin)
# 4.5) Wait for the boxes to collide
helper.wait_for_condition(lambda: BoxesCollided.value, TIME_OUT)
Report.result(Tests.boxes_collided, BoxesCollided.value)
# 5) Check that the upper box's x-angular velocity is insignificant at time t1 upon collision
# The torque on the upper box caused by its momentum and the normal force from the lower box is resisted at time t1
upper_box_angular_velocity = azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "GetAngularVelocity", upper_box_id
)
Report.info("Upper Box's x-angular velocity at time t1 upon collision: {}".format(upper_box_angular_velocity.x))
Report.result(Tests.upper_box_did_not_topple_t1, XAngularVelocityIsValid(upper_box_angular_velocity.x))
# 6) Check that the upper box does not collide with the terrain in a given time
# This will also validate that the angular velocity does not reach a local maximum
class UpperBoxCollidedWithTerrain:
value = False
def on_ground_collision_begin(args):
other_id = args[0]
if other_id.Equal(physx_terrain_id):
Report.info("Upper Box collided with terrain")
UpperBoxCollidedWithTerrain.value = True
handler.add_callback("OnCollisionBegin", on_ground_collision_begin)
# 6.5) Wait for the given time
helper.wait_for_condition(lambda: UpperBoxCollidedWithTerrain.value, TIME_OUT)
Report.result(Tests.upper_box_did_not_touch_ground, not UpperBoxCollidedWithTerrain.value)
# 7) Check that the upper box's x-angular velocity remains insignificant at time t2 after collision
# The torque on the upper box caused by gravity and the normal force from the lower box is resisted through time t2
upper_box_angular_velocity = azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "GetAngularVelocity", upper_box_id
)
Report.info("Upper Box's x-angular velocity at time t2 after collision: {}".format(upper_box_angular_velocity.x))
Report.result(Tests.upper_box_did_not_topple_t2, XAngularVelocityIsValid(upper_box_angular_velocity.x))
# 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(RigidBody_MomentOfInertiaManualSetting)
@@ -0,0 +1,121 @@
"""
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 : C14976307
# Test Case Title : Check that Set Gravity Enabled works on an entity with gravity that starts as disabled
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_entities = ("Entities are found", "Entities are not found")
gravity_initially_disabled = ("Gravity was initially disabled", "Gravity was initially enabled")
gravity_enabled = ("Enabled gravity successfully", "Failed to enable gravity")
collision_occured = ("Sphere collided with terrain", "Sphere did not collide with terrain")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_SetGravityWorks():
"""
Summary:
Check that Set Gravity Enabled works on an entity with gravity that starts as disabled
Level Description:
Terrain (entity) - Terrain entity is created in the level
Sphere (entity) - Entity with PhysX rigid body, mesh and collider with gravity disabled placed above
the terrain
Expected Behavior:
After 5 seconds, when SetGravity is called, the entity falls to the ground
We are checking if entities are valid and enabling the gravity after 5 seconds in game mode to check if ball
falls on the terrain.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Gravity check for entity
5) Enabling gravity after 5 seconds
6) Adding collision handlers for terrain
7) Checking if the object collides with terrain
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
TIME_OUT = 3.0
WAIT_TIME = 5.0
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_SetGravityWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
terrain_id = general.find_game_entity("Terrain")
sphere_id = general.find_game_entity("Sphere")
Report.critical_result(Tests.find_entities, terrain_id.IsValid() and sphere_id.IsValid())
sphere_gravity_enabled = False
class Sphere:
sphere_collision_occured = False
# 4) Gravity check for entities
sphere_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", sphere_id)
Report.result(Tests.gravity_initially_disabled, not sphere_gravity_enabled)
# 5) Adding collision handlers for terrain
def on_collision_begin(args):
other_id = args[0]
if other_id.Equal(sphere_id):
Report.info("Sphere collided with the terrain")
Sphere.sphere_collision_occured = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(terrain_id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
# 6) Enabling gravity after 5 seconds
general.idle_wait(WAIT_TIME)
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", sphere_id, True)
sphere_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", sphere_id)
Report.result(Tests.gravity_enabled, sphere_gravity_enabled)
# 7) Checking if the object collides with terrain
helper.wait_for_condition(lambda: Sphere.sphere_collision_occured, TIME_OUT)
Report.result(Tests.collision_occured, Sphere.sphere_collision_occured)
# 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(RigidBody_SetGravityWorks)
@@ -0,0 +1,325 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
"""
Test case ID : C4976202
Test Case Title : Verify that if the object is moving with Kinetic energy less than
the sleep threshold value, then physX will put it to stop after 0.4 secs (once the
wake counter goes to zero) if the KE is still below the threshold
"""
# fmt: off
class Tests:
enter_game_mode_1 = ("Entered game mode_1", "Failed to enter game mode_1" )
Trigger1_exists_1 = ("Trigger1 entity was found_1", "Trigger1 entity was not found_1" )
Trigger2_exists_1 = ("Trigger2 entity was found_1", "Trigger2 entity was not found_1" )
Trigger3_exists_1 = ("Trigger3 entity was found_1", "Trigger3 entity was not found_1" )
Pushing_Cube_exists_1 = ("Pushing_Cube entity was found_1", "Pushing_Cube entity was not found_1" )
Target_Ball_exists_1 = ("Target_Ball entity was found_1", "Target_Ball entity was not found_1" )
threshold_setup_1 = ("The sleep threshold was setup properly_1", "The sleep threshold was not setup properly_1" )
cube_collided_with_ball_1 = ("The cube hit the ball the_1", "The cube did not hit the ball the_1" )
ball_stopped_moving_1 = ("The ball has stopped moving before timeout_1", "The ball did not stop moving before timeout was reached_1" )
trigger_patterns_match_1 = ("The actual trigger pattern matches expected_1", "The actual trigger pattern did not match expected_1" )
check_y_z_movement_1 = ("The ball did not move too far in Y or Z_1", "The ball moved farther than expected in Y or Z after collision_1")
exit_game_mode_1 = ("Exited game mode_1", "Couldn't exit game mode_1" )
enter_game_mode_2 = ("Entered game mode_2", "Failed to enter game mode_2" )
Trigger1_exists_2 = ("Trigger1 entity was found_2", "Trigger1 entity was not found_2" )
Trigger2_exists_2 = ("Trigger2 entity was found_2", "Trigger2 entity was not found_2" )
Trigger3_exists_2 = ("Trigger3 entity was found_2", "Trigger3 entity was not found_2" )
Pushing_Cube_exists_2 = ("Pushing_Cube entity was found_2", "Pushing_Cube entity was not found_2" )
Target_Ball_exists_2 = ("Target_Ball entity was found_2", "Target_Ball entity was not found_2" )
threshold_setup_2 = ("The sleep threshold was setup properly_2", "The sleep threshold was not setup properly_2" )
cube_collided_with_ball_2 = ("The cube hit the ball the_2", "The cube did not hit the ball the_2" )
ball_stopped_moving_2 = ("The ball has stopped moving before timeout_2", "The ball did not stop moving before timeout was reached_2" )
trigger_patterns_match_2 = ("The actual trigger pattern matches expected_2", "The actual trigger pattern did not match expected_2" )
check_y_z_movement_2 = ("The ball did not move too far in Y or Z_2", "The ball moved farther than expected in Y or Z after collision_2")
exit_game_mode_2 = ("Exited game mode_2", "Couldn't exit game mode_2" )
enter_game_mode_3 = ("Entered game mode_3", "Failed to enter game mode_3" )
Trigger1_exists_3 = ("Trigger1 entity was found_3", "Trigger1 entity was not found_3" )
Trigger2_exists_3 = ("Trigger2 entity was found_3", "Trigger2 entity was not found_3" )
Trigger3_exists_3 = ("Trigger3 entity was found_3", "Trigger3 entity was not found_3" )
Pushing_Cube_exists_3 = ("Pushing_Cube entity was found_3", "Pushing_Cube entity was not found_3" )
Target_Ball_exists_3 = ("Target_Ball entity was found_3", "Target_Ball entity was not found_3" )
threshold_setup_3 = ("The sleep threshold was setup properly_3", "The sleep threshold was not setup properly_3" )
cube_collided_with_ball_3 = ("The cube hit the ball the_3", "The cube did not hit the ball the_3" )
ball_stopped_moving_3 = ("The ball has stopped moving before timeout_3", "The ball did not stop moving before timeout was reached_3" )
trigger_patterns_match_3 = ("The actual trigger pattern matches expected_3", "The actual trigger pattern did not match expected_3" )
check_y_z_movement_3 = ("The ball did not move too far in Y or Z_3", "The ball moved farther than expected in Y or Z after collision_3")
exit_game_mode_3 = ("Exited game mode_3", "Couldn't exit game mode_3" )
stop_locations_comparison = ("The stop locations are correctly ordered", "The stop locations are not ordered correctly" )
# fmt: on
def RigidBody_SleepWhenBelowKineticThreshold():
"""
Summary:
A Pushing Cube and a Sphere are suspended over the PhysX Terrain.
The cube entity has an initial velocity and on start will move toward the ball
and collide with it. When the kinetic energy of the ball is below a certain threshold,
it should stop moving.
Level Description:
Terrain (entity):
PhysX Terrain component: default settings
PushingCube (entity): Start Inactive
Mesh component: Box shape
PhysX Collider component: Box shape; default settings
PhysX Rigid Body component: Initial linear velocity: 5m/s in positive X direction;
gravity disabled; mass 1.0kg;
TargetBall (entity):
Mesh component: Sphere shape
PhysX Collider component: Sphere shape; default settings
PhysX Rigid Body component: Linear damping: 0.5; mass 10kg; gravity disabled; sleep threshold: 1.0;
Trigger1 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Trigger2 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Trigger3 (entity):
Box Shape component: Dimensions (0.1, 1.0, 1.0); Visible: Checked; Game View: Checked
PhysX Collider component: Box shape; Dimensions (0.1, 1.0, 1.0);
Expected Behavior:
The cube entity should push the ball entity into motion.
When the Kinetic Energy of the ball drops below the sleep threshold, it should stop moving.
The test steps will run for each sleep threshold value (1.0, 5.0, and 10.0). The ball should stop
before touching a specific Trigger (1.0 & Trigger3, 5.0 & Trigger 2, 10.0 & Trigger 1)
Test Steps:
1) Open the level
# Steps 2-9 are repeated for each threshold value
2) Enter game mode
3) Find entities and setup handlers or values
4) Activate the PushingCube entity to start movement
5) Wait for the ball to be hit by the pushing block
6) Wait for the ball to come to a stop in X direction
7) Check that the triggers match the expected trigger results
8) Check that the ball didn't move too far in Y or Z directions
9) Exit game mode
10) Run test_steps_per_threshold for remaining threshold values
11) Compare the stop locations to each other
12) Close the editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import os
import sys
import azlmbr.legacy.general as general
import azlmbr.bus
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
helper.init_idle()
TRIGGER_TIMEOUT = 5.0
TIMEOUT_SECONDS = 2.0
VELOCITY_TOLERANCE = 0.05
Y_Z_BUFFER = 0.01
class TestData:
"""
This is a placeholder to store the values persisting the duration of the test
"""
threshold_count = 0
stop_locations = []
class Entity(object):
"""
Base class for Entities to add basic common functionality such as fetch_id, validate_existence
"""
def __init__(self, name, test_steps_run):
self.name = name
self.test_steps_run = test_steps_run
self.id = None
self.fetch_id()
self.validate_exist()
def fetch_id(self):
self.id = general.find_game_entity(self.name)
def validate_exist(self):
Report.critical_result(Tests.__dict__[self.name + "_exists_" + str(self.test_steps_run)], self.id.IsValid())
def get_location(self):
return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id)
class Trigger(Entity):
"""
Trigger entities used to tell if the Target_Ball has moved far enough/too far compared to expected distances
"""
def __init__(self, test_steps_run, name):
super(Trigger, self).__init__(name, test_steps_run)
self.handler = None
self.triggered = False
self.triggered_by = None
self.attach_handler()
def on_trigger_enter(self, args):
other_id = args[0]
self.triggered = True
self.triggered_by = azlmbr.entity.GameEntityContextRequestBus(
azlmbr.bus.Broadcast, "GetEntityName", other_id
)
Report.info("{} was triggered by {}.".format(self.name, self.triggered_by))
def attach_handler(self):
self.handler = azlmbr.physics.TriggerNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnTriggerEnter", self.on_trigger_enter)
class PushingCube(Entity):
def __init__(self, test_steps_run, name="Pushing_Cube"):
super(PushingCube, self).__init__(name, test_steps_run)
def activate(self):
azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "ActivateGameEntity", self.id)
class TargetBall(Entity):
def __init__(self, test_steps_run, sleep_threshold, name="Target_Ball"):
super(TargetBall, self).__init__(name, test_steps_run)
self.expected_sleep_threshold = sleep_threshold
self.handler = None
self.init_velocity = None
self.grabbed_velocity = None
self.collided_with_box = False
self.stopped_moving_x = False
self.trigger_timed_out = True
self.pushing_cube_id = None
self.setup_sleep_threshold()
self.attach_collision_handler()
def setup_sleep_threshold(self):
azlmbr.physics.RigidBodyRequestBus(
azlmbr.bus.Event, "SetSleepThreshold", self.id, self.expected_sleep_threshold
)
grabbed_value = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetSleepThreshold", self.id)
Report.info("Expected sleep threshold value: {}".format(self.expected_sleep_threshold))
Report.info("Actual value: {}".format(grabbed_value))
Report.critical_result(
Tests.__dict__["threshold_setup_" + str(TestData.threshold_count)],
grabbed_value == self.expected_sleep_threshold,
)
def on_collision_begin(self, args):
other_id = args[0]
if other_id.Equal(self.pushing_cube_id):
Report.info("Push_Box collided with ball.")
self.init_velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
Report.info_vector3(self.init_velocity, "Initial Velocity of {}".format(self.name))
self.collided_with_box = True
def attach_collision_handler(self):
self.handler = azlmbr.physics.CollisionNotificationBusHandler()
self.handler.connect(self.id)
self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
def has_stopped_moving_in_x(self):
"""
This method is used as a condition for helper.wait_for_condition()
"""
velocity = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "GetLinearVelocity", self.id)
if abs(velocity.x) < VELOCITY_TOLERANCE:
Report.info("{} has stopped moving in the X direction.".format(self.name))
self.stopped_moving_x = True
self.trigger_timed_out = False
return True
return False
def check_y_z_delta(self):
"""
Used to check that the entity has not moved too far in either the Y or Z direction
"""
def is_within_tolerance(velocity_one_direction):
return abs(velocity_one_direction) < Y_Z_BUFFER
Report.info_vector3(self.init_velocity, "Initial Velocity: ")
Report.result(
Tests.__dict__["check_y_z_movement_" + str(TestData.threshold_count)],
is_within_tolerance(self.init_velocity.y) and is_within_tolerance(self.init_velocity.z),
)
# 1) Open the level
helper.open_level("Physics", "RigidBody_SleepWhenBelowKineticThreshold")
def test_steps(sleep_threshold_value, trigger_pattern):
TestData.threshold_count += 1
# 2) Enter game mode
helper.enter_game_mode(Tests.__dict__["enter_game_mode_" + str(TestData.threshold_count)])
# 3) Find entities and setup handlers or values
target_ball = TargetBall(TestData.threshold_count, sleep_threshold_value)
pushing_cube = PushingCube(TestData.threshold_count)
target_ball.pushing_cube_id = pushing_cube.id
triggers = []
for i in range(1, 4):
triggers.append(Trigger(TestData.threshold_count, "Trigger" + str(i)))
# 4) Activate the Pushing_Box entity to start movement
pushing_cube.activate()
# 5) Wait for the ball to be hit by the pushing block
helper.wait_for_condition(lambda: target_ball.collided_with_box, TIMEOUT_SECONDS)
Report.result(
Tests.__dict__["cube_collided_with_ball_" + str(TestData.threshold_count)], target_ball.collided_with_box
)
# 6) Wait for the ball to come to a stop in X direction
helper.wait_for_condition(target_ball.has_stopped_moving_in_x, TRIGGER_TIMEOUT)
Report.result(
Tests.__dict__["ball_stopped_moving_" + str(TestData.threshold_count)], target_ball.stopped_moving_x
)
# record the location of the stopping point
TestData.stop_locations.append(target_ball.get_location())
# 7) Check that the triggers match the expected trigger results
trigger_result = (triggers[0].triggered, triggers[1].triggered, triggers[2].triggered)
triggers_matched = trigger_result == trigger_pattern
Report.result(Tests.__dict__["trigger_patterns_match_" + str(TestData.threshold_count)], triggers_matched)
# 8) Check that the ball didn't move too far in Y or Z directions
target_ball.check_y_z_delta()
# 9) Exit game mode
helper.exit_game_mode(Tests.__dict__["exit_game_mode_" + str(TestData.threshold_count)])
# 10) Run test_steps_per_threshold for each threshold value
test_steps(1.0, (True, True, False))
test_steps(5.0, (True, False, False))
test_steps(10.0, (False, False, False))
# 11) Compare the stop locations to each other
order = TestData.stop_locations[0].x > TestData.stop_locations[1].x > TestData.stop_locations[2].x
Report.result(Tests.stop_locations_comparison, order)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(RigidBody_SleepWhenBelowKineticThreshold)
@@ -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 : C4976204
# Test Case Title : Verify that when Start Asleep is checked, the object in air does not fall down due to
# gravity or does not start moving with initial linear velocity assigned to it when switched to game mode
# fmt: off
class Tests():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
find_terrain = ("Terrain found", "Terrain not found")
find_sphere = ("Sphere found", "Sphere not found")
gravity_enabled = ("Gravity is enabled", "Gravity is disabled")
start_asleep_enabled = ("Start asleep is enabled", "Start asleep is disabled")
sphere_position_fixed = ("Sphere position did not change", "Sphere position changed")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
def RigidBody_StartAsleepWorks():
"""
Summary:
Verify that when Start Asleep is checked, the object in air does not fall down due to gravity or
does not start moving with initial linear velocity assigned to it when switched to game mode
Level Description:
Terrain (entity) - Entity with PhysX Terrain component
Sphere (entity) - Entity with components PhysX Rigid Body, PhysX Collider and Rendering Mesh (sphere shape)
initial velocity in x direction - 5 m/s
gravity enabled, Start asleep enabled.
Expected Behavior:
Nothing happens. The sphere is in its position in the air.
We are checking if the sphere is in the same position as it was earlier.
Test Steps:
1) Open level
2) Enter game mode
3) Retrieve and validate entities
4) Get the initial x,y,z positions of the sphere
5) Check gravity, start asleep values for the entity
6) Wait until timeout to check if sphere position changed
7) Check the final x, y, z positions of the sphere
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
TIME_OUT = 3.0 # waits for 3 seconds to verify if the sphere moved
helper.init_idle()
# 1) Open level
helper.open_level("Physics", "RigidBody_StartAsleepWorks")
# 2) Enter game mode
helper.enter_game_mode(Tests.enter_game_mode)
# 3) Retrieve and validate entities
terrain_id = general.find_game_entity("Terrain")
sphere_id = general.find_game_entity("Sphere")
Report.critical_result(Tests.find_terrain, terrain_id.IsValid())
Report.critical_result(Tests.find_sphere, sphere_id.IsValid())
# 4) Get the initial x,y,z positions of the sphere
init_sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
# 5) Check gravity, start asleep values for the entity
is_gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", sphere_id)
Report.critical_result(Tests.gravity_enabled, is_gravity_enabled)
is_awake = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsAwake", sphere_id)
Report.critical_result(Tests.start_asleep_enabled, not is_awake)
# 6) Wait until timeout to check if sphere position changed
general.idle_wait(TIME_OUT)
# 7) Check the final x, y, z positions of the sphere
final_sphere_pos = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", sphere_id)
Report.critical_result(Tests.sphere_position_fixed, init_sphere_pos.Equal(final_sphere_pos))
# 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(RigidBody_StartAsleepWorks)
@@ -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 : C4976206
# Test Case Title : Verify that when Gravity enables is checked, the object falls down due to gravity [sic]
# 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_terrain = ("Terrain entity found", "Terrain entity not found")
gravity_started_disabled = ("Gravity started disabled", "Gravity didn't start disabled")
gravity_set_enabled = ("Gravity has been enabled", "Gravity wasn't enabled")
ball_fell = ("Ball fell", "Ball didn't fall")
touched_ground = ("Ball touched the ground", "Ball did not touch the ground")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
# fmt: on
# Wait times defined in frames for waiting while in game mode
class Frames:
entity_load = 2
delta_frames = 1
def RigidBody_StartGravityEnabledWorks():
"""
Summary:
Runs automated test to verify that when the gravity enabled checkbox is ticked,
the object will fall down due to gravity.
Level Description:
A ball entity is suspended in the air with gravity disabled by default
Below the ball is a terrain entity with a PhysX Terrain component
Expected Behavior:
The level opens and by default gravity is not enabled on the ball.
When game mode is entered, the ball should not fall until gravity is enabled,
then the ball should fall and collide with the terrain.
Test Steps:
1) Open level and enter game mode
2) Find the entities
3) Make sure that gravity is turned off by default
4) Get the Z position before enabling gravity
5) Activate gravity
6) Check that the ball is falling towards the terrain
7) Check that there is a collision with the PhysX Terrain
8) 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
timeout_seconds = 3.0
helper.init_idle()
# 1) Open level and enter game mode
helper.open_level("Physics", "RigidBody_StartGravityEnabledWorks")
helper.enter_game_mode(Tests.enter_game_mode)
# 2) Retrieve entities
class Ball:
id = None
gravity_enabled = None
starting_height = None
current_height = None
fell = False
touched_ground = False
general.idle_wait_frames(Frames.entity_load)
ball_id = general.find_game_entity("RigidBody")
Ball.id = ball_id
Report.critical_result(Tests.find_ball, Ball.id.IsValid())
terrain_id = general.find_game_entity("Terrain")
Report.critical_result(Tests.find_terrain, terrain_id.IsValid())
# 3) Make sure gravity is off from the start
Ball.gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", Ball.id)
Report.result(Tests.gravity_started_disabled, not Ball.gravity_enabled)
# 4) Get the Z position before enabling the physics
Ball.starting_height = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Ball.id)
# 5) Activate gravity
general.idle_wait_frames(Frames.delta_frames)
Report.info("Enabling Gravity")
azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", Ball.id, True)
Ball.gravity_enabled = azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", Ball.id)
Report.critical_result(Tests.gravity_set_enabled, Ball.gravity_enabled)
# 6) Compare the Z position after enabling the gravity and check that there is a collision with the PhysX Terrain
def ball_moved_down():
Ball.current_height = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", Ball.id)
if Ball.current_height < (Ball.starting_height - 1):
Ball.fell = True
return True
return False
def on_collision_begin(args):
other_id = args[0]
if other_id.Equal(terrain_id):
Report.info("Touched ground")
Ball.touched_ground = True
handler = azlmbr.physics.CollisionNotificationBusHandler()
handler.connect(Ball.id)
handler.add_callback("OnCollisionBegin", on_collision_begin)
helper.wait_for_condition(lambda : (ball_moved_down() and Ball.touched_ground), timeout_seconds)
Report.info("Ball.start_height: {} Ball.current_height: {}".format(Ball.starting_height, Ball.current_height))
Report.result(Tests.ball_fell, Ball.fell)
Report.result(Tests.touched_ground, Ball.touched_ground)
# 8) 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(RigidBody_StartGravityEnabledWorks)