You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
14 KiB
Python
293 lines
14 KiB
Python
"""
|
|
Copyright (c) Contributors to the Open 3D Engine Project.
|
|
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
|
|
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
"""
|
|
|
|
# Test case ID : 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 C4976200_RigidBody_AngularDampingObjectRotation():
|
|
"""
|
|
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
|
|
|
|
import ImportPathHelper as imports
|
|
|
|
imports.init()
|
|
|
|
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", "C4976200_RigidBody_AngularDampingObjectRotation")
|
|
|
|
# 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__":
|
|
import ImportPathHelper as imports
|
|
imports.init()
|
|
|
|
from editor_python_test_tools.utils import Report
|
|
Report.start_test(C4976200_RigidBody_AngularDampingObjectRotation)
|