Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/servers/test_navigation_server_2d.cpp
23449 views
1
/**************************************************************************/
2
/* test_navigation_server_2d.cpp */
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
#include "tests/test_macros.h"
32
33
TEST_FORCE_LINK(test_navigation_server_2d)
34
35
#include "modules/modules_enabled.gen.h" // For navigation 2D.
36
37
#ifdef MODULE_NAVIGATION_2D_ENABLED
38
39
#include "scene/2d/polygon_2d.h"
40
#include "scene/main/window.h"
41
#include "servers/navigation_2d/navigation_server_2d.h"
42
#include "tests/signal_watcher.h"
43
44
namespace TestNavigationServer2D {
45
46
// TODO: Find a more generic way to create `Callable` mocks.
47
class CallableMock : public Object {
48
GDCLASS(CallableMock, Object);
49
50
public:
51
void function1(Variant arg0) {
52
function1_calls++;
53
function1_latest_arg0 = arg0;
54
}
55
56
unsigned function1_calls{ 0 };
57
Variant function1_latest_arg0;
58
};
59
60
struct GreaterThan {
61
bool operator()(int p_a, int p_b) const { return p_a > p_b; }
62
};
63
64
struct CompareArrayValues {
65
const int *array;
66
67
CompareArrayValues(const int *p_array) :
68
array(p_array) {}
69
70
bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {
71
return array[p_index_a] < array[p_index_b];
72
}
73
};
74
75
struct RegisterHeapIndexes {
76
uint32_t *indexes;
77
78
RegisterHeapIndexes(uint32_t *p_indexes) :
79
indexes(p_indexes) {}
80
81
void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {
82
indexes[p_vector_index] = p_heap_index;
83
}
84
};
85
86
TEST_SUITE("[Navigation2D]") {
87
TEST_CASE("[NavigationServer2D] Server should be empty when initialized") {
88
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
89
CHECK_EQ(navigation_server->get_maps().size(), 0);
90
91
SUBCASE("'ProcessInfo' should report all counters empty as well") {
92
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);
93
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);
94
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);
95
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);
96
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_POLYGON_COUNT), 0);
97
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_COUNT), 0);
98
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_MERGE_COUNT), 0);
99
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_CONNECTION_COUNT), 0);
100
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_EDGE_FREE_COUNT), 0);
101
}
102
}
103
104
TEST_CASE("[NavigationServer2D] Server should manage agent properly") {
105
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
106
107
RID agent = navigation_server->agent_create();
108
CHECK(agent.is_valid());
109
110
SUBCASE("'ProcessInfo' should not report dangling agent") {
111
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);
112
}
113
114
SUBCASE("Setters/getters should work") {
115
bool initial_avoidance_enabled = navigation_server->agent_get_avoidance_enabled(agent);
116
navigation_server->agent_set_avoidance_enabled(agent, !initial_avoidance_enabled);
117
navigation_server->physics_process(0.0); // Give server some cycles to commit.
118
119
CHECK_EQ(navigation_server->agent_get_avoidance_enabled(agent), !initial_avoidance_enabled);
120
// TODO: Add remaining setters/getters once the missing getters are added.
121
}
122
123
SUBCASE("'ProcessInfo' should report agent with active map") {
124
RID map = navigation_server->map_create();
125
CHECK(map.is_valid());
126
navigation_server->map_set_active(map, true);
127
navigation_server->agent_set_map(agent, map);
128
navigation_server->physics_process(0.0); // Give server some cycles to commit.
129
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 1);
130
navigation_server->agent_set_map(agent, RID());
131
navigation_server->free_rid(map);
132
navigation_server->physics_process(0.0); // Give server some cycles to commit.
133
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_AGENT_COUNT), 0);
134
}
135
136
navigation_server->free_rid(agent);
137
}
138
139
TEST_CASE("[NavigationServer2D] Server should manage map properly") {
140
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
141
142
RID map;
143
CHECK_FALSE(map.is_valid());
144
145
SUBCASE("Queries against invalid map should return empty or invalid values") {
146
ERR_PRINT_OFF;
147
CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());
148
CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());
149
CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);
150
CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);
151
152
Ref<NavigationPathQueryParameters2D> query_parameters;
153
query_parameters.instantiate();
154
query_parameters->set_map(map);
155
query_parameters->set_start_position(Vector2(7, 7));
156
query_parameters->set_target_position(Vector2(8, 8));
157
Ref<NavigationPathQueryResult2D> query_result;
158
query_result.instantiate();
159
navigation_server->query_path(query_parameters, query_result);
160
CHECK_EQ(query_result->get_path().size(), 0);
161
CHECK_EQ(query_result->get_path_types().size(), 0);
162
CHECK_EQ(query_result->get_path_rids().size(), 0);
163
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
164
ERR_PRINT_ON;
165
}
166
167
map = navigation_server->map_create();
168
CHECK(map.is_valid());
169
CHECK_EQ(navigation_server->get_maps().size(), 1);
170
171
SUBCASE("'ProcessInfo' should not report inactive map") {
172
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);
173
}
174
175
SUBCASE("Setters/getters should work") {
176
navigation_server->map_set_cell_size(map, 0.55);
177
navigation_server->map_set_edge_connection_margin(map, 0.66);
178
navigation_server->map_set_link_connection_radius(map, 0.77);
179
bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map);
180
navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections);
181
navigation_server->physics_process(0.0); // Give server some cycles to commit.
182
183
CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55));
184
CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66));
185
CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77));
186
CHECK_EQ(navigation_server->map_get_use_edge_connections(map), !initial_use_edge_connections);
187
}
188
189
SUBCASE("'ProcessInfo' should report map iff active") {
190
navigation_server->map_set_active(map, true);
191
navigation_server->physics_process(0.0); // Give server some cycles to commit.
192
CHECK(navigation_server->map_is_active(map));
193
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 1);
194
navigation_server->map_set_active(map, false);
195
navigation_server->physics_process(0.0); // Give server some cycles to commit.
196
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_ACTIVE_MAPS), 0);
197
}
198
199
SUBCASE("Number of agents should be reported properly") {
200
RID agent = navigation_server->agent_create();
201
CHECK(agent.is_valid());
202
navigation_server->agent_set_map(agent, map);
203
navigation_server->physics_process(0.0); // Give server some cycles to commit.
204
CHECK_EQ(navigation_server->map_get_agents(map).size(), 1);
205
navigation_server->free_rid(agent);
206
navigation_server->physics_process(0.0); // Give server some cycles to commit.
207
CHECK_EQ(navigation_server->map_get_agents(map).size(), 0);
208
}
209
210
SUBCASE("Number of links should be reported properly") {
211
RID link = navigation_server->link_create();
212
CHECK(link.is_valid());
213
navigation_server->link_set_map(link, map);
214
navigation_server->physics_process(0.0); // Give server some cycles to commit.
215
CHECK_EQ(navigation_server->map_get_links(map).size(), 1);
216
navigation_server->free_rid(link);
217
navigation_server->physics_process(0.0); // Give server some cycles to commit.
218
CHECK_EQ(navigation_server->map_get_links(map).size(), 0);
219
}
220
221
SUBCASE("Number of obstacles should be reported properly") {
222
RID obstacle = navigation_server->obstacle_create();
223
CHECK(obstacle.is_valid());
224
navigation_server->obstacle_set_map(obstacle, map);
225
navigation_server->physics_process(0.0); // Give server some cycles to commit.
226
CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1);
227
navigation_server->free_rid(obstacle);
228
navigation_server->physics_process(0.0); // Give server some cycles to commit.
229
CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0);
230
}
231
232
SUBCASE("Number of regions should be reported properly") {
233
RID region = navigation_server->region_create();
234
CHECK(region.is_valid());
235
navigation_server->region_set_map(region, map);
236
navigation_server->physics_process(0.0); // Give server some cycles to commit.
237
CHECK_EQ(navigation_server->map_get_regions(map).size(), 1);
238
navigation_server->free_rid(region);
239
navigation_server->physics_process(0.0); // Give server some cycles to commit.
240
CHECK_EQ(navigation_server->map_get_regions(map).size(), 0);
241
}
242
243
SUBCASE("Queries against empty map should return empty or invalid values") {
244
navigation_server->map_set_active(map, true);
245
navigation_server->physics_process(0.0); // Give server some cycles to commit.
246
247
ERR_PRINT_OFF;
248
CHECK_EQ(navigation_server->map_get_closest_point(map, Vector2(7, 7)), Vector2());
249
CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector2(7, 7)).is_valid());
250
CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), true).size(), 0);
251
CHECK_EQ(navigation_server->map_get_path(map, Vector2(7, 7), Vector2(8, 8), false).size(), 0);
252
253
Ref<NavigationPathQueryParameters2D> query_parameters;
254
query_parameters.instantiate();
255
query_parameters->set_map(map);
256
query_parameters->set_start_position(Vector2(7, 7));
257
query_parameters->set_target_position(Vector2(8, 8));
258
Ref<NavigationPathQueryResult2D> query_result;
259
query_result.instantiate();
260
navigation_server->query_path(query_parameters, query_result);
261
CHECK_EQ(query_result->get_path().size(), 0);
262
CHECK_EQ(query_result->get_path_types().size(), 0);
263
CHECK_EQ(query_result->get_path_rids().size(), 0);
264
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
265
ERR_PRINT_ON;
266
267
navigation_server->map_set_active(map, false);
268
navigation_server->physics_process(0.0); // Give server some cycles to commit.
269
}
270
271
navigation_server->free_rid(map);
272
navigation_server->physics_process(0.0); // Give server some cycles to actually remove map.
273
CHECK_EQ(navigation_server->get_maps().size(), 0);
274
}
275
276
TEST_CASE("[NavigationServer2D] Server should manage link properly") {
277
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
278
279
RID link = navigation_server->link_create();
280
CHECK(link.is_valid());
281
282
SUBCASE("'ProcessInfo' should not report dangling link") {
283
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);
284
}
285
286
SUBCASE("Setters/getters should work") {
287
bool initial_bidirectional = navigation_server->link_is_bidirectional(link);
288
navigation_server->link_set_bidirectional(link, !initial_bidirectional);
289
navigation_server->link_set_end_position(link, Vector2(7, 7));
290
navigation_server->link_set_enter_cost(link, 0.55);
291
navigation_server->link_set_navigation_layers(link, 6);
292
navigation_server->link_set_owner_id(link, ObjectID((int64_t)7));
293
navigation_server->link_set_start_position(link, Vector2(8, 8));
294
navigation_server->link_set_travel_cost(link, 0.66);
295
navigation_server->physics_process(0.0); // Give server some cycles to commit.
296
297
CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional);
298
CHECK_EQ(navigation_server->link_get_end_position(link), Vector2(7, 7));
299
CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55));
300
CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6);
301
CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7));
302
CHECK_EQ(navigation_server->link_get_start_position(link), Vector2(8, 8));
303
CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66));
304
}
305
306
SUBCASE("'ProcessInfo' should report link with active map") {
307
RID map = navigation_server->map_create();
308
CHECK(map.is_valid());
309
navigation_server->map_set_active(map, true);
310
navigation_server->link_set_map(link, map);
311
navigation_server->physics_process(0.0); // Give server some cycles to commit.
312
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 1);
313
navigation_server->link_set_map(link, RID());
314
navigation_server->free_rid(map);
315
navigation_server->physics_process(0.0); // Give server some cycles to commit.
316
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_LINK_COUNT), 0);
317
}
318
319
navigation_server->free_rid(link);
320
}
321
322
TEST_CASE("[NavigationServer2D] Server should manage obstacles properly") {
323
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
324
325
RID obstacle = navigation_server->obstacle_create();
326
CHECK(obstacle.is_valid());
327
328
// TODO: Add tests for setters/getters once getters are added.
329
330
navigation_server->free_rid(obstacle);
331
}
332
333
TEST_CASE("[NavigationServer2D] Server should manage regions properly") {
334
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
335
336
RID region = navigation_server->region_create();
337
CHECK(region.is_valid());
338
339
SUBCASE("'ProcessInfo' should not report dangling region") {
340
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);
341
}
342
343
SUBCASE("Setters/getters should work") {
344
bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region);
345
navigation_server->region_set_enter_cost(region, 0.55);
346
navigation_server->region_set_navigation_layers(region, 5);
347
navigation_server->region_set_owner_id(region, ObjectID((int64_t)7));
348
navigation_server->region_set_travel_cost(region, 0.66);
349
navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections);
350
navigation_server->physics_process(0.0); // Give server some cycles to commit.
351
352
CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55));
353
CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5);
354
CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7));
355
CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66));
356
CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections);
357
}
358
359
SUBCASE("'ProcessInfo' should report region with active map") {
360
RID map = navigation_server->map_create();
361
CHECK(map.is_valid());
362
navigation_server->map_set_active(map, true);
363
navigation_server->region_set_map(region, map);
364
navigation_server->physics_process(0.0); // Give server some cycles to commit.
365
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 1);
366
navigation_server->region_set_map(region, RID());
367
navigation_server->free_rid(map);
368
navigation_server->physics_process(0.0); // Give server some cycles to commit.
369
CHECK_EQ(navigation_server->get_process_info(NavigationServer2D::INFO_REGION_COUNT), 0);
370
}
371
372
SUBCASE("Queries against empty region should return empty or invalid values") {
373
ERR_PRINT_OFF;
374
CHECK_EQ(navigation_server->region_get_connections_count(region), 0);
375
CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector2());
376
CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector2());
377
ERR_PRINT_ON;
378
}
379
380
navigation_server->free_rid(region);
381
}
382
383
// This test case does not check precise values on purpose - to not be too sensitivte.
384
TEST_CASE("[NavigationServer2D] Server should move agent properly") {
385
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
386
387
RID map = navigation_server->map_create();
388
RID agent = navigation_server->agent_create();
389
390
navigation_server->map_set_active(map, true);
391
navigation_server->agent_set_map(agent, map);
392
navigation_server->agent_set_avoidance_enabled(agent, true);
393
navigation_server->agent_set_velocity(agent, Vector2(1, 1));
394
CallableMock agent_avoidance_callback_mock;
395
navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1));
396
CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0);
397
navigation_server->physics_process(0.0); // Give server some cycles to commit.
398
CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1);
399
CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector2(0, 0));
400
401
navigation_server->free_rid(agent);
402
navigation_server->free_rid(map);
403
}
404
405
// This test case does not check precise values on purpose - to not be too sensitivte.
406
TEST_CASE("[NavigationServer2D] Server should make agents avoid each other when avoidance enabled") {
407
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
408
409
RID map = navigation_server->map_create();
410
RID agent_1 = navigation_server->agent_create();
411
RID agent_2 = navigation_server->agent_create();
412
413
navigation_server->map_set_active(map, true);
414
415
navigation_server->agent_set_map(agent_1, map);
416
navigation_server->agent_set_avoidance_enabled(agent_1, true);
417
navigation_server->agent_set_position(agent_1, Vector2(0, 0));
418
navigation_server->agent_set_radius(agent_1, 1);
419
navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));
420
CallableMock agent_1_avoidance_callback_mock;
421
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
422
423
navigation_server->agent_set_map(agent_2, map);
424
navigation_server->agent_set_avoidance_enabled(agent_2, true);
425
navigation_server->agent_set_position(agent_2, Vector2(2.5, 0.5));
426
navigation_server->agent_set_radius(agent_2, 1);
427
navigation_server->agent_set_velocity(agent_2, Vector2(-1, 0));
428
CallableMock agent_2_avoidance_callback_mock;
429
navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));
430
431
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
432
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);
433
navigation_server->physics_process(0.0); // Give server some cycles to commit.
434
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
435
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);
436
Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
437
Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;
438
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)");
439
CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)");
440
CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "agent 1 should move a bit to the side so that it avoids agent 2");
441
CHECK_MESSAGE(agent_2_safe_velocity.y > 0, "agent 2 should move a bit to the side so that it avoids agent 1");
442
443
navigation_server->free_rid(agent_2);
444
navigation_server->free_rid(agent_1);
445
navigation_server->free_rid(map);
446
}
447
448
TEST_CASE("[NavigationServer2D] Server should make agents avoid dynamic obstacles when avoidance enabled") {
449
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
450
451
RID map = navigation_server->map_create();
452
RID agent_1 = navigation_server->agent_create();
453
RID obstacle_1 = navigation_server->obstacle_create();
454
455
navigation_server->map_set_active(map, true);
456
457
navigation_server->agent_set_map(agent_1, map);
458
navigation_server->agent_set_avoidance_enabled(agent_1, true);
459
navigation_server->agent_set_position(agent_1, Vector2(0, 0));
460
navigation_server->agent_set_radius(agent_1, 1);
461
navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));
462
CallableMock agent_1_avoidance_callback_mock;
463
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
464
465
navigation_server->obstacle_set_map(obstacle_1, map);
466
navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
467
navigation_server->obstacle_set_position(obstacle_1, Vector2(2.5, 0.5));
468
navigation_server->obstacle_set_radius(obstacle_1, 1);
469
470
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
471
navigation_server->physics_process(0.0); // Give server some cycles to commit.
472
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
473
Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
474
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
475
CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
476
477
navigation_server->free_rid(obstacle_1);
478
navigation_server->free_rid(agent_1);
479
navigation_server->free_rid(map);
480
navigation_server->physics_process(0.0); // Give server some cycles to commit.
481
}
482
483
TEST_CASE("[NavigationServer2D] Server should make agents avoid static obstacles when avoidance enabled") {
484
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
485
486
RID map = navigation_server->map_create();
487
RID agent_1 = navigation_server->agent_create();
488
RID agent_2 = navigation_server->agent_create();
489
RID obstacle_1 = navigation_server->obstacle_create();
490
491
navigation_server->map_set_active(map, true);
492
493
navigation_server->agent_set_map(agent_1, map);
494
navigation_server->agent_set_avoidance_enabled(agent_1, true);
495
navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.
496
navigation_server->agent_set_velocity(agent_1, Vector2(1, 0));
497
CallableMock agent_1_avoidance_callback_mock;
498
navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
499
500
navigation_server->agent_set_map(agent_2, map);
501
navigation_server->agent_set_avoidance_enabled(agent_2, true);
502
navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.
503
navigation_server->agent_set_velocity(agent_2, Vector2(1, 0));
504
CallableMock agent_2_avoidance_callback_mock;
505
navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));
506
507
navigation_server->obstacle_set_map(obstacle_1, map);
508
navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
509
PackedVector2Array obstacle_1_vertices;
510
511
SUBCASE("Static obstacles should work on ground level") {
512
navigation_server->agent_set_position(agent_1, Vector2(0, 0));
513
navigation_server->agent_set_position(agent_2, Vector2(0, 5));
514
obstacle_1_vertices.push_back(Vector2(1.5, 0.5));
515
obstacle_1_vertices.push_back(Vector2(1.5, 4.5));
516
}
517
518
navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);
519
520
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
521
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);
522
navigation_server->physics_process(0.0); // Give server some cycles to commit.
523
CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
524
CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);
525
Vector2 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
526
Vector2 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;
527
CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
528
CHECK_MESSAGE(agent_1_safe_velocity.y < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
529
CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");
530
CHECK_MESSAGE(agent_2_safe_velocity.y == 0, "Agent 2 should not move to the side.");
531
532
navigation_server->free_rid(obstacle_1);
533
navigation_server->free_rid(agent_2);
534
navigation_server->free_rid(agent_1);
535
navigation_server->free_rid(map);
536
navigation_server->physics_process(0.0); // Give server some cycles to commit.
537
}
538
539
TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to parse geometry") {
540
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
541
542
// Prepare scene tree with simple mesh to serve as an input geometry.
543
Node2D *node_2d = memnew(Node2D);
544
SceneTree::get_singleton()->get_root()->add_child(node_2d);
545
Polygon2D *polygon = memnew(Polygon2D);
546
polygon->set_polygon(PackedVector2Array({ Vector2(200.0, 200.0), Vector2(400.0, 200.0), Vector2(400.0, 400.0), Vector2(200.0, 400.0) }));
547
node_2d->add_child(polygon);
548
549
// TODO: Use MeshInstance2D as well?
550
551
Ref<NavigationPolygon> navigation_polygon;
552
navigation_polygon.instantiate();
553
Ref<NavigationMeshSourceGeometryData2D> source_geometry;
554
source_geometry.instantiate();
555
CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);
556
CHECK_EQ(source_geometry->get_obstruction_outlines().size(), 0);
557
558
navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, polygon);
559
CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);
560
REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 1);
561
CHECK_EQ(((PackedVector2Array)source_geometry->get_obstruction_outlines()[0]).size(), 4);
562
563
SUBCASE("By default, parsing should remove any data that was parsed before") {
564
navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, polygon);
565
CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);
566
REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 1);
567
CHECK_EQ(((PackedVector2Array)source_geometry->get_obstruction_outlines()[0]).size(), 4);
568
}
569
570
SUBCASE("Parsed geometry should be extendible with other geometry") {
571
source_geometry->merge(source_geometry); // Merging with itself.
572
CHECK_EQ(source_geometry->get_traversable_outlines().size(), 0);
573
REQUIRE_EQ(source_geometry->get_obstruction_outlines().size(), 2);
574
const PackedVector2Array obstruction_outline_1 = source_geometry->get_obstruction_outlines()[0];
575
const PackedVector2Array obstruction_outline_2 = source_geometry->get_obstruction_outlines()[1];
576
REQUIRE_EQ(obstruction_outline_1.size(), 4);
577
REQUIRE_EQ(obstruction_outline_2.size(), 4);
578
CHECK_EQ(obstruction_outline_1[0], obstruction_outline_2[0]);
579
CHECK_EQ(obstruction_outline_1[1], obstruction_outline_2[1]);
580
CHECK_EQ(obstruction_outline_1[2], obstruction_outline_2[2]);
581
CHECK_EQ(obstruction_outline_1[3], obstruction_outline_2[3]);
582
}
583
584
memdelete(polygon);
585
memdelete(node_2d);
586
}
587
588
// This test case uses only public APIs on purpose - other test cases use simplified baking.
589
TEST_CASE("[NavigationServer2D][SceneTree] Server should be able to bake map correctly") {
590
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
591
592
// Prepare scene tree with simple mesh to serve as an input geometry.
593
Node2D *node_2d = memnew(Node2D);
594
SceneTree::get_singleton()->get_root()->add_child(node_2d);
595
Polygon2D *polygon = memnew(Polygon2D);
596
polygon->set_polygon(PackedVector2Array({ Vector2(-200.0, -200.0), Vector2(200.0, -200.0), Vector2(200.0, 200.0), Vector2(-200.0, 200.0) }));
597
node_2d->add_child(polygon);
598
599
// TODO: Use MeshInstance2D as well?
600
601
// Prepare anything necessary to bake navigation polygon.
602
RID map = navigation_server->map_create();
603
RID region = navigation_server->region_create();
604
Ref<NavigationPolygon> navigation_polygon;
605
navigation_polygon.instantiate();
606
navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));
607
navigation_server->map_set_active(map, true);
608
navigation_server->map_set_use_async_iterations(map, false);
609
navigation_server->region_set_use_async_iterations(region, false);
610
navigation_server->region_set_map(region, map);
611
navigation_server->region_set_navigation_polygon(region, navigation_polygon);
612
navigation_server->physics_process(0.0); // Give server some cycles to commit.
613
614
CHECK_EQ(navigation_polygon->get_polygon_count(), 0);
615
CHECK_EQ(navigation_polygon->get_vertices().size(), 0);
616
CHECK_EQ(navigation_polygon->get_outline_count(), 1);
617
618
Ref<NavigationMeshSourceGeometryData2D> source_geometry;
619
source_geometry.instantiate();
620
navigation_server->parse_source_geometry_data(navigation_polygon, source_geometry, node_2d);
621
navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());
622
// FIXME: The above line should trigger the update (line below) under the hood.
623
navigation_server->region_set_navigation_polygon(region, navigation_polygon); // Force update.
624
CHECK_EQ(navigation_polygon->get_polygon_count(), 4);
625
CHECK_EQ(navigation_polygon->get_vertices().size(), 8);
626
CHECK_EQ(navigation_polygon->get_outline_count(), 1);
627
628
SUBCASE("Map should emit signal and take newly baked navigation mesh into account") {
629
SIGNAL_WATCH(navigation_server, "map_changed");
630
SIGNAL_CHECK_FALSE("map_changed");
631
navigation_server->physics_process(0.0); // Give server some cycles to commit.
632
SIGNAL_CHECK("map_changed", { { map } });
633
SIGNAL_UNWATCH(navigation_server, "map_changed");
634
CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0, 0)), Vector2(0, 0));
635
}
636
637
navigation_server->free_rid(region);
638
navigation_server->free_rid(map);
639
navigation_server->physics_process(0.0); // Give server some cycles to commit.
640
641
memdelete(polygon);
642
memdelete(node_2d);
643
}
644
645
// This test case does not check precise values on purpose - to not be too sensitivte.
646
TEST_CASE("[NavigationServer2D] Server should respond to queries against valid map properly") {
647
NavigationServer2D *navigation_server = NavigationServer2D::get_singleton();
648
Ref<NavigationPolygon> navigation_polygon;
649
navigation_polygon.instantiate();
650
Ref<NavigationMeshSourceGeometryData2D> source_geometry;
651
source_geometry.instantiate();
652
653
navigation_polygon->add_outline(PackedVector2Array({ Vector2(-1000.0, -1000.0), Vector2(1000.0, -1000.0), Vector2(1000.0, 1000.0), Vector2(-1000.0, 1000.0) }));
654
655
// TODO: Other input?
656
source_geometry->add_obstruction_outline(PackedVector2Array({ Vector2(-200.0, -200.0), Vector2(200.0, -200.0), Vector2(200.0, 200.0), Vector2(-200.0, 200.0) }));
657
658
navigation_server->bake_from_source_geometry_data(navigation_polygon, source_geometry, Callable());
659
CHECK_NE(navigation_polygon->get_polygon_count(), 0);
660
CHECK_NE(navigation_polygon->get_vertices().size(), 0);
661
CHECK_NE(navigation_polygon->get_outline_count(), 0);
662
663
RID map = navigation_server->map_create();
664
RID region = navigation_server->region_create();
665
navigation_server->map_set_active(map, true);
666
navigation_server->map_set_use_async_iterations(map, false);
667
navigation_server->region_set_use_async_iterations(region, false);
668
navigation_server->region_set_map(region, map);
669
navigation_server->region_set_navigation_polygon(region, navigation_polygon);
670
navigation_server->physics_process(0.0); // Give server some cycles to commit.
671
672
SUBCASE("Simple queries should return non-default values") {
673
CHECK_NE(navigation_server->map_get_closest_point(map, Vector2(0.0, 0.0)), Vector2(0, 0));
674
CHECK(navigation_server->map_get_closest_point_owner(map, Vector2(0.0, 0.0)).is_valid());
675
CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), true).size(), 0);
676
CHECK_NE(navigation_server->map_get_path(map, Vector2(0, 0), Vector2(10, 10), false).size(), 0);
677
}
678
679
SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") {
680
Ref<NavigationPathQueryParameters2D> query_parameters;
681
query_parameters.instantiate();
682
query_parameters->set_map(map);
683
query_parameters->set_start_position(Vector2(0, 0));
684
query_parameters->set_target_position(Vector2(10, 10));
685
query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_CORRIDORFUNNEL);
686
Ref<NavigationPathQueryResult2D> query_result;
687
query_result.instantiate();
688
navigation_server->query_path(query_parameters, query_result);
689
CHECK_NE(query_result->get_path().size(), 0);
690
CHECK_NE(query_result->get_path_types().size(), 0);
691
CHECK_NE(query_result->get_path_rids().size(), 0);
692
CHECK_NE(query_result->get_path_owner_ids().size(), 0);
693
}
694
695
SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") {
696
Ref<NavigationPathQueryParameters2D> query_parameters;
697
query_parameters.instantiate();
698
query_parameters->set_map(map);
699
query_parameters->set_start_position(Vector2(10, 10));
700
query_parameters->set_target_position(Vector2(0, 0));
701
query_parameters->set_path_postprocessing(NavigationPathQueryParameters2D::PATH_POSTPROCESSING_EDGECENTERED);
702
Ref<NavigationPathQueryResult2D> query_result;
703
query_result.instantiate();
704
navigation_server->query_path(query_parameters, query_result);
705
CHECK_NE(query_result->get_path().size(), 0);
706
CHECK_NE(query_result->get_path_types().size(), 0);
707
CHECK_NE(query_result->get_path_rids().size(), 0);
708
CHECK_NE(query_result->get_path_owner_ids().size(), 0);
709
}
710
711
SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") {
712
Ref<NavigationPathQueryParameters2D> query_parameters;
713
query_parameters.instantiate();
714
query_parameters->set_map(map);
715
query_parameters->set_start_position(Vector2(10, 10));
716
query_parameters->set_target_position(Vector2(0, 0));
717
query_parameters->set_navigation_layers(2);
718
Ref<NavigationPathQueryResult2D> query_result;
719
query_result.instantiate();
720
navigation_server->query_path(query_parameters, query_result);
721
CHECK_EQ(query_result->get_path().size(), 0);
722
CHECK_EQ(query_result->get_path_types().size(), 0);
723
CHECK_EQ(query_result->get_path_rids().size(), 0);
724
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
725
}
726
727
SUBCASE("Elaborate query without metadata flags should yield path only") {
728
Ref<NavigationPathQueryParameters2D> query_parameters;
729
query_parameters.instantiate();
730
query_parameters->set_map(map);
731
query_parameters->set_start_position(Vector2(10, 10));
732
query_parameters->set_target_position(Vector2(0, 0));
733
query_parameters->set_metadata_flags(0);
734
Ref<NavigationPathQueryResult2D> query_result;
735
query_result.instantiate();
736
navigation_server->query_path(query_parameters, query_result);
737
CHECK_NE(query_result->get_path().size(), 0);
738
CHECK_EQ(query_result->get_path_types().size(), 0);
739
CHECK_EQ(query_result->get_path_rids().size(), 0);
740
CHECK_EQ(query_result->get_path_owner_ids().size(), 0);
741
}
742
743
navigation_server->free_rid(region);
744
navigation_server->free_rid(map);
745
navigation_server->physics_process(0.0); // Give server some cycles to commit.
746
}
747
748
TEST_CASE("[NavigationServer2D] Server should simplify path properly") {
749
real_t simplify_epsilon = 0.2;
750
Vector<Vector2> source_path;
751
source_path.resize(7);
752
source_path.write[0] = Vector2(0.0, 0.0);
753
source_path.write[1] = Vector2(0.0, 1.0); // This point needs to go.
754
source_path.write[2] = Vector2(0.0, 2.0); // This point needs to go.
755
source_path.write[3] = Vector2(0.0, 2.0);
756
source_path.write[4] = Vector2(2.0, 3.0);
757
source_path.write[5] = Vector2(2.5, 4.0); // This point needs to go.
758
source_path.write[6] = Vector2(3.0, 5.0);
759
Vector<Vector2> simplified_path = NavigationServer2D::get_singleton()->simplify_path(source_path, simplify_epsilon);
760
CHECK_EQ(simplified_path.size(), 4);
761
}
762
}
763
764
} // namespace TestNavigationServer2D
765
766
#endif // MODULE_NAVIGATION_2D_ENABLED
767
768