diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py index b64fbb5656..fb2744deda 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py @@ -91,4 +91,16 @@ class TestAutomation(TestAutomationBase): @revert_physics_config def test_C15425929_Undo_Redo(self, request, workspace, editor, launcher_platform): from .tests import Physics_UndoRedoWorksOnEntityWithPhysComponents as test_module - self._run_test(request, workspace, editor, test_module) \ No newline at end of file + self._run_test(request, workspace, editor, test_module) + + @pytest.mark.GROUP_tick + @pytest.mark.xfail(reason="Test still under development.") + def test_Tick_InterpolatedRigidBodyMotionIsSmooth(self, request, workspace, editor, launcher_platform): + from .tests.tick import Tick_InterpolatedRigidBodyMotionIsSmooth as test_module + self._run_test(request, workspace, editor, test_module) + + @pytest.mark.GROUP_tick + @pytest.mark.xfail(reason="Test still under development.") + def test_Tick_CharacterGameplayComponentMotionIsSmooth(self, request, workspace, editor, launcher_platform): + from .tests.tick import Tick_CharacterGameplayComponentMotionIsSmooth as test_module + self._run_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py new file mode 100644 index 0000000000..fe718d7247 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py @@ -0,0 +1,99 @@ +""" +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 Title : Verify that an entity with a character gameplay component moves smoothly. +""" + + +# fmt: off +class Tests(): + create_entity = ("Created test entity", "Failed to create test entity") + character_controller_added = ("Added PhysX Character Controller component", "Failed to add PhysX Character Controller component") + character_gameplay_added = ("Added PhysX Character Gameplay component", "Failed to add PhysX Character Gameplay component") + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Failed to exit game mode") + character_motion_smooth = ("Character motion passed smoothness threshold", "Failed to meet smoothness threshold for character motion") +# fmt: on + + +def Tick_CharacterGameplayComponentMotionIsSmooth(): + """ + Summary: + Create entity with PhysX Character Controller and PhysX Character Gameplay components. + Verify that the motion of the character controller under gravity is smooth. + + Expected Behavior: + 1) The motion of the character controller under gravity is a smooth curve, rather than an erratic/jittery movement. + + Test Steps: + 1) Load the empty level + 2) Create an entity + 3) Add a PhysX Character Controller Component and PhysX Character Gameplay component + 4) Enter game mode and collect data for the character controller's z co-ordinate and the time values for a series of frames + 5) Check if the motion of the character controller was sufficiently smooth + + :return: None + """ + # imports + import os + import azlmbr.legacy.general as general + import azlmbr.math as math + from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.asset_utils import Asset + import numpy as np + + # constants + COEFFICIENT_OF_DETERMINATION_THRESHOLD = 1 - 1e-4 # curves with values below this are not considered sufficiently smooth + + helper.init_idle() + # 1) Load the empty level + helper.open_level("", "Base") + + # 2) Create an entity + test_entity = Entity.create_editor_entity("test_entity") + Report.result(Tests.create_entity, test_entity.id.IsValid()) + + azlmbr.components.TransformBus( + azlmbr.bus.Event, "SetWorldTranslation", test_entity.id, math.Vector3(0.0, 0.0, 0.0)) + + # 3) Add character controller and character gameplay components + character_controller_component = test_entity.add_component("PhysX Character Controller") + Report.result(Tests.character_controller_added, test_entity.has_component("PhysX Character Controller")) + character_gameplay_component = test_entity.add_component("PhysX Character Gameplay") + Report.result(Tests.character_gameplay_added, test_entity.has_component("PhysX Character Gameplay")) + + # 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + t = [] + z = [] + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + game_entity_id = general.find_game_entity("test_entity") + for frame in range(100): + t.append(azlmbr.components.TickRequestBus(azlmbr.bus.Broadcast, "GetTimeAtCurrentTick").GetSeconds()) + z.append(azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", game_entity_id)) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 5) Test that the z vs t curve is sufficiently smooth (if the interpolation is not working well, the curve will be less smooth) + # normalize the t and z data + t = np.array(t) - np.mean(t) + z = np.array(z) - np.mean(z) + # fit a polynomial to the z vs t curve + fit = np.poly1d(np.polyfit(t, z, 4)) + residual = fit(t) - z + # calculate the coefficient of determination (a measure of how closely the polynomial curve fits the data) + # if the coefficient is very close to 1, then the curve fits the data very well, suggesting that the rigid body motion is smooth + # if the coefficient is significantly less than 1, then the z values vary more erratically relative to the smooth curve, + # indicating that the motion of the rigid body is not smooth + coefficient_of_determination = (1 - np.sum(residual * residual) / np.sum(z * z)) + Report.result(Tests.character_motion_smooth, bool(coefficient_of_determination > COEFFICIENT_OF_DETERMINATION_THRESHOLD)) + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Tick_CharacterGameplayComponentMotionIsSmooth) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py new file mode 100644 index 0000000000..19e79355d9 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py @@ -0,0 +1,98 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +Test Case Title : Verify that a rigid body with "Interpolate motion" option selected moves smoothly. +""" + + +# fmt: off +class Tests(): + create_entity = ("Created test entity", "Failed to create test entity") + rigid_body_added = ("Added PhysX Rigid Body component", "Failed to add PhysX Rigid Body component") + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Failed to exit game mode") + rigid_body_smooth = ("Rigid body motion passed smoothness threshold", "Failed to meet smoothness threshold for rigid body motion") +# fmt: on + + +def Tick_InterpolatedRigidBodyMotionIsSmooth(): + """ + Summary: + Create entity with PhysX Rigid Body component and turn on the Interpolate motion setting. + Verify that the position of the rigid body varies smoothly with time. + + Expected Behavior: + 1) The motion of the rigid body under gravity is a smooth curve, rather than an erratic/jittery movement. + + Test Steps: + 1) Load the empty level + 2) Create an entity + 3) Add rigid body component + 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + 5) Check if the motion of the rigid body was sufficiently smooth + + :return: None + """ + # imports + import os + import azlmbr.legacy.general as general + import azlmbr.math as math + from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.asset_utils import Asset + import numpy as np + + # constants + COEFFICIENT_OF_DETERMINATION_THRESHOLD = 1 - 1e-4 # curves with values below this are not considered sufficiently smooth + + helper.init_idle() + # 1) Load the empty level + helper.open_level("", "Base") + + # 2) Create an entity + test_entity = Entity.create_editor_entity("test_entity") + Report.result(Tests.create_entity, test_entity.id.IsValid()) + + azlmbr.components.TransformBus( + azlmbr.bus.Event, "SetWorldTranslation", test_entity.id, math.Vector3(0.0, 0.0, 0.0)) + + # 3) Add rigid body component + rigid_body_component = test_entity.add_component("PhysX Rigid Body") + rigid_body_component.set_component_property_value("Configuration|Interpolate motion", True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", test_entity.id, 0.0) + Report.result(Tests.rigid_body_added, test_entity.has_component("PhysX Rigid Body")) + + # 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + t = [] + z = [] + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + game_entity_id = general.find_game_entity("test_entity") + for frame in range(100): + t.append(azlmbr.components.TickRequestBus(azlmbr.bus.Broadcast, "GetTimeAtCurrentTick").GetSeconds()) + z.append(azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", game_entity_id)) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 5) Test that the z vs t curve is sufficiently smooth (if the interpolation is not working well, the curve will be less smooth) + # normalize the t and z data + t = np.array(t) - np.mean(t) + z = np.array(z) - np.mean(z) + # fit a polynomial to the z vs t curve + fit = np.poly1d(np.polyfit(t, z, 4)) + residual = fit(t) - z + # calculate the coefficient of determination (a measure of how closely the polynomial curve fits the data) + # if the coefficient is very close to 1, then the curve fits the data very well, suggesting that the rigid body motion is smooth + # if the coefficient is significantly less than 1, then the z values vary more erratically relative to the smooth curve, + # indicating that the motion of the rigid body is not smooth + coefficient_of_determination = (1 - np.sum(residual * residual) / np.sum(z * z)) + Report.result(Tests.rigid_body_smooth, bool(coefficient_of_determination > COEFFICIENT_OF_DETERMINATION_THRESHOLD)) + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Tick_InterpolatedRigidBodyMotionIsSmooth) diff --git a/pytest.ini b/pytest.ini index 65c93e0eb2..a229b19a4d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -22,4 +22,5 @@ markers = SUITE_smoke: Tiny, quick tests of fundamental operation (tests with no SUITE_awsi: Time consuming AWS integration end-to-end tests # secondary markers which may appear alongisde a suite marker: REQUIRES_gpu: Tests which require a physical GPU + GROUP_tick: Tests which verify if systems update correctly with system ticks (for example, physics bodies should move smoothly) # custom markers not listed above will cause pytest to emit a typo warning