123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- """
- 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
- """
- #fmt: off
- class Tests:
- create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity")
- create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity")
- create_test_ball = ("Ball created successfully", "Failed to create Ball")
- box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions")
- shape_changed = ("Shape changed successfully", "Failed Shape change")
- entity_added = ("Entity added successfully", "Failed Entity add")
- frequency_changed = ("Frequency changed successfully", "Failed Frequency change")
- shape_set = ("Shape set to Sphere successfully", "Failed to set Sphere shape")
- test_collision = ("Ball collided with terrain", "Ball failed to collide with terrain")
- no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings")
- #fmt: on
- def Terrain_SupportsPhysics():
- """
- Summary:
- General validation that terrain physics heightfields work within the context of the PhysX integration.
- Expected Behavior:
- The terrain system is initialized, gets a hilly physics heightfield generated, and detects a collision between a sphere and a hill
- in a timely fashion without errors or warnings.
- Test Steps:
- 1) Load the base level
- 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
- 2a) Create a ball at 600.0, 600.0, 46.0 - This position intersects the terrain when it has hills generated correctly.
- 3) Start the Tracer to catch any errors and warnings
- 4) Change the Axis Aligned Box Shape dimensions
- 5) Set the Reference Shape to TestEntity1
- 6) Set the FastNoise gradient frequency to 0.01
- 7) Set the Gradient List to TestEntity2
- 8) Set the PhysX Primitive Collider to Sphere mode
- 9) Set the Rigid Body to start with no gravity enabled, so that it sits in place, intersecting the expected terrain
- 10) Enter game mode and test if the ball detects the heightfield intersection within 3 seconds
- 11) Verify there are no errors and warnings in the logs
- :return: None
- """
- from editor_python_test_tools.wait_utils import PrefabWaiter
- from editor_python_test_tools.utils import TestHelper as helper
- from editor_python_test_tools.utils import Report, Tracer
- from consts.physics import PHYSX_PRIMITIVE_COLLIDER
- import editor_python_test_tools.hydra_editor_utils as hydra
- import azlmbr.math as azmath
- import azlmbr.legacy.general as general
- import azlmbr.bus as bus
- import azlmbr.editor as editor
- import math
- SET_BOX_X_SIZE = 1024.0
- SET_BOX_Y_SIZE = 1024.0
- SET_BOX_Z_SIZE = 100.0
- # 1) Load the level
- hydra.open_base_level()
- #1a) Load the level components
- hydra.add_level_component("Terrain World")
- hydra.add_level_component("Terrain World Renderer")
- # 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
- entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"]
- entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"]
- ball_components_to_add = ["Sphere Shape", PHYSX_PRIMITIVE_COLLIDER, "PhysX Dynamic Rigid Body"]
- terrain_spawner_entity = hydra.Entity("TestEntity1")
- terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)
- Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid())
- height_provider_entity = hydra.Entity("TestEntity2")
- height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id)
- Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid())
- # 2a) Create a ball at 600.0, 600.0, 46.0 - The ball is created as a collider with a Rigid Body, but at rest and without gravity,
- # so that it will stay in place. This specific location is chosen because the ball should intersect the terrain.
- ball = hydra.Entity("Ball")
- ball.create_entity(azmath.Vector3(600.0, 600.0, 46.0), ball_components_to_add)
- Report.result(Tests.create_test_ball, ball.id.IsValid())
- # Give everything a chance to finish initializing.
- general.idle_wait_frames(1)
- # 3) Start the Tracer to catch any errors and warnings
- with Tracer() as section_tracer:
- # 4) Change the Axis Aligned Box Shape dimensions
- box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE)
- terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
- box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions")
- Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions)
-
- # 5) Set the Reference Shape to TestEntity1
- height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id)
- entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id")
- Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id)
- # 6) Set the FastNoise Gradient frequency to 0.01
- Frequency = 0.01
- height_provider_entity.get_set_test(2, "Configuration|Frequency", Frequency)
- FrequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency")
- Report.result(Tests.frequency_changed, math.isclose(Frequency, FrequencyVal, abs_tol = 0.00001))
- # 7) Set the Gradient List to TestEntity2
- propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2])
- propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id)
- checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0)
- Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id)
- # 7a) Disable and Enable the Terrain Height Gradient List so that the change to the container is recognized
- editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]])
- PrefabWaiter.wait_for_propagation()
-
- # 8) Set the PhysX Primitive Collider to Sphere mode
- shape = 0
- hydra.get_set_test(ball, 1, "Shape Configuration|Shape", shape)
- setShape = hydra.get_component_property_value(ball.components[1], "Shape Configuration|Shape")
- Report.result(Tests.shape_set, shape == setShape)
- # 9) Set the PhysX Rigid Body to not use gravity
- hydra.get_set_test(ball, 2, "Configuration|Gravity enabled", False)
- general.enter_game_mode()
- general.idle_wait_frames(1)
- # 10) Enter game mode and test if the ball detects the heightfield collision within 5 seconds
- TIMEOUT = 5.0
- class Collider:
- id = general.find_game_entity("Ball")
- touched_ground = False
- terrain_id = general.find_game_entity("TestEntity1")
-
- def on_collision_persist(args):
- other_id = args[0]
- if other_id.Equal(terrain_id):
- Report.info("Ball intersected with heightfield")
- Collider.touched_ground = True
- handler = azlmbr.physics.CollisionNotificationBusHandler()
- handler.connect(Collider.id)
- handler.add_callback("OnCollisionPersist", on_collision_persist)
- helper.wait_for_condition(lambda: Collider.touched_ground, TIMEOUT)
- Report.result(Tests.test_collision, Collider.touched_ground)
- general.exit_game_mode()
- # 11) Verify there are no errors and warnings in the logs
- helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0)
- for error_info in section_tracer.errors:
- Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
- for assert_info in section_tracer.asserts:
- Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
-
- if __name__ == "__main__":
- from editor_python_test_tools.utils import Report
- Report.start_test(Terrain_SupportsPhysics)
|