Path: blob/master/tests/servers/test_navigation_server_3d.cpp
23449 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 "scene/3d/mesh_instance_3d.h"39#include "scene/main/window.h"40#include "scene/resources/3d/primitive_meshes.h"41#include "servers/navigation_3d/navigation_server_3d.h"42#include "tests/signal_watcher.h"4344namespace TestNavigationServer3D {4546// TODO: Find a more generic way to create `Callable` mocks.47class CallableMock : public Object {48GDCLASS(CallableMock, Object);4950public:51void function1(Variant arg0) {52function1_calls++;53function1_latest_arg0 = arg0;54}5556unsigned function1_calls{ 0 };57Variant function1_latest_arg0;58};5960TEST_SUITE("[Navigation3D]") {61TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {62NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();63CHECK_EQ(navigation_server->get_maps().size(), 0);6465SUBCASE("'ProcessInfo' should report all counters empty as well") {66CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);67CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);68CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);69CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);70CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_POLYGON_COUNT), 0);71CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_COUNT), 0);72CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_MERGE_COUNT), 0);73CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_CONNECTION_COUNT), 0);74CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_FREE_COUNT), 0);75}76}7778TEST_CASE("[NavigationServer3D] Server should manage agent properly") {79NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();8081RID agent = navigation_server->agent_create();82CHECK(agent.is_valid());8384SUBCASE("'ProcessInfo' should not report dangling agent") {85CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);86}8788SUBCASE("Setters/getters should work") {89bool initial_use_3d_avoidance = navigation_server->agent_get_use_3d_avoidance(agent);90navigation_server->agent_set_use_3d_avoidance(agent, !initial_use_3d_avoidance);91navigation_server->physics_process(0.0); // Give server some cycles to commit.9293CHECK_EQ(navigation_server->agent_get_use_3d_avoidance(agent), !initial_use_3d_avoidance);94// TODO: Add remaining setters/getters once the missing getters are added.95}9697SUBCASE("'ProcessInfo' should report agent with active map") {98RID map = navigation_server->map_create();99CHECK(map.is_valid());100navigation_server->map_set_active(map, true);101navigation_server->agent_set_map(agent, map);102navigation_server->physics_process(0.0); // Give server some cycles to commit.103CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 1);104navigation_server->agent_set_map(agent, RID());105navigation_server->free_rid(map);106navigation_server->physics_process(0.0); // Give server some cycles to commit.107CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);108}109110navigation_server->free_rid(agent);111}112113TEST_CASE("[NavigationServer3D] Server should manage map properly") {114NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();115116RID map;117CHECK_FALSE(map.is_valid());118119SUBCASE("Queries against invalid map should return empty or invalid values") {120ERR_PRINT_OFF;121CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());122CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());123CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());124CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());125CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());126CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);127CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);128129Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);130query_parameters->set_map(map);131query_parameters->set_start_position(Vector3(7, 7, 7));132query_parameters->set_target_position(Vector3(8, 8, 8));133Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);134navigation_server->query_path(query_parameters, query_result);135CHECK_EQ(query_result->get_path().size(), 0);136CHECK_EQ(query_result->get_path_types().size(), 0);137CHECK_EQ(query_result->get_path_rids().size(), 0);138CHECK_EQ(query_result->get_path_owner_ids().size(), 0);139ERR_PRINT_ON;140}141142map = navigation_server->map_create();143CHECK(map.is_valid());144CHECK_EQ(navigation_server->get_maps().size(), 1);145146SUBCASE("'ProcessInfo' should not report inactive map") {147CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);148}149150SUBCASE("Setters/getters should work") {151navigation_server->map_set_cell_size(map, 0.55);152navigation_server->map_set_edge_connection_margin(map, 0.66);153navigation_server->map_set_link_connection_radius(map, 0.77);154navigation_server->map_set_up(map, Vector3(1, 0, 0));155bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);156navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);157navigation_server->physics_process(0.0); // Give server some cycles to commit.158159CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));160CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));161CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));162CHECK_EQ(navigation_server->map_get_up(map), Vector3(1, 0, 0));163CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);164}165166SUBCASE("'ProcessInfo' should report map iff active") {167navigation_server->map_set_active(map, true);168navigation_server->physics_process(0.0); // Give server some cycles to commit.169CHECK(navigation_server->map_is_active(map));170CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 1);171navigation_server->map_set_active(map, false);172navigation_server->physics_process(0.0); // Give server some cycles to commit.173CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);174}175176SUBCASE("Number of agents should be reported properly") {177RID agent = navigation_server->agent_create();178CHECK(agent.is_valid());179navigation_server->agent_set_map(agent, map);180navigation_server->physics_process(0.0); // Give server some cycles to commit.181CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);182navigation_server->free_rid(agent);183navigation_server->physics_process(0.0); // Give server some cycles to commit.184CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);185}186187SUBCASE("Number of links should be reported properly") {188RID link = navigation_server->link_create();189CHECK(link.is_valid());190navigation_server->link_set_map(link, map);191navigation_server->physics_process(0.0); // Give server some cycles to commit.192CHECK_EQ(navigation_server->map_get_links(map).size(), 1);193navigation_server->free_rid(link);194navigation_server->physics_process(0.0); // Give server some cycles to commit.195CHECK_EQ(navigation_server->map_get_links(map).size(), 0);196}197198SUBCASE("Number of obstacles should be reported properly") {199RID obstacle = navigation_server->obstacle_create();200CHECK(obstacle.is_valid());201navigation_server->obstacle_set_map(obstacle, map);202navigation_server->physics_process(0.0); // Give server some cycles to commit.203CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);204navigation_server->free_rid(obstacle);205navigation_server->physics_process(0.0); // Give server some cycles to commit.206CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);207}208209SUBCASE("Number of regions should be reported properly") {210RID region = navigation_server->region_create();211CHECK(region.is_valid());212navigation_server->region_set_map(region, map);213navigation_server->physics_process(0.0); // Give server some cycles to commit.214CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);215navigation_server->free_rid(region);216navigation_server->physics_process(0.0); // Give server some cycles to commit.217CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);218}219220SUBCASE("Queries against empty map should return empty or invalid values") {221navigation_server->map_set_active(map, true);222navigation_server->physics_process(0.0); // Give server some cycles to commit.223224ERR_PRINT_OFF;225CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());226CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());227CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());228CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());229CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());230CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);231CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);232233Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);234query_parameters->set_map(map);235query_parameters->set_start_position(Vector3(7, 7, 7));236query_parameters->set_target_position(Vector3(8, 8, 8));237Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);238navigation_server->query_path(query_parameters, query_result);239CHECK_EQ(query_result->get_path().size(), 0);240CHECK_EQ(query_result->get_path_types().size(), 0);241CHECK_EQ(query_result->get_path_rids().size(), 0);242CHECK_EQ(query_result->get_path_owner_ids().size(), 0);243ERR_PRINT_ON;244245navigation_server->map_set_active(map, false);246navigation_server->physics_process(0.0); // Give server some cycles to commit.247}248249navigation_server->free_rid(map);250navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.251CHECK_EQ(navigation_server->get_maps().size(), 0);252}253254TEST_CASE("[NavigationServer3D] Server should manage link properly") {255NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();256257RID link = navigation_server->link_create();258CHECK(link.is_valid());259260SUBCASE("'ProcessInfo' should not report dangling link") {261CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);262}263264SUBCASE("Setters/getters should work") {265bool initial_bidirectional = navigation_server->link_is_bidirectional(link);266navigation_server->link_set_bidirectional(link, !initial_bidirectional);267navigation_server->link_set_end_position(link, Vector3(7, 7, 7));268navigation_server->link_set_enter_cost(link, 0.55);269navigation_server->link_set_navigation_layers(link, 6);270navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));271navigation_server->link_set_start_position(link, Vector3(8, 8, 8));272navigation_server->link_set_travel_cost(link, 0.66);273navigation_server->physics_process(0.0); // Give server some cycles to commit.274275CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);276CHECK_EQ(navigation_server->link_get_end_position(link), Vector3(7, 7, 7));277CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));278CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);279CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));280CHECK_EQ(navigation_server->link_get_start_position(link), Vector3(8, 8, 8));281CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));282}283284SUBCASE("'ProcessInfo' should report link with active map") {285RID map = navigation_server->map_create();286CHECK(map.is_valid());287navigation_server->map_set_active(map, true);288navigation_server->link_set_map(link, map);289navigation_server->physics_process(0.0); // Give server some cycles to commit.290CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 1);291navigation_server->link_set_map(link, RID());292navigation_server->free_rid(map);293navigation_server->physics_process(0.0); // Give server some cycles to commit.294CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);295}296297navigation_server->free_rid(link);298}299300TEST_CASE("[NavigationServer3D] Server should manage obstacles properly") {301NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();302303RID obstacle = navigation_server->obstacle_create();304CHECK(obstacle.is_valid());305306// TODO: Add tests for setters/getters once getters are added.307308navigation_server->free_rid(obstacle);309}310311TEST_CASE("[NavigationServer3D] Server should manage regions properly") {312NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();313314RID region = navigation_server->region_create();315CHECK(region.is_valid());316317SUBCASE("'ProcessInfo' should not report dangling region") {318CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);319}320321SUBCASE("Setters/getters should work") {322bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);323navigation_server->region_set_enter_cost(region, 0.55);324navigation_server->region_set_navigation_layers(region, 5);325navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));326navigation_server->region_set_travel_cost(region, 0.66);327navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);328navigation_server->physics_process(0.0); // Give server some cycles to commit.329330CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));331CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);332CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));333CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));334CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);335}336337SUBCASE("'ProcessInfo' should report region with active map") {338RID map = navigation_server->map_create();339CHECK(map.is_valid());340navigation_server->map_set_active(map, true);341navigation_server->region_set_map(region, map);342navigation_server->physics_process(0.0); // Give server some cycles to commit.343CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 1);344navigation_server->region_set_map(region, RID());345navigation_server->free_rid(map);346navigation_server->physics_process(0.0); // Give server some cycles to commit.347CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);348}349350SUBCASE("Queries against empty region should return empty or invalid values") {351ERR_PRINT_OFF;352CHECK_EQ(navigation_server->region_get_connections_count(region), 0);353CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector3());354CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector3());355ERR_PRINT_ON;356}357358navigation_server->free_rid(region);359}360361// This test case does not check precise values on purpose - to not be too sensitivte.362TEST_CASE("[NavigationServer3D] Server should move agent properly") {363NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();364365RID map = navigation_server->map_create();366RID agent = navigation_server->agent_create();367368navigation_server->map_set_active(map, true);369navigation_server->agent_set_map(agent, map);370navigation_server->agent_set_avoidance_enabled(agent, true);371navigation_server->agent_set_velocity(agent, Vector3(1, 0, 1));372CallableMock agent_avoidance_callback_mock;373navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));374CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);375navigation_server->physics_process(0.0); // Give server some cycles to commit.376CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);377CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector3(0, 0, 0));378379navigation_server->free_rid(agent);380navigation_server->free_rid(map);381}382383// This test case does not check precise values on purpose - to not be too sensitivte.384TEST_CASE("[NavigationServer3D] Server should make agents avoid each other when avoidance enabled") {385NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();386387RID map = navigation_server->map_create();388RID agent_1 = navigation_server->agent_create();389RID agent_2 = navigation_server->agent_create();390391navigation_server->map_set_active(map, true);392393navigation_server->agent_set_map(agent_1, map);394navigation_server->agent_set_avoidance_enabled(agent_1, true);395navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));396navigation_server->agent_set_radius(agent_1, 1);397navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));398CallableMock agent_1_avoidance_callback_mock;399navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));400401navigation_server->agent_set_map(agent_2, map);402navigation_server->agent_set_avoidance_enabled(agent_2, true);403navigation_server->agent_set_position(agent_2, Vector3(2.5, 0, 0.5));404navigation_server->agent_set_radius(agent_2, 1);405navigation_server->agent_set_velocity(agent_2, Vector3(-1, 0, 0));406CallableMock agent_2_avoidance_callback_mock;407navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));408409CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);410CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);411navigation_server->physics_process(0.0); // Give server some cycles to commit.412CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);413CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);414Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;415Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;416CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");417CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");418CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "agent 1 should move a bit to the side so that it avoids agent 2");419CHECK_MESSAGE(agent_2_safe_velocity.z > 0, "agent 2 should move a bit to the side so that it avoids agent 1");420421navigation_server->free_rid(agent_2);422navigation_server->free_rid(agent_1);423navigation_server->free_rid(map);424}425426TEST_CASE("[NavigationServer3D] Server should make agents avoid dynamic obstacles when avoidance enabled") {427NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();428429RID map = navigation_server->map_create();430RID agent_1 = navigation_server->agent_create();431RID obstacle_1 = navigation_server->obstacle_create();432433navigation_server->map_set_active(map, true);434435navigation_server->agent_set_map(agent_1, map);436navigation_server->agent_set_avoidance_enabled(agent_1, true);437navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));438navigation_server->agent_set_radius(agent_1, 1);439navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));440CallableMock agent_1_avoidance_callback_mock;441navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));442443navigation_server->obstacle_set_map(obstacle_1, map);444navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);445navigation_server->obstacle_set_position(obstacle_1, Vector3(2.5, 0, 0.5));446navigation_server->obstacle_set_radius(obstacle_1, 1);447448CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);449navigation_server->physics_process(0.0); // Give server some cycles to commit.450CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);451Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;452CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");453CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");454455navigation_server->free_rid(obstacle_1);456navigation_server->free_rid(agent_1);457navigation_server->free_rid(map);458navigation_server->physics_process(0.0); // Give server some cycles to commit.459}460461TEST_CASE("[NavigationServer3D] Server should make agents avoid static obstacles when avoidance enabled") {462NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();463464RID map = navigation_server->map_create();465RID agent_1 = navigation_server->agent_create();466RID agent_2 = navigation_server->agent_create();467RID obstacle_1 = navigation_server->obstacle_create();468469navigation_server->map_set_active(map, true);470471navigation_server->agent_set_map(agent_1, map);472navigation_server->agent_set_avoidance_enabled(agent_1, true);473navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.474navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));475CallableMock agent_1_avoidance_callback_mock;476navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));477478navigation_server->agent_set_map(agent_2, map);479navigation_server->agent_set_avoidance_enabled(agent_2, true);480navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.481navigation_server->agent_set_velocity(agent_2, Vector3(1, 0, 0));482CallableMock agent_2_avoidance_callback_mock;483navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));484485navigation_server->obstacle_set_map(obstacle_1, map);486navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);487PackedVector3Array obstacle_1_vertices;488489SUBCASE("Static obstacles should work on ground level") {490navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));491navigation_server->agent_set_position(agent_2, Vector3(0, 0, 5));492obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));493obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));494}495496SUBCASE("Static obstacles should work when elevated") {497navigation_server->agent_set_position(agent_1, Vector3(0, 5, 0));498navigation_server->agent_set_position(agent_2, Vector3(0, 5, 5));499obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));500obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));501navigation_server->obstacle_set_position(obstacle_1, Vector3(0, 5, 0));502}503504navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);505506CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);507CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);508navigation_server->physics_process(0.0); // Give server some cycles to commit.509CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);510CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);511Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;512Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;513CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");514CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");515CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");516CHECK_MESSAGE(agent_2_safe_velocity.z == 0, "Agent 2 should not move to the side.");517518navigation_server->free_rid(obstacle_1);519navigation_server->free_rid(agent_2);520navigation_server->free_rid(agent_1);521navigation_server->free_rid(map);522navigation_server->physics_process(0.0); // Give server some cycles to commit.523}524525#ifndef DISABLE_DEPRECATED526// This test case uses only public APIs on purpose - other test cases use simplified baking.527// FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed.528TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") {529NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();530531// Prepare scene tree with simple mesh to serve as an input geometry.532Node3D *node_3d = memnew(Node3D);533SceneTree::get_singleton()->get_root()->add_child(node_3d);534Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);535plane_mesh->set_size(Size2(10.0, 10.0));536MeshInstance3D *mesh_instance = memnew(MeshInstance3D);537mesh_instance->set_mesh(plane_mesh);538node_3d->add_child(mesh_instance);539540// Prepare anything necessary to bake navigation mesh.541RID map = navigation_server->map_create();542RID region = navigation_server->region_create();543Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);544navigation_server->map_set_use_async_iterations(map, false);545navigation_server->map_set_active(map, true);546navigation_server->region_set_use_async_iterations(region, false);547navigation_server->region_set_map(region, map);548navigation_server->region_set_navigation_mesh(region, navigation_mesh);549navigation_server->physics_process(0.0); // Give server some cycles to commit.550551CHECK_EQ(navigation_mesh->get_polygon_count(), 0);552CHECK_EQ(navigation_mesh->get_vertices().size(), 0);553554ERR_PRINT_OFF;555navigation_server->region_bake_navigation_mesh(navigation_mesh, node_3d);556ERR_PRINT_ON;557// FIXME: The above line should trigger the update (line below) under the hood.558navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.559CHECK_EQ(navigation_mesh->get_polygon_count(), 2);560CHECK_EQ(navigation_mesh->get_vertices().size(), 4);561562SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {563SIGNAL_WATCH(navigation_server, "map_changed");564SIGNAL_CHECK_FALSE("map_changed");565navigation_server->physics_process(0.0); // Give server some cycles to commit.566SIGNAL_CHECK("map_changed", { { map } });567SIGNAL_UNWATCH(navigation_server, "map_changed");568CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));569}570571navigation_server->free_rid(region);572navigation_server->free_rid(map);573navigation_server->physics_process(0.0); // Give server some cycles to commit.574memdelete(mesh_instance);575memdelete(node_3d);576}577#endif // DISABLE_DEPRECATED578579TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to parse geometry") {580NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();581582// Prepare scene tree with simple mesh to serve as an input geometry.583Node3D *node_3d = memnew(Node3D);584SceneTree::get_singleton()->get_root()->add_child(node_3d);585Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);586plane_mesh->set_size(Size2(10.0, 10.0));587MeshInstance3D *mesh_instance = memnew(MeshInstance3D);588mesh_instance->set_mesh(plane_mesh);589node_3d->add_child(mesh_instance);590591Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);592Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);593CHECK_EQ(source_geometry->get_vertices().size(), 0);594CHECK_EQ(source_geometry->get_indices().size(), 0);595596navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);597CHECK_EQ(source_geometry->get_vertices().size(), 12);598CHECK_EQ(source_geometry->get_indices().size(), 6);599600SUBCASE("By default, parsing should remove any data that was parsed before") {601navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);602CHECK_EQ(source_geometry->get_vertices().size(), 12);603CHECK_EQ(source_geometry->get_indices().size(), 6);604}605606SUBCASE("Parsed geometry should be extendable with other geometry") {607source_geometry->merge(source_geometry); // Merging with itself.608const Vector<float> vertices = source_geometry->get_vertices();609const Vector<int> indices = source_geometry->get_indices();610REQUIRE_EQ(vertices.size(), 24);611REQUIRE_EQ(indices.size(), 12);612// Check if first newly added vertex is the same as first vertex.613CHECK_EQ(vertices[0], vertices[12]);614CHECK_EQ(vertices[1], vertices[13]);615CHECK_EQ(vertices[2], vertices[14]);616// Check if first newly added index is the same as first index.617CHECK_EQ(indices[0] + 4, indices[6]);618}619620memdelete(mesh_instance);621memdelete(node_3d);622}623624// This test case uses only public APIs on purpose - other test cases use simplified baking.625TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") {626NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();627628// Prepare scene tree with simple mesh to serve as an input geometry.629Node3D *node_3d = memnew(Node3D);630SceneTree::get_singleton()->get_root()->add_child(node_3d);631Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);632plane_mesh->set_size(Size2(10.0, 10.0));633MeshInstance3D *mesh_instance = memnew(MeshInstance3D);634mesh_instance->set_mesh(plane_mesh);635node_3d->add_child(mesh_instance);636637// Prepare anything necessary to bake navigation mesh.638RID map = navigation_server->map_create();639RID region = navigation_server->region_create();640Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);641navigation_server->map_set_use_async_iterations(map, false);642navigation_server->map_set_active(map, true);643navigation_server->region_set_use_async_iterations(region, false);644navigation_server->region_set_map(region, map);645navigation_server->region_set_navigation_mesh(region, navigation_mesh);646navigation_server->physics_process(0.0); // Give server some cycles to commit.647648CHECK_EQ(navigation_mesh->get_polygon_count(), 0);649CHECK_EQ(navigation_mesh->get_vertices().size(), 0);650651Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);652navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, node_3d);653navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());654// FIXME: The above line should trigger the update (line below) under the hood.655navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.656CHECK_EQ(navigation_mesh->get_polygon_count(), 2);657CHECK_EQ(navigation_mesh->get_vertices().size(), 4);658659SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {660SIGNAL_WATCH(navigation_server, "map_changed");661SIGNAL_CHECK_FALSE("map_changed");662navigation_server->physics_process(0.0); // Give server some cycles to commit.663SIGNAL_CHECK("map_changed", { { map } });664SIGNAL_UNWATCH(navigation_server, "map_changed");665CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));666}667668navigation_server->free_rid(region);669navigation_server->free_rid(map);670navigation_server->physics_process(0.0); // Give server some cycles to commit.671memdelete(mesh_instance);672memdelete(node_3d);673}674675// This test case does not check precise values on purpose - to not be too sensitivte.676TEST_CASE("[NavigationServer3D] Server should respond to queries against valid map properly") {677NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();678Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);679Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);680681Array arr;682arr.resize(RSE::ARRAY_MAX);683BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));684source_geometry->add_mesh_array(arr, Transform3D());685navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());686CHECK_NE(navigation_mesh->get_polygon_count(), 0);687CHECK_NE(navigation_mesh->get_vertices().size(), 0);688689RID map = navigation_server->map_create();690RID region = navigation_server->region_create();691navigation_server->map_set_active(map, true);692navigation_server->map_set_use_async_iterations(map, false);693navigation_server->region_set_use_async_iterations(region, false);694navigation_server->region_set_map(region, map);695navigation_server->region_set_navigation_mesh(region, navigation_mesh);696navigation_server->physics_process(0.0); // Give server some cycles to commit.697698SUBCASE("Simple queries should return non-default values") {699CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));700CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3());701CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid());702CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3());703CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3());704CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0);705CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0);706}707708SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") {709CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3());710}711712SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {713Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);714query_parameters->set_map(map);715query_parameters->set_start_position(Vector3(0, 0, 0));716query_parameters->set_target_position(Vector3(10, 0, 10));717query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_CORRIDORFUNNEL);718Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);719navigation_server->query_path(query_parameters, query_result);720CHECK_NE(query_result->get_path().size(), 0);721CHECK_NE(query_result->get_path_types().size(), 0);722CHECK_NE(query_result->get_path_rids().size(), 0);723CHECK_NE(query_result->get_path_owner_ids().size(), 0);724}725726SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {727Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);728query_parameters->set_map(map);729query_parameters->set_start_position(Vector3(10, 0, 10));730query_parameters->set_target_position(Vector3(0, 0, 0));731query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_EDGECENTERED);732Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);733navigation_server->query_path(query_parameters, query_result);734CHECK_NE(query_result->get_path().size(), 0);735CHECK_NE(query_result->get_path_types().size(), 0);736CHECK_NE(query_result->get_path_rids().size(), 0);737CHECK_NE(query_result->get_path_owner_ids().size(), 0);738}739740SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {741Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);742query_parameters->set_map(map);743query_parameters->set_start_position(Vector3(10, 0, 10));744query_parameters->set_target_position(Vector3(0, 0, 0));745query_parameters->set_navigation_layers(2);746Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);747navigation_server->query_path(query_parameters, query_result);748CHECK_EQ(query_result->get_path().size(), 0);749CHECK_EQ(query_result->get_path_types().size(), 0);750CHECK_EQ(query_result->get_path_rids().size(), 0);751CHECK_EQ(query_result->get_path_owner_ids().size(), 0);752}753754SUBCASE("Elaborate query without metadata flags should yield path only") {755Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);756query_parameters->set_map(map);757query_parameters->set_start_position(Vector3(10, 0, 10));758query_parameters->set_target_position(Vector3(0, 0, 0));759query_parameters->set_metadata_flags(0);760Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);761navigation_server->query_path(query_parameters, query_result);762CHECK_NE(query_result->get_path().size(), 0);763CHECK_EQ(query_result->get_path_types().size(), 0);764CHECK_EQ(query_result->get_path_rids().size(), 0);765CHECK_EQ(query_result->get_path_owner_ids().size(), 0);766}767768SUBCASE("Elaborate query with excluded region should yield empty path") {769Ref<NavigationPathQueryParameters3D> query_parameters;770query_parameters.instantiate();771query_parameters->set_map(map);772query_parameters->set_start_position(Vector3(10, 0, 10));773query_parameters->set_target_position(Vector3(0, 0, 0));774query_parameters->set_excluded_regions({ region });775Ref<NavigationPathQueryResult3D> query_result;776query_result.instantiate();777navigation_server->query_path(query_parameters, query_result);778CHECK_EQ(query_result->get_path().size(), 0);779}780781SUBCASE("Elaborate query with included region should yield path") {782Ref<NavigationPathQueryParameters3D> query_parameters;783query_parameters.instantiate();784query_parameters->set_map(map);785query_parameters->set_start_position(Vector3(10, 0, 10));786query_parameters->set_target_position(Vector3(0, 0, 0));787query_parameters->set_included_regions({ region });788Ref<NavigationPathQueryResult3D> query_result;789query_result.instantiate();790navigation_server->query_path(query_parameters, query_result);791CHECK_NE(query_result->get_path().size(), 0);792}793794SUBCASE("Elaborate query with excluded and included region should yield empty path") {795Ref<NavigationPathQueryParameters3D> query_parameters;796query_parameters.instantiate();797query_parameters->set_map(map);798query_parameters->set_start_position(Vector3(10, 0, 10));799query_parameters->set_target_position(Vector3(0, 0, 0));800query_parameters->set_excluded_regions({ region });801query_parameters->set_included_regions({ region });802Ref<NavigationPathQueryResult3D> query_result;803query_result.instantiate();804navigation_server->query_path(query_parameters, query_result);805CHECK_EQ(query_result->get_path().size(), 0);806}807808navigation_server->free_rid(region);809navigation_server->free_rid(map);810navigation_server->physics_process(0.0); // Give server some cycles to commit.811}812813// FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613).814/*815TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") {816NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();817Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);818Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);819820Array arr;821arr.resize(RSE::ARRAY_MAX);822BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));823source_geometry->add_mesh_array(arr, Transform3D());824825// Race condition is present below, but baking should take many orders of magnitude826// longer than basic checks on the main thread, so it's fine.827navigation_server->bake_from_source_geometry_data_async(navigation_mesh, source_geometry, Callable());828CHECK(navigation_server->is_baking_navigation_mesh(navigation_mesh));829CHECK_EQ(navigation_mesh->get_polygon_count(), 0);830CHECK_EQ(navigation_mesh->get_vertices().size(), 0);831}832*/833834TEST_CASE("[NavigationServer3D] Server should simplify path properly") {835real_t simplify_epsilon = 0.2;836Vector<Vector3> source_path;837source_path.resize(7);838source_path.write[0] = Vector3(0.0, 0.0, 0.0);839source_path.write[1] = Vector3(0.0, 0.0, 1.0); // This point needs to go.840source_path.write[2] = Vector3(0.0, 0.0, 2.0); // This point needs to go.841source_path.write[3] = Vector3(0.0, 0.0, 2.0);842source_path.write[4] = Vector3(2.0, 1.0, 3.0);843source_path.write[5] = Vector3(2.0, 1.5, 4.0); // This point needs to go.844source_path.write[6] = Vector3(2.0, 2.0, 5.0);845Vector<Vector3> simplified_path = NavigationServer3D::get_singleton()->simplify_path(source_path, simplify_epsilon);846CHECK_EQ(simplified_path.size(), 4);847}848}849850} // namespace TestNavigationServer3D851852#endif // MODULE_NAVIGATION_3D_ENABLED853854855