Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/servers/test_navigation_server_3d.h
10277 views
1
/**************************************************************************/
2
/* test_navigation_server_3d.h */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#pragma once
32
33
#include "scene/3d/mesh_instance_3d.h"
34
#include "scene/resources/3d/primitive_meshes.h"
35
#include "servers/navigation_server_3d.h"
36
37
namespace TestNavigationServer3D {
38
39
// TODO: Find a more generic way to create `Callable` mocks.
40
class CallableMock : public Object {
41
GDCLASS(CallableMock, Object);
42
43
public:
44
void function1(Variant arg0) {
45
function1_calls++;
46
function1_latest_arg0 = arg0;
47
}
48
49
unsigned function1_calls{ 0 };
50
Variant function1_latest_arg0;
51
};
52
53
TEST_SUITE("[Navigation3D]") {
54
TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {
55
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
56
CHECK_EQ(navigation_server->get_maps().size(), 0);
57
58
SUBCASE("'ProcessInfo' should report all counters empty as well") {
59
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);
60
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);
61
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);
62
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);
63
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_POLYGON_COUNT), 0);
64
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_COUNT), 0);
65
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_MERGE_COUNT), 0);
66
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_CONNECTION_COUNT), 0);
67
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_EDGE_FREE_COUNT), 0);
68
}
69
}
70
71
TEST_CASE("[NavigationServer3D] Server should manage agent properly") {
72
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
73
74
RID agent = navigation_server->agent_create();
75
CHECK(agent.is_valid());
76
77
SUBCASE("'ProcessInfo' should not report dangling agent") {
78
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);
79
}
80
81
SUBCASE("Setters/getters should work") {
82
bool initial_use_3d_avoidance = navigation_server->agent_get_use_3d_avoidance(agent);
83
navigation_server->agent_set_use_3d_avoidance(agent, !initial_use_3d_avoidance);
84
navigation_server->physics_process(0.0); // Give server some cycles to commit.
85
86
CHECK_EQ(navigation_server->agent_get_use_3d_avoidance(agent), !initial_use_3d_avoidance);
87
// TODO: Add remaining setters/getters once the missing getters are added.
88
}
89
90
SUBCASE("'ProcessInfo' should report agent with active map") {
91
RID map = navigation_server->map_create();
92
CHECK(map.is_valid());
93
navigation_server->map_set_active(map, true);
94
navigation_server->agent_set_map(agent, map);
95
navigation_server->physics_process(0.0); // Give server some cycles to commit.
96
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 1);
97
navigation_server->agent_set_map(agent, RID());
98
navigation_server->free(map);
99
navigation_server->physics_process(0.0); // Give server some cycles to commit.
100
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0);
101
}
102
103
navigation_server->free(agent);
104
}
105
106
TEST_CASE("[NavigationServer3D] Server should manage map properly") {
107
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
108
109
RID map;
110
CHECK_FALSE(map.is_valid());
111
112
SUBCASE("Queries against invalid map should return empty or invalid values") {
113
ERR_PRINT_OFF;
114
CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());
115
CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());
116
CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());
117
CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());
118
CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());
119
CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);
120
CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);
121
122
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
123
query_parameters->set_map(map);
124
query_parameters->set_start_position(Vector3(7, 7, 7));
125
query_parameters->set_target_position(Vector3(8, 8, 8));
126
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
127
navigation_server->query_path(query_parameters, query_result);
128
CHECK_EQ(query_result->get_path().size(), 0);
129
CHECK_EQ(query_result->get_path_types().size(), 0);
130
CHECK_EQ(query_result->get_path_rids().size(), 0);
131
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
132
ERR_PRINT_ON;
133
}
134
135
map = navigation_server->map_create();
136
CHECK(map.is_valid());
137
CHECK_EQ(navigation_server->get_maps().size(), 1);
138
139
SUBCASE("'ProcessInfo' should not report inactive map") {
140
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);
141
}
142
143
SUBCASE("Setters/getters should work") {
144
navigation_server->map_set_cell_size(map, 0.55);
145
navigation_server->map_set_edge_connection_margin(map, 0.66);
146
navigation_server->map_set_link_connection_radius(map, 0.77);
147
navigation_server->map_set_up(map, Vector3(1, 0, 0));
148
bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);
149
navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);
150
navigation_server->physics_process(0.0); // Give server some cycles to commit.
151
152
CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));
153
CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));
154
CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));
155
CHECK_EQ(navigation_server->map_get_up(map), Vector3(1, 0, 0));
156
CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);
157
}
158
159
SUBCASE("'ProcessInfo' should report map iff active") {
160
navigation_server->map_set_active(map, true);
161
navigation_server->physics_process(0.0); // Give server some cycles to commit.
162
CHECK(navigation_server->map_is_active(map));
163
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 1);
164
navigation_server->map_set_active(map, false);
165
navigation_server->physics_process(0.0); // Give server some cycles to commit.
166
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_ACTIVE_MAPS), 0);
167
}
168
169
SUBCASE("Number of agents should be reported properly") {
170
RID agent = navigation_server->agent_create();
171
CHECK(agent.is_valid());
172
navigation_server->agent_set_map(agent, map);
173
navigation_server->physics_process(0.0); // Give server some cycles to commit.
174
CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);
175
navigation_server->free(agent);
176
navigation_server->physics_process(0.0); // Give server some cycles to commit.
177
CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);
178
}
179
180
SUBCASE("Number of links should be reported properly") {
181
RID link = navigation_server->link_create();
182
CHECK(link.is_valid());
183
navigation_server->link_set_map(link, map);
184
navigation_server->physics_process(0.0); // Give server some cycles to commit.
185
CHECK_EQ(navigation_server->map_get_links(map).size(), 1);
186
navigation_server->free(link);
187
navigation_server->physics_process(0.0); // Give server some cycles to commit.
188
CHECK_EQ(navigation_server->map_get_links(map).size(), 0);
189
}
190
191
SUBCASE("Number of obstacles should be reported properly") {
192
RID obstacle = navigation_server->obstacle_create();
193
CHECK(obstacle.is_valid());
194
navigation_server->obstacle_set_map(obstacle, map);
195
navigation_server->physics_process(0.0); // Give server some cycles to commit.
196
CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);
197
navigation_server->free(obstacle);
198
navigation_server->physics_process(0.0); // Give server some cycles to commit.
199
CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);
200
}
201
202
SUBCASE("Number of regions should be reported properly") {
203
RID region = navigation_server->region_create();
204
CHECK(region.is_valid());
205
navigation_server->region_set_map(region, map);
206
navigation_server->physics_process(0.0); // Give server some cycles to commit.
207
CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);
208
navigation_server->free(region);
209
navigation_server->physics_process(0.0); // Give server some cycles to commit.
210
CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);
211
}
212
213
SUBCASE("Queries against empty map should return empty or invalid values") {
214
navigation_server->map_set_active(map, true);
215
navigation_server->physics_process(0.0); // Give server some cycles to commit.
216
217
ERR_PRINT_OFF;
218
CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3());
219
CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3());
220
CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid());
221
CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3());
222
CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3());
223
CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0);
224
CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0);
225
226
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
227
query_parameters->set_map(map);
228
query_parameters->set_start_position(Vector3(7, 7, 7));
229
query_parameters->set_target_position(Vector3(8, 8, 8));
230
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
231
navigation_server->query_path(query_parameters, query_result);
232
CHECK_EQ(query_result->get_path().size(), 0);
233
CHECK_EQ(query_result->get_path_types().size(), 0);
234
CHECK_EQ(query_result->get_path_rids().size(), 0);
235
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
236
ERR_PRINT_ON;
237
238
navigation_server->map_set_active(map, false);
239
navigation_server->physics_process(0.0); // Give server some cycles to commit.
240
}
241
242
navigation_server->free(map);
243
navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.
244
CHECK_EQ(navigation_server->get_maps().size(), 0);
245
}
246
247
TEST_CASE("[NavigationServer3D] Server should manage link properly") {
248
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
249
250
RID link = navigation_server->link_create();
251
CHECK(link.is_valid());
252
253
SUBCASE("'ProcessInfo' should not report dangling link") {
254
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);
255
}
256
257
SUBCASE("Setters/getters should work") {
258
bool initial_bidirectional = navigation_server->link_is_bidirectional(link);
259
navigation_server->link_set_bidirectional(link, !initial_bidirectional);
260
navigation_server->link_set_end_position(link, Vector3(7, 7, 7));
261
navigation_server->link_set_enter_cost(link, 0.55);
262
navigation_server->link_set_navigation_layers(link, 6);
263
navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));
264
navigation_server->link_set_start_position(link, Vector3(8, 8, 8));
265
navigation_server->link_set_travel_cost(link, 0.66);
266
navigation_server->physics_process(0.0); // Give server some cycles to commit.
267
268
CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);
269
CHECK_EQ(navigation_server->link_get_end_position(link), Vector3(7, 7, 7));
270
CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));
271
CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);
272
CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));
273
CHECK_EQ(navigation_server->link_get_start_position(link), Vector3(8, 8, 8));
274
CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));
275
}
276
277
SUBCASE("'ProcessInfo' should report link with active map") {
278
RID map = navigation_server->map_create();
279
CHECK(map.is_valid());
280
navigation_server->map_set_active(map, true);
281
navigation_server->link_set_map(link, map);
282
navigation_server->physics_process(0.0); // Give server some cycles to commit.
283
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 1);
284
navigation_server->link_set_map(link, RID());
285
navigation_server->free(map);
286
navigation_server->physics_process(0.0); // Give server some cycles to commit.
287
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0);
288
}
289
290
navigation_server->free(link);
291
}
292
293
TEST_CASE("[NavigationServer3D] Server should manage obstacles properly") {
294
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
295
296
RID obstacle = navigation_server->obstacle_create();
297
CHECK(obstacle.is_valid());
298
299
// TODO: Add tests for setters/getters once getters are added.
300
301
navigation_server->free(obstacle);
302
}
303
304
TEST_CASE("[NavigationServer3D] Server should manage regions properly") {
305
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
306
307
RID region = navigation_server->region_create();
308
CHECK(region.is_valid());
309
310
SUBCASE("'ProcessInfo' should not report dangling region") {
311
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);
312
}
313
314
SUBCASE("Setters/getters should work") {
315
bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);
316
navigation_server->region_set_enter_cost(region, 0.55);
317
navigation_server->region_set_navigation_layers(region, 5);
318
navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));
319
navigation_server->region_set_travel_cost(region, 0.66);
320
navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);
321
navigation_server->physics_process(0.0); // Give server some cycles to commit.
322
323
CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));
324
CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);
325
CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));
326
CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));
327
CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);
328
}
329
330
SUBCASE("'ProcessInfo' should report region with active map") {
331
RID map = navigation_server->map_create();
332
CHECK(map.is_valid());
333
navigation_server->map_set_active(map, true);
334
navigation_server->region_set_map(region, map);
335
navigation_server->physics_process(0.0); // Give server some cycles to commit.
336
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 1);
337
navigation_server->region_set_map(region, RID());
338
navigation_server->free(map);
339
navigation_server->physics_process(0.0); // Give server some cycles to commit.
340
CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0);
341
}
342
343
SUBCASE("Queries against empty region should return empty or invalid values") {
344
ERR_PRINT_OFF;
345
CHECK_EQ(navigation_server->region_get_connections_count(region), 0);
346
CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector3());
347
CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector3());
348
ERR_PRINT_ON;
349
}
350
351
navigation_server->free(region);
352
}
353
354
// This test case does not check precise values on purpose - to not be too sensitivte.
355
TEST_CASE("[NavigationServer3D] Server should move agent properly") {
356
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
357
358
RID map = navigation_server->map_create();
359
RID agent = navigation_server->agent_create();
360
361
navigation_server->map_set_active(map, true);
362
navigation_server->agent_set_map(agent, map);
363
navigation_server->agent_set_avoidance_enabled(agent, true);
364
navigation_server->agent_set_velocity(agent, Vector3(1, 0, 1));
365
CallableMock agent_avoidance_callback_mock;
366
navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));
367
CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);
368
navigation_server->physics_process(0.0); // Give server some cycles to commit.
369
CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);
370
CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector3(0, 0, 0));
371
372
navigation_server->free(agent);
373
navigation_server->free(map);
374
}
375
376
// This test case does not check precise values on purpose - to not be too sensitivte.
377
TEST_CASE("[NavigationServer3D] Server should make agents avoid each other when avoidance enabled") {
378
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
379
380
RID map = navigation_server->map_create();
381
RID agent_1 = navigation_server->agent_create();
382
RID agent_2 = navigation_server->agent_create();
383
384
navigation_server->map_set_active(map, true);
385
386
navigation_server->agent_set_map(agent_1, map);
387
navigation_server->agent_set_avoidance_enabled(agent_1, true);
388
navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));
389
navigation_server->agent_set_radius(agent_1, 1);
390
navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));
391
CallableMock agent_1_avoidance_callback_mock;
392
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
393
394
navigation_server->agent_set_map(agent_2, map);
395
navigation_server->agent_set_avoidance_enabled(agent_2, true);
396
navigation_server->agent_set_position(agent_2, Vector3(2.5, 0, 0.5));
397
navigation_server->agent_set_radius(agent_2, 1);
398
navigation_server->agent_set_velocity(agent_2, Vector3(-1, 0, 0));
399
CallableMock agent_2_avoidance_callback_mock;
400
navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));
401
402
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
403
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);
404
navigation_server->physics_process(0.0); // Give server some cycles to commit.
405
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
406
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);
407
Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
408
Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;
409
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");
410
CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");
411
CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "agent 1 should move a bit to the side so that it avoids agent 2");
412
CHECK_MESSAGE(agent_2_safe_velocity.z > 0, "agent 2 should move a bit to the side so that it avoids agent 1");
413
414
navigation_server->free(agent_2);
415
navigation_server->free(agent_1);
416
navigation_server->free(map);
417
}
418
419
TEST_CASE("[NavigationServer3D] Server should make agents avoid dynamic obstacles when avoidance enabled") {
420
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
421
422
RID map = navigation_server->map_create();
423
RID agent_1 = navigation_server->agent_create();
424
RID obstacle_1 = navigation_server->obstacle_create();
425
426
navigation_server->map_set_active(map, true);
427
428
navigation_server->agent_set_map(agent_1, map);
429
navigation_server->agent_set_avoidance_enabled(agent_1, true);
430
navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));
431
navigation_server->agent_set_radius(agent_1, 1);
432
navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));
433
CallableMock agent_1_avoidance_callback_mock;
434
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
435
436
navigation_server->obstacle_set_map(obstacle_1, map);
437
navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
438
navigation_server->obstacle_set_position(obstacle_1, Vector3(2.5, 0, 0.5));
439
navigation_server->obstacle_set_radius(obstacle_1, 1);
440
441
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
442
navigation_server->physics_process(0.0); // Give server some cycles to commit.
443
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
444
Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
445
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
446
CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
447
448
navigation_server->free(obstacle_1);
449
navigation_server->free(agent_1);
450
navigation_server->free(map);
451
navigation_server->physics_process(0.0); // Give server some cycles to commit.
452
}
453
454
TEST_CASE("[NavigationServer3D] Server should make agents avoid static obstacles when avoidance enabled") {
455
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
456
457
RID map = navigation_server->map_create();
458
RID agent_1 = navigation_server->agent_create();
459
RID agent_2 = navigation_server->agent_create();
460
RID obstacle_1 = navigation_server->obstacle_create();
461
462
navigation_server->map_set_active(map, true);
463
464
navigation_server->agent_set_map(agent_1, map);
465
navigation_server->agent_set_avoidance_enabled(agent_1, true);
466
navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.
467
navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));
468
CallableMock agent_1_avoidance_callback_mock;
469
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
470
471
navigation_server->agent_set_map(agent_2, map);
472
navigation_server->agent_set_avoidance_enabled(agent_2, true);
473
navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.
474
navigation_server->agent_set_velocity(agent_2, Vector3(1, 0, 0));
475
CallableMock agent_2_avoidance_callback_mock;
476
navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));
477
478
navigation_server->obstacle_set_map(obstacle_1, map);
479
navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
480
PackedVector3Array obstacle_1_vertices;
481
482
SUBCASE("Static obstacles should work on ground level") {
483
navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));
484
navigation_server->agent_set_position(agent_2, Vector3(0, 0, 5));
485
obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));
486
obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));
487
}
488
489
SUBCASE("Static obstacles should work when elevated") {
490
navigation_server->agent_set_position(agent_1, Vector3(0, 5, 0));
491
navigation_server->agent_set_position(agent_2, Vector3(0, 5, 5));
492
obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));
493
obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));
494
navigation_server->obstacle_set_position(obstacle_1, Vector3(0, 5, 0));
495
}
496
497
navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);
498
499
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
500
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);
501
navigation_server->physics_process(0.0); // Give server some cycles to commit.
502
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
503
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);
504
Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
505
Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;
506
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
507
CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
508
CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");
509
CHECK_MESSAGE(agent_2_safe_velocity.z == 0, "Agent 2 should not move to the side.");
510
511
navigation_server->free(obstacle_1);
512
navigation_server->free(agent_2);
513
navigation_server->free(agent_1);
514
navigation_server->free(map);
515
navigation_server->physics_process(0.0); // Give server some cycles to commit.
516
}
517
518
#ifndef DISABLE_DEPRECATED
519
// This test case uses only public APIs on purpose - other test cases use simplified baking.
520
// FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed.
521
TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") {
522
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
523
524
// Prepare scene tree with simple mesh to serve as an input geometry.
525
Node3D *node_3d = memnew(Node3D);
526
SceneTree::get_singleton()->get_root()->add_child(node_3d);
527
Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);
528
plane_mesh->set_size(Size2(10.0, 10.0));
529
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
530
mesh_instance->set_mesh(plane_mesh);
531
node_3d->add_child(mesh_instance);
532
533
// Prepare anything necessary to bake navigation mesh.
534
RID map = navigation_server->map_create();
535
RID region = navigation_server->region_create();
536
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
537
navigation_server->map_set_use_async_iterations(map, false);
538
navigation_server->map_set_active(map, true);
539
navigation_server->region_set_use_async_iterations(region, false);
540
navigation_server->region_set_map(region, map);
541
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
542
navigation_server->physics_process(0.0); // Give server some cycles to commit.
543
544
CHECK_EQ(navigation_mesh->get_polygon_count(), 0);
545
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
546
547
ERR_PRINT_OFF;
548
navigation_server->region_bake_navigation_mesh(navigation_mesh, node_3d);
549
ERR_PRINT_ON;
550
// FIXME: The above line should trigger the update (line below) under the hood.
551
navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.
552
CHECK_EQ(navigation_mesh->get_polygon_count(), 2);
553
CHECK_EQ(navigation_mesh->get_vertices().size(), 4);
554
555
SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {
556
SIGNAL_WATCH(navigation_server, "map_changed");
557
SIGNAL_CHECK_FALSE("map_changed");
558
navigation_server->physics_process(0.0); // Give server some cycles to commit.
559
SIGNAL_CHECK("map_changed", { { map } });
560
SIGNAL_UNWATCH(navigation_server, "map_changed");
561
CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));
562
}
563
564
navigation_server->free(region);
565
navigation_server->free(map);
566
navigation_server->physics_process(0.0); // Give server some cycles to commit.
567
memdelete(mesh_instance);
568
memdelete(node_3d);
569
}
570
#endif // DISABLE_DEPRECATED
571
572
TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to parse geometry") {
573
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
574
575
// Prepare scene tree with simple mesh to serve as an input geometry.
576
Node3D *node_3d = memnew(Node3D);
577
SceneTree::get_singleton()->get_root()->add_child(node_3d);
578
Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);
579
plane_mesh->set_size(Size2(10.0, 10.0));
580
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
581
mesh_instance->set_mesh(plane_mesh);
582
node_3d->add_child(mesh_instance);
583
584
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
585
Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);
586
CHECK_EQ(source_geometry->get_vertices().size(), 0);
587
CHECK_EQ(source_geometry->get_indices().size(), 0);
588
589
navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);
590
CHECK_EQ(source_geometry->get_vertices().size(), 12);
591
CHECK_EQ(source_geometry->get_indices().size(), 6);
592
593
SUBCASE("By default, parsing should remove any data that was parsed before") {
594
navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, mesh_instance);
595
CHECK_EQ(source_geometry->get_vertices().size(), 12);
596
CHECK_EQ(source_geometry->get_indices().size(), 6);
597
}
598
599
SUBCASE("Parsed geometry should be extendable with other geometry") {
600
source_geometry->merge(source_geometry); // Merging with itself.
601
const Vector<float> vertices = source_geometry->get_vertices();
602
const Vector<int> indices = source_geometry->get_indices();
603
REQUIRE_EQ(vertices.size(), 24);
604
REQUIRE_EQ(indices.size(), 12);
605
// Check if first newly added vertex is the same as first vertex.
606
CHECK_EQ(vertices[0], vertices[12]);
607
CHECK_EQ(vertices[1], vertices[13]);
608
CHECK_EQ(vertices[2], vertices[14]);
609
// Check if first newly added index is the same as first index.
610
CHECK_EQ(indices[0] + 4, indices[6]);
611
}
612
613
memdelete(mesh_instance);
614
memdelete(node_3d);
615
}
616
617
// This test case uses only public APIs on purpose - other test cases use simplified baking.
618
TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") {
619
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
620
621
// Prepare scene tree with simple mesh to serve as an input geometry.
622
Node3D *node_3d = memnew(Node3D);
623
SceneTree::get_singleton()->get_root()->add_child(node_3d);
624
Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);
625
plane_mesh->set_size(Size2(10.0, 10.0));
626
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
627
mesh_instance->set_mesh(plane_mesh);
628
node_3d->add_child(mesh_instance);
629
630
// Prepare anything necessary to bake navigation mesh.
631
RID map = navigation_server->map_create();
632
RID region = navigation_server->region_create();
633
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
634
navigation_server->map_set_use_async_iterations(map, false);
635
navigation_server->map_set_active(map, true);
636
navigation_server->region_set_use_async_iterations(region, false);
637
navigation_server->region_set_map(region, map);
638
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
639
navigation_server->physics_process(0.0); // Give server some cycles to commit.
640
641
CHECK_EQ(navigation_mesh->get_polygon_count(), 0);
642
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
643
644
Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);
645
navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, node_3d);
646
navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());
647
// FIXME: The above line should trigger the update (line below) under the hood.
648
navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update.
649
CHECK_EQ(navigation_mesh->get_polygon_count(), 2);
650
CHECK_EQ(navigation_mesh->get_vertices().size(), 4);
651
652
SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {
653
SIGNAL_WATCH(navigation_server, "map_changed");
654
SIGNAL_CHECK_FALSE("map_changed");
655
navigation_server->physics_process(0.0); // Give server some cycles to commit.
656
SIGNAL_CHECK("map_changed", { { map } });
657
SIGNAL_UNWATCH(navigation_server, "map_changed");
658
CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));
659
}
660
661
navigation_server->free(region);
662
navigation_server->free(map);
663
navigation_server->physics_process(0.0); // Give server some cycles to commit.
664
memdelete(mesh_instance);
665
memdelete(node_3d);
666
}
667
668
// This test case does not check precise values on purpose - to not be too sensitivte.
669
TEST_CASE("[NavigationServer3D] Server should respond to queries against valid map properly") {
670
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
671
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
672
Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);
673
674
Array arr;
675
arr.resize(RS::ARRAY_MAX);
676
BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));
677
source_geometry->add_mesh_array(arr, Transform3D());
678
navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable());
679
CHECK_NE(navigation_mesh->get_polygon_count(), 0);
680
CHECK_NE(navigation_mesh->get_vertices().size(), 0);
681
682
RID map = navigation_server->map_create();
683
RID region = navigation_server->region_create();
684
navigation_server->map_set_active(map, true);
685
navigation_server->map_set_use_async_iterations(map, false);
686
navigation_server->region_set_use_async_iterations(region, false);
687
navigation_server->region_set_map(region, map);
688
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
689
navigation_server->physics_process(0.0); // Give server some cycles to commit.
690
691
SUBCASE("Simple queries should return non-default values") {
692
CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0));
693
CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3());
694
CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid());
695
CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3());
696
CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3());
697
CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0);
698
CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0);
699
}
700
701
SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") {
702
CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3());
703
}
704
705
SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {
706
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
707
query_parameters->set_map(map);
708
query_parameters->set_start_position(Vector3(0, 0, 0));
709
query_parameters->set_target_position(Vector3(10, 0, 10));
710
query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_CORRIDORFUNNEL);
711
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
712
navigation_server->query_path(query_parameters, query_result);
713
CHECK_NE(query_result->get_path().size(), 0);
714
CHECK_NE(query_result->get_path_types().size(), 0);
715
CHECK_NE(query_result->get_path_rids().size(), 0);
716
CHECK_NE(query_result->get_path_owner_ids().size(), 0);
717
}
718
719
SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {
720
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
721
query_parameters->set_map(map);
722
query_parameters->set_start_position(Vector3(10, 0, 10));
723
query_parameters->set_target_position(Vector3(0, 0, 0));
724
query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_EDGECENTERED);
725
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
726
navigation_server->query_path(query_parameters, query_result);
727
CHECK_NE(query_result->get_path().size(), 0);
728
CHECK_NE(query_result->get_path_types().size(), 0);
729
CHECK_NE(query_result->get_path_rids().size(), 0);
730
CHECK_NE(query_result->get_path_owner_ids().size(), 0);
731
}
732
733
SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {
734
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
735
query_parameters->set_map(map);
736
query_parameters->set_start_position(Vector3(10, 0, 10));
737
query_parameters->set_target_position(Vector3(0, 0, 0));
738
query_parameters->set_navigation_layers(2);
739
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
740
navigation_server->query_path(query_parameters, query_result);
741
CHECK_EQ(query_result->get_path().size(), 0);
742
CHECK_EQ(query_result->get_path_types().size(), 0);
743
CHECK_EQ(query_result->get_path_rids().size(), 0);
744
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
745
}
746
747
SUBCASE("Elaborate query without metadata flags should yield path only") {
748
Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D);
749
query_parameters->set_map(map);
750
query_parameters->set_start_position(Vector3(10, 0, 10));
751
query_parameters->set_target_position(Vector3(0, 0, 0));
752
query_parameters->set_metadata_flags(0);
753
Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D);
754
navigation_server->query_path(query_parameters, query_result);
755
CHECK_NE(query_result->get_path().size(), 0);
756
CHECK_EQ(query_result->get_path_types().size(), 0);
757
CHECK_EQ(query_result->get_path_rids().size(), 0);
758
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
759
}
760
761
SUBCASE("Elaborate query with excluded region should yield empty path") {
762
Ref<NavigationPathQueryParameters3D> query_parameters;
763
query_parameters.instantiate();
764
query_parameters->set_map(map);
765
query_parameters->set_start_position(Vector3(10, 0, 10));
766
query_parameters->set_target_position(Vector3(0, 0, 0));
767
query_parameters->set_excluded_regions({ region });
768
Ref<NavigationPathQueryResult3D> query_result;
769
query_result.instantiate();
770
navigation_server->query_path(query_parameters, query_result);
771
CHECK_EQ(query_result->get_path().size(), 0);
772
}
773
774
SUBCASE("Elaborate query with included region should yield path") {
775
Ref<NavigationPathQueryParameters3D> query_parameters;
776
query_parameters.instantiate();
777
query_parameters->set_map(map);
778
query_parameters->set_start_position(Vector3(10, 0, 10));
779
query_parameters->set_target_position(Vector3(0, 0, 0));
780
query_parameters->set_included_regions({ region });
781
Ref<NavigationPathQueryResult3D> query_result;
782
query_result.instantiate();
783
navigation_server->query_path(query_parameters, query_result);
784
CHECK_NE(query_result->get_path().size(), 0);
785
}
786
787
SUBCASE("Elaborate query with excluded and included region should yield empty path") {
788
Ref<NavigationPathQueryParameters3D> query_parameters;
789
query_parameters.instantiate();
790
query_parameters->set_map(map);
791
query_parameters->set_start_position(Vector3(10, 0, 10));
792
query_parameters->set_target_position(Vector3(0, 0, 0));
793
query_parameters->set_excluded_regions({ region });
794
query_parameters->set_included_regions({ region });
795
Ref<NavigationPathQueryResult3D> query_result;
796
query_result.instantiate();
797
navigation_server->query_path(query_parameters, query_result);
798
CHECK_EQ(query_result->get_path().size(), 0);
799
}
800
801
navigation_server->free(region);
802
navigation_server->free(map);
803
navigation_server->physics_process(0.0); // Give server some cycles to commit.
804
}
805
806
// FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613).
807
/*
808
TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") {
809
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
810
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
811
Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D);
812
813
Array arr;
814
arr.resize(RS::ARRAY_MAX);
815
BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0));
816
source_geometry->add_mesh_array(arr, Transform3D());
817
818
// Race condition is present below, but baking should take many orders of magnitude
819
// longer than basic checks on the main thread, so it's fine.
820
navigation_server->bake_from_source_geometry_data_async(navigation_mesh, source_geometry, Callable());
821
CHECK(navigation_server->is_baking_navigation_mesh(navigation_mesh));
822
CHECK_EQ(navigation_mesh->get_polygon_count(), 0);
823
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
824
}
825
*/
826
827
TEST_CASE("[NavigationServer3D] Server should simplify path properly") {
828
real_t simplify_epsilon = 0.2;
829
Vector<Vector3> source_path;
830
source_path.resize(7);
831
source_path.write[0] = Vector3(0.0, 0.0, 0.0);
832
source_path.write[1] = Vector3(0.0, 0.0, 1.0); // This point needs to go.
833
source_path.write[2] = Vector3(0.0, 0.0, 2.0); // This point needs to go.
834
source_path.write[3] = Vector3(0.0, 0.0, 2.0);
835
source_path.write[4] = Vector3(2.0, 1.0, 3.0);
836
source_path.write[5] = Vector3(2.0, 1.5, 4.0); // This point needs to go.
837
source_path.write[6] = Vector3(2.0, 2.0, 5.0);
838
Vector<Vector3> simplified_path = NavigationServer3D::get_singleton()->simplify_path(source_path, simplify_epsilon);
839
CHECK_EQ(simplified_path.size(), 4);
840
}
841
}
842
} //namespace TestNavigationServer3D
843
844