Path: blob/master/tests/servers/test_navigation_server_3d.h
10277 views
/**************************************************************************/1/* test_navigation_server_3d.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 "scene/3d/mesh_instance_3d.h"33#include "scene/resources/3d/primitive_meshes.h"34#include "servers/navigation_server_3d.h"3536namespace TestNavigationServer3D {3738// TODO: Find a more generic way to create `Callable` mocks.39class CallableMock : public Object {40GDCLASS(CallableMock, Object);4142public:43void function1(Variant arg0) {44function1_calls++;45function1_latest_arg0 = arg0;46}4748unsigned function1_calls{ 0 };49Variant function1_latest_arg0;50};5152TEST_SUITE("[Navigation3D]") {53TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {54NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();55CHECK_EQ(navigation_server->get_maps().size(), 0);5657SUBCASE("'ProcessInfo' should report all counters empty as well") {58CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);59CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);60CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);61CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);62CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_POLYGON_COUNT), 0);63CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_COUNT), 0);64CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_MERGE_COUNT), 0);65CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_CONNECTION_COUNT), 0);66CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_FREE_COUNT), 0);67}68}6970TEST_CASE("[NavigationServer3D] Server should manage agent properly") {71NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();7273RID agent = navigation_server->agent_create();74CHECK(agent.is_valid());7576SUBCASE("'ProcessInfo' should not report dangling agent") {77CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);78}7980SUBCASE("Setters/getters should work") {81bool initial_use_3d_avoidance = navigation_server->agent_get_use_3d_avoidance(agent);82navigation_server->agent_set_use_3d_avoidance(agent, !initial_use_3d_avoidance);83navigation_server->physics_process(0.0); // Give server some cycles to commit.8485CHECK_EQ(navigation_server->agent_get_use_3d_avoidance(agent), !initial_use_3d_avoidance);86// TODO: Add remaining setters/getters once the missing getters are added.87}8889SUBCASE("'ProcessInfo' should report agent with active map") {90RID map = navigation_server->map_create();91CHECK(map.is_valid());92navigation_server->map_set_active(map, true);93navigation_server->agent_set_map(agent, map);94navigation_server->physics_process(0.0); // Give server some cycles to commit.95CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 1);96navigation_server->agent_set_map(agent, RID());97navigation_server->free(map);98navigation_server->physics_process(0.0); // Give server some cycles to commit.99CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);100}101102navigation_server->free(agent);103}104105TEST_CASE("[NavigationServer3D] Server should manage map properly") {106NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();107108RID map;109CHECK_FALSE(map.is_valid());110111SUBCASE("Queries against invalid map should return empty or invalid values") {112ERR_PRINT_OFF;113CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());114CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());115CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());116CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());117CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());118CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);119CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);120121Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);122query_parameters->set_map(map);123query_parameters->set_start_position(Vector3(7, 7, 7));124query_parameters->set_target_position(Vector3(8, 8, 8));125Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);126navigation_server->query_path(query_parameters, query_result);127CHECK_EQ(query_result->get_path().size(), 0);128CHECK_EQ(query_result->get_path_types().size(), 0);129CHECK_EQ(query_result->get_path_rids().size(), 0);130CHECK_EQ(query_result->get_path_owner_ids().size(), 0);131ERR_PRINT_ON;132}133134map = navigation_server->map_create();135CHECK(map.is_valid());136CHECK_EQ(navigation_server->get_maps().size(), 1);137138SUBCASE("'ProcessInfo' should not report inactive map") {139CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);140}141142SUBCASE("Setters/getters should work") {143navigation_server->map_set_cell_size(map, 0.55);144navigation_server->map_set_edge_connection_margin(map, 0.66);145navigation_server->map_set_link_connection_radius(map, 0.77);146navigation_server->map_set_up(map, Vector3(1, 0, 0));147bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);148navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);149navigation_server->physics_process(0.0); // Give server some cycles to commit.150151CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));152CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));153CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));154CHECK_EQ(navigation_server->map_get_up(map), Vector3(1, 0, 0));155CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);156}157158SUBCASE("'ProcessInfo' should report map iff active") {159navigation_server->map_set_active(map, true);160navigation_server->physics_process(0.0); // Give server some cycles to commit.161CHECK(navigation_server->map_is_active(map));162CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 1);163navigation_server->map_set_active(map, false);164navigation_server->physics_process(0.0); // Give server some cycles to commit.165CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);166}167168SUBCASE("Number of agents should be reported properly") {169RID agent = navigation_server->agent_create();170CHECK(agent.is_valid());171navigation_server->agent_set_map(agent, map);172navigation_server->physics_process(0.0); // Give server some cycles to commit.173CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);174navigation_server->free(agent);175navigation_server->physics_process(0.0); // Give server some cycles to commit.176CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);177}178179SUBCASE("Number of links should be reported properly") {180RID link = navigation_server->link_create();181CHECK(link.is_valid());182navigation_server->link_set_map(link, map);183navigation_server->physics_process(0.0); // Give server some cycles to commit.184CHECK_EQ(navigation_server->map_get_links(map).size(), 1);185navigation_server->free(link);186navigation_server->physics_process(0.0); // Give server some cycles to commit.187CHECK_EQ(navigation_server->map_get_links(map).size(), 0);188}189190SUBCASE("Number of obstacles should be reported properly") {191RID obstacle = navigation_server->obstacle_create();192CHECK(obstacle.is_valid());193navigation_server->obstacle_set_map(obstacle, map);194navigation_server->physics_process(0.0); // Give server some cycles to commit.195CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);196navigation_server->free(obstacle);197navigation_server->physics_process(0.0); // Give server some cycles to commit.198CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);199}200201SUBCASE("Number of regions should be reported properly") {202RID region = navigation_server->region_create();203CHECK(region.is_valid());204navigation_server->region_set_map(region, map);205navigation_server->physics_process(0.0); // Give server some cycles to commit.206CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);207navigation_server->free(region);208navigation_server->physics_process(0.0); // Give server some cycles to commit.209CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);210}211212SUBCASE("Queries against empty map should return empty or invalid values") {213navigation_server->map_set_active(map, true);214navigation_server->physics_process(0.0); // Give server some cycles to commit.215216ERR_PRINT_OFF;217CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());218CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());219CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());220CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());221CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());222CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);223CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);224225Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);226query_parameters->set_map(map);227query_parameters->set_start_position(Vector3(7, 7, 7));228query_parameters->set_target_position(Vector3(8, 8, 8));229Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);230navigation_server->query_path(query_parameters, query_result);231CHECK_EQ(query_result->get_path().size(), 0);232CHECK_EQ(query_result->get_path_types().size(), 0);233CHECK_EQ(query_result->get_path_rids().size(), 0);234CHECK_EQ(query_result->get_path_owner_ids().size(), 0);235ERR_PRINT_ON;236237navigation_server->map_set_active(map, false);238navigation_server->physics_process(0.0); // Give server some cycles to commit.239}240241navigation_server->free(map);242navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.243CHECK_EQ(navigation_server->get_maps().size(), 0);244}245246TEST_CASE("[NavigationServer3D] Server should manage link properly") {247NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();248249RID link = navigation_server->link_create();250CHECK(link.is_valid());251252SUBCASE("'ProcessInfo' should not report dangling link") {253CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);254}255256SUBCASE("Setters/getters should work") {257bool initial_bidirectional = navigation_server->link_is_bidirectional(link);258navigation_server->link_set_bidirectional(link, !initial_bidirectional);259navigation_server->link_set_end_position(link, Vector3(7, 7, 7));260navigation_server->link_set_enter_cost(link, 0.55);261navigation_server->link_set_navigation_layers(link, 6);262navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));263navigation_server->link_set_start_position(link, Vector3(8, 8, 8));264navigation_server->link_set_travel_cost(link, 0.66);265navigation_server->physics_process(0.0); // Give server some cycles to commit.266267CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);268CHECK_EQ(navigation_server->link_get_end_position(link), Vector3(7, 7, 7));269CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));270CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);271CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));272CHECK_EQ(navigation_server->link_get_start_position(link), Vector3(8, 8, 8));273CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));274}275276SUBCASE("'ProcessInfo' should report link with active map") {277RID map = navigation_server->map_create();278CHECK(map.is_valid());279navigation_server->map_set_active(map, true);280navigation_server->link_set_map(link, map);281navigation_server->physics_process(0.0); // Give server some cycles to commit.282CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 1);283navigation_server->link_set_map(link, RID());284navigation_server->free(map);285navigation_server->physics_process(0.0); // Give server some cycles to commit.286CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);287}288289navigation_server->free(link);290}291292TEST_CASE("[NavigationServer3D] Server should manage obstacles properly") {293NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();294295RID obstacle = navigation_server->obstacle_create();296CHECK(obstacle.is_valid());297298// TODO: Add tests for setters/getters once getters are added.299300navigation_server->free(obstacle);301}302303TEST_CASE("[NavigationServer3D] Server should manage regions properly") {304NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();305306RID region = navigation_server->region_create();307CHECK(region.is_valid());308309SUBCASE("'ProcessInfo' should not report dangling region") {310CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);311}312313SUBCASE("Setters/getters should work") {314bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);315navigation_server->region_set_enter_cost(region, 0.55);316navigation_server->region_set_navigation_layers(region, 5);317navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));318navigation_server->region_set_travel_cost(region, 0.66);319navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);320navigation_server->physics_process(0.0); // Give server some cycles to commit.321322CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));323CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);324CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));325CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));326CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);327}328329SUBCASE("'ProcessInfo' should report region with active map") {330RID map = navigation_server->map_create();331CHECK(map.is_valid());332navigation_server->map_set_active(map, true);333navigation_server->region_set_map(region, map);334navigation_server->physics_process(0.0); // Give server some cycles to commit.335CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 1);336navigation_server->region_set_map(region, RID());337navigation_server->free(map);338navigation_server->physics_process(0.0); // Give server some cycles to commit.339CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);340}341342SUBCASE("Queries against empty region should return empty or invalid values") {343ERR_PRINT_OFF;344CHECK_EQ(navigation_server->region_get_connections_count(region), 0);345CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector3());346CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector3());347ERR_PRINT_ON;348}349350navigation_server->free(region);351}352353// This test case does not check precise values on purpose - to not be too sensitivte.354TEST_CASE("[NavigationServer3D] Server should move agent properly") {355NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();356357RID map = navigation_server->map_create();358RID agent = navigation_server->agent_create();359360navigation_server->map_set_active(map, true);361navigation_server->agent_set_map(agent, map);362navigation_server->agent_set_avoidance_enabled(agent, true);363navigation_server->agent_set_velocity(agent, Vector3(1, 0, 1));364CallableMock agent_avoidance_callback_mock;365navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));366CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);367navigation_server->physics_process(0.0); // Give server some cycles to commit.368CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);369CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector3(0, 0, 0));370371navigation_server->free(agent);372navigation_server->free(map);373}374375// This test case does not check precise values on purpose - to not be too sensitivte.376TEST_CASE("[NavigationServer3D] Server should make agents avoid each other when avoidance enabled") {377NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();378379RID map = navigation_server->map_create();380RID agent_1 = navigation_server->agent_create();381RID agent_2 = navigation_server->agent_create();382383navigation_server->map_set_active(map, true);384385navigation_server->agent_set_map(agent_1, map);386navigation_server->agent_set_avoidance_enabled(agent_1, true);387navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));388navigation_server->agent_set_radius(agent_1, 1);389navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));390CallableMock agent_1_avoidance_callback_mock;391navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));392393navigation_server->agent_set_map(agent_2, map);394navigation_server->agent_set_avoidance_enabled(agent_2, true);395navigation_server->agent_set_position(agent_2, Vector3(2.5, 0, 0.5));396navigation_server->agent_set_radius(agent_2, 1);397navigation_server->agent_set_velocity(agent_2, Vector3(-1, 0, 0));398CallableMock agent_2_avoidance_callback_mock;399navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));400401CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);402CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);403navigation_server->physics_process(0.0); // Give server some cycles to commit.404CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);405CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);406Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;407Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;408CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");409CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");410CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "agent 1 should move a bit to the side so that it avoids agent 2");411CHECK_MESSAGE(agent_2_safe_velocity.z > 0, "agent 2 should move a bit to the side so that it avoids agent 1");412413navigation_server->free(agent_2);414navigation_server->free(agent_1);415navigation_server->free(map);416}417418TEST_CASE("[NavigationServer3D] Server should make agents avoid dynamic obstacles when avoidance enabled") {419NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();420421RID map = navigation_server->map_create();422RID agent_1 = navigation_server->agent_create();423RID obstacle_1 = navigation_server->obstacle_create();424425navigation_server->map_set_active(map, true);426427navigation_server->agent_set_map(agent_1, map);428navigation_server->agent_set_avoidance_enabled(agent_1, true);429navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));430navigation_server->agent_set_radius(agent_1, 1);431navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));432CallableMock agent_1_avoidance_callback_mock;433navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));434435navigation_server->obstacle_set_map(obstacle_1, map);436navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);437navigation_server->obstacle_set_position(obstacle_1, Vector3(2.5, 0, 0.5));438navigation_server->obstacle_set_radius(obstacle_1, 1);439440CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);441navigation_server->physics_process(0.0); // Give server some cycles to commit.442CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);443Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;444CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");445CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");446447navigation_server->free(obstacle_1);448navigation_server->free(agent_1);449navigation_server->free(map);450navigation_server->physics_process(0.0); // Give server some cycles to commit.451}452453TEST_CASE("[NavigationServer3D] Server should make agents avoid static obstacles when avoidance enabled") {454NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();455456RID map = navigation_server->map_create();457RID agent_1 = navigation_server->agent_create();458RID agent_2 = navigation_server->agent_create();459RID obstacle_1 = navigation_server->obstacle_create();460461navigation_server->map_set_active(map, true);462463navigation_server->agent_set_map(agent_1, map);464navigation_server->agent_set_avoidance_enabled(agent_1, true);465navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.466navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));467CallableMock agent_1_avoidance_callback_mock;468navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));469470navigation_server->agent_set_map(agent_2, map);471navigation_server->agent_set_avoidance_enabled(agent_2, true);472navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.473navigation_server->agent_set_velocity(agent_2, Vector3(1, 0, 0));474CallableMock agent_2_avoidance_callback_mock;475navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));476477navigation_server->obstacle_set_map(obstacle_1, map);478navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);479PackedVector3Array obstacle_1_vertices;480481SUBCASE("Static obstacles should work on ground level") {482navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));483navigation_server->agent_set_position(agent_2, Vector3(0, 0, 5));484obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));485obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));486}487488SUBCASE("Static obstacles should work when elevated") {489navigation_server->agent_set_position(agent_1, Vector3(0, 5, 0));490navigation_server->agent_set_position(agent_2, Vector3(0, 5, 5));491obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));492obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));493navigation_server->obstacle_set_position(obstacle_1, Vector3(0, 5, 0));494}495496navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);497498CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);499CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);500navigation_server->physics_process(0.0); // Give server some cycles to commit.501CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);502CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);503Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;504Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;505CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");506CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");507CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");508CHECK_MESSAGE(agent_2_safe_velocity.z == 0, "Agent 2 should not move to the side.");509510navigation_server->free(obstacle_1);511navigation_server->free(agent_2);512navigation_server->free(agent_1);513navigation_server->free(map);514navigation_server->physics_process(0.0); // Give server some cycles to commit.515}516517#ifndef DISABLE_DEPRECATED518// This test case uses only public APIs on purpose - other test cases use simplified baking.519// FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed.520TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") {521NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();522523// Prepare scene tree with simple mesh to serve as an input geometry.524Node3D *node_3d = memnew(Node3D);525SceneTree::get_singleton()->get_root()->add_child(node_3d);526Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);527plane_mesh->set_size(Size2(10.0, 10.0));528MeshInstance3D *mesh_instance = memnew(MeshInstance3D);529mesh_instance->set_mesh(plane_mesh);530node_3d->add_child(mesh_instance);531532// Prepare anything necessary to bake navigation mesh.533RID map = navigation_server->map_create();534RID region = navigation_server->region_create();535Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);536navigation_server->map_set_use_async_iterations(map, false);537navigation_server->map_set_active(map, true);538navigation_server->region_set_use_async_iterations(region, false);539navigation_server->region_set_map(region, map);540navigation_server->region_set_navigation_mesh(region, navigation_mesh);541navigation_server->physics_process(0.0); // Give server some cycles to commit.542543CHECK_EQ(navigation_mesh->get_polygon_count(), 0);544CHECK_EQ(navigation_mesh->get_vertices().size(), 0);545546ERR_PRINT_OFF;547navigation_server->region_bake_navigation_mesh(navigation_mesh, node_3d);548ERR_PRINT_ON;549// FIXME: The above line should trigger the update (line below) under the hood.550navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.551CHECK_EQ(navigation_mesh->get_polygon_count(), 2);552CHECK_EQ(navigation_mesh->get_vertices().size(), 4);553554SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {555SIGNAL_WATCH(navigation_server, "map_changed");556SIGNAL_CHECK_FALSE("map_changed");557navigation_server->physics_process(0.0); // Give server some cycles to commit.558SIGNAL_CHECK("map_changed", { { map } });559SIGNAL_UNWATCH(navigation_server, "map_changed");560CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));561}562563navigation_server->free(region);564navigation_server->free(map);565navigation_server->physics_process(0.0); // Give server some cycles to commit.566memdelete(mesh_instance);567memdelete(node_3d);568}569#endif // DISABLE_DEPRECATED570571TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to parse geometry") {572NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();573574// Prepare scene tree with simple mesh to serve as an input geometry.575Node3D *node_3d = memnew(Node3D);576SceneTree::get_singleton()->get_root()->add_child(node_3d);577Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);578plane_mesh->set_size(Size2(10.0, 10.0));579MeshInstance3D *mesh_instance = memnew(MeshInstance3D);580mesh_instance->set_mesh(plane_mesh);581node_3d->add_child(mesh_instance);582583Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);584Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);585CHECK_EQ(source_geometry->get_vertices().size(), 0);586CHECK_EQ(source_geometry->get_indices().size(), 0);587588navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);589CHECK_EQ(source_geometry->get_vertices().size(), 12);590CHECK_EQ(source_geometry->get_indices().size(), 6);591592SUBCASE("By default, parsing should remove any data that was parsed before") {593navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);594CHECK_EQ(source_geometry->get_vertices().size(), 12);595CHECK_EQ(source_geometry->get_indices().size(), 6);596}597598SUBCASE("Parsed geometry should be extendable with other geometry") {599source_geometry->merge(source_geometry); // Merging with itself.600const Vector<float> vertices = source_geometry->get_vertices();601const Vector<int> indices = source_geometry->get_indices();602REQUIRE_EQ(vertices.size(), 24);603REQUIRE_EQ(indices.size(), 12);604// Check if first newly added vertex is the same as first vertex.605CHECK_EQ(vertices[0], vertices[12]);606CHECK_EQ(vertices[1], vertices[13]);607CHECK_EQ(vertices[2], vertices[14]);608// Check if first newly added index is the same as first index.609CHECK_EQ(indices[0] + 4, indices[6]);610}611612memdelete(mesh_instance);613memdelete(node_3d);614}615616// This test case uses only public APIs on purpose - other test cases use simplified baking.617TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") {618NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();619620// Prepare scene tree with simple mesh to serve as an input geometry.621Node3D *node_3d = memnew(Node3D);622SceneTree::get_singleton()->get_root()->add_child(node_3d);623Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);624plane_mesh->set_size(Size2(10.0, 10.0));625MeshInstance3D *mesh_instance = memnew(MeshInstance3D);626mesh_instance->set_mesh(plane_mesh);627node_3d->add_child(mesh_instance);628629// Prepare anything necessary to bake navigation mesh.630RID map = navigation_server->map_create();631RID region = navigation_server->region_create();632Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);633navigation_server->map_set_use_async_iterations(map, false);634navigation_server->map_set_active(map, true);635navigation_server->region_set_use_async_iterations(region, false);636navigation_server->region_set_map(region, map);637navigation_server->region_set_navigation_mesh(region, navigation_mesh);638navigation_server->physics_process(0.0); // Give server some cycles to commit.639640CHECK_EQ(navigation_mesh->get_polygon_count(), 0);641CHECK_EQ(navigation_mesh->get_vertices().size(), 0);642643Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);644navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, node_3d);645navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());646// FIXME: The above line should trigger the update (line below) under the hood.647navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.648CHECK_EQ(navigation_mesh->get_polygon_count(), 2);649CHECK_EQ(navigation_mesh->get_vertices().size(), 4);650651SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {652SIGNAL_WATCH(navigation_server, "map_changed");653SIGNAL_CHECK_FALSE("map_changed");654navigation_server->physics_process(0.0); // Give server some cycles to commit.655SIGNAL_CHECK("map_changed", { { map } });656SIGNAL_UNWATCH(navigation_server, "map_changed");657CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));658}659660navigation_server->free(region);661navigation_server->free(map);662navigation_server->physics_process(0.0); // Give server some cycles to commit.663memdelete(mesh_instance);664memdelete(node_3d);665}666667// This test case does not check precise values on purpose - to not be too sensitivte.668TEST_CASE("[NavigationServer3D] Server should respond to queries against valid map properly") {669NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();670Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);671Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);672673Array arr;674arr.resize(RS::ARRAY_MAX);675BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));676source_geometry->add_mesh_array(arr, Transform3D());677navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());678CHECK_NE(navigation_mesh->get_polygon_count(), 0);679CHECK_NE(navigation_mesh->get_vertices().size(), 0);680681RID map = navigation_server->map_create();682RID region = navigation_server->region_create();683navigation_server->map_set_active(map, true);684navigation_server->map_set_use_async_iterations(map, false);685navigation_server->region_set_use_async_iterations(region, false);686navigation_server->region_set_map(region, map);687navigation_server->region_set_navigation_mesh(region, navigation_mesh);688navigation_server->physics_process(0.0); // Give server some cycles to commit.689690SUBCASE("Simple queries should return non-default values") {691CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));692CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3());693CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid());694CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3());695CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3());696CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0);697CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0);698}699700SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") {701CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3());702}703704SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {705Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);706query_parameters->set_map(map);707query_parameters->set_start_position(Vector3(0, 0, 0));708query_parameters->set_target_position(Vector3(10, 0, 10));709query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_CORRIDORFUNNEL);710Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);711navigation_server->query_path(query_parameters, query_result);712CHECK_NE(query_result->get_path().size(), 0);713CHECK_NE(query_result->get_path_types().size(), 0);714CHECK_NE(query_result->get_path_rids().size(), 0);715CHECK_NE(query_result->get_path_owner_ids().size(), 0);716}717718SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {719Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);720query_parameters->set_map(map);721query_parameters->set_start_position(Vector3(10, 0, 10));722query_parameters->set_target_position(Vector3(0, 0, 0));723query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_EDGECENTERED);724Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);725navigation_server->query_path(query_parameters, query_result);726CHECK_NE(query_result->get_path().size(), 0);727CHECK_NE(query_result->get_path_types().size(), 0);728CHECK_NE(query_result->get_path_rids().size(), 0);729CHECK_NE(query_result->get_path_owner_ids().size(), 0);730}731732SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {733Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);734query_parameters->set_map(map);735query_parameters->set_start_position(Vector3(10, 0, 10));736query_parameters->set_target_position(Vector3(0, 0, 0));737query_parameters->set_navigation_layers(2);738Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);739navigation_server->query_path(query_parameters, query_result);740CHECK_EQ(query_result->get_path().size(), 0);741CHECK_EQ(query_result->get_path_types().size(), 0);742CHECK_EQ(query_result->get_path_rids().size(), 0);743CHECK_EQ(query_result->get_path_owner_ids().size(), 0);744}745746SUBCASE("Elaborate query without metadata flags should yield path only") {747Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);748query_parameters->set_map(map);749query_parameters->set_start_position(Vector3(10, 0, 10));750query_parameters->set_target_position(Vector3(0, 0, 0));751query_parameters->set_metadata_flags(0);752Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);753navigation_server->query_path(query_parameters, query_result);754CHECK_NE(query_result->get_path().size(), 0);755CHECK_EQ(query_result->get_path_types().size(), 0);756CHECK_EQ(query_result->get_path_rids().size(), 0);757CHECK_EQ(query_result->get_path_owner_ids().size(), 0);758}759760SUBCASE("Elaborate query with excluded region should yield empty path") {761Ref<NavigationPathQueryParameters3D> query_parameters;762query_parameters.instantiate();763query_parameters->set_map(map);764query_parameters->set_start_position(Vector3(10, 0, 10));765query_parameters->set_target_position(Vector3(0, 0, 0));766query_parameters->set_excluded_regions({ region });767Ref<NavigationPathQueryResult3D> query_result;768query_result.instantiate();769navigation_server->query_path(query_parameters, query_result);770CHECK_EQ(query_result->get_path().size(), 0);771}772773SUBCASE("Elaborate query with included region should yield path") {774Ref<NavigationPathQueryParameters3D> query_parameters;775query_parameters.instantiate();776query_parameters->set_map(map);777query_parameters->set_start_position(Vector3(10, 0, 10));778query_parameters->set_target_position(Vector3(0, 0, 0));779query_parameters->set_included_regions({ region });780Ref<NavigationPathQueryResult3D> query_result;781query_result.instantiate();782navigation_server->query_path(query_parameters, query_result);783CHECK_NE(query_result->get_path().size(), 0);784}785786SUBCASE("Elaborate query with excluded and included region should yield empty path") {787Ref<NavigationPathQueryParameters3D> query_parameters;788query_parameters.instantiate();789query_parameters->set_map(map);790query_parameters->set_start_position(Vector3(10, 0, 10));791query_parameters->set_target_position(Vector3(0, 0, 0));792query_parameters->set_excluded_regions({ region });793query_parameters->set_included_regions({ region });794Ref<NavigationPathQueryResult3D> query_result;795query_result.instantiate();796navigation_server->query_path(query_parameters, query_result);797CHECK_EQ(query_result->get_path().size(), 0);798}799800navigation_server->free(region);801navigation_server->free(map);802navigation_server->physics_process(0.0); // Give server some cycles to commit.803}804805// FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613).806/*807TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") {808NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();809Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);810Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);811812Array arr;813arr.resize(RS::ARRAY_MAX);814BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));815source_geometry->add_mesh_array(arr, Transform3D());816817// Race condition is present below, but baking should take many orders of magnitude818// longer than basic checks on the main thread, so it's fine.819navigation_server->bake_from_source_geometry_data_async(navigation_mesh, source_geometry, Callable());820CHECK(navigation_server->is_baking_navigation_mesh(navigation_mesh));821CHECK_EQ(navigation_mesh->get_polygon_count(), 0);822CHECK_EQ(navigation_mesh->get_vertices().size(), 0);823}824*/825826TEST_CASE("[NavigationServer3D] Server should simplify path properly") {827real_t simplify_epsilon = 0.2;828Vector<Vector3> source_path;829source_path.resize(7);830source_path.write[0] = Vector3(0.0, 0.0, 0.0);831source_path.write[1] = Vector3(0.0, 0.0, 1.0); // This point needs to go.832source_path.write[2] = Vector3(0.0, 0.0, 2.0); // This point needs to go.833source_path.write[3] = Vector3(0.0, 0.0, 2.0);834source_path.write[4] = Vector3(2.0, 1.0, 3.0);835source_path.write[5] = Vector3(2.0, 1.5, 4.0); // This point needs to go.836source_path.write[6] = Vector3(2.0, 2.0, 5.0);837Vector<Vector3> simplified_path = NavigationServer3D::get_singleton()->simplify_path(source_path, simplify_epsilon);838CHECK_EQ(simplified_path.size(), 4);839}840}841} //namespace TestNavigationServer3D842843844