Path: blob/master/tests/servers/test_navigation_server_3d.cpp
45991 views
/**************************************************************************/1/* test_navigation_server_3d.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_3d)3334#include "modules/modules_enabled.gen.h" // For navigation 3D.3536#ifdef MODULE_NAVIGATION_3D_ENABLED3738#include "core/object/callable_mp.h"39#include "scene/3d/mesh_instance_3d.h"40#include "scene/main/scene_tree.h"41#include "scene/main/window.h"42#include "scene/resources/3d/primitive_meshes.h"43#include "servers/navigation_3d/navigation_server_3d.h"44#include "tests/signal_watcher.h"4546namespace TestNavigationServer3D {4748// TODO: Find a more generic way to create `Callable` mocks.49class CallableMock : public Object {50GDCLASS(CallableMock, Object);5152public:53void function1(Variant arg0) {54function1_calls++;55function1_latest_arg0 = arg0;56}5758unsigned function1_calls{ 0 };59Variant function1_latest_arg0;60};6162TEST_SUITE("[Navigation3D]") {63TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {64NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();65CHECK_EQ(navigation_server->get_maps().size(), 0);6667SUBCASE("'ProcessInfo' should report all counters empty as well") {68CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);69CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);70CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);71CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);72CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_POLYGON_COUNT), 0);73CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_COUNT), 0);74CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_MERGE_COUNT), 0);75CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_CONNECTION_COUNT), 0);76CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_FREE_COUNT), 0);77}78}7980TEST_CASE("[NavigationServer3D] Server should manage agent properly") {81NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();8283RID agent = navigation_server->agent_create();84CHECK(agent.is_valid());8586SUBCASE("'ProcessInfo' should not report dangling agent") {87CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);88}8990SUBCASE("Setters/getters should work") {91bool initial_use_3d_avoidance = navigation_server->agent_get_use_3d_avoidance(agent);92navigation_server->agent_set_use_3d_avoidance(agent, !initial_use_3d_avoidance);93navigation_server->physics_process(0.0); // Give server some cycles to commit.9495CHECK_EQ(navigation_server->agent_get_use_3d_avoidance(agent), !initial_use_3d_avoidance);96// TODO: Add remaining setters/getters once the missing getters are added.97}9899SUBCASE("'ProcessInfo' should report agent with active map") {100RID map = navigation_server->map_create();101CHECK(map.is_valid());102navigation_server->map_set_active(map, true);103navigation_server->agent_set_map(agent, map);104navigation_server->physics_process(0.0); // Give server some cycles to commit.105CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 1);106navigation_server->agent_set_map(agent, RID());107navigation_server->free_rid(map);108navigation_server->physics_process(0.0); // Give server some cycles to commit.109CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);110}111112navigation_server->free_rid(agent);113}114115TEST_CASE("[NavigationServer3D] Server should manage map properly") {116NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();117118RID map;119CHECK_FALSE(map.is_valid());120121SUBCASE("Queries against invalid map should return empty or invalid values") {122ERR_PRINT_OFF;123CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());124CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());125CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());126CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());127CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());128CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);129CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);130131Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);132query_parameters->set_map(map);133query_parameters->set_start_position(Vector3(7, 7, 7));134query_parameters->set_target_position(Vector3(8, 8, 8));135Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);136navigation_server->query_path(query_parameters, query_result);137CHECK_EQ(query_result->get_path().size(), 0);138CHECK_EQ(query_result->get_path_types().size(), 0);139CHECK_EQ(query_result->get_path_rids().size(), 0);140CHECK_EQ(query_result->get_path_owner_ids().size(), 0);141ERR_PRINT_ON;142}143144map = navigation_server->map_create();145CHECK(map.is_valid());146CHECK_EQ(navigation_server->get_maps().size(), 1);147148SUBCASE("'ProcessInfo' should not report inactive map") {149CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);150}151152SUBCASE("Setters/getters should work") {153navigation_server->map_set_cell_size(map, 0.55);154navigation_server->map_set_edge_connection_margin(map, 0.66);155navigation_server->map_set_link_connection_radius(map, 0.77);156navigation_server->map_set_up(map, Vector3(1, 0, 0));157bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);158navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);159navigation_server->physics_process(0.0); // Give server some cycles to commit.160161CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));162CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));163CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));164CHECK_EQ(navigation_server->map_get_up(map), Vector3(1, 0, 0));165CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);166}167168SUBCASE("'ProcessInfo' should report map iff active") {169navigation_server->map_set_active(map, true);170navigation_server->physics_process(0.0); // Give server some cycles to commit.171CHECK(navigation_server->map_is_active(map));172CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 1);173navigation_server->map_set_active(map, false);174navigation_server->physics_process(0.0); // Give server some cycles to commit.175CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);176}177178SUBCASE("Number of agents should be reported properly") {179RID agent = navigation_server->agent_create();180CHECK(agent.is_valid());181navigation_server->agent_set_map(agent, map);182navigation_server->physics_process(0.0); // Give server some cycles to commit.183CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);184navigation_server->free_rid(agent);185navigation_server->physics_process(0.0); // Give server some cycles to commit.186CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);187}188189SUBCASE("Number of links should be reported properly") {190RID link = navigation_server->link_create();191CHECK(link.is_valid());192navigation_server->link_set_map(link, map);193navigation_server->physics_process(0.0); // Give server some cycles to commit.194CHECK_EQ(navigation_server->map_get_links(map).size(), 1);195navigation_server->free_rid(link);196navigation_server->physics_process(0.0); // Give server some cycles to commit.197CHECK_EQ(navigation_server->map_get_links(map).size(), 0);198}199200SUBCASE("Number of obstacles should be reported properly") {201RID obstacle = navigation_server->obstacle_create();202CHECK(obstacle.is_valid());203navigation_server->obstacle_set_map(obstacle, map);204navigation_server->physics_process(0.0); // Give server some cycles to commit.205CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);206navigation_server->free_rid(obstacle);207navigation_server->physics_process(0.0); // Give server some cycles to commit.208CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);209}210211SUBCASE("Number of regions should be reported properly") {212RID region = navigation_server->region_create();213CHECK(region.is_valid());214navigation_server->region_set_map(region, map);215navigation_server->physics_process(0.0); // Give server some cycles to commit.216CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);217navigation_server->free_rid(region);218navigation_server->physics_process(0.0); // Give server some cycles to commit.219CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);220}221222SUBCASE("Queries against empty map should return empty or invalid values") {223navigation_server->map_set_active(map, true);224navigation_server->physics_process(0.0); // Give server some cycles to commit.225226ERR_PRINT_OFF;227CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());228CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());229CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());230CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());231CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());232CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);233CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);234235Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);236query_parameters->set_map(map);237query_parameters->set_start_position(Vector3(7, 7, 7));238query_parameters->set_target_position(Vector3(8, 8, 8));239Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);240navigation_server->query_path(query_parameters, query_result);241CHECK_EQ(query_result->get_path().size(), 0);242CHECK_EQ(query_result->get_path_types().size(), 0);243CHECK_EQ(query_result->get_path_rids().size(), 0);244CHECK_EQ(query_result->get_path_owner_ids().size(), 0);245ERR_PRINT_ON;246247navigation_server->map_set_active(map, false);248navigation_server->physics_process(0.0); // Give server some cycles to commit.249}250251navigation_server->free_rid(map);252navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.253CHECK_EQ(navigation_server->get_maps().size(), 0);254}255256TEST_CASE("[NavigationServer3D] Server should manage link properly") {257NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();258259RID link = navigation_server->link_create();260CHECK(link.is_valid());261262SUBCASE("'ProcessInfo' should not report dangling link") {263CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);264}265266SUBCASE("Setters/getters should work") {267bool initial_bidirectional = navigation_server->link_is_bidirectional(link);268navigation_server->link_set_bidirectional(link, !initial_bidirectional);269navigation_server->link_set_end_position(link, Vector3(7, 7, 7));270navigation_server->link_set_enter_cost(link, 0.55);271navigation_server->link_set_navigation_layers(link, 6);272navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));273navigation_server->link_set_start_position(link, Vector3(8, 8, 8));274navigation_server->link_set_travel_cost(link, 0.66);275navigation_server->physics_process(0.0); // Give server some cycles to commit.276277CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);278CHECK_EQ(navigation_server->link_get_end_position(link), Vector3(7, 7, 7));279CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));280CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);281CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));282CHECK_EQ(navigation_server->link_get_start_position(link), Vector3(8, 8, 8));283CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));284}285286SUBCASE("'ProcessInfo' should report link with active map") {287RID map = navigation_server->map_create();288CHECK(map.is_valid());289navigation_server->map_set_active(map, true);290navigation_server->link_set_map(link, map);291navigation_server->physics_process(0.0); // Give server some cycles to commit.292CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 1);293navigation_server->link_set_map(link, RID());294navigation_server->free_rid(map);295navigation_server->physics_process(0.0); // Give server some cycles to commit.296CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);297}298299navigation_server->free_rid(link);300}301302TEST_CASE("[NavigationServer3D] Server should manage obstacles properly") {303NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();304305RID obstacle = navigation_server->obstacle_create();306CHECK(obstacle.is_valid());307308// TODO: Add tests for setters/getters once getters are added.309310navigation_server->free_rid(obstacle);311}312313TEST_CASE("[NavigationServer3D] Server should manage regions properly") {314NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();315316RID region = navigation_server->region_create();317CHECK(region.is_valid());318319SUBCASE("'ProcessInfo' should not report dangling region") {320CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);321}322323SUBCASE("Setters/getters should work") {324bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);325navigation_server->region_set_enter_cost(region, 0.55);326navigation_server->region_set_navigation_layers(region, 5);327navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));328navigation_server->region_set_travel_cost(region, 0.66);329navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);330navigation_server->physics_process(0.0); // Give server some cycles to commit.331332CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));333CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);334CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));335CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));336CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);337}338339SUBCASE("'ProcessInfo' should report region with active map") {340RID map = navigation_server->map_create();341CHECK(map.is_valid());342navigation_server->map_set_active(map, true);343navigation_server->region_set_map(region, map);344navigation_server->physics_process(0.0); // Give server some cycles to commit.345CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 1);346navigation_server->region_set_map(region, RID());347navigation_server->free_rid(map);348navigation_server->physics_process(0.0); // Give server some cycles to commit.349CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);350}351352SUBCASE("Queries against empty region should return empty or invalid values") {353ERR_PRINT_OFF;354CHECK_EQ(navigation_server->region_get_connections_count(region), 0);355CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector3());356CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector3());357ERR_PRINT_ON;358}359360navigation_server->free_rid(region);361}362363// This test case does not check precise values on purpose - to not be too sensitivte.364TEST_CASE("[NavigationServer3D] Server should move agent properly") {365NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();366367RID map = navigation_server->map_create();368RID agent = navigation_server->agent_create();369370navigation_server->map_set_active(map, true);371navigation_server->agent_set_map(agent, map);372navigation_server->agent_set_avoidance_enabled(agent, true);373navigation_server->agent_set_velocity(agent, Vector3(1, 0, 1));374CallableMock agent_avoidance_callback_mock;375navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));376CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);377navigation_server->physics_process(0.0); // Give server some cycles to commit.378CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);379CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector3(0, 0, 0));380381navigation_server->free_rid(agent);382navigation_server->free_rid(map);383}384385// This test case does not check precise values on purpose - to not be too sensitivte.386TEST_CASE("[NavigationServer3D] Server should make agents avoid each other when avoidance enabled") {387NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();388389RID map = navigation_server->map_create();390RID agent_1 = navigation_server->agent_create();391RID agent_2 = navigation_server->agent_create();392393navigation_server->map_set_active(map, true);394395navigation_server->agent_set_map(agent_1, map);396navigation_server->agent_set_avoidance_enabled(agent_1, true);397navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));398navigation_server->agent_set_radius(agent_1, 1);399navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));400CallableMock agent_1_avoidance_callback_mock;401navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));402403navigation_server->agent_set_map(agent_2, map);404navigation_server->agent_set_avoidance_enabled(agent_2, true);405navigation_server->agent_set_position(agent_2, Vector3(2.5, 0, 0.5));406navigation_server->agent_set_radius(agent_2, 1);407navigation_server->agent_set_velocity(agent_2, Vector3(-1, 0, 0));408CallableMock agent_2_avoidance_callback_mock;409navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));410411CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);412CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);413navigation_server->physics_process(0.0); // Give server some cycles to commit.414CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);415CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);416Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;417Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;418CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");419CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");420CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "agent 1 should move a bit to the side so that it avoids agent 2");421CHECK_MESSAGE(agent_2_safe_velocity.z > 0, "agent 2 should move a bit to the side so that it avoids agent 1");422423navigation_server->free_rid(agent_2);424navigation_server->free_rid(agent_1);425navigation_server->free_rid(map);426}427428TEST_CASE("[NavigationServer3D] Server should make agents avoid dynamic obstacles when avoidance enabled") {429NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();430431RID map = navigation_server->map_create();432RID agent_1 = navigation_server->agent_create();433RID obstacle_1 = navigation_server->obstacle_create();434435navigation_server->map_set_active(map, true);436437navigation_server->agent_set_map(agent_1, map);438navigation_server->agent_set_avoidance_enabled(agent_1, true);439navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));440navigation_server->agent_set_radius(agent_1, 1);441navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));442CallableMock agent_1_avoidance_callback_mock;443navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));444445navigation_server->obstacle_set_map(obstacle_1, map);446navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);447navigation_server->obstacle_set_position(obstacle_1, Vector3(2.5, 0, 0.5));448navigation_server->obstacle_set_radius(obstacle_1, 1);449450CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);451navigation_server->physics_process(0.0); // Give server some cycles to commit.452CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);453Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;454CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");455CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");456457navigation_server->free_rid(obstacle_1);458navigation_server->free_rid(agent_1);459navigation_server->free_rid(map);460navigation_server->physics_process(0.0); // Give server some cycles to commit.461}462463TEST_CASE("[NavigationServer3D] Server should make agents avoid static obstacles when avoidance enabled") {464NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();465466RID map = navigation_server->map_create();467RID agent_1 = navigation_server->agent_create();468RID agent_2 = navigation_server->agent_create();469RID obstacle_1 = navigation_server->obstacle_create();470471navigation_server->map_set_active(map, true);472473navigation_server->agent_set_map(agent_1, map);474navigation_server->agent_set_avoidance_enabled(agent_1, true);475navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.476navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));477CallableMock agent_1_avoidance_callback_mock;478navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));479480navigation_server->agent_set_map(agent_2, map);481navigation_server->agent_set_avoidance_enabled(agent_2, true);482navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.483navigation_server->agent_set_velocity(agent_2, Vector3(1, 0, 0));484CallableMock agent_2_avoidance_callback_mock;485navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));486487navigation_server->obstacle_set_map(obstacle_1, map);488navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);489PackedVector3Array obstacle_1_vertices;490491SUBCASE("Static obstacles should work on ground level") {492navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));493navigation_server->agent_set_position(agent_2, Vector3(0, 0, 5));494obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));495obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));496}497498SUBCASE("Static obstacles should work when elevated") {499navigation_server->agent_set_position(agent_1, Vector3(0, 5, 0));500navigation_server->agent_set_position(agent_2, Vector3(0, 5, 5));501obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));502obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));503navigation_server->obstacle_set_position(obstacle_1, Vector3(0, 5, 0));504}505506navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);507508CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);509CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);510navigation_server->physics_process(0.0); // Give server some cycles to commit.511CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);512CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);513Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;514Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;515CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");516CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");517CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");518CHECK_MESSAGE(agent_2_safe_velocity.z == 0, "Agent 2 should not move to the side.");519520navigation_server->free_rid(obstacle_1);521navigation_server->free_rid(agent_2);522navigation_server->free_rid(agent_1);523navigation_server->free_rid(map);524navigation_server->physics_process(0.0); // Give server some cycles to commit.525}526527#ifndef DISABLE_DEPRECATED528// This test case uses only public APIs on purpose - other test cases use simplified baking.529// FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed.530TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") {531NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();532533// Prepare scene tree with simple mesh to serve as an input geometry.534Node3D *node_3d = memnew(Node3D);535SceneTree::get_singleton()->get_root()->add_child(node_3d);536Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);537plane_mesh->set_size(Size2(10.0, 10.0));538MeshInstance3D *mesh_instance = memnew(MeshInstance3D);539mesh_instance->set_mesh(plane_mesh);540node_3d->add_child(mesh_instance);541542// Prepare anything necessary to bake navigation mesh.543RID map = navigation_server->map_create();544RID region = navigation_server->region_create();545Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);546navigation_server->map_set_use_async_iterations(map, false);547navigation_server->map_set_active(map, true);548navigation_server->region_set_use_async_iterations(region, false);549navigation_server->region_set_map(region, map);550navigation_server->region_set_navigation_mesh(region, navigation_mesh);551navigation_server->physics_process(0.0); // Give server some cycles to commit.552553CHECK_EQ(navigation_mesh->get_polygon_count(), 0);554CHECK_EQ(navigation_mesh->get_vertices().size(), 0);555556ERR_PRINT_OFF;557navigation_server->region_bake_navigation_mesh(navigation_mesh, node_3d);558ERR_PRINT_ON;559// FIXME: The above line should trigger the update (line below) under the hood.560navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.561CHECK_EQ(navigation_mesh->get_polygon_count(), 2);562CHECK_EQ(navigation_mesh->get_vertices().size(), 4);563564SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {565SIGNAL_WATCH(navigation_server, "map_changed");566SIGNAL_CHECK_FALSE("map_changed");567navigation_server->physics_process(0.0); // Give server some cycles to commit.568SIGNAL_CHECK("map_changed", { { map } });569SIGNAL_UNWATCH(navigation_server, "map_changed");570CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));571}572573navigation_server->free_rid(region);574navigation_server->free_rid(map);575navigation_server->physics_process(0.0); // Give server some cycles to commit.576memdelete(mesh_instance);577memdelete(node_3d);578}579#endif // DISABLE_DEPRECATED580581TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to parse geometry") {582NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();583584// Prepare scene tree with simple mesh to serve as an input geometry.585Node3D *node_3d = memnew(Node3D);586SceneTree::get_singleton()->get_root()->add_child(node_3d);587Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);588plane_mesh->set_size(Size2(10.0, 10.0));589MeshInstance3D *mesh_instance = memnew(MeshInstance3D);590mesh_instance->set_mesh(plane_mesh);591node_3d->add_child(mesh_instance);592593Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);594Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);595CHECK_EQ(source_geometry->get_vertices().size(), 0);596CHECK_EQ(source_geometry->get_indices().size(), 0);597598navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);599CHECK_EQ(source_geometry->get_vertices().size(), 12);600CHECK_EQ(source_geometry->get_indices().size(), 6);601602SUBCASE("By default, parsing should remove any data that was parsed before") {603navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);604CHECK_EQ(source_geometry->get_vertices().size(), 12);605CHECK_EQ(source_geometry->get_indices().size(), 6);606}607608SUBCASE("Parsed geometry should be extendable with other geometry") {609source_geometry->merge(source_geometry); // Merging with itself.610const Vector<float> vertices = source_geometry->get_vertices();611const Vector<int> indices = source_geometry->get_indices();612REQUIRE_EQ(vertices.size(), 24);613REQUIRE_EQ(indices.size(), 12);614// Check if first newly added vertex is the same as first vertex.615CHECK_EQ(vertices[0], vertices[12]);616CHECK_EQ(vertices[1], vertices[13]);617CHECK_EQ(vertices[2], vertices[14]);618// Check if first newly added index is the same as first index.619CHECK_EQ(indices[0] + 4, indices[6]);620}621622memdelete(mesh_instance);623memdelete(node_3d);624}625626// This test case uses only public APIs on purpose - other test cases use simplified baking.627TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") {628NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();629630// Prepare scene tree with simple mesh to serve as an input geometry.631Node3D *node_3d = memnew(Node3D);632SceneTree::get_singleton()->get_root()->add_child(node_3d);633Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);634plane_mesh->set_size(Size2(10.0, 10.0));635MeshInstance3D *mesh_instance = memnew(MeshInstance3D);636mesh_instance->set_mesh(plane_mesh);637node_3d->add_child(mesh_instance);638639// Prepare anything necessary to bake navigation mesh.640RID map = navigation_server->map_create();641RID region = navigation_server->region_create();642Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);643navigation_server->map_set_use_async_iterations(map, false);644navigation_server->map_set_active(map, true);645navigation_server->region_set_use_async_iterations(region, false);646navigation_server->region_set_map(region, map);647navigation_server->region_set_navigation_mesh(region, navigation_mesh);648navigation_server->physics_process(0.0); // Give server some cycles to commit.649650CHECK_EQ(navigation_mesh->get_polygon_count(), 0);651CHECK_EQ(navigation_mesh->get_vertices().size(), 0);652653Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);654navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, node_3d);655navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());656// FIXME: The above line should trigger the update (line below) under the hood.657navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.658CHECK_EQ(navigation_mesh->get_polygon_count(), 2);659CHECK_EQ(navigation_mesh->get_vertices().size(), 4);660661SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {662SIGNAL_WATCH(navigation_server, "map_changed");663SIGNAL_CHECK_FALSE("map_changed");664navigation_server->physics_process(0.0); // Give server some cycles to commit.665SIGNAL_CHECK("map_changed", { { map } });666SIGNAL_UNWATCH(navigation_server, "map_changed");667CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));668}669670navigation_server->free_rid(region);671navigation_server->free_rid(map);672navigation_server->physics_process(0.0); // Give server some cycles to commit.673memdelete(mesh_instance);674memdelete(node_3d);675}676677// This test case does not check precise values on purpose - to not be too sensitivte.678TEST_CASE("[NavigationServer3D] Server should respond to queries against valid map properly") {679NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();680Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);681Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);682683Array arr;684arr.resize(RSE::ARRAY_MAX);685BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));686source_geometry->add_mesh_array(arr, Transform3D());687navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());688CHECK_NE(navigation_mesh->get_polygon_count(), 0);689CHECK_NE(navigation_mesh->get_vertices().size(), 0);690691RID map = navigation_server->map_create();692RID region = navigation_server->region_create();693navigation_server->map_set_active(map, true);694navigation_server->map_set_use_async_iterations(map, false);695navigation_server->region_set_use_async_iterations(region, false);696navigation_server->region_set_map(region, map);697navigation_server->region_set_navigation_mesh(region, navigation_mesh);698navigation_server->physics_process(0.0); // Give server some cycles to commit.699700SUBCASE("Simple queries should return non-default values") {701CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));702CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3());703CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid());704CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3());705CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3());706CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0);707CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0);708}709710SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") {711CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3());712}713714SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {715Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);716query_parameters->set_map(map);717query_parameters->set_start_position(Vector3(0, 0, 0));718query_parameters->set_target_position(Vector3(10, 0, 10));719query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_CORRIDORFUNNEL);720Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);721navigation_server->query_path(query_parameters, query_result);722CHECK_NE(query_result->get_path().size(), 0);723CHECK_NE(query_result->get_path_types().size(), 0);724CHECK_NE(query_result->get_path_rids().size(), 0);725CHECK_NE(query_result->get_path_owner_ids().size(), 0);726}727728SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {729Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);730query_parameters->set_map(map);731query_parameters->set_start_position(Vector3(10, 0, 10));732query_parameters->set_target_position(Vector3(0, 0, 0));733query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_EDGECENTERED);734Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);735navigation_server->query_path(query_parameters, query_result);736CHECK_NE(query_result->get_path().size(), 0);737CHECK_NE(query_result->get_path_types().size(), 0);738CHECK_NE(query_result->get_path_rids().size(), 0);739CHECK_NE(query_result->get_path_owner_ids().size(), 0);740}741742SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {743Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);744query_parameters->set_map(map);745query_parameters->set_start_position(Vector3(10, 0, 10));746query_parameters->set_target_position(Vector3(0, 0, 0));747query_parameters->set_navigation_layers(2);748Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);749navigation_server->query_path(query_parameters, query_result);750CHECK_EQ(query_result->get_path().size(), 0);751CHECK_EQ(query_result->get_path_types().size(), 0);752CHECK_EQ(query_result->get_path_rids().size(), 0);753CHECK_EQ(query_result->get_path_owner_ids().size(), 0);754}755756SUBCASE("Elaborate query without metadata flags should yield path only") {757Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);758query_parameters->set_map(map);759query_parameters->set_start_position(Vector3(10, 0, 10));760query_parameters->set_target_position(Vector3(0, 0, 0));761query_parameters->set_metadata_flags(0);762Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);763navigation_server->query_path(query_parameters, query_result);764CHECK_NE(query_result->get_path().size(), 0);765CHECK_EQ(query_result->get_path_types().size(), 0);766CHECK_EQ(query_result->get_path_rids().size(), 0);767CHECK_EQ(query_result->get_path_owner_ids().size(), 0);768}769770SUBCASE("Elaborate query with excluded region should yield empty path") {771Ref<NavigationPathQueryParameters3D> query_parameters;772query_parameters.instantiate();773query_parameters->set_map(map);774query_parameters->set_start_position(Vector3(10, 0, 10));775query_parameters->set_target_position(Vector3(0, 0, 0));776query_parameters->set_excluded_regions({ region });777Ref<NavigationPathQueryResult3D> query_result;778query_result.instantiate();779navigation_server->query_path(query_parameters, query_result);780CHECK_EQ(query_result->get_path().size(), 0);781}782783SUBCASE("Elaborate query with included region should yield path") {784Ref<NavigationPathQueryParameters3D> query_parameters;785query_parameters.instantiate();786query_parameters->set_map(map);787query_parameters->set_start_position(Vector3(10, 0, 10));788query_parameters->set_target_position(Vector3(0, 0, 0));789query_parameters->set_included_regions({ region });790Ref<NavigationPathQueryResult3D> query_result;791query_result.instantiate();792navigation_server->query_path(query_parameters, query_result);793CHECK_NE(query_result->get_path().size(), 0);794}795796SUBCASE("Elaborate query with excluded and included region should yield empty path") {797Ref<NavigationPathQueryParameters3D> query_parameters;798query_parameters.instantiate();799query_parameters->set_map(map);800query_parameters->set_start_position(Vector3(10, 0, 10));801query_parameters->set_target_position(Vector3(0, 0, 0));802query_parameters->set_excluded_regions({ region });803query_parameters->set_included_regions({ region });804Ref<NavigationPathQueryResult3D> query_result;805query_result.instantiate();806navigation_server->query_path(query_parameters, query_result);807CHECK_EQ(query_result->get_path().size(), 0);808}809810navigation_server->free_rid(region);811navigation_server->free_rid(map);812navigation_server->physics_process(0.0); // Give server some cycles to commit.813}814815// FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613).816/*817TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") {818NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();819Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);820Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);821822Array arr;823arr.resize(RSE::ARRAY_MAX);824BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));825source_geometry->add_mesh_array(arr, Transform3D());826827// Race condition is present below, but baking should take many orders of magnitude828// longer than basic checks on the main thread, so it's fine.829navigation_server->bake_from_source_geometry_data_async(navigation_mesh, source_geometry, Callable());830CHECK(navigation_server->is_baking_navigation_mesh(navigation_mesh));831CHECK_EQ(navigation_mesh->get_polygon_count(), 0);832CHECK_EQ(navigation_mesh->get_vertices().size(), 0);833}834*/835836TEST_CASE("[NavigationServer3D] Server should simplify path properly") {837real_t simplify_epsilon = 0.2;838Vector<Vector3> source_path;839source_path.resize(7);840source_path.write[0] = Vector3(0.0, 0.0, 0.0);841source_path.write[1] = Vector3(0.0, 0.0, 1.0); // This point needs to go.842source_path.write[2] = Vector3(0.0, 0.0, 2.0); // This point needs to go.843source_path.write[3] = Vector3(0.0, 0.0, 2.0);844source_path.write[4] = Vector3(2.0, 1.0, 3.0);845source_path.write[5] = Vector3(2.0, 1.5, 4.0); // This point needs to go.846source_path.write[6] = Vector3(2.0, 2.0, 5.0);847Vector<Vector3> simplified_path = NavigationServer3D::get_singleton()->simplify_path(source_path, simplify_epsilon);848CHECK_EQ(simplified_path.size(), 4);849}850}851852} // namespace TestNavigationServer3D853854#endif // MODULE_NAVIGATION_3D_ENABLED855856857