Path: blob/master/tests/servers/test_navigation_server_2d.h
10277 views
/**************************************************************************/1/* test_navigation_server_2d.h */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#pragma once3132#include "modules/navigation_2d/nav_utils_2d.h"33#include "servers/navigation_server_2d.h"3435#include "scene/2d/polygon_2d.h"3637#include "tests/test_macros.h"3839namespace TestNavigationServer2D {4041// TODO: Find a more generic way to create `Callable` mocks.42class CallableMock : public Object {43GDCLASS(CallableMock, Object);4445public:46void function1(Variant arg0) {47function1_calls++;48function1_latest_arg0 = arg0;49}5051unsigned function1_calls{ 0 };52Variant function1_latest_arg0;53};5455struct GreaterThan {56bool operator()(int p_a, int p_b) const { return p_a > p_b; }57};5859struct CompareArrayValues {60const int *array;6162CompareArrayValues(const int *p_array) :63array(p_array) {}6465bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {66return array[p_index_a] < array[p_index_b];67}68};6970struct RegisterHeapIndexes {71uint32_t *indexes;7273RegisterHeapIndexes(uint32_t *p_indexes) :74indexes(p_indexes) {}7576void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {77indexes[p_vector_index] = p_heap_index;78}79};8081TEST_SUITE("[Navigation2D]") {82TEST_CASE("[NavigationServer2D] Server should be empty when initialized") {83NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();84CHECK_EQ(navigation_server->get_maps().size(), 0);8586SUBCASE("'ProcessInfo' should report all counters empty as well") {87CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);88CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);89CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);90CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);91CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_POLYGON_COUNT), 0);92CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_COUNT), 0);93CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_MERGE_COUNT), 0);94CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_CONNECTION_COUNT), 0);95CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_FREE_COUNT), 0);96}97}9899TEST_CASE("[NavigationServer2D] Server should manage agent properly") {100NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();101102RID agent = navigation_server->agent_create();103CHECK(agent.is_valid());104105SUBCASE("'ProcessInfo' should not report dangling agent") {106CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);107}108109SUBCASE("Setters/getters should work") {110bool initial_avoidance_enabled = navigation_server->agent_get_avoidance_enabled(agent);111navigation_server->agent_set_avoidance_enabled(agent, !initial_avoidance_enabled);112navigation_server->physics_process(0.0); // Give server some cycles to commit.113114CHECK_EQ(navigation_server->agent_get_avoidance_enabled(agent), !initial_avoidance_enabled);115// TODO: Add remaining setters/getters once the missing getters are added.116}117118SUBCASE("'ProcessInfo' should report agent with active map") {119RID map = navigation_server->map_create();120CHECK(map.is_valid());121navigation_server->map_set_active(map, true);122navigation_server->agent_set_map(agent, map);123navigation_server->physics_process(0.0); // Give server some cycles to commit.124CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 1);125navigation_server->agent_set_map(agent, RID());126navigation_server->free(map);127navigation_server->physics_process(0.0); // Give server some cycles to commit.128CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);129}130131navigation_server->free(agent);132}133134TEST_CASE("[NavigationServer2D] Server should manage map properly") {135NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();136137RID map;138CHECK_FALSE(map.is_valid());139140SUBCASE("Queries against invalid map should return empty or invalid values") {141ERR_PRINT_OFF;142CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());143CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());144CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);145CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);146147Ref<NavigationPathQueryParameters2D> query_parameters;148query_parameters.instantiate();149query_parameters->set_map(map);150query_parameters->set_start_position(Vector2(7, 7));151query_parameters->set_target_position(Vector2(8, 8));152Ref<NavigationPathQueryResult2D> query_result;153query_result.instantiate();154navigation_server->query_path(query_parameters, query_result);155CHECK_EQ(query_result->get_path().size(), 0);156CHECK_EQ(query_result->get_path_types().size(), 0);157CHECK_EQ(query_result->get_path_rids().size(), 0);158CHECK_EQ(query_result->get_path_owner_ids().size(), 0);159ERR_PRINT_ON;160}161162map = navigation_server->map_create();163CHECK(map.is_valid());164CHECK_EQ(navigation_server->get_maps().size(), 1);165166SUBCASE("'ProcessInfo' should not report inactive map") {167CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);168}169170SUBCASE("Setters/getters should work") {171navigation_server->map_set_cell_size(map, 0.55);172navigation_server->map_set_edge_connection_margin(map, 0.66);173navigation_server->map_set_link_connection_radius(map, 0.77);174bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);175navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);176navigation_server->physics_process(0.0); // Give server some cycles to commit.177178CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));179CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));180CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));181CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);182}183184SUBCASE("'ProcessInfo' should report map iff active") {185navigation_server->map_set_active(map, true);186navigation_server->physics_process(0.0); // Give server some cycles to commit.187CHECK(navigation_server->map_is_active(map));188CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 1);189navigation_server->map_set_active(map, false);190navigation_server->physics_process(0.0); // Give server some cycles to commit.191CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);192}193194SUBCASE("Number of agents should be reported properly") {195RID agent = navigation_server->agent_create();196CHECK(agent.is_valid());197navigation_server->agent_set_map(agent, map);198navigation_server->physics_process(0.0); // Give server some cycles to commit.199CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);200navigation_server->free(agent);201navigation_server->physics_process(0.0); // Give server some cycles to commit.202CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);203}204205SUBCASE("Number of links should be reported properly") {206RID link = navigation_server->link_create();207CHECK(link.is_valid());208navigation_server->link_set_map(link, map);209navigation_server->physics_process(0.0); // Give server some cycles to commit.210CHECK_EQ(navigation_server->map_get_links(map).size(), 1);211navigation_server->free(link);212navigation_server->physics_process(0.0); // Give server some cycles to commit.213CHECK_EQ(navigation_server->map_get_links(map).size(), 0);214}215216SUBCASE("Number of obstacles should be reported properly") {217RID obstacle = navigation_server->obstacle_create();218CHECK(obstacle.is_valid());219navigation_server->obstacle_set_map(obstacle, map);220navigation_server->physics_process(0.0); // Give server some cycles to commit.221CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);222navigation_server->free(obstacle);223navigation_server->physics_process(0.0); // Give server some cycles to commit.224CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);225}226227SUBCASE("Number of regions should be reported properly") {228RID region = navigation_server->region_create();229CHECK(region.is_valid());230navigation_server->region_set_map(region, map);231navigation_server->physics_process(0.0); // Give server some cycles to commit.232CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);233navigation_server->free(region);234navigation_server->physics_process(0.0); // Give server some cycles to commit.235CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);236}237238SUBCASE("Queries against empty map should return empty or invalid values") {239navigation_server->map_set_active(map, true);240navigation_server->physics_process(0.0); // Give server some cycles to commit.241242ERR_PRINT_OFF;243CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());244CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());245CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);246CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);247248Ref<NavigationPathQueryParameters2D> query_parameters;249query_parameters.instantiate();250query_parameters->set_map(map);251query_parameters->set_start_position(Vector2(7, 7));252query_parameters->set_target_position(Vector2(8, 8));253Ref<NavigationPathQueryResult2D> query_result;254query_result.instantiate();255navigation_server->query_path(query_parameters, query_result);256CHECK_EQ(query_result->get_path().size(), 0);257CHECK_EQ(query_result->get_path_types().size(), 0);258CHECK_EQ(query_result->get_path_rids().size(), 0);259CHECK_EQ(query_result->get_path_owner_ids().size(), 0);260ERR_PRINT_ON;261262navigation_server->map_set_active(map, false);263navigation_server->physics_process(0.0); // Give server some cycles to commit.264}265266navigation_server->free(map);267navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.268CHECK_EQ(navigation_server->get_maps().size(), 0);269}270271TEST_CASE("[NavigationServer2D] Server should manage link properly") {272NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();273274RID link = navigation_server->link_create();275CHECK(link.is_valid());276277SUBCASE("'ProcessInfo' should not report dangling link") {278CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);279}280281SUBCASE("Setters/getters should work") {282bool initial_bidirectional = navigation_server->link_is_bidirectional(link);283navigation_server->link_set_bidirectional(link, !initial_bidirectional);284navigation_server->link_set_end_position(link, Vector2(7, 7));285navigation_server->link_set_enter_cost(link, 0.55);286navigation_server->link_set_navigation_layers(link, 6);287navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));288navigation_server->link_set_start_position(link, Vector2(8, 8));289navigation_server->link_set_travel_cost(link, 0.66);290navigation_server->physics_process(0.0); // Give server some cycles to commit.291292CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);293CHECK_EQ(navigation_server->link_get_end_position(link), Vector2(7, 7));294CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));295CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);296CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));297CHECK_EQ(navigation_server->link_get_start_position(link), Vector2(8, 8));298CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));299}300301SUBCASE("'ProcessInfo' should report link with active map") {302RID map = navigation_server->map_create();303CHECK(map.is_valid());304navigation_server->map_set_active(map, true);305navigation_server->link_set_map(link, map);306navigation_server->physics_process(0.0); // Give server some cycles to commit.307CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 1);308navigation_server->link_set_map(link, RID());309navigation_server->free(map);310navigation_server->physics_process(0.0); // Give server some cycles to commit.311CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);312}313314navigation_server->free(link);315}316317TEST_CASE("[NavigationServer2D] Server should manage obstacles properly") {318NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();319320RID obstacle = navigation_server->obstacle_create();321CHECK(obstacle.is_valid());322323// TODO: Add tests for setters/getters once getters are added.324325navigation_server->free(obstacle);326}327328TEST_CASE("[NavigationServer2D] Server should manage regions properly") {329NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();330331RID region = navigation_server->region_create();332CHECK(region.is_valid());333334SUBCASE("'ProcessInfo' should not report dangling region") {335CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);336}337338SUBCASE("Setters/getters should work") {339bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);340navigation_server->region_set_enter_cost(region, 0.55);341navigation_server->region_set_navigation_layers(region, 5);342navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));343navigation_server->region_set_travel_cost(region, 0.66);344navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);345navigation_server->physics_process(0.0); // Give server some cycles to commit.346347CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));348CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);349CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));350CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));351CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);352}353354SUBCASE("'ProcessInfo' should report region with active map") {355RID map = navigation_server->map_create();356CHECK(map.is_valid());357navigation_server->map_set_active(map, true);358navigation_server->region_set_map(region, map);359navigation_server->physics_process(0.0); // Give server some cycles to commit.360CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 1);361navigation_server->region_set_map(region, RID());362navigation_server->free(map);363navigation_server->physics_process(0.0); // Give server some cycles to commit.364CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);365}366367SUBCASE("Queries against empty region should return empty or invalid values") {368ERR_PRINT_OFF;369CHECK_EQ(navigation_server->region_get_connections_count(region), 0);370CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector2());371CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector2());372ERR_PRINT_ON;373}374375navigation_server->free(region);376}377378// This test case does not check precise values on purpose - to not be too sensitivte.379TEST_CASE("[NavigationServer2D] Server should move agent properly") {380NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();381382RID map = navigation_server->map_create();383RID agent = navigation_server->agent_create();384385navigation_server->map_set_active(map, true);386navigation_server->agent_set_map(agent, map);387navigation_server->agent_set_avoidance_enabled(agent, true);388navigation_server->agent_set_velocity(agent, Vector2(1, 1));389CallableMock agent_avoidance_callback_mock;390navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));391CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);392navigation_server->physics_process(0.0); // Give server some cycles to commit.393CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);394CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector2(0, 0));395396navigation_server->free(agent);397navigation_server->free(map);398}399400// This test case does not check precise values on purpose - to not be too sensitivte.401TEST_CASE("[NavigationServer2D] Server should make agents avoid each other when avoidance enabled") {402NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();403404RID map = navigation_server->map_create();405RID agent_1 = navigation_server->agent_create();406RID agent_2 = navigation_server->agent_create();407408navigation_server->map_set_active(map, true);409410navigation_server->agent_set_map(agent_1, map);411navigation_server->agent_set_avoidance_enabled(agent_1, true);412navigation_server->agent_set_position(agent_1, Vector2(0, 0));413navigation_server->agent_set_radius(agent_1, 1);414navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));415CallableMock agent_1_avoidance_callback_mock;416navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));417418navigation_server->agent_set_map(agent_2, map);419navigation_server->agent_set_avoidance_enabled(agent_2, true);420navigation_server->agent_set_position(agent_2, Vector2(2.5, 0.5));421navigation_server->agent_set_radius(agent_2, 1);422navigation_server->agent_set_velocity(agent_2, Vector2(-1, 0));423CallableMock agent_2_avoidance_callback_mock;424navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));425426CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);427CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);428navigation_server->physics_process(0.0); // Give server some cycles to commit.429CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);430CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);431Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;432Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;433CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");434CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");435CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "agent 1 should move a bit to the side so that it avoids agent 2");436CHECK_MESSAGE(agent_2_safe_velocity.y > 0, "agent 2 should move a bit to the side so that it avoids agent 1");437438navigation_server->free(agent_2);439navigation_server->free(agent_1);440navigation_server->free(map);441}442443TEST_CASE("[NavigationServer2D] Server should make agents avoid dynamic obstacles when avoidance enabled") {444NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();445446RID map = navigation_server->map_create();447RID agent_1 = navigation_server->agent_create();448RID obstacle_1 = navigation_server->obstacle_create();449450navigation_server->map_set_active(map, true);451452navigation_server->agent_set_map(agent_1, map);453navigation_server->agent_set_avoidance_enabled(agent_1, true);454navigation_server->agent_set_position(agent_1, Vector2(0, 0));455navigation_server->agent_set_radius(agent_1, 1);456navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));457CallableMock agent_1_avoidance_callback_mock;458navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));459460navigation_server->obstacle_set_map(obstacle_1, map);461navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);462navigation_server->obstacle_set_position(obstacle_1, Vector2(2.5, 0.5));463navigation_server->obstacle_set_radius(obstacle_1, 1);464465CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);466navigation_server->physics_process(0.0); // Give server some cycles to commit.467CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);468Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;469CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");470CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");471472navigation_server->free(obstacle_1);473navigation_server->free(agent_1);474navigation_server->free(map);475navigation_server->physics_process(0.0); // Give server some cycles to commit.476}477478TEST_CASE("[NavigationServer2D] Server should make agents avoid static obstacles when avoidance enabled") {479NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();480481RID map = navigation_server->map_create();482RID agent_1 = navigation_server->agent_create();483RID agent_2 = navigation_server->agent_create();484RID obstacle_1 = navigation_server->obstacle_create();485486navigation_server->map_set_active(map, true);487488navigation_server->agent_set_map(agent_1, map);489navigation_server->agent_set_avoidance_enabled(agent_1, true);490navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.491navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));492CallableMock agent_1_avoidance_callback_mock;493navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));494495navigation_server->agent_set_map(agent_2, map);496navigation_server->agent_set_avoidance_enabled(agent_2, true);497navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.498navigation_server->agent_set_velocity(agent_2, Vector2(1, 0));499CallableMock agent_2_avoidance_callback_mock;500navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));501502navigation_server->obstacle_set_map(obstacle_1, map);503navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);504PackedVector2Array obstacle_1_vertices;505506SUBCASE("Static obstacles should work on ground level") {507navigation_server->agent_set_position(agent_1, Vector2(0, 0));508navigation_server->agent_set_position(agent_2, Vector2(0, 5));509obstacle_1_vertices.push_back(Vector2(1.5, 0.5));510obstacle_1_vertices.push_back(Vector2(1.5, 4.5));511}512513navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);514515CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);516CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);517navigation_server->physics_process(0.0); // Give server some cycles to commit.518CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);519CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);520Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;521Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;522CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");523CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");524CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");525CHECK_MESSAGE(agent_2_safe_velocity.y == 0, "Agent 2 should not move to the side.");526527navigation_server->free(obstacle_1);528navigation_server->free(agent_2);529navigation_server->free(agent_1);530navigation_server->free(map);531navigation_server->physics_process(0.0); // Give server some cycles to commit.532}533534TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to parse geometry") {535NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();536537// Prepare scene tree with simple mesh to serve as an input geometry.538Node2D *node_2d = memnew(Node2D);539SceneTree::get_singleton()->get_root()->add_child(node_2d);540Polygon2D *polygon = memnew(Polygon2D);541polygon->set_polygon(PackedVector2Array({ Vector2(200.0, 200.0), Vector2(400.0, 200.0), Vector2(400.0, 400.0), Vector2(200.0, 400.0) }));542node_2d->add_child(polygon);543544// TODO: Use MeshInstance2D as well?545546Ref<NavigationPolygon> navigation_polygon;547navigation_polygon.instantiate();548Ref<NavigationMeshSourceGeometryData2D> source_geometry;549source_geometry.instantiate();550CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);551CHECK_EQ(source_geometry->get_obstruction_outlines().size(), 0);552553navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, polygon);554CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);555REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 1);556CHECK_EQ(((PackedVector2Array)source_geometry->get_obstruction_outlines()[0]).size(), 4);557558SUBCASE("By default, parsing should remove any data that was parsed before") {559navigation_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);563}564565SUBCASE("Parsed geometry should be extendible with other geometry") {566source_geometry->merge(source_geometry); // Merging with itself.567CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);568REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 2);569const PackedVector2Array obstruction_outline_1 = source_geometry->get_obstruction_outlines()[0];570const PackedVector2Array obstruction_outline_2 = source_geometry->get_obstruction_outlines()[1];571REQUIRE_EQ(obstruction_outline_1.size(), 4);572REQUIRE_EQ(obstruction_outline_2.size(), 4);573CHECK_EQ(obstruction_outline_1[0], obstruction_outline_2[0]);574CHECK_EQ(obstruction_outline_1[1], obstruction_outline_2[1]);575CHECK_EQ(obstruction_outline_1[2], obstruction_outline_2[2]);576CHECK_EQ(obstruction_outline_1[3], obstruction_outline_2[3]);577}578579memdelete(polygon);580memdelete(node_2d);581}582583// This test case uses only public APIs on purpose - other test cases use simplified baking.584TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to bake map correctly") {585NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();586587// Prepare scene tree with simple mesh to serve as an input geometry.588Node2D *node_2d = memnew(Node2D);589SceneTree::get_singleton()->get_root()->add_child(node_2d);590Polygon2D *polygon = memnew(Polygon2D);591polygon->set_polygon(PackedVector2Array({ Vector2(-200.0, -200.0), Vector2(200.0, -200.0), Vector2(200.0, 200.0), Vector2(-200.0, 200.0) }));592node_2d->add_child(polygon);593594// TODO: Use MeshInstance2D as well?595596// Prepare anything necessary to bake navigation polygon.597RID map = navigation_server->map_create();598RID region = navigation_server->region_create();599Ref<NavigationPolygon> navigation_polygon;600navigation_polygon.instantiate();601navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));602navigation_server->map_set_active(map, true);603navigation_server->map_set_use_async_iterations(map, false);604navigation_server->region_set_use_async_iterations(region, false);605navigation_server->region_set_map(region, map);606navigation_server->region_set_navigation_polygon(region, navigation_polygon);607navigation_server->physics_process(0.0); // Give server some cycles to commit.608609CHECK_EQ(navigation_polygon->get_polygon_count(), 0);610CHECK_EQ(navigation_polygon->get_vertices().size(), 0);611CHECK_EQ(navigation_polygon->get_outline_count(), 1);612613Ref<NavigationMeshSourceGeometryData2D> source_geometry;614source_geometry.instantiate();615navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, node_2d);616navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());617// FIXME: The above line should trigger the update (line below) under the hood.618navigation_server->region_set_navigation_polygon(region, navigation_polygon); // Force update.619CHECK_EQ(navigation_polygon->get_polygon_count(), 4);620CHECK_EQ(navigation_polygon->get_vertices().size(), 8);621CHECK_EQ(navigation_polygon->get_outline_count(), 1);622623SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {624SIGNAL_WATCH(navigation_server, "map_changed");625SIGNAL_CHECK_FALSE("map_changed");626navigation_server->physics_process(0.0); // Give server some cycles to commit.627SIGNAL_CHECK("map_changed", { { map } });628SIGNAL_UNWATCH(navigation_server, "map_changed");629CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0, 0)), Vector2(0, 0));630}631632navigation_server->free(region);633navigation_server->free(map);634navigation_server->physics_process(0.0); // Give server some cycles to commit.635636memdelete(polygon);637memdelete(node_2d);638}639640// This test case does not check precise values on purpose - to not be too sensitivte.641TEST_CASE("[NavigationServer2D] Server should respond to queries against valid map properly") {642NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();643Ref<NavigationPolygon> navigation_polygon;644navigation_polygon.instantiate();645Ref<NavigationMeshSourceGeometryData2D> source_geometry;646source_geometry.instantiate();647648navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));649650// TODO: Other input?651source_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) }));652653navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());654CHECK_NE(navigation_polygon->get_polygon_count(), 0);655CHECK_NE(navigation_polygon->get_vertices().size(), 0);656CHECK_NE(navigation_polygon->get_outline_count(), 0);657658RID map = navigation_server->map_create();659RID region = navigation_server->region_create();660navigation_server->map_set_active(map, true);661navigation_server->map_set_use_async_iterations(map, false);662navigation_server->region_set_use_async_iterations(region, false);663navigation_server->region_set_map(region, map);664navigation_server->region_set_navigation_polygon(region, navigation_polygon);665navigation_server->physics_process(0.0); // Give server some cycles to commit.666667SUBCASE("Simple queries should return non-default values") {668CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0.0, 0.0)), Vector2(0, 0));669CHECK(navigation_server->map_get_closest_point_owner(map, Vector2(0.0, 0.0)).is_valid());670CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), true).size(), 0);671CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), false).size(), 0);672}673674SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {675Ref<NavigationPathQueryParameters2D> query_parameters;676query_parameters.instantiate();677query_parameters->set_map(map);678query_parameters->set_start_position(Vector2(0, 0));679query_parameters->set_target_position(Vector2(10, 10));680query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_CORRIDORFUNNEL);681Ref<NavigationPathQueryResult2D> query_result;682query_result.instantiate();683navigation_server->query_path(query_parameters, query_result);684CHECK_NE(query_result->get_path().size(), 0);685CHECK_NE(query_result->get_path_types().size(), 0);686CHECK_NE(query_result->get_path_rids().size(), 0);687CHECK_NE(query_result->get_path_owner_ids().size(), 0);688}689690SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {691Ref<NavigationPathQueryParameters2D> query_parameters;692query_parameters.instantiate();693query_parameters->set_map(map);694query_parameters->set_start_position(Vector2(10, 10));695query_parameters->set_target_position(Vector2(0, 0));696query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_EDGECENTERED);697Ref<NavigationPathQueryResult2D> query_result;698query_result.instantiate();699navigation_server->query_path(query_parameters, query_result);700CHECK_NE(query_result->get_path().size(), 0);701CHECK_NE(query_result->get_path_types().size(), 0);702CHECK_NE(query_result->get_path_rids().size(), 0);703CHECK_NE(query_result->get_path_owner_ids().size(), 0);704}705706SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {707Ref<NavigationPathQueryParameters2D> query_parameters;708query_parameters.instantiate();709query_parameters->set_map(map);710query_parameters->set_start_position(Vector2(10, 10));711query_parameters->set_target_position(Vector2(0, 0));712query_parameters->set_navigation_layers(2);713Ref<NavigationPathQueryResult2D> query_result;714query_result.instantiate();715navigation_server->query_path(query_parameters, query_result);716CHECK_EQ(query_result->get_path().size(), 0);717CHECK_EQ(query_result->get_path_types().size(), 0);718CHECK_EQ(query_result->get_path_rids().size(), 0);719CHECK_EQ(query_result->get_path_owner_ids().size(), 0);720}721722SUBCASE("Elaborate query without metadata flags should yield path only") {723Ref<NavigationPathQueryParameters2D> query_parameters;724query_parameters.instantiate();725query_parameters->set_map(map);726query_parameters->set_start_position(Vector2(10, 10));727query_parameters->set_target_position(Vector2(0, 0));728query_parameters->set_metadata_flags(0);729Ref<NavigationPathQueryResult2D> query_result;730query_result.instantiate();731navigation_server->query_path(query_parameters, query_result);732CHECK_NE(query_result->get_path().size(), 0);733CHECK_EQ(query_result->get_path_types().size(), 0);734CHECK_EQ(query_result->get_path_rids().size(), 0);735CHECK_EQ(query_result->get_path_owner_ids().size(), 0);736}737738navigation_server->free(region);739navigation_server->free(map);740navigation_server->physics_process(0.0); // Give server some cycles to commit.741}742743TEST_CASE("[NavigationServer2D] Server should simplify path properly") {744real_t simplify_epsilon = 0.2;745Vector<Vector2> source_path;746source_path.resize(7);747source_path.write[0] = Vector2(0.0, 0.0);748source_path.write[1] = Vector2(0.0, 1.0); // This point needs to go.749source_path.write[2] = Vector2(0.0, 2.0); // This point needs to go.750source_path.write[3] = Vector2(0.0, 2.0);751source_path.write[4] = Vector2(2.0, 3.0);752source_path.write[5] = Vector2(2.5, 4.0); // This point needs to go.753source_path.write[6] = Vector2(3.0, 5.0);754Vector<Vector2> simplified_path = NavigationServer2D::get_singleton()->simplify_path(source_path, simplify_epsilon);755CHECK_EQ(simplified_path.size(), 4);756}757}758} //namespace TestNavigationServer2D759760761