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