From 9014e9e424ef5b2dc7039aec020fb9316085e0e1 Mon Sep 17 00:00:00 2001 From: Markus Sauermann <6299227+Sauermann@users.noreply.github.com> Date: Fri, 10 Feb 2023 02:09:07 +0100 Subject: [PATCH] Add Unit tests for viewport.cpp Physics 2D Picking --- tests/scene/test_viewport.h | 407 +++++++++++++++++++++++++++++++++++- tests/test_macros.h | 14 ++ 2 files changed, 420 insertions(+), 1 deletion(-) diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index f0fdf655c01..ab17459a415 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -31,10 +31,13 @@ #ifndef TEST_VIEWPORT_H #define TEST_VIEWPORT_H -#include "scene/2d/node_2d.h" +#include "scene/2d/area_2d.h" +#include "scene/2d/collision_shape_2d.h" #include "scene/gui/control.h" #include "scene/gui/subviewport_container.h" +#include "scene/main/canvas_layer.h" #include "scene/main/window.h" +#include "scene/resources/rectangle_shape_2d.h" #include "tests/test_macros.h" @@ -756,6 +759,408 @@ TEST_CASE("[SceneTree][Viewport] Control mouse cursor shape") { } } +class TestArea2D : public Area2D { + GDCLASS(TestArea2D, Area2D); + + void _on_mouse_entered() { + enter_id = ++TestArea2D::counter; // > 0, if activated. + } + + void _on_mouse_exited() { + exit_id = ++TestArea2D::counter; // > 0, if activated. + } + + void _on_input_event(Node *p_vp, Ref p_ev, int p_shape) { + last_input_event = p_ev; + } + +public: + static int counter; + int enter_id = 0; + int exit_id = 0; + Ref last_input_event; + + void init_signals() { + connect(SNAME("mouse_entered"), callable_mp(this, &TestArea2D::_on_mouse_entered)); + connect(SNAME("mouse_exited"), callable_mp(this, &TestArea2D::_on_mouse_exited)); + connect(SNAME("input_event"), callable_mp(this, &TestArea2D::_on_input_event)); + } + + void test_reset() { + enter_id = 0; + exit_id = 0; + last_input_event.unref(); + } +}; + +int TestArea2D::counter = 0; + +TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { + // FIXME: MOUSE_MODE_CAPTURED if-conditions are not testable, because DisplayServerMock doesn't support it. + + struct PickingCollider { + TestArea2D *a; + CollisionShape2D *c; + Ref r; + }; + + SceneTree *tree = SceneTree::get_singleton(); + Window *root = tree->get_root(); + root->set_physics_object_picking(true); + + Point2i on_background = Point2i(800, 800); + Point2i on_outside = Point2i(-1, -1); + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + Vector v; + for (int i = 0; i < 4; i++) { + PickingCollider pc; + pc.a = memnew(TestArea2D); + pc.c = memnew(CollisionShape2D); + pc.r = Ref(memnew(RectangleShape2D)); + pc.r->set_size(Size2(150, 150)); + pc.c->set_shape(pc.r); + pc.a->add_child(pc.c); + pc.a->set_name("A" + itos(i)); + pc.c->set_name("C" + itos(i)); + v.push_back(pc); + SIGNAL_WATCH(pc.a, SNAME("mouse_entered")); + SIGNAL_WATCH(pc.a, SNAME("mouse_exited")); + } + + Node2D *node_a = memnew(Node2D); + node_a->set_position(Point2i(0, 0)); + v[0].a->set_position(Point2i(0, 0)); + v[1].a->set_position(Point2i(0, 100)); + node_a->add_child(v[0].a); + node_a->add_child(v[1].a); + Node2D *node_b = memnew(Node2D); + node_b->set_position(Point2i(100, 0)); + v[2].a->set_position(Point2i(0, 0)); + v[3].a->set_position(Point2i(0, 100)); + node_b->add_child(v[2].a); + node_b->add_child(v[3].a); + root->add_child(node_a); + root->add_child(node_b); + Point2i on_all = Point2i(50, 50); + Point2i on_0 = Point2i(10, 10); + Point2i on_01 = Point2i(10, 50); + Point2i on_02 = Point2i(50, 10); + + Array empty_signal_args_2; + empty_signal_args_2.push_back(Array()); + empty_signal_args_2.push_back(Array()); + + Array empty_signal_args_4; + empty_signal_args_4.push_back(Array()); + empty_signal_args_4.push_back(Array()); + empty_signal_args_4.push_back(Array()); + empty_signal_args_4.push_back(Array()); + + for (PickingCollider E : v) { + E.a->init_signals(); + } + + SUBCASE("[Viewport][Picking2D] Mouse Motion") { + SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + SIGNAL_CHECK(SNAME("mouse_entered"), empty_signal_args_4); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + for (PickingCollider E : v) { + CHECK(E.a->enter_id); + CHECK_FALSE(E.a->exit_id); + E.a->test_reset(); + } + + SEND_GUI_MOUSE_MOTION_EVENT(on_01, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + if (i < 2) { + CHECK_FALSE(v[i].a->exit_id); + } else { + CHECK(v[i].a->exit_id); + } + v[i].a->test_reset(); + } + + SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + if (i < 2) { + CHECK(v[i].a->exit_id); + } else { + CHECK_FALSE(v[i].a->exit_id); + } + v[i].a->test_reset(); + } + } + + SUBCASE("[Viewport][Picking2D] Object moved / passive hovering") { + SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + for (int i = 0; i < v.size(); i++) { + CHECK(v[i].a->enter_id); + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + + node_b->set_position(Point2i(200, 0)); + tree->physics_process(1); + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + if (i < 2) { + CHECK_FALSE(v[i].a->exit_id); + } else { + CHECK(v[i].a->exit_id); + } + v[i].a->test_reset(); + } + + node_b->set_position(Point2i(100, 0)); + tree->physics_process(1); + for (int i = 0; i < v.size(); i++) { + if (i < 2) { + CHECK_FALSE(v[i].a->enter_id); + } else { + CHECK(v[i].a->enter_id); + } + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + } + + SUBCASE("[Viewport][Picking2D] No Processing") { + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + for (PickingCollider E : v) { + E.a->test_reset(); + } + + v[0].a->set_process_mode(Node::PROCESS_MODE_DISABLED); + v[0].c->set_process_mode(Node::PROCESS_MODE_DISABLED); + SEND_GUI_MOUSE_MOTION_EVENT(on_02, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + CHECK_FALSE(v[0].a->enter_id); + CHECK_FALSE(v[0].a->exit_id); + CHECK(v[2].a->enter_id); + CHECK_FALSE(v[2].a->exit_id); + for (PickingCollider E : v) { + E.a->test_reset(); + } + + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + CHECK_FALSE(v[0].a->enter_id); + CHECK_FALSE(v[0].a->exit_id); + CHECK_FALSE(v[2].a->enter_id); + CHECK(v[2].a->exit_id); + + for (PickingCollider E : v) { + E.a->test_reset(); + } + v[0].a->set_process_mode(Node::PROCESS_MODE_ALWAYS); + v[0].c->set_process_mode(Node::PROCESS_MODE_ALWAYS); + } + + SUBCASE("[Viewport][Picking2D] Multiple events in series") { + SEND_GUI_MOUSE_MOTION_EVENT(on_0, MouseButtonMask::NONE, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_0 + Point2i(10, 0), MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + if (i < 1) { + CHECK(v[i].a->enter_id); + } else { + CHECK_FALSE(v[i].a->enter_id); + } + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(on_background + Point2i(10, 10), MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + if (i < 1) { + CHECK(v[i].a->exit_id); + } else { + CHECK_FALSE(v[i].a->exit_id); + } + v[i].a->test_reset(); + } + } + + SUBCASE("[Viewport][Picking2D] Disable Picking") { + SEND_GUI_MOUSE_MOTION_EVENT(on_02, MouseButtonMask::NONE, Key::NONE); + + root->set_physics_object_picking(false); + CHECK_FALSE(root->get_physics_object_picking()); + + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + v[i].a->test_reset(); + } + + root->set_physics_object_picking(true); + CHECK(root->get_physics_object_picking()); + } + + SUBCASE("[Viewport][Picking2D] CollisionObject in CanvasLayer") { + CanvasLayer *node_c = memnew(CanvasLayer); + node_c->set_rotation(Math_PI); + node_c->set_offset(Point2i(100, 100)); + root->add_child(node_c); + + v[2].a->reparent(node_c, false); + v[3].a->reparent(node_c, false); + + SEND_GUI_MOUSE_MOTION_EVENT(on_02, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + if (i == 0 || i == 3) { + CHECK(v[i].a->enter_id); + } else { + CHECK_FALSE(v[i].a->enter_id); + } + v[i].a->test_reset(); + } + + v[2].a->reparent(node_b, false); + v[3].a->reparent(node_b, false); + root->remove_child(node_c); + memdelete(node_c); + } + + SUBCASE("[Viewport][Picking2D] Picking Sort") { + root->set_physics_object_picking_sort(true); + CHECK(root->get_physics_object_picking_sort()); + + SUBCASE("[Viewport][Picking2D] Picking Sort Z-Index") { + node_a->set_z_index(10); + v[0].a->set_z_index(0); + v[1].a->set_z_index(2); + node_b->set_z_index(5); + v[2].a->set_z_index(8); + v[3].a->set_z_index(11); + v[3].a->set_z_as_relative(false); + + TestArea2D::counter = 0; + SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + CHECK(v[0].a->enter_id == 4); + CHECK(v[1].a->enter_id == 2); + CHECK(v[2].a->enter_id == 1); + CHECK(v[3].a->enter_id == 3); + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + + TestArea2D::counter = 0; + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + CHECK(v[0].a->exit_id == 4); + CHECK(v[1].a->exit_id == 2); + CHECK(v[2].a->exit_id == 1); + CHECK(v[3].a->exit_id == 3); + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + v[i].a->set_z_as_relative(true); + v[i].a->set_z_index(0); + v[i].a->test_reset(); + } + + node_a->set_z_index(0); + node_b->set_z_index(0); + } + + SUBCASE("[Viewport][Picking2D] Picking Sort Scene Tree Location") { + TestArea2D::counter = 0; + SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + CHECK(v[i].a->enter_id == 4 - i); + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + + TestArea2D::counter = 0; + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + CHECK(v[i].a->exit_id == 4 - i); + v[i].a->test_reset(); + } + } + + root->set_physics_object_picking_sort(false); + CHECK_FALSE(root->get_physics_object_picking_sort()); + } + + SUBCASE("[Viewport][Picking2D] Mouse Button") { + SEND_GUI_MOUSE_BUTTON_EVENT(on_0, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + if (i == 0) { + CHECK(v[i].a->enter_id); + } else { + CHECK_FALSE(v[i].a->enter_id); + } + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_0, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + tree->physics_process(1); + + for (int i = 0; i < v.size(); i++) { + CHECK_FALSE(v[i].a->enter_id); + CHECK_FALSE(v[i].a->exit_id); + v[i].a->test_reset(); + } + } + + SUBCASE("[Viewport][Picking2D] Screen Touch") { + SEND_GUI_TOUCH_EVENT(on_01, true, false); + tree->physics_process(1); + for (int i = 0; i < v.size(); i++) { + if (i < 2) { + Ref st = v[i].a->last_input_event; + CHECK(st.is_valid()); + } else { + CHECK(v[i].a->last_input_event.is_null()); + } + v[i].a->test_reset(); + } + } + + for (PickingCollider E : v) { + SIGNAL_UNWATCH(E.a, SNAME("mouse_entered")); + SIGNAL_UNWATCH(E.a, SNAME("mouse_exited")); + memdelete(E.c); + memdelete(E.a); + } +} + TEST_CASE("[SceneTree][Viewport] Embedded Windows") { Window *root = SceneTree::get_singleton()->get_root(); Window *w = memnew(Window); diff --git a/tests/test_macros.h b/tests/test_macros.h index d39da7f8e8c..bc85ec6ddc9 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -177,6 +177,13 @@ int register_test_command(String p_command, TestFunc p_function); _UPDATE_EVENT_MODIFERS(event, m_modifers); \ event->set_pressed(true); +#define _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + Ref event; \ + event.instantiate(); \ + event->set_position(m_screen_pos); \ + event->set_pressed(m_pressed); \ + event->set_double_tap(m_double); + #define SEND_GUI_MOUSE_BUTTON_EVENT(m_screen_pos, m_input, m_mask, m_modifers) \ { \ _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifers); \ @@ -215,6 +222,13 @@ int register_test_command(String p_command, TestFunc p_function); CoreGlobals::print_error_enabled = errors_enabled; \ } +#define SEND_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + { \ + _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \ + _SEND_DISPLAYSERVER_EVENT(event); \ + MessageQueue::get_singleton()->flush(); \ + } + // Utility class / macros for testing signals // // Use SIGNAL_WATCH(*object, "signal_name") to start watching