Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/core/math/test_astar.h
10278 views
1
/**************************************************************************/
2
/* test_astar.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 "core/math/a_star.h"
34
35
#include "tests/test_macros.h"
36
37
namespace TestAStar {
38
39
class ABCX : public AStar3D {
40
public:
41
enum {
42
A,
43
B,
44
C,
45
X,
46
};
47
48
ABCX() {
49
add_point(A, Vector3(0, 0, 0));
50
add_point(B, Vector3(1, 0, 0));
51
add_point(C, Vector3(0, 1, 0));
52
add_point(X, Vector3(0, 0, 1));
53
connect_points(A, B);
54
connect_points(A, C);
55
connect_points(B, C);
56
connect_points(X, A);
57
}
58
59
// Disable heuristic completely.
60
real_t _compute_cost(int64_t p_from, int64_t p_to) {
61
if (p_from == A && p_to == C) {
62
return 1000;
63
}
64
return 100;
65
}
66
};
67
68
TEST_CASE("[AStar3D] ABC path") {
69
ABCX abcx;
70
Vector<int64_t> path = abcx.get_id_path(ABCX::A, ABCX::C);
71
REQUIRE(path.size() == 3);
72
CHECK(path[0] == ABCX::A);
73
CHECK(path[1] == ABCX::B);
74
CHECK(path[2] == ABCX::C);
75
}
76
77
TEST_CASE("[AStar3D] ABCX path") {
78
ABCX abcx;
79
Vector<int64_t> path = abcx.get_id_path(ABCX::X, ABCX::C);
80
REQUIRE(path.size() == 4);
81
CHECK(path[0] == ABCX::X);
82
CHECK(path[1] == ABCX::A);
83
CHECK(path[2] == ABCX::B);
84
CHECK(path[3] == ABCX::C);
85
}
86
87
TEST_CASE("[AStar3D] Add/Remove") {
88
AStar3D a;
89
90
// Manual tests.
91
a.add_point(1, Vector3(0, 0, 0));
92
a.add_point(2, Vector3(0, 1, 0));
93
a.add_point(3, Vector3(1, 1, 0));
94
a.add_point(4, Vector3(2, 0, 0));
95
a.connect_points(1, 2, true);
96
a.connect_points(1, 3, true);
97
a.connect_points(1, 4, false);
98
99
CHECK(a.are_points_connected(2, 1));
100
CHECK(a.are_points_connected(4, 1));
101
CHECK(a.are_points_connected(2, 1, false));
102
CHECK_FALSE(a.are_points_connected(4, 1, false));
103
104
a.disconnect_points(1, 2, true);
105
CHECK(a.get_point_connections(1).size() == 2); // 3, 4
106
CHECK(a.get_point_connections(2).size() == 0);
107
108
a.disconnect_points(4, 1, false);
109
CHECK(a.get_point_connections(1).size() == 2); // 3, 4
110
CHECK(a.get_point_connections(4).size() == 0);
111
112
a.disconnect_points(4, 1, true);
113
CHECK(a.get_point_connections(1).size() == 1); // 3
114
CHECK(a.get_point_connections(4).size() == 0);
115
116
a.connect_points(2, 3, false);
117
CHECK(a.get_point_connections(2).size() == 1); // 3
118
CHECK(a.get_point_connections(3).size() == 1); // 1
119
120
a.connect_points(2, 3, true);
121
CHECK(a.get_point_connections(2).size() == 1); // 3
122
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
123
124
a.disconnect_points(2, 3, false);
125
CHECK(a.get_point_connections(2).size() == 0);
126
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
127
128
a.connect_points(4, 3, true);
129
CHECK(a.get_point_connections(3).size() == 3); // 1, 2, 4
130
CHECK(a.get_point_connections(4).size() == 1); // 3
131
132
a.disconnect_points(3, 4, false);
133
CHECK(a.get_point_connections(3).size() == 2); // 1, 2
134
CHECK(a.get_point_connections(4).size() == 1); // 3
135
136
a.remove_point(3);
137
CHECK(a.get_point_connections(1).size() == 0);
138
CHECK(a.get_point_connections(2).size() == 0);
139
CHECK(a.get_point_connections(4).size() == 0);
140
141
a.add_point(0, Vector3(0, -1, 0));
142
a.add_point(3, Vector3(2, 1, 0));
143
// 0: (0, -1)
144
// 1: (0, 0)
145
// 2: (0, 1)
146
// 3: (2, 1)
147
// 4: (2, 0)
148
149
// Tests for get_closest_position_in_segment.
150
a.connect_points(2, 3);
151
CHECK(a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0));
152
153
a.connect_points(3, 4);
154
a.connect_points(0, 3);
155
a.connect_points(1, 4);
156
a.disconnect_points(1, 4, false);
157
a.disconnect_points(4, 3, false);
158
a.disconnect_points(3, 4, false);
159
// Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed).
160
CHECK(a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0));
161
CHECK(a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0));
162
CHECK(a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0));
163
164
Math::seed(0);
165
166
// Random tests for connectivity checks
167
for (int i = 0; i < 20000; i++) {
168
int u = Math::rand() % 5;
169
int v = Math::rand() % 4;
170
if (u == v) {
171
v = 4;
172
}
173
if (Math::rand() % 2 == 1) {
174
// Add a (possibly existing) directed edge and confirm connectivity.
175
a.connect_points(u, v, false);
176
CHECK(a.are_points_connected(u, v, false));
177
} else {
178
// Remove a (possibly nonexistent) directed edge and confirm disconnectivity.
179
a.disconnect_points(u, v, false);
180
CHECK_FALSE(a.are_points_connected(u, v, false));
181
}
182
}
183
184
// Random tests for point removal.
185
for (int i = 0; i < 20000; i++) {
186
a.clear();
187
for (int j = 0; j < 5; j++) {
188
a.add_point(j, Vector3(0, 0, 0));
189
}
190
191
// Add or remove random edges.
192
for (int j = 0; j < 10; j++) {
193
int u = Math::rand() % 5;
194
int v = Math::rand() % 4;
195
if (u == v) {
196
v = 4;
197
}
198
if (Math::rand() % 2 == 1) {
199
a.connect_points(u, v, false);
200
} else {
201
a.disconnect_points(u, v, false);
202
}
203
}
204
205
// Remove point 0.
206
a.remove_point(0);
207
// White box: this will check all edges remaining in the segments set.
208
for (int j = 1; j < 5; j++) {
209
CHECK_FALSE(a.are_points_connected(0, j, true));
210
}
211
}
212
// It's been great work, cheers. \(^ ^)/
213
}
214
215
TEST_CASE("[Stress][AStar3D] Find paths") {
216
// Random stress tests with Floyd-Warshall.
217
constexpr int N = 30;
218
Math::seed(0);
219
220
for (int test = 0; test < 1000; test++) {
221
AStar3D a;
222
Vector3 p[N];
223
bool adj[N][N] = { { false } };
224
225
// Assign initial coordinates.
226
for (int u = 0; u < N; u++) {
227
p[u].x = Math::rand() % 100;
228
p[u].y = Math::rand() % 100;
229
p[u].z = Math::rand() % 100;
230
a.add_point(u, p[u]);
231
}
232
// Generate a random sequence of operations.
233
for (int i = 0; i < 1000; i++) {
234
// Pick two different vertices.
235
int u, v;
236
u = Math::rand() % N;
237
v = Math::rand() % (N - 1);
238
if (u == v) {
239
v = N - 1;
240
}
241
// Pick a random operation.
242
int op = Math::rand();
243
switch (op % 9) {
244
case 0:
245
case 1:
246
case 2:
247
case 3:
248
case 4:
249
case 5:
250
// Add edge (u, v); possibly bidirectional.
251
a.connect_points(u, v, op % 2);
252
adj[u][v] = true;
253
if (op % 2) {
254
adj[v][u] = true;
255
}
256
break;
257
case 6:
258
case 7:
259
// Remove edge (u, v); possibly bidirectional.
260
a.disconnect_points(u, v, op % 2);
261
adj[u][v] = false;
262
if (op % 2) {
263
adj[v][u] = false;
264
}
265
break;
266
case 8:
267
// Remove point u and add it back; clears adjacent edges and changes coordinates.
268
a.remove_point(u);
269
p[u].x = Math::rand() % 100;
270
p[u].y = Math::rand() % 100;
271
p[u].z = Math::rand() % 100;
272
a.add_point(u, p[u]);
273
for (v = 0; v < N; v++) {
274
adj[u][v] = adj[v][u] = false;
275
}
276
break;
277
}
278
}
279
// Floyd-Warshall.
280
float d[N][N];
281
for (int u = 0; u < N; u++) {
282
for (int v = 0; v < N; v++) {
283
d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : Math::INF;
284
}
285
}
286
for (int w = 0; w < N; w++) {
287
for (int u = 0; u < N; u++) {
288
for (int v = 0; v < N; v++) {
289
if (d[u][v] > d[u][w] + d[w][v]) {
290
d[u][v] = d[u][w] + d[w][v];
291
}
292
}
293
}
294
}
295
// Display statistics.
296
int count = 0;
297
for (int u = 0; u < N; u++) {
298
for (int v = 0; v < N; v++) {
299
if (adj[u][v]) {
300
count++;
301
}
302
}
303
}
304
print_verbose(vformat("Test #%4d: %3d edges, ", test + 1, count));
305
count = 0;
306
for (int u = 0; u < N; u++) {
307
for (int v = 0; v < N; v++) {
308
if (!Math::is_inf(d[u][v])) {
309
count++;
310
}
311
}
312
}
313
print_verbose(vformat("%3d/%d pairs of reachable points\n", count - N, N * (N - 1)));
314
315
// Check A*'s output.
316
bool match = true;
317
for (int u = 0; u < N; u++) {
318
for (int v = 0; v < N; v++) {
319
if (u != v) {
320
Vector<int64_t> route = a.get_id_path(u, v);
321
if (!Math::is_inf(d[u][v])) {
322
// Reachable.
323
if (route.size() == 0) {
324
print_verbose(vformat("From %d to %d: A* did not find a path\n", u, v));
325
match = false;
326
goto exit;
327
}
328
float astar_dist = 0;
329
for (int i = 1; i < route.size(); i++) {
330
if (!adj[route[i - 1]][route[i]]) {
331
print_verbose(vformat("From %d to %d: edge (%d, %d) does not exist\n",
332
u, v, route[i - 1], route[i]));
333
match = false;
334
goto exit;
335
}
336
astar_dist += p[route[i - 1]].distance_to(p[route[i]]);
337
}
338
if (!Math::is_equal_approx(astar_dist, d[u][v])) {
339
print_verbose(vformat("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n",
340
u, v, d[u][v], astar_dist));
341
match = false;
342
goto exit;
343
}
344
} else {
345
// Unreachable.
346
if (route.size() > 0) {
347
print_verbose(vformat("From %d to %d: A* somehow found a nonexistent path\n", u, v));
348
match = false;
349
goto exit;
350
}
351
}
352
}
353
}
354
}
355
exit:
356
CHECK_MESSAGE(match, "Found all paths.");
357
}
358
}
359
} // namespace TestAStar
360
361