diff --git a/2d/physics_tests/README.md b/2d/physics_tests/README.md new file mode 100644 index 00000000..d0601c43 --- /dev/null +++ b/2d/physics_tests/README.md @@ -0,0 +1,18 @@ +# 2D Physics Tests + +This demo contains a series of tests for the 2D +physics engine. + +They can be used for different purpose: +- Functional tests to check for regressions and + behavior of the 2D physics engine +- Performance tests to evaluate performance + of the 2D physics engine + +Language: GDScript + +Renderer: GLES 2 + +## Screenshots + +![Screenshot](screenshots/screenshot.png) diff --git a/2d/physics_tests/assets/godot-head.png b/2d/physics_tests/assets/godot-head.png new file mode 100644 index 00000000..f1bbf7fa Binary files /dev/null and b/2d/physics_tests/assets/godot-head.png differ diff --git a/2d/physics_tests/assets/godot-head.png.import b/2d/physics_tests/assets/godot-head.png.import new file mode 100644 index 00000000..7659f2a0 --- /dev/null +++ b/2d/physics_tests/assets/godot-head.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/godot-head.png-cc6844293d74c4f45ec6c4ab08de67ee.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/godot-head.png" +dest_files=[ "res://.import/godot-head.png-cc6844293d74c4f45ec6c4ab08de67ee.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/2d/physics_tests/icon.png b/2d/physics_tests/icon.png new file mode 100644 index 00000000..65024fbc Binary files /dev/null and b/2d/physics_tests/icon.png differ diff --git a/2d/physics_tests/icon.png.import b/2d/physics_tests/icon.png.import new file mode 100644 index 00000000..96cbf462 --- /dev/null +++ b/2d/physics_tests/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/2d/physics_tests/main.tscn b/2d/physics_tests/main.tscn new file mode 100644 index 00000000..c7fe1681 --- /dev/null +++ b/2d/physics_tests/main.tscn @@ -0,0 +1,174 @@ +[gd_scene load_steps=10 format=2] + +[ext_resource path="res://utils/label_fps.gd" type="Script" id=1] +[ext_resource path="res://utils/label_version.gd" type="Script" id=2] +[ext_resource path="res://utils/label_engine.gd" type="Script" id=3] +[ext_resource path="res://tests_menu.gd" type="Script" id=4] +[ext_resource path="res://utils/label_test.gd" type="Script" id=5] +[ext_resource path="res://utils/container_log.gd" type="Script" id=10] +[ext_resource path="res://utils/scroll_log.gd" type="Script" id=11] +[ext_resource path="res://tests.gd" type="Script" id=12] + +[sub_resource type="StyleBoxFlat" id=1] +bg_color = Color( 0, 0, 0, 0.176471 ) + +[node name="Main" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 2 +script = ExtResource( 12 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TestsMenu" type="MenuButton" parent="."] +margin_left = 10.0 +margin_top = 10.0 +margin_right = 125.0 +margin_bottom = 30.0 +text = "TESTS" +flat = false +script = ExtResource( 4 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelControls" type="Label" parent="."] +margin_left = 157.0 +margin_top = 13.0 +margin_right = 646.0 +margin_bottom = 27.0 +text = "R - RESTART / D - TOGGLE COLLISION / F - TOGGLE FULL SCREEN / ESC - QUIT" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelFPS" type="Label" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +margin_top = -19.0 +margin_right = 50.0 +margin_bottom = -5.0 +text = "FPS: 0" +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelEngine" type="Label" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +margin_top = -39.0 +margin_right = 50.0 +margin_bottom = -25.0 +text = "Physics engine:" +script = ExtResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelVersion" type="Label" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +margin_top = -59.0 +margin_right = 50.0 +margin_bottom = -45.0 +text = "Godot Version:" +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelTest" type="Label" parent="."] +anchor_top = 1.0 +anchor_bottom = 1.0 +margin_left = 10.0 +margin_top = -79.0 +margin_right = 50.0 +margin_bottom = -65.0 +text = "Test:" +script = ExtResource( 5 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PanelLog" type="Panel" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -428.0 +margin_top = -125.0 +custom_styles/panel = SubResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ButtonClear" type="Button" parent="PanelLog"] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -48.0 +margin_top = -25.0 +margin_right = -5.0 +margin_bottom = -5.0 +focus_mode = 0 +enabled_focus_mode = 0 +text = "clear" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="CheckBoxScroll" type="CheckBox" parent="PanelLog"] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -150.0 +margin_top = -27.0 +margin_right = -54.0 +margin_bottom = -3.0 +focus_mode = 0 +pressed = true +enabled_focus_mode = 0 +text = "auto-scroll" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ScrollLog" type="ScrollContainer" parent="PanelLog"] +margin_left = 10.0 +margin_top = 5.0 +margin_right = 418.0 +margin_bottom = 94.0 +scroll_horizontal_enabled = false +script = ExtResource( 11 ) +__meta__ = { +"_edit_use_anchors_": false +} +auto_scroll = true + +[node name="VBoxLog" type="VBoxContainer" parent="PanelLog/ScrollLog"] +margin_right = 408.0 +margin_bottom = 89.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +alignment = 2 +script = ExtResource( 10 ) + +[node name="LabelLog" type="Label" parent="PanelLog/ScrollLog/VBoxLog"] +margin_top = 75.0 +margin_right = 408.0 +margin_bottom = 89.0 +text = "Log start" +valign = 2 +max_lines_visible = 5 +__meta__ = { +"_edit_use_anchors_": false +} +[connection signal="pressed" from="PanelLog/ButtonClear" to="PanelLog/ScrollLog/VBoxLog" method="clear"] +[connection signal="toggled" from="PanelLog/CheckBoxScroll" to="PanelLog/ScrollLog" method="set_auto_scroll"] diff --git a/2d/physics_tests/project.godot b/2d/physics_tests/project.godot new file mode 100644 index 00000000..b3ee0402 --- /dev/null +++ b/2d/physics_tests/project.godot @@ -0,0 +1,75 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ { +"base": "MenuButton", +"class": "OptionMenu", +"language": "GDScript", +"path": "res://utils/option_menu.gd" +}, { +"base": "Node2D", +"class": "Test", +"language": "GDScript", +"path": "res://test.gd" +} ] +_global_script_class_icons={ +"OptionMenu": "", +"Test": "" +} + +[application] + +config/name="2D Physics Tests" +run/main_scene="res://main.tscn" +config/icon="res://icon.png" + +[autoload] + +Log="*res://utils/system_log.gd" +System="*res://utils/system.gd" + +[debug] + +gdscript/warnings/return_value_discarded=false + +[display] + +window/dpi/allow_hidpi=true +window/stretch/mode="2d" +window/stretch/aspect="expand" + +[input] + +toggle_full_screen={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":70,"unicode":0,"echo":false,"script":null) + ] +} +exit={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null) + ] +} +toggle_debug_collision={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null) + ] +} +restart_test={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":82,"unicode":0,"echo":false,"script":null) + ] +} + +[rendering] + +quality/driver/driver_name="GLES2" +environment/default_clear_color=Color( 0.184314, 0.184314, 0.184314, 1 ) +quality/filters/msaa=2 diff --git a/2d/physics_tests/screenshots/.gdignore b/2d/physics_tests/screenshots/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/2d/physics_tests/screenshots/screenshot.png b/2d/physics_tests/screenshots/screenshot.png new file mode 100644 index 00000000..80b94631 Binary files /dev/null and b/2d/physics_tests/screenshots/screenshot.png differ diff --git a/2d/physics_tests/test.gd b/2d/physics_tests/test.gd new file mode 100644 index 00000000..9e9099d9 --- /dev/null +++ b/2d/physics_tests/test.gd @@ -0,0 +1,92 @@ +class_name Test +extends Node2D + + +signal wait_done() + +var _timer +var _timer_started = false + +var _wait_physics_ticks_counter = 0 + + +class Line: + var pos_start + var pos_end + var color + +var _lines = [] + + +func _physics_process(_delta): + if (_wait_physics_ticks_counter > 0): + _wait_physics_ticks_counter -= 1 + if (_wait_physics_ticks_counter == 0): + emit_signal("wait_done") + + +func _draw(): + for line in _lines: + draw_line(line.pos_start, line.pos_end, line.color, 1.5) + + +func add_line(pos_start, pos_end, color): + var line = Line.new() + line.pos_start = pos_start + line.pos_end = pos_end + line.color = color + _lines.push_back(line) + update() + + +func clear_lines(): + _lines.clear() + update() + + +func create_rigidbody_box(size): + var template_shape = RectangleShape2D.new() + template_shape.extents = 0.5 * size + + var template_collision = CollisionShape2D.new() + template_collision.shape = template_shape + + var template_body = RigidBody2D.new() + template_body.add_child(template_collision) + + return template_body + + +func start_timer(timeout): + if _timer == null: + _timer = Timer.new() + _timer.one_shot = true + add_child(_timer) + _timer.connect("timeout", self, "_on_timer_done") + else: + cancel_timer() + + _timer.start(timeout) + _timer_started = true + + return _timer + + +func cancel_timer(): + if _timer_started: + _timer.paused = true + _timer.emit_signal("timeout") + _timer.paused = false + + +func is_timer_canceled(): + return _timer.paused + + +func wait_for_physics_ticks(tick_count): + _wait_physics_ticks_counter = tick_count + return self + + +func _on_timer_done(): + _timer_started = false diff --git a/2d/physics_tests/tests.gd b/2d/physics_tests/tests.gd new file mode 100644 index 00000000..43eb805e --- /dev/null +++ b/2d/physics_tests/tests.gd @@ -0,0 +1,35 @@ +extends Node + + +var _tests = [ + { + "id": "Functional Tests/Shapes", + "path": "res://tests/functional/test_shapes.tscn", + }, + { + "id": "Functional Tests/Box Stack", + "path": "res://tests/functional/test_stack.tscn", + }, + { + "id": "Functional Tests/Box Pyramid", + "path": "res://tests/functional/test_pyramid.tscn", + }, + { + "id": "Functional Tests/Raycasting", + "path": "res://tests/functional/test_raycasting.tscn", + }, + { + "id": "Performance Tests/Broadphase", + "path": "res://tests/performance/test_perf_broadphase.tscn", + }, + { + "id": "Performance Tests/Contacts", + "path": "res://tests/performance/test_perf_contacts.tscn", + }, +] + + +func _ready(): + var test_menu = $TestsMenu + for test in _tests: + test_menu.add_test(test.id, test.path) diff --git a/2d/physics_tests/tests/dynamic_box.tscn b/2d/physics_tests/tests/dynamic_box.tscn new file mode 100644 index 00000000..716f9309 --- /dev/null +++ b/2d/physics_tests/tests/dynamic_box.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=2] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 20, 20 ) + +[node name="StackBox" type="RigidBody2D"] +position = Vector2( -180, -20 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource( 1 ) diff --git a/2d/physics_tests/tests/functional/test_pyramid.gd b/2d/physics_tests/tests/functional/test_pyramid.gd new file mode 100644 index 00000000..8291b246 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_pyramid.gd @@ -0,0 +1,41 @@ +extends Test + + +export(int, 1, 100) var height = 10 +export(Vector2) var box_size = Vector2(40.0, 40.0) +export(Vector2) var box_spacing = Vector2(0.0, 0.0) + + +func _ready(): + _create_pyramid() + + +func _create_pyramid(): + var root_node = $Pyramid + + var template_body = create_rigidbody_box(box_size) + + var pos_y = -0.5 * box_size.y - box_spacing.y + + for level in height: + var level_index = height - level - 1 + var num_boxes = 2 * level_index + 1 + + var row_node = Node2D.new() + row_node.position = Vector2(0.0, pos_y) + row_node.name = "Row%02d" % (level + 1) + root_node.add_child(row_node) + + var pos_x = -0.5 * (num_boxes - 1) * (box_size.x + box_spacing.x) + + for box_index in num_boxes: + var box = template_body.duplicate() + box.position = Vector2(pos_x, 0.0) + box.name = "Box%02d" % (box_index + 1) + row_node.add_child(box) + + pos_x += box_size.x + box_spacing.x + + pos_y -= box_size.y + box_spacing.y + + template_body.queue_free() diff --git a/2d/physics_tests/tests/functional/test_pyramid.tscn b/2d/physics_tests/tests/functional/test_pyramid.tscn new file mode 100644 index 00000000..f229c410 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_pyramid.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://tests/functional/test_pyramid.gd" type="Script" id=1] +[ext_resource path="res://tests/static_scene_flat.tscn" type="PackedScene" id=2] + +[node name="Test" type="Node2D"] +script = ExtResource( 1 ) + +[node name="Pyramid" type="Node2D" parent="."] +position = Vector2( 512, 500 ) + +[node name="StaticSceneFlat" parent="." instance=ExtResource( 2 )] diff --git a/2d/physics_tests/tests/functional/test_raycasting.gd b/2d/physics_tests/tests/functional/test_raycasting.gd new file mode 100644 index 00000000..c05c3653 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_raycasting.gd @@ -0,0 +1,69 @@ +extends Test + + +var _do_raycasts = false + + +func _ready(): + yield(start_timer(0.5), "timeout") + if is_timer_canceled(): + return + + _do_raycasts = true + + +func _physics_process(_delta): + if !_do_raycasts: + return + + _do_raycasts = false + + Log.print_log("* Start Raycasting...") + + clear_lines() + + for shape in $Shapes.get_children(): + var body = shape as PhysicsBody2D + var space_state = body.get_world_2d().direct_space_state + + Log.print_log("* Testing: %s" % body.name) + + var center = body.global_transform.origin + + # Raycast entering from the top. + var res = _add_raycast(space_state, center - Vector2(0, 100), center) + Log.print_log("Raycast in: %s" % ("HIT" if res else "NO HIT")) + + # Raycast exiting from inside. + center.x -= 20 + res = _add_raycast(space_state, center, center + Vector2(0, 200)) + Log.print_log("Raycast out: %s" % ("HIT" if res else "NO HIT")) + + # Raycast all inside. + center.x += 40 + res = _add_raycast(space_state, center, center + Vector2(0, 40)) + Log.print_log("Raycast inside: %s" % ("HIT" if res else "NO HIT")) + + if body.name.ends_with("ConcavePolygon"): + # Raycast inside an internal face. + center.x += 20 + res = _add_raycast(space_state, center, center + Vector2(0, 40)) + Log.print_log("Raycast inside face: %s" % ("HIT" if res else "NO HIT")) + + +func _add_raycast(space_state, pos_start, pos_end): + var result = space_state.intersect_ray(pos_start, pos_end) + var color + if result: + color = Color.green + else: + color = Color.red.darkened(0.5) + + # Draw raycast line. + add_line(pos_start, pos_end, color) + + # Draw raycast arrow. + add_line(pos_end, pos_end + Vector2(-5, -10), color) + add_line(pos_end, pos_end + Vector2(5, -10), color) + + return result diff --git a/2d/physics_tests/tests/functional/test_raycasting.tscn b/2d/physics_tests/tests/functional/test_raycasting.tscn new file mode 100644 index 00000000..63990b92 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_raycasting.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://assets/godot-head.png" type="Texture" id=1] +[ext_resource path="res://tests/functional/test_raycasting.gd" type="Script" id=2] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 40, 60 ) + +[sub_resource type="CapsuleShape2D" id=2] +radius = 30.0 +height = 50.0 + +[sub_resource type="CircleShape2D" id=3] +radius = 60.0 + +[node name="Test" type="Node2D"] +script = ExtResource( 2 ) + +[node name="Shapes" type="Node2D" parent="."] +z_index = -1 +z_as_relative = false + +[node name="RigidBodyRectangle" type="RigidBody2D" parent="Shapes"] +position = Vector2( 114.877, 248.76 ) +mode = 3 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyRectangle"] +rotation = -1.19206 +scale = Vector2( 1.5, 1.5 ) +shape = SubResource( 1 ) + +[node name="RigidBodyCapsule" type="RigidBody2D" parent="Shapes"] +position = Vector2( 313.583, 261.204 ) +mode = 3 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyCapsule"] +rotation = -0.202458 +scale = Vector2( 1.5, 1.5 ) +shape = SubResource( 2 ) + +[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="Shapes"] +position = Vector2( 514.899, 252.771 ) +mode = 3 + +[node name="GodotIcon" type="Sprite" parent="Shapes/RigidBodyConcavePolygon"] +modulate = Color( 1, 1, 1, 0.392157 ) +texture = ExtResource( 1 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConcavePolygon"] +polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 ) + +[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="Shapes"] +position = Vector2( 738.975, 252.771 ) +mode = 3 + +[node name="GodotIcon" type="Sprite" parent="Shapes/RigidBodyConvexPolygon"] +modulate = Color( 1, 1, 1, 0.392157 ) +texture = ExtResource( 1 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConvexPolygon"] +polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 ) + +[node name="RigidBodySphere" type="RigidBody2D" parent="Shapes"] +position = Vector2( 917.136, 270.868 ) +mode = 3 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodySphere"] +shape = SubResource( 3 ) diff --git a/2d/physics_tests/tests/functional/test_shapes.tscn b/2d/physics_tests/tests/functional/test_shapes.tscn new file mode 100644 index 00000000..93cce1f1 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_shapes.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=7 format=2] + +[ext_resource path="res://assets/godot-head.png" type="Texture" id=1] +[ext_resource path="res://test.gd" type="Script" id=2] +[ext_resource path="res://tests/static_scene.tscn" type="PackedScene" id=6] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 20, 30 ) + +[sub_resource type="CapsuleShape2D" id=2] +radius = 20.0 +height = 30.0 + +[sub_resource type="CircleShape2D" id=3] +radius = 30.0 + +[node name="Test" type="Node2D"] +script = ExtResource( 2 ) + +[node name="DynamicShapes" type="Node2D" parent="."] + +[node name="RigidBodyRectangle" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 96, 127 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyRectangle"] +rotation = 0.675442 +shape = SubResource( 1 ) + +[node name="RigidBodyCapsule" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 270.165, 139.444 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyCapsule"] +rotation = -0.202458 +shape = SubResource( 2 ) + +[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 683.614, 132.749 ) + +[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConcavePolygon"] +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 1 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConcavePolygon"] +scale = Vector2( 0.5, 0.5 ) +polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 ) + +[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 473.536, 134.336 ) + +[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConvexPolygon"] +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 1 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConvexPolygon"] +scale = Vector2( 0.5, 0.5 ) +polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 ) + +[node name="RigidBodySphere" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 919.968, 115.129 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodySphere"] +shape = SubResource( 3 ) + +[node name="StaticScene" parent="." instance=ExtResource( 6 )] diff --git a/2d/physics_tests/tests/functional/test_stack.gd b/2d/physics_tests/tests/functional/test_stack.gd new file mode 100644 index 00000000..7198b589 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_stack.gd @@ -0,0 +1,39 @@ +extends Test + + +export(int) var height = 10 +export(int) var width = 1 +export(Vector2) var box_size = Vector2(40.0, 40.0) +export(Vector2) var box_spacing = Vector2(0.0, 0.0) + + +func _ready(): + _create_stack() + + +func _create_stack(): + var root_node = $Stack + + var template_body = create_rigidbody_box(box_size) + + var pos_y = -0.5 * box_size.y - box_spacing.y + + for level in height: + var row_node = Node2D.new() + row_node.position = Vector2(0.0, pos_y) + row_node.name = "Row%02d" % (level + 1) + root_node.add_child(row_node) + + var pos_x = -0.5 * (width - 1) * (box_size.x + box_spacing.x) + + for box_index in width: + var box = template_body.duplicate() + box.position = Vector2(pos_x, 0.0) + box.name = "Box%02d" % (box_index + 1) + row_node.add_child(box) + + pos_x += box_size.x + box_spacing.x + + pos_y -= box_size.y + box_spacing.y + + template_body.queue_free() diff --git a/2d/physics_tests/tests/functional/test_stack.tscn b/2d/physics_tests/tests/functional/test_stack.tscn new file mode 100644 index 00000000..d4009805 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_stack.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://tests/functional/test_stack.gd" type="Script" id=1] +[ext_resource path="res://tests/static_scene_flat.tscn" type="PackedScene" id=2] + +[node name="Test" type="Node2D"] +script = ExtResource( 1 ) + +[node name="Stack" type="Node2D" parent="."] +position = Vector2( 512, 500 ) + +[node name="StaticSceneFlat" parent="." instance=ExtResource( 2 )] diff --git a/2d/physics_tests/tests/performance/test_perf_broadphase.gd b/2d/physics_tests/tests/performance/test_perf_broadphase.gd new file mode 100644 index 00000000..21d5edaa --- /dev/null +++ b/2d/physics_tests/tests/performance/test_perf_broadphase.gd @@ -0,0 +1,149 @@ +extends Test + + +const BOX_SIZE = Vector2(40, 40) +const BOX_SPACE = Vector2(50, 50) + +export(int, 1, 1000) var row_size = 100 +export(int, 1, 1000) var column_size = 100 + +var _objects = [] + +var _log_physics = false +var _log_physics_time = 0 +var _log_physics_time_start = 0 + + +func _ready(): + _create_objects() + + _log_physics_start() + yield(wait_for_physics_ticks(5), "wait_done") + _log_physics_stop() + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _add_objects() + + _log_physics_start() + yield(wait_for_physics_ticks(5), "wait_done") + _log_physics_stop() + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _move_objects() + + _log_physics_start() + yield(wait_for_physics_ticks(5), "wait_done") + _log_physics_stop() + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _remove_objects() + + _log_physics_start() + yield(wait_for_physics_ticks(5), "wait_done") + _log_physics_stop() + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + Log.print_log("* Done.") + + +func _exit_tree(): + for object in _objects: + object.free() + + +func _physics_process(_delta): + if _log_physics: + var time = OS.get_ticks_usec() + var time_delta = time - _log_physics_time + var time_total = time - _log_physics_time_start + _log_physics_time = time + Log.print_log(" Physics Tick: %.3f ms (total = %.3f ms)" % [0.001 * time_delta, 0.001 * time_total]) + + +func _log_physics_start(): + _log_physics = true + _log_physics_time_start = OS.get_ticks_usec() + _log_physics_time = _log_physics_time_start + + +func _log_physics_stop(): + _log_physics = false + + +func _create_objects(): + _objects.clear() + + var template_body = create_rigidbody_box(BOX_SIZE) + template_body.gravity_scale = 0.0 + + Log.print_log("* Creating objects...") + var timer = OS.get_ticks_usec() + + var pos_x = -0.5 * (row_size - 1) * BOX_SPACE.x + + for row in row_size: + var pos_y = -0.5 * (column_size - 1) * BOX_SPACE.y + + for column in column_size: + var box = template_body.duplicate() + box.position = Vector2(pos_x, pos_y) + box.name = "Box%03d" % (row * column + 1) + _objects.push_back(box) + + pos_y += BOX_SPACE.y + + pos_x += BOX_SPACE.x + + timer = OS.get_ticks_usec() - timer + Log.print_log(" Create Time: %.3f ms" % (0.001 * timer)) + + template_body.queue_free() + + +func _add_objects(): + var root_node = $Objects + + Log.print_log("* Adding objects...") + var timer = OS.get_ticks_usec() + + for object in _objects: + root_node.add_child(object) + + timer = OS.get_ticks_usec() - timer + Log.print_log(" Add Time: %.3f ms" % (0.001 * timer)) + + +func _move_objects(): + Log.print_log("* Moving objects...") + var timer = OS.get_ticks_usec() + + for object in _objects: + object.position += BOX_SPACE + + timer = OS.get_ticks_usec() - timer + Log.print_log(" Move Time: %.3f ms" % (0.001 * timer)) + + +func _remove_objects(): + var root_node = $Objects + + Log.print_log("* Removing objects...") + var timer = OS.get_ticks_usec() + + for object in _objects: + root_node.remove_child(object) + + timer = OS.get_ticks_usec() - timer + Log.print_log(" Remove Time: %.3f ms" % (0.001 * timer)) diff --git a/2d/physics_tests/tests/performance/test_perf_broadphase.tscn b/2d/physics_tests/tests/performance/test_perf_broadphase.tscn new file mode 100644 index 00000000..0cc10fa1 --- /dev/null +++ b/2d/physics_tests/tests/performance/test_perf_broadphase.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://tests/performance/test_perf_broadphase.gd" type="Script" id=1] + +[node name="Test" type="Node2D"] +script = ExtResource( 1 ) + +[node name="Objects" type="Node2D" parent="."] +position = Vector2( 512, 300 ) diff --git a/2d/physics_tests/tests/performance/test_perf_contacts.gd b/2d/physics_tests/tests/performance/test_perf_contacts.gd new file mode 100644 index 00000000..23c50797 --- /dev/null +++ b/2d/physics_tests/tests/performance/test_perf_contacts.gd @@ -0,0 +1,161 @@ +extends Test + + +const OPTION_TYPE_ALL = "Shape type/All" +const OPTION_TYPE_RECTANGLE = "Shape type/Rectangle" +const OPTION_TYPE_SPHERE = "Shape type/Sphere" +const OPTION_TYPE_CAPSULE = "Shape type/Capsule" +const OPTION_TYPE_CONVEX_POLYGON = "Shape type/Convex Polygon" +const OPTION_TYPE_CONCAVE_POLYGON = "Shape type/Concave Polygon" +export(Array) var spawns = Array() + +export(int) var spawn_count = 100 +export(int, 1, 10) var spawn_multiplier = 5 + +var _object_templates = [] + + +func _ready(): + yield(start_timer(0.5), "timeout") + if is_timer_canceled(): + return + + while $DynamicShapes.get_child_count(): + var type_node = $DynamicShapes.get_child(0) + type_node.position = Vector2.ZERO + _object_templates.push_back(type_node) + $DynamicShapes.remove_child(type_node) + + $Options.add_menu_item(OPTION_TYPE_ALL) + $Options.add_menu_item(OPTION_TYPE_RECTANGLE) + $Options.add_menu_item(OPTION_TYPE_SPHERE) + $Options.add_menu_item(OPTION_TYPE_CAPSULE) + $Options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON) + $Options.add_menu_item(OPTION_TYPE_CONCAVE_POLYGON) + $Options.connect("option_selected", self, "_on_option_selected") + + _start_all_types() + + +func _exit_tree(): + for object_template in _object_templates: + object_template.free() + + +func _on_option_selected(option): + cancel_timer() + + _despawn_objects() + + match option: + OPTION_TYPE_ALL: + _start_all_types() + OPTION_TYPE_RECTANGLE: + _start_type(_find_type_index("Rectangle")) + OPTION_TYPE_SPHERE: + _start_type(_find_type_index("Sphere")) + OPTION_TYPE_CAPSULE: + _start_type(_find_type_index("Capsule")) + OPTION_TYPE_CONVEX_POLYGON: + _start_type(_find_type_index("ConvexPolygon")) + OPTION_TYPE_CONCAVE_POLYGON: + _start_type(_find_type_index("ConcavePolygon")) + + +func _find_type_index(type_name): + for type_index in _object_templates.size(): + var type_node = _object_templates[type_index] + if type_node.name.find(type_name) > -1: + return type_index + + Log.print_error("Invalid shape type: " + type_name) + return -1 + + +func _start_type(type_index): + if type_index < 0: + return + if type_index >= _object_templates.size(): + return + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _spawn_objects(type_index) + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _activate_objects() + + yield(start_timer(5.0), "timeout") + if is_timer_canceled(): + return + + _despawn_objects() + + Log.print_log("* Done.") + + +func _start_all_types(): + for type_index in _object_templates.size(): + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _spawn_objects(type_index) + + yield(start_timer(1.0), "timeout") + if is_timer_canceled(): + return + + _activate_objects() + + yield(start_timer(5.0), "timeout") + if is_timer_canceled(): + return + + _despawn_objects() + + Log.print_log("* Done.") + + +func _spawn_objects(type_index): + var template_node = _object_templates[type_index] + for spawn in spawns: + var spawn_parent = get_node(spawn) + + Log.print_log("* Spawning: " + template_node.name) + + for _index in range(spawn_multiplier): + for _node_index in spawn_count / spawn_multiplier: + var node = template_node.duplicate() as Node2D + spawn_parent.add_child(node) + + +func _activate_objects(): + var spawn_parent = $SpawnTarget1 + + Log.print_log("* Activating") + + for node_index in spawn_parent.get_child_count(): + var node = spawn_parent.get_child(node_index) as RigidBody2D + node.set_sleeping(false) + + +func _despawn_objects(): + for spawn in spawns: + var spawn_parent = get_node(spawn) + + if spawn_parent.get_child_count() == 0: + return + + Log.print_log("* Despawning") + + while spawn_parent.get_child_count(): + var node_index = spawn_parent.get_child_count() - 1 + var node = spawn_parent.get_child(node_index) + spawn_parent.remove_child(node) + node.queue_free() diff --git a/2d/physics_tests/tests/performance/test_perf_contacts.tscn b/2d/physics_tests/tests/performance/test_perf_contacts.tscn new file mode 100644 index 00000000..de40af28 --- /dev/null +++ b/2d/physics_tests/tests/performance/test_perf_contacts.tscn @@ -0,0 +1,70 @@ +[gd_scene load_steps=8 format=2] + +[ext_resource path="res://tests/static_scene.tscn" type="PackedScene" id=1] +[ext_resource path="res://tests/performance/test_perf_contacts.gd" type="Script" id=2] +[ext_resource path="res://assets/godot-head.png" type="Texture" id=3] +[ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=4] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 20, 30 ) + +[sub_resource type="CircleShape2D" id=2] +radius = 30.0 + +[sub_resource type="CapsuleShape2D" id=3] +radius = 20.0 +height = 30.0 + +[node name="Test" type="Node2D"] +script = ExtResource( 2 ) +spawns = [ NodePath("SpawnTarget1") ] + +[node name="Options" parent="." instance=ExtResource( 4 )] + +[node name="SpawnTarget1" type="Node2D" parent="."] +position = Vector2( 512, 400 ) + +[node name="StaticScene" parent="." instance=ExtResource( 1 )] +position = Vector2( 0, 125.017 ) + +[node name="DynamicShapes" type="Node2D" parent="."] + +[node name="RigidBodyRectangle" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 0, 1024 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyRectangle"] +shape = SubResource( 1 ) + +[node name="RigidBodySphere" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 100, 1024 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodySphere"] +shape = SubResource( 2 ) + +[node name="RigidBodyCapsule" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 200, 1024 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyCapsule"] +shape = SubResource( 3 ) + +[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 300, 1024 ) + +[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConvexPolygon"] +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 3 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConvexPolygon"] +scale = Vector2( 0.5, 0.5 ) +polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 ) + +[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="DynamicShapes"] +position = Vector2( 400, 1024 ) + +[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConcavePolygon"] +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 3 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConcavePolygon"] +scale = Vector2( 0.5, 0.5 ) +polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 ) diff --git a/2d/physics_tests/tests/static_scene.tscn b/2d/physics_tests/tests/static_scene.tscn new file mode 100644 index 00000000..0fdb2895 --- /dev/null +++ b/2d/physics_tests/tests/static_scene.tscn @@ -0,0 +1,10 @@ +[gd_scene format=2] + +[node name="StaticScene" type="Node2D"] + +[node name="StaticBodyPolygon" type="StaticBody2D" parent="."] +position = Vector2( -7.85718, 399.596 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBodyPolygon"] +build_mode = 1 +polygon = PoolVector2Array( 16.3331, -129.432, 154.006, -20.0078, 292.354, 30.3943, 447.054, 33.9161, 584.899, -14.7955, 751.156, -15.5179, 894.098, -65.4518, 1000.73, -209.127, 1037.77, -398.823, 1029.92, 253.327, 6.2309, 261.185, 7.35339, -398.823 ) diff --git a/2d/physics_tests/tests/static_scene_flat.tscn b/2d/physics_tests/tests/static_scene_flat.tscn new file mode 100644 index 00000000..f07a3f8c --- /dev/null +++ b/2d/physics_tests/tests/static_scene_flat.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=2] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 512, 50 ) + +[node name="StaticSceneFlat" type="Node2D"] + +[node name="StaticBodyPolygon" type="StaticBody2D" parent="."] +position = Vector2( 512, 550 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBodyPolygon"] +shape = SubResource( 1 ) diff --git a/2d/physics_tests/tests/test_options.tscn b/2d/physics_tests/tests/test_options.tscn new file mode 100644 index 00000000..dcceccdb --- /dev/null +++ b/2d/physics_tests/tests/test_options.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://utils/option_menu.gd" type="Script" id=1] + +[node name="Options" type="MenuButton"] +margin_left = 10.0 +margin_top = 106.719 +margin_right = 125.0 +margin_bottom = 126.719 +text = "TEST OPTIONS" +flat = false +align = 0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/2d/physics_tests/tests_menu.gd b/2d/physics_tests/tests_menu.gd new file mode 100644 index 00000000..bb3cad61 --- /dev/null +++ b/2d/physics_tests/tests_menu.gd @@ -0,0 +1,54 @@ +extends OptionMenu + + +class TestData: + var id + var scene_path + + +var _test_list = [] + +var _current_test = null +var _current_test_scene = null + + +func _ready(): + connect("option_selected", self, "_on_option_selected") + + +func _process(_delta): + if Input.is_action_just_pressed("restart_test"): + if _current_test: + _start_test(_current_test) + + +func add_test(id, scene_path): + var test_data = TestData.new() + test_data.id = id + test_data.scene_path = scene_path + _test_list.append(test_data) + + add_menu_item(id) + + +func _on_option_selected(item_path): + for test in _test_list: + if test.id == item_path: + _start_test(test) + + +func _start_test(test): + _current_test = test + + if _current_test_scene: + _current_test_scene.queue_free() + _current_test_scene = null + + Log.print_log("*** STARTING TEST: " + test.id) + var scene = load(test.scene_path) + _current_test_scene = scene.instance() + get_tree().root.add_child(_current_test_scene) + get_tree().root.move_child(_current_test_scene, 0) + + var label_test = get_node("../LabelTest") + label_test.test_name = test.id diff --git a/2d/physics_tests/utils/container_log.gd b/2d/physics_tests/utils/container_log.gd new file mode 100644 index 00000000..2baff145 --- /dev/null +++ b/2d/physics_tests/utils/container_log.gd @@ -0,0 +1,41 @@ +extends Control + + +const MAX_ENTRIES = 100 + +var _entry_template + + +func _enter_tree(): + Log.connect("entry_logged", self, "_on_log_entry") + + _entry_template = get_child(0) as Label + remove_child(_entry_template) + + +func _exit_tree(): + _entry_template.free() + + +func clear(): + while get_child_count(): + var entry = get_child(get_child_count() - 1) + remove_child(entry) + entry.queue_free() + + +func _on_log_entry(message, type): + var new_entry = _entry_template.duplicate() as Label + + new_entry.set_text(message) + if type == Log.LogType.ERROR: + new_entry.modulate = Color.red + else: + new_entry.modulate = Color.white + + if get_child_count() >= MAX_ENTRIES: + var first_entry = get_child(0) as Label + remove_child(first_entry) + first_entry.queue_free() + + add_child(new_entry) diff --git a/2d/physics_tests/utils/label_engine.gd b/2d/physics_tests/utils/label_engine.gd new file mode 100644 index 00000000..65fd7035 --- /dev/null +++ b/2d/physics_tests/utils/label_engine.gd @@ -0,0 +1,12 @@ +extends Label + + +func _process(_delta): + var engine_name = "" + match System.get_physics_engine(): + System.PhysicsEngine.GODOT_PHYSICS: + engine_name = "Godot Physics" + System.PhysicsEngine.OTHER: + var engine_setting = ProjectSettings.get_setting("physics/2d/physics_engine") + engine_name = "Other (%s)" % engine_setting + set_text("Physics engine: %s" % engine_name) diff --git a/2d/physics_tests/utils/label_fps.gd b/2d/physics_tests/utils/label_fps.gd new file mode 100644 index 00000000..6f2c6f30 --- /dev/null +++ b/2d/physics_tests/utils/label_fps.gd @@ -0,0 +1,5 @@ +extends Label + + +func _process(_delta): + set_text("FPS: %d" % Engine.get_frames_per_second()) diff --git a/2d/physics_tests/utils/label_test.gd b/2d/physics_tests/utils/label_test.gd new file mode 100644 index 00000000..d5de3d49 --- /dev/null +++ b/2d/physics_tests/utils/label_test.gd @@ -0,0 +1,13 @@ +extends Label + + +var test_name setget _set_test_name + + +func _ready(): + set_text("Select a test from the menu to start it") + + +func _set_test_name(value): + test_name = value + set_text("Test: %s" % test_name) diff --git a/2d/physics_tests/utils/label_version.gd b/2d/physics_tests/utils/label_version.gd new file mode 100644 index 00000000..b2185fb9 --- /dev/null +++ b/2d/physics_tests/utils/label_version.gd @@ -0,0 +1,5 @@ +extends Label + + +func _process(_delta): + set_text("Godot Version: %s" % Engine.get_version_info().string) diff --git a/2d/physics_tests/utils/option_menu.gd b/2d/physics_tests/utils/option_menu.gd new file mode 100644 index 00000000..a39c6c81 --- /dev/null +++ b/2d/physics_tests/utils/option_menu.gd @@ -0,0 +1,47 @@ +class_name OptionMenu +extends MenuButton + + +signal option_selected(item_path) + + +func add_menu_item(item_path): + var path_elements = item_path.split("/", false) + var path_element_count = path_elements.size() + assert(path_element_count > 0) + + var path = "" + var popup = get_popup() + for element_index in path_element_count - 1: + var popup_label = path_elements[element_index] + path += popup_label + "/" + popup = _add_popup(popup, path, popup_label) + + _add_item(popup, path_elements[path_element_count - 1]) + + +func _add_item(parent_popup, label): + parent_popup.add_item(label) + + +func _add_popup(parent_popup, path, label): + if parent_popup.has_node(label): + var popup_node = parent_popup.get_node(label) + var popup_menu = popup_node as PopupMenu + assert(popup_menu) + return popup_menu + + var popup_menu = PopupMenu.new() + popup_menu.name = label + + parent_popup.add_child(popup_menu) + parent_popup.add_submenu_item(label, label) + + popup_menu.connect("index_pressed", self, "_on_item_pressed", [popup_menu, path]) + + return popup_menu + + +func _on_item_pressed(item_index, popup_menu, path): + var item_path = path + popup_menu.get_item_text(item_index) + emit_signal("option_selected", item_path) diff --git a/2d/physics_tests/utils/scroll_log.gd b/2d/physics_tests/utils/scroll_log.gd new file mode 100644 index 00000000..d362a8f2 --- /dev/null +++ b/2d/physics_tests/utils/scroll_log.gd @@ -0,0 +1,14 @@ +extends ScrollContainer + + +export(bool) var auto_scroll = false setget set_auto_scroll + + +func _process(_delta): + if auto_scroll: + var scrollbar = get_v_scrollbar() + scrollbar.value = scrollbar.max_value + + +func set_auto_scroll(value): + auto_scroll = value diff --git a/2d/physics_tests/utils/system.gd b/2d/physics_tests/utils/system.gd new file mode 100644 index 00000000..9e78cf19 --- /dev/null +++ b/2d/physics_tests/utils/system.gd @@ -0,0 +1,50 @@ +extends Node + + +enum PhysicsEngine { + GODOT_PHYSICS, + OTHER, +} + +var _engine = PhysicsEngine.OTHER + + +func _enter_tree(): + get_tree().debug_collisions_hint = true + + var engine_string = ProjectSettings.get_setting("physics/2d/physics_engine") + match engine_string: + "DEFAULT": + _engine = PhysicsEngine.GODOT_PHYSICS + "GodotPhysics": + _engine = PhysicsEngine.GODOT_PHYSICS + _: + _engine = PhysicsEngine.OTHER + + +func _process(_delta): + if Input.is_action_just_pressed("toggle_full_screen"): + OS.window_fullscreen = not OS.window_fullscreen + + if Input.is_action_just_pressed("toggle_debug_collision"): + var debug_collision_enabled = not _is_debug_collision_enabled() + _set_debug_collision_enabled(debug_collision_enabled) + if debug_collision_enabled: + Log.print_log("Debug Collision ON") + else: + Log.print_log("Debug Collision OFF") + + if Input.is_action_just_pressed("exit"): + get_tree().quit() + + +func get_physics_engine(): + return _engine + + +func _set_debug_collision_enabled(enabled): + get_tree().debug_collisions_hint = enabled + + +func _is_debug_collision_enabled(): + return get_tree().debug_collisions_hint diff --git a/2d/physics_tests/utils/system_log.gd b/2d/physics_tests/utils/system_log.gd new file mode 100644 index 00000000..dc094d25 --- /dev/null +++ b/2d/physics_tests/utils/system_log.gd @@ -0,0 +1,20 @@ +extends Node + + +enum LogType { + LOG, + ERROR, +} + +signal entry_logged(message, type) + + +func print_log(message): + print(message) + emit_signal("entry_logged", message, LogType.LOG) + + +func print_error(message): + push_error(message) + printerr(message) + emit_signal("entry_logged", message, LogType.ERROR) diff --git a/3d/physics_tests/tests/functional/test_raycasting.gd b/3d/physics_tests/tests/functional/test_raycasting.gd new file mode 100644 index 00000000..e69de29b