Path: blob/master/tests/servers/test_navigation_server_2d.cpp
45991 views
/**************************************************************************/1/* test_navigation_server_2d.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "tests/test_macros.h"3132TEST_FORCE_LINK(test_navigation_server_2d)3334#include "modules/modules_enabled.gen.h" // For navigation 2D.3536#ifdef MODULE_NAVIGATION_2D_ENABLED3738#include "core/object/callable_mp.h"39#include "scene/2d/polygon_2d.h"40#include "scene/main/scene_tree.h"41#include "scene/main/window.h"42#include "servers/navigation_2d/navigation_server_2d.h"43#include "tests/signal_watcher.h"4445namespace TestNavigationServer2D {4647// TODO: Find a more generic way to create `Callable` mocks.48class CallableMock : public Object {49GDCLASS(CallableMock, Object);5051public:52void function1(Variant arg0) {53function1_calls++;54function1_latest_arg0 = arg0;55}5657unsigned function1_calls{ 0 };58Variant function1_latest_arg0;59};6061struct GreaterThan {62bool operator()(int p_a, int p_b) const { return p_a > p_b; }63};6465struct CompareArrayValues {66const int *array;6768CompareArrayValues(const int *p_array) :69array(p_array) {}7071bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {72return array[p_index_a] < array[p_index_b];73}74};7576struct RegisterHeapIndexes {77uint32_t *indexes;7879RegisterHeapIndexes(uint32_t *p_indexes) :80indexes(p_indexes) {}8182void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {83indexes[p_vector_index] = p_heap_index;84}85};8687TEST_SUITE("[Navigation2D]") {88TEST_CASE("[NavigationServer2D] Server should be empty when initialized") {89NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();90CHECK_EQ(navigation_server->get_maps().size(), 0);9192SUBCASE("'ProcessInfo' should report all counters empty as well") {93CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);94CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);95CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);96CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);97CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_POLYGON_COUNT), 0);98CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_COUNT), 0);99CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_MERGE_COUNT), 0);100CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_CONNECTION_COUNT), 0);101CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_FREE_COUNT), 0);102}103}104105TEST_CASE("[NavigationServer2D] Server should manage agent properly") {106NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();107108RID agent = navigation_server->agent_create();109CHECK(agent.is_valid());110111SUBCASE("'ProcessInfo' should not report dangling agent") {112CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);113}114115SUBCASE("Setters/getters should work") {116bool initial_avoidance_enabled = navigation_server->agent_get_avoidance_enabled(agent);117navigation_server->agent_set_avoidance_enabled(agent, !initial_avoidance_enabled);118navigation_server->physics_process(0.0); // Give server some cycles to commit.119120CHECK_EQ(navigation_server->agent_get_avoidance_enabled(agent), !initial_avoidance_enabled);121// TODO: Add remaining setters/getters once the missing getters are added.122}123124SUBCASE("'ProcessInfo' should report agent with active map") {125RID map = navigation_server->map_create();126CHECK(map.is_valid());127navigation_server->map_set_active(map, true);128navigation_server->agent_set_map(agent, map);129navigation_server->physics_process(0.0); // Give server some cycles to commit.130CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 1);131navigation_server->agent_set_map(agent, RID());132navigation_server->free_rid(map);133navigation_server->physics_process(0.0); // Give server some cycles to commit.134CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);135}136137navigation_server->free_rid(agent);138}139140TEST_CASE("[NavigationServer2D] Server should manage map properly") {141NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();142143RID map;144CHECK_FALSE(map.is_valid());145146SUBCASE("Queries against invalid map should return empty or invalid values") {147ERR_PRINT_OFF;148CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());149CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());150CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);151CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);152153Ref<NavigationPathQueryParameters2D> query_parameters;154query_parameters.instantiate();155query_parameters->set_map(map);156query_parameters->set_start_position(Vector2(7, 7));157query_parameters->set_target_position(Vector2(8, 8));158Ref<NavigationPathQueryResult2D> query_result;159query_result.instantiate();160navigation_server->query_path(query_parameters, query_result);161CHECK_EQ(query_result->get_path().size(), 0);162CHECK_EQ(query_result->get_path_types().size(), 0);163CHECK_EQ(query_result->get_path_rids().size(), 0);164CHECK_EQ(query_result->get_path_owner_ids().size(), 0);165ERR_PRINT_ON;166}167168map = navigation_server->map_create();169CHECK(map.is_valid());170CHECK_EQ(navigation_server->get_maps().size(), 1);171172SUBCASE("'ProcessInfo' should not report inactive map") {173CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);174}175176SUBCASE("Setters/getters should work") {177navigation_server->map_set_cell_size(map, 0.55);178navigation_server->map_set_edge_connection_margin(map, 0.66);179navigation_server->map_set_link_connection_radius(map, 0.77);180bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);181navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);182navigation_server->physics_process(0.0); // Give server some cycles to commit.183184CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));185CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));186CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));187CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);188}189190SUBCASE("'ProcessInfo' should report map iff active") {191navigation_server->map_set_active(map, true);192navigation_server->physics_process(0.0); // Give server some cycles to commit.193CHECK(navigation_server->map_is_active(map));194CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 1);195navigation_server->map_set_active(map, false);196navigation_server->physics_process(0.0); // Give server some cycles to commit.197CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);198}199200SUBCASE("Number of agents should be reported properly") {201RID agent = navigation_server->agent_create();202CHECK(agent.is_valid());203navigation_server->agent_set_map(agent, map);204navigation_server->physics_process(0.0); // Give server some cycles to commit.205CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);206navigation_server->free_rid(agent);207navigation_server->physics_process(0.0); // Give server some cycles to commit.208CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);209}210211SUBCASE("Number of links should be reported properly") {212RID link = navigation_server->link_create();213CHECK(link.is_valid());214navigation_server->link_set_map(link, map);215navigation_server->physics_process(0.0); // Give server some cycles to commit.216CHECK_EQ(navigation_server->map_get_links(map).size(), 1);217navigation_server->free_rid(link);218navigation_server->physics_process(0.0); // Give server some cycles to commit.219CHECK_EQ(navigation_server->map_get_links(map).size(), 0);220}221222SUBCASE("Number of obstacles should be reported properly") {223RID obstacle = navigation_server->obstacle_create();224CHECK(obstacle.is_valid());225navigation_server->obstacle_set_map(obstacle, map);226navigation_server->physics_process(0.0); // Give server some cycles to commit.227CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);228navigation_server->free_rid(obstacle);229navigation_server->physics_process(0.0); // Give server some cycles to commit.230CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);231}232233SUBCASE("Number of regions should be reported properly") {234RID region = navigation_server->region_create();235CHECK(region.is_valid());236navigation_server->region_set_map(region, map);237navigation_server->physics_process(0.0); // Give server some cycles to commit.238CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);239navigation_server->free_rid(region);240navigation_server->physics_process(0.0); // Give server some cycles to commit.241CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);242}243244SUBCASE("Queries against empty map should return empty or invalid values") {245navigation_server->map_set_active(map, true);246navigation_server->physics_process(0.0); // Give server some cycles to commit.247248ERR_PRINT_OFF;249CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());250CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());251CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);252CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);253254Ref<NavigationPathQueryParameters2D> query_parameters;255query_parameters.instantiate();256query_parameters->set_map(map);257query_parameters->set_start_position(Vector2(7, 7));258query_parameters->set_target_position(Vector2(8, 8));259Ref<NavigationPathQueryResult2D> query_result;260query_result.instantiate();261navigation_server->query_path(query_parameters, query_result);262CHECK_EQ(query_result->get_path().size(), 0);263CHECK_EQ(query_result->get_path_types().size(), 0);264CHECK_EQ(query_result->get_path_rids().size(), 0);265CHECK_EQ(query_result->get_path_owner_ids().size(), 0);266ERR_PRINT_ON;267268navigation_server->map_set_active(map, false);269navigation_server->physics_process(0.0); // Give server some cycles to commit.270}271272navigation_server->free_rid(map);273navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.274CHECK_EQ(navigation_server->get_maps().size(), 0);275}276277TEST_CASE("[NavigationServer2D] Server should manage link properly") {278NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();279280RID link = navigation_server->link_create();281CHECK(link.is_valid());282283SUBCASE("'ProcessInfo' should not report dangling link") {284CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);285}286287SUBCASE("Setters/getters should work") {288bool initial_bidirectional = navigation_server->link_is_bidirectional(link);289navigation_server->link_set_bidirectional(link, !initial_bidirectional);290navigation_server->link_set_end_position(link, Vector2(7, 7));291navigation_server->link_set_enter_cost(link, 0.55);292navigation_server->link_set_navigation_layers(link, 6);293navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));294navigation_server->link_set_start_position(link, Vector2(8, 8));295navigation_server->link_set_travel_cost(link, 0.66);296navigation_server->physics_process(0.0); // Give server some cycles to commit.297298CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);299CHECK_EQ(navigation_server->link_get_end_position(link), Vector2(7, 7));300CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));301CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);302CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));303CHECK_EQ(navigation_server->link_get_start_position(link), Vector2(8, 8));304CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));305}306307SUBCASE("'ProcessInfo' should report link with active map") {308RID map = navigation_server->map_create();309CHECK(map.is_valid());310navigation_server->map_set_active(map, true);311navigation_server->link_set_map(link, map);312navigation_server->physics_process(0.0); // Give server some cycles to commit.313CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 1);314navigation_server->link_set_map(link, RID());315navigation_server->free_rid(map);316navigation_server->physics_process(0.0); // Give server some cycles to commit.317CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);318}319320navigation_server->free_rid(link);321}322323TEST_CASE("[NavigationServer2D] Server should manage obstacles properly") {324NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();325326RID obstacle = navigation_server->obstacle_create();327CHECK(obstacle.is_valid());328329// TODO: Add tests for setters/getters once getters are added.330331navigation_server->free_rid(obstacle);332}333334TEST_CASE("[NavigationServer2D] Server should manage regions properly") {335NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();336337RID region = navigation_server->region_create();338CHECK(region.is_valid());339340SUBCASE("'ProcessInfo' should not report dangling region") {341CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);342}343344SUBCASE("Setters/getters should work") {345bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);346navigation_server->region_set_enter_cost(region, 0.55);347navigation_server->region_set_navigation_layers(region, 5);348navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));349navigation_server->region_set_travel_cost(region, 0.66);350navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);351navigation_server->physics_process(0.0); // Give server some cycles to commit.352353CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));354CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);355CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));356CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));357CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);358}359360SUBCASE("'ProcessInfo' should report region with active map") {361RID map = navigation_server->map_create();362CHECK(map.is_valid());363navigation_server->map_set_active(map, true);364navigation_server->region_set_map(region, map);365navigation_server->physics_process(0.0); // Give server some cycles to commit.366CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 1);367navigation_server->region_set_map(region, RID());368navigation_server->free_rid(map);369navigation_server->physics_process(0.0); // Give server some cycles to commit.370CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);371}372373SUBCASE("Queries against empty region should return empty or invalid values") {374ERR_PRINT_OFF;375CHECK_EQ(navigation_server->region_get_connections_count(region), 0);376CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector2());377CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector2());378ERR_PRINT_ON;379}380381navigation_server->free_rid(region);382}383384// This test case does not check precise values on purpose - to not be too sensitivte.385TEST_CASE("[NavigationServer2D] Server should move agent properly") {386NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();387388RID map = navigation_server->map_create();389RID agent = navigation_server->agent_create();390391navigation_server->map_set_active(map, true);392navigation_server->agent_set_map(agent, map);393navigation_server->agent_set_avoidance_enabled(agent, true);394navigation_server->agent_set_velocity(agent, Vector2(1, 1));395CallableMock agent_avoidance_callback_mock;396navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));397CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);398navigation_server->physics_process(0.0); // Give server some cycles to commit.399CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);400CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector2(0, 0));401402navigation_server->free_rid(agent);403navigation_server->free_rid(map);404}405406// This test case does not check precise values on purpose - to not be too sensitivte.407TEST_CASE("[NavigationServer2D] Server should make agents avoid each other when avoidance enabled") {408NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();409410RID map = navigation_server->map_create();411RID agent_1 = navigation_server->agent_create();412RID agent_2 = navigation_server->agent_create();413414navigation_server->map_set_active(map, true);415416navigation_server->agent_set_map(agent_1, map);417navigation_server->agent_set_avoidance_enabled(agent_1, true);418navigation_server->agent_set_position(agent_1, Vector2(0, 0));419navigation_server->agent_set_radius(agent_1, 1);420navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));421CallableMock agent_1_avoidance_callback_mock;422navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));423424navigation_server->agent_set_map(agent_2, map);425navigation_server->agent_set_avoidance_enabled(agent_2, true);426navigation_server->agent_set_position(agent_2, Vector2(2.5, 0.5));427navigation_server->agent_set_radius(agent_2, 1);428navigation_server->agent_set_velocity(agent_2, Vector2(-1, 0));429CallableMock agent_2_avoidance_callback_mock;430navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));431432CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);433CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);434navigation_server->physics_process(0.0); // Give server some cycles to commit.435CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);436CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);437Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;438Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;439CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");440CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");441CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "agent 1 should move a bit to the side so that it avoids agent 2");442CHECK_MESSAGE(agent_2_safe_velocity.y > 0, "agent 2 should move a bit to the side so that it avoids agent 1");443444navigation_server->free_rid(agent_2);445navigation_server->free_rid(agent_1);446navigation_server->free_rid(map);447}448449TEST_CASE("[NavigationServer2D] Server should make agents avoid dynamic obstacles when avoidance enabled") {450NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();451452RID map = navigation_server->map_create();453RID agent_1 = navigation_server->agent_create();454RID obstacle_1 = navigation_server->obstacle_create();455456navigation_server->map_set_active(map, true);457458navigation_server->agent_set_map(agent_1, map);459navigation_server->agent_set_avoidance_enabled(agent_1, true);460navigation_server->agent_set_position(agent_1, Vector2(0, 0));461navigation_server->agent_set_radius(agent_1, 1);462navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));463CallableMock agent_1_avoidance_callback_mock;464navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));465466navigation_server->obstacle_set_map(obstacle_1, map);467navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);468navigation_server->obstacle_set_position(obstacle_1, Vector2(2.5, 0.5));469navigation_server->obstacle_set_radius(obstacle_1, 1);470471CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);472navigation_server->physics_process(0.0); // Give server some cycles to commit.473CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);474Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;475CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");476CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");477478navigation_server->free_rid(obstacle_1);479navigation_server->free_rid(agent_1);480navigation_server->free_rid(map);481navigation_server->physics_process(0.0); // Give server some cycles to commit.482}483484TEST_CASE("[NavigationServer2D] Server should make agents avoid static obstacles when avoidance enabled") {485NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();486487RID map = navigation_server->map_create();488RID agent_1 = navigation_server->agent_create();489RID agent_2 = navigation_server->agent_create();490RID obstacle_1 = navigation_server->obstacle_create();491492navigation_server->map_set_active(map, true);493494navigation_server->agent_set_map(agent_1, map);495navigation_server->agent_set_avoidance_enabled(agent_1, true);496navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.497navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));498CallableMock agent_1_avoidance_callback_mock;499navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));500501navigation_server->agent_set_map(agent_2, map);502navigation_server->agent_set_avoidance_enabled(agent_2, true);503navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.504navigation_server->agent_set_velocity(agent_2, Vector2(1, 0));505CallableMock agent_2_avoidance_callback_mock;506navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));507508navigation_server->obstacle_set_map(obstacle_1, map);509navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);510PackedVector2Array obstacle_1_vertices;511512SUBCASE("Static obstacles should work on ground level") {513navigation_server->agent_set_position(agent_1, Vector2(0, 0));514navigation_server->agent_set_position(agent_2, Vector2(0, 5));515obstacle_1_vertices.push_back(Vector2(1.5, 0.5));516obstacle_1_vertices.push_back(Vector2(1.5, 4.5));517}518519navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);520521CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);522CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);523navigation_server->physics_process(0.0); // Give server some cycles to commit.524CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);525CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);526Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;527Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;528CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");529CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");530CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");531CHECK_MESSAGE(agent_2_safe_velocity.y == 0, "Agent 2 should not move to the side.");532533navigation_server->free_rid(obstacle_1);534navigation_server->free_rid(agent_2);535navigation_server->free_rid(agent_1);536navigation_server->free_rid(map);537navigation_server->physics_process(0.0); // Give server some cycles to commit.538}539540TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to parse geometry") {541NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();542543// Prepare scene tree with simple mesh to serve as an input geometry.544Node2D *node_2d = memnew(Node2D);545SceneTree::get_singleton()->get_root()->add_child(node_2d);546Polygon2D *polygon = memnew(Polygon2D);547polygon->set_polygon(PackedVector2Array({ Vector2(200.0, 200.0), Vector2(400.0, 200.0), Vector2(400.0, 400.0), Vector2(200.0, 400.0) }));548node_2d->add_child(polygon);549550// TODO: Use MeshInstance2D as well?551552Ref<NavigationPolygon> navigation_polygon;553navigation_polygon.instantiate();554Ref<NavigationMeshSourceGeometryData2D> source_geometry;555source_geometry.instantiate();556CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);557CHECK_EQ(source_geometry->get_obstruction_outlines().size(), 0);558559navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, polygon);560CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);561REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 1);562CHECK_EQ(((PackedVector2Array)source_geometry->get_obstruction_outlines()[0]).size(), 4);563564SUBCASE("By default, parsing should remove any data that was parsed before") {565navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, polygon);566CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);567REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 1);568CHECK_EQ(((PackedVector2Array)source_geometry->get_obstruction_outlines()[0]).size(), 4);569}570571SUBCASE("Parsed geometry should be extendible with other geometry") {572source_geometry->merge(source_geometry); // Merging with itself.573CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);574REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 2);575const PackedVector2Array obstruction_outline_1 = source_geometry->get_obstruction_outlines()[0];576const PackedVector2Array obstruction_outline_2 = source_geometry->get_obstruction_outlines()[1];577REQUIRE_EQ(obstruction_outline_1.size(), 4);578REQUIRE_EQ(obstruction_outline_2.size(), 4);579CHECK_EQ(obstruction_outline_1[0], obstruction_outline_2[0]);580CHECK_EQ(obstruction_outline_1[1], obstruction_outline_2[1]);581CHECK_EQ(obstruction_outline_1[2], obstruction_outline_2[2]);582CHECK_EQ(obstruction_outline_1[3], obstruction_outline_2[3]);583}584585memdelete(polygon);586memdelete(node_2d);587}588589// This test case uses only public APIs on purpose - other test cases use simplified baking.590TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to bake map correctly") {591NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();592593// Prepare scene tree with simple mesh to serve as an input geometry.594Node2D *node_2d = memnew(Node2D);595SceneTree::get_singleton()->get_root()->add_child(node_2d);596Polygon2D *polygon = memnew(Polygon2D);597polygon->set_polygon(PackedVector2Array({ Vector2(-200.0, -200.0), Vector2(200.0, -200.0), Vector2(200.0, 200.0), Vector2(-200.0, 200.0) }));598node_2d->add_child(polygon);599600// TODO: Use MeshInstance2D as well?601602// Prepare anything necessary to bake navigation polygon.603RID map = navigation_server->map_create();604RID region = navigation_server->region_create();605Ref<NavigationPolygon> navigation_polygon;606navigation_polygon.instantiate();607navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));608navigation_server->map_set_active(map, true);609navigation_server->map_set_use_async_iterations(map, false);610navigation_server->region_set_use_async_iterations(region, false);611navigation_server->region_set_map(region, map);612navigation_server->region_set_navigation_polygon(region, navigation_polygon);613navigation_server->physics_process(0.0); // Give server some cycles to commit.614615CHECK_EQ(navigation_polygon->get_polygon_count(), 0);616CHECK_EQ(navigation_polygon->get_vertices().size(), 0);617CHECK_EQ(navigation_polygon->get_outline_count(), 1);618619Ref<NavigationMeshSourceGeometryData2D> source_geometry;620source_geometry.instantiate();621navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, node_2d);622navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());623// FIXME: The above line should trigger the update (line below) under the hood.624navigation_server->region_set_navigation_polygon(region, navigation_polygon); // Force update.625CHECK_EQ(navigation_polygon->get_polygon_count(), 4);626CHECK_EQ(navigation_polygon->get_vertices().size(), 8);627CHECK_EQ(navigation_polygon->get_outline_count(), 1);628629SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {630SIGNAL_WATCH(navigation_server, "map_changed");631SIGNAL_CHECK_FALSE("map_changed");632navigation_server->physics_process(0.0); // Give server some cycles to commit.633SIGNAL_CHECK("map_changed", { { map } });634SIGNAL_UNWATCH(navigation_server, "map_changed");635CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0, 0)), Vector2(0, 0));636}637638navigation_server->free_rid(region);639navigation_server->free_rid(map);640navigation_server->physics_process(0.0); // Give server some cycles to commit.641642memdelete(polygon);643memdelete(node_2d);644}645646// This test case does not check precise values on purpose - to not be too sensitivte.647TEST_CASE("[NavigationServer2D] Server should respond to queries against valid map properly") {648NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();649Ref<NavigationPolygon> navigation_polygon;650navigation_polygon.instantiate();651Ref<NavigationMeshSourceGeometryData2D> source_geometry;652source_geometry.instantiate();653654navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));655656// TODO: Other input?657source_geometry->add_obstruction_outline(PackedVector2Array({ Vector2(-200.0, -200.0), Vector2(200.0, -200.0), Vector2(200.0, 200.0), Vector2(-200.0, 200.0) }));658659navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());660CHECK_NE(navigation_polygon->get_polygon_count(), 0);661CHECK_NE(navigation_polygon->get_vertices().size(), 0);662CHECK_NE(navigation_polygon->get_outline_count(), 0);663664RID map = navigation_server->map_create();665RID region = navigation_server->region_create();666navigation_server->map_set_active(map, true);667navigation_server->map_set_use_async_iterations(map, false);668navigation_server->region_set_use_async_iterations(region, false);669navigation_server->region_set_map(region, map);670navigation_server->region_set_navigation_polygon(region, navigation_polygon);671navigation_server->physics_process(0.0); // Give server some cycles to commit.672673SUBCASE("Simple queries should return non-default values") {674CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0.0, 0.0)), Vector2(0, 0));675CHECK(navigation_server->map_get_closest_point_owner(map, Vector2(0.0, 0.0)).is_valid());676CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), true).size(), 0);677CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), false).size(), 0);678}679680SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {681Ref<NavigationPathQueryParameters2D> query_parameters;682query_parameters.instantiate();683query_parameters->set_map(map);684query_parameters->set_start_position(Vector2(0, 0));685query_parameters->set_target_position(Vector2(10, 10));686query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_CORRIDORFUNNEL);687Ref<NavigationPathQueryResult2D> query_result;688query_result.instantiate();689navigation_server->query_path(query_parameters, query_result);690CHECK_NE(query_result->get_path().size(), 0);691CHECK_NE(query_result->get_path_types().size(), 0);692CHECK_NE(query_result->get_path_rids().size(), 0);693CHECK_NE(query_result->get_path_owner_ids().size(), 0);694}695696SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {697Ref<NavigationPathQueryParameters2D> query_parameters;698query_parameters.instantiate();699query_parameters->set_map(map);700query_parameters->set_start_position(Vector2(10, 10));701query_parameters->set_target_position(Vector2(0, 0));702query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_EDGECENTERED);703Ref<NavigationPathQueryResult2D> query_result;704query_result.instantiate();705navigation_server->query_path(query_parameters, query_result);706CHECK_NE(query_result->get_path().size(), 0);707CHECK_NE(query_result->get_path_types().size(), 0);708CHECK_NE(query_result->get_path_rids().size(), 0);709CHECK_NE(query_result->get_path_owner_ids().size(), 0);710}711712SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {713Ref<NavigationPathQueryParameters2D> query_parameters;714query_parameters.instantiate();715query_parameters->set_map(map);716query_parameters->set_start_position(Vector2(10, 10));717query_parameters->set_target_position(Vector2(0, 0));718query_parameters->set_navigation_layers(2);719Ref<NavigationPathQueryResult2D> query_result;720query_result.instantiate();721navigation_server->query_path(query_parameters, query_result);722CHECK_EQ(query_result->get_path().size(), 0);723CHECK_EQ(query_result->get_path_types().size(), 0);724CHECK_EQ(query_result->get_path_rids().size(), 0);725CHECK_EQ(query_result->get_path_owner_ids().size(), 0);726}727728SUBCASE("Elaborate query without metadata flags should yield path only") {729Ref<NavigationPathQueryParameters2D> query_parameters;730query_parameters.instantiate();731query_parameters->set_map(map);732query_parameters->set_start_position(Vector2(10, 10));733query_parameters->set_target_position(Vector2(0, 0));734query_parameters->set_metadata_flags(0);735Ref<NavigationPathQueryResult2D> query_result;736query_result.instantiate();737navigation_server->query_path(query_parameters, query_result);738CHECK_NE(query_result->get_path().size(), 0);739CHECK_EQ(query_result->get_path_types().size(), 0);740CHECK_EQ(query_result->get_path_rids().size(), 0);741CHECK_EQ(query_result->get_path_owner_ids().size(), 0);742}743744navigation_server->free_rid(region);745navigation_server->free_rid(map);746navigation_server->physics_process(0.0); // Give server some cycles to commit.747}748749TEST_CASE("[NavigationServer2D] Server should simplify path properly") {750real_t simplify_epsilon = 0.2;751Vector<Vector2> source_path;752source_path.resize(7);753source_path.write[0] = Vector2(0.0, 0.0);754source_path.write[1] = Vector2(0.0, 1.0); // This point needs to go.755source_path.write[2] = Vector2(0.0, 2.0); // This point needs to go.756source_path.write[3] = Vector2(0.0, 2.0);757source_path.write[4] = Vector2(2.0, 3.0);758source_path.write[5] = Vector2(2.5, 4.0); // This point needs to go.759source_path.write[6] = Vector2(3.0, 5.0);760Vector<Vector2> simplified_path = NavigationServer2D::get_singleton()->simplify_path(source_path, simplify_epsilon);761CHECK_EQ(simplified_path.size(), 4);762}763}764765} // namespace TestNavigationServer2D766767#endif // MODULE_NAVIGATION_2D_ENABLED768769770