diff --git a/2d/physics_tests/tests.gd b/2d/physics_tests/tests.gd index cd735be864..9bb68c5169 100644 --- a/2d/physics_tests/tests.gd +++ b/2d/physics_tests/tests.gd @@ -18,6 +18,10 @@ var _tests = [ "id": "Functional Tests/Collision Pairs", "path": "res://tests/functional/test_collision_pairs.tscn", }, + { + "id": "Functional Tests/One Way Collision", + "path": "res://tests/functional/test_one_way_collision.tscn", + }, { "id": "Functional Tests/Joints", "path": "res://tests/functional/test_joints.tscn", diff --git a/2d/physics_tests/tests/functional/test_one_way_collision.gd b/2d/physics_tests/tests/functional/test_one_way_collision.gd new file mode 100644 index 0000000000..fcbc9611b0 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_one_way_collision.gd @@ -0,0 +1,277 @@ +extends Test +tool + +const OPTION_OBJECT_TYPE_RIGIDBODY = "Object type/Rigid body (1)" +const OPTION_OBJECT_TYPE_KINEMATIC = "Object type/Kinematic body (2)" + +const OPTION_TEST_CASE_ALL_ANGLES = "Test case/Around the clock (0)" + +const TEST_ALL_ANGLES_STEP = 15.0 +const TEST_ALL_ANGLES_MAX = 344.0 + +export(float, 32, 128, 0.1) var _platform_size = 64.0 setget _set_platform_size +export(float, 0, 360, 0.1) var _platform_angle = 0.0 setget _set_platform_angle +export(float, 0, 360, 0.1) var _body_angle = 0.0 setget _set_rigidbody_angle +export(Vector2) var _body_velocity = Vector2(400.0, 0.0) +export(bool) var _use_kinematic_body = false + +var _rigid_body_template = null +var _kinematic_body_template = null +var _moving_body = null + +var _contact_detected = false +var _target_entered = false +var _test_passed = false +var _test_step = 0 + +var _test_all_angles = false +var _lock_controls = false + + +func _ready(): + if not Engine.editor_hint: + $Options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, not _use_kinematic_body, true) + $Options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC, true, _use_kinematic_body, true) + + $Options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES) + + $Options.connect("option_selected", self, "_on_option_selected") + + $Controls/PlatformSize/HSlider.value = _platform_size + $Controls/PlatformAngle/HSlider.value = _platform_angle + $Controls/BodyAngle/HSlider.value = _body_angle + + $TargetArea2D.connect("body_entered", self, "_on_target_entered") + $Timer.connect("timeout", self, "_on_timeout") + + _rigid_body_template = $RigidBody2D + remove_child(_rigid_body_template) + + _kinematic_body_template = $KinematicBody2D + remove_child(_kinematic_body_template) + + _start_test() + + +func _process(_delta): + if not Engine.editor_hint: + if Input.is_action_just_pressed("ui_accept"): + _reset_test(false) + + +func _physics_process(_delta): + if not Engine.editor_hint: + if _moving_body and _use_kinematic_body: + _moving_body.move_and_slide(_body_velocity) + if _moving_body.get_slide_count() > 0: + var colliding_body = _moving_body.get_slide_collision(0).collider + _on_contact_detected(colliding_body) + + +func _input(event): + var key_event = event as InputEventKey + if key_event and not key_event.pressed: + if key_event.scancode == KEY_0: + _on_option_selected(OPTION_TEST_CASE_ALL_ANGLES) + if key_event.scancode == KEY_1: + _on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY) + elif key_event.scancode == KEY_2: + _on_option_selected(OPTION_OBJECT_TYPE_KINEMATIC) + + +func _exit_tree(): + if not Engine.editor_hint: + _rigid_body_template.free() + _kinematic_body_template.free() + + +func _set_platform_size(value): + if _lock_controls: + return + if value == _platform_size: + return + _platform_size = value + if is_inside_tree(): + $OneWayRigidBody2D/CollisionShape2D.shape.extents.x = value + + if not Engine.editor_hint: + # Bug: need to re-add when changing shape. + var platform = $OneWayRigidBody2D + var child_index = platform.get_index() + remove_child(platform) + add_child(platform) + move_child(platform, child_index) + + _reset_test() + + +func _set_platform_angle(value): + if _lock_controls: + return + if value == _platform_angle: + return + _platform_angle = value + if is_inside_tree(): + $OneWayRigidBody2D.rotation = deg2rad(value) + if not Engine.editor_hint: + _reset_test() + + +func _set_rigidbody_angle(value): + if _lock_controls: + return + if value == _body_angle: + return + _body_angle = value + if is_inside_tree(): + if Engine.editor_hint: + $RigidBody2D.rotation = deg2rad(value) + $KinematicBody2D.rotation = deg2rad(value) + else: + if _moving_body: + _moving_body.rotation = deg2rad(value) + _rigid_body_template.rotation = deg2rad(value) + _kinematic_body_template.rotation = deg2rad(value) + _reset_test() + + +func _on_option_selected(option): + match option: + OPTION_OBJECT_TYPE_KINEMATIC: + _use_kinematic_body = true + _reset_test() + OPTION_OBJECT_TYPE_RIGIDBODY: + _use_kinematic_body = false + _reset_test() + OPTION_TEST_CASE_ALL_ANGLES: + _test_all_angles = true + _reset_test(false) + + +func _start_test(): + var test_label = "Testing: " + + if _use_kinematic_body: + test_label += _kinematic_body_template.name + _moving_body = _kinematic_body_template.duplicate() + else: + test_label += _rigid_body_template.name + _moving_body = _rigid_body_template.duplicate() + _moving_body.linear_velocity = _body_velocity + _moving_body.connect("body_entered", self, "_on_contact_detected") + add_child(_moving_body) + + if _test_all_angles: + test_label += " - All angles" + + $LabelTestType.text = test_label + + _contact_detected = false + _target_entered = false + _test_passed = false + _test_step += 1 + + $Timer.start() + + $LabelResult.text = "..." + $LabelResult.self_modulate = Color.white + + +func _reset_test(cancel_test = true): + $Timer.stop() + + _test_step = 0 + + if _test_all_angles: + if cancel_test: + Log.print_log("*** Stop around the clock tests") + _test_all_angles = false + else: + Log.print_log("*** Start around the clock tests") + $OneWayRigidBody2D.rotation = deg2rad(_platform_angle) + _lock_controls = true + $Controls/PlatformAngle/HSlider.value = _platform_angle + _lock_controls = false + + _next_test(true) + + +func _next_test(force_start = false): + if _moving_body: + remove_child(_moving_body) + _moving_body.queue_free() + _moving_body = null + + if _test_all_angles: + var angle = rad2deg($OneWayRigidBody2D.rotation) + if angle >= _platform_angle + TEST_ALL_ANGLES_MAX: + $OneWayRigidBody2D.rotation = deg2rad(_platform_angle) + _lock_controls = true + $Controls/PlatformAngle/HSlider.value = _platform_angle + _lock_controls = false + Log.print_log("*** Done all angles") + else: + angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP + $OneWayRigidBody2D.rotation = deg2rad(angle) + _lock_controls = true + $Controls/PlatformAngle/HSlider.value = angle + _lock_controls = false + _start_test() + elif force_start: + _start_test() + + +func _on_contact_detected(_body): + if _contact_detected or _target_entered: + return + + _contact_detected = true + _test_passed = _should_collide() + _set_result() + _on_timeout() + + +func _on_target_entered(_body): + if _contact_detected or _target_entered: + return + + _target_entered = true + _test_passed = not _should_collide() + _set_result() + _on_timeout() + + +func _should_collide(): + var platform_rotation = round(rad2deg($OneWayRigidBody2D.rotation)) + + var angle = fposmod(platform_rotation, 360) + return angle > 180 + + +func _on_timeout(): + if not _contact_detected and not _target_entered: + Log.print_log("Test TIMEOUT") + _set_result() + + $Timer.stop() + + yield(get_tree().create_timer(0.5), "timeout") + + _next_test() + + +func _set_result(): + var result = "" + if _test_passed: + result = "PASSED" + $LabelResult.self_modulate = Color.green + else: + result = "FAILED" + $LabelResult.self_modulate = Color.red + + $LabelResult.text = result + + var platform_angle = rad2deg($OneWayRigidBody2D.rotation) + + result += ": size=%.1f, angle=%.1f, body angle=%.1f" % [_platform_size, platform_angle, _body_angle] + Log.print_log("Test %s" % result) diff --git a/2d/physics_tests/tests/functional/test_one_way_collision.tscn b/2d/physics_tests/tests/functional/test_one_way_collision.tscn new file mode 100644 index 0000000000..e33761d6f1 --- /dev/null +++ b/2d/physics_tests/tests/functional/test_one_way_collision.tscn @@ -0,0 +1,243 @@ +[gd_scene load_steps=9 format=2] + +[ext_resource path="res://tests/functional/test_one_way_collision.gd" type="Script" id=1] +[ext_resource path="res://icon.png" type="Texture" id=2] +[ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=3] +[ext_resource path="res://utils/label_slider_value.gd" type="Script" id=4] +[ext_resource path="res://utils/slider.gd" type="Script" id=5] + +[sub_resource type="RectangleShape2D" id=1] +extents = Vector2( 32, 32 ) + +[sub_resource type="RectangleShape2D" id=2] +extents = Vector2( 64, 32 ) + +[sub_resource type="RectangleShape2D" id=3] +extents = Vector2( 32, 32 ) + +[node name="Test" type="Node2D"] +script = ExtResource( 1 ) + +[node name="LabelTestType" type="Label" parent="."] +margin_left = 14.0 +margin_top = 79.0 +margin_right = 145.0 +margin_bottom = 93.0 +text = "Testing: " +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Options" parent="." instance=ExtResource( 3 )] + +[node name="Controls" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 25.3619 +margin_top = 416.765 +margin_right = 265.362 +margin_bottom = 484.765 +custom_constants/separation = 10 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PlatformSize" type="HBoxContainer" parent="Controls"] +margin_right = 432.0 +margin_bottom = 16.0 +custom_constants/separation = 20 +alignment = 2 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="Controls/PlatformSize"] +margin_left = 8.0 +margin_top = 1.0 +margin_right = 92.0 +margin_bottom = 15.0 +text = "Platform size" + +[node name="HSlider" type="HSlider" parent="Controls/PlatformSize"] +margin_left = 112.0 +margin_right = 312.0 +margin_bottom = 16.0 +rect_min_size = Vector2( 200, 0 ) +min_value = 32.0 +max_value = 128.0 +value = 64.0 +script = ExtResource( 5 ) + +[node name="LabelValue" type="Label" parent="Controls/PlatformSize"] +margin_left = 332.0 +margin_top = 1.0 +margin_right = 432.0 +margin_bottom = 15.0 +rect_min_size = Vector2( 100, 0 ) +text = "64.0" +script = ExtResource( 4 ) + +[node name="PlatformAngle" type="HBoxContainer" parent="Controls"] +margin_top = 26.0 +margin_right = 432.0 +margin_bottom = 42.0 +custom_constants/separation = 20 +alignment = 2 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="Controls/PlatformAngle"] +margin_top = 1.0 +margin_right = 92.0 +margin_bottom = 15.0 +text = "Platform angle" + +[node name="HSlider" type="HSlider" parent="Controls/PlatformAngle"] +margin_left = 112.0 +margin_right = 312.0 +margin_bottom = 16.0 +rect_min_size = Vector2( 200, 0 ) +max_value = 360.0 +script = ExtResource( 5 ) +snap_step = 5.0 + +[node name="LabelValue" type="Label" parent="Controls/PlatformAngle"] +margin_left = 332.0 +margin_top = 1.0 +margin_right = 432.0 +margin_bottom = 15.0 +rect_min_size = Vector2( 100, 0 ) +text = "0.0" +script = ExtResource( 4 ) + +[node name="BodyAngle" type="HBoxContainer" parent="Controls"] +margin_top = 52.0 +margin_right = 432.0 +margin_bottom = 68.0 +custom_constants/separation = 20 +alignment = 2 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="Controls/BodyAngle"] +margin_left = 22.0 +margin_top = 1.0 +margin_right = 92.0 +margin_bottom = 15.0 +text = "Body angle" + +[node name="HSlider" type="HSlider" parent="Controls/BodyAngle"] +margin_left = 112.0 +margin_right = 312.0 +margin_bottom = 16.0 +rect_min_size = Vector2( 200, 0 ) +max_value = 360.0 +script = ExtResource( 5 ) +snap_step = 5.0 + +[node name="LabelValue" type="Label" parent="Controls/BodyAngle"] +margin_left = 332.0 +margin_top = 1.0 +margin_right = 432.0 +margin_bottom = 15.0 +rect_min_size = Vector2( 100, 0 ) +text = "0.0" +script = ExtResource( 4 ) + +[node name="LabelResultTitle" type="Label" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = 34.1273 +margin_top = 251.131 +margin_right = 88.1273 +margin_bottom = 265.131 +text = "RESULT: " +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelResult" type="Label" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = 34.1273 +margin_top = 266.131 +margin_right = 88.1273 +margin_bottom = 280.131 +text = "..." +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LabelRestart" type="Label" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = 34.1273 +margin_top = 304.841 +margin_right = 139.127 +margin_bottom = 318.841 +text = "SPACE - RESTART" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Timer" type="Timer" parent="."] +wait_time = 5.0 +one_shot = true + +[node name="TargetArea2D" type="Area2D" parent="."] +position = Vector2( 724, 300 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetArea2D"] +shape = SubResource( 1 ) + +[node name="OneWayRigidBody2D" type="RigidBody2D" parent="."] +position = Vector2( 512, 300 ) +mode = 3 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWayRigidBody2D"] +shape = SubResource( 2 ) +one_way_collision = true + +[node name="RigidBody2D" type="RigidBody2D" parent="."] +position = Vector2( 300, 300 ) +collision_mask = 2147483649 +gravity_scale = 0.0 +contacts_reported = 1 +contact_monitor = true + +[node name="Sprite" type="Sprite" parent="RigidBody2D"] +self_modulate = Color( 1, 1, 1, 0.501961 ) +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 2 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] +shape = SubResource( 3 ) + +[node name="KinematicBody2D" type="KinematicBody2D" parent="."] +position = Vector2( 300, 300 ) +collision_mask = 2147483649 + +[node name="Sprite" type="Sprite" parent="KinematicBody2D"] +self_modulate = Color( 1, 1, 1, 0.501961 ) +scale = Vector2( 0.5, 0.5 ) +texture = ExtResource( 2 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="KinematicBody2D"] +shape = SubResource( 3 ) +[connection signal="value_changed" from="Controls/PlatformSize/HSlider" to="." method="_set_platform_size"] +[connection signal="value_changed" from="Controls/PlatformAngle/HSlider" to="." method="_set_platform_angle"] +[connection signal="value_changed" from="Controls/BodyAngle/HSlider" to="." method="_set_rigidbody_angle"] diff --git a/2d/physics_tests/utils/label_slider_value.gd b/2d/physics_tests/utils/label_slider_value.gd new file mode 100644 index 0000000000..67f736980f --- /dev/null +++ b/2d/physics_tests/utils/label_slider_value.gd @@ -0,0 +1,7 @@ +extends Label +tool + + +func _process(_delta): + var slider = get_node("../HSlider") + text = "%.1f" % slider.value diff --git a/2d/physics_tests/utils/option_menu.gd b/2d/physics_tests/utils/option_menu.gd index 05f161ccae..562034cadf 100644 --- a/2d/physics_tests/utils/option_menu.gd +++ b/2d/physics_tests/utils/option_menu.gd @@ -6,7 +6,7 @@ signal option_selected(item_path) signal option_changed(item_path, checked) -func add_menu_item(item_path, checkbox = false, checked = false): +func add_menu_item(item_path, checkbox = false, checked = false, radio = false): var path_elements = item_path.split("/", false) var path_element_count = path_elements.size() assert(path_element_count > 0) @@ -19,7 +19,10 @@ func add_menu_item(item_path, checkbox = false, checked = false): popup = _add_popup(popup, path, popup_label) var label = path_elements[path_element_count - 1] - if checkbox: + if radio: + popup.add_radio_check_item(label) + popup.set_item_checked(popup.get_item_count() - 1, checked) + elif checkbox: popup.add_check_item(label) popup.set_item_checked(popup.get_item_count() - 1, checked) else: @@ -52,7 +55,15 @@ func _add_popup(parent_popup, path, label): func _on_item_pressed(item_index, popup_menu, path): var item_path = path + popup_menu.get_item_text(item_index) - if popup_menu.is_item_checkable(item_index): + if popup_menu.is_item_radio_checkable(item_index): + var checked = popup_menu.is_item_checked(item_index) + if not checked: + popup_menu.set_item_checked(item_index, true) + for other_index in popup_menu.get_item_count(): + if other_index != item_index: + popup_menu.set_item_checked(other_index, false) + emit_signal("option_selected", item_path) + elif popup_menu.is_item_checkable(item_index): var checked = not popup_menu.is_item_checked(item_index) popup_menu.set_item_checked(item_index, checked) emit_signal("option_changed", item_path, checked) diff --git a/2d/physics_tests/utils/slider.gd b/2d/physics_tests/utils/slider.gd new file mode 100644 index 0000000000..96c22e560c --- /dev/null +++ b/2d/physics_tests/utils/slider.gd @@ -0,0 +1,11 @@ +extends Slider + + +export(float) var snap_step = 1.0 + + +func _process(_delta): + if Input.is_key_pressed(KEY_SHIFT): + step = 0.1 + else: + step = snap_step