Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/math/a_star.cpp
20791 views
1
/**************************************************************************/
2
/* a_star.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 "a_star.h"
32
#include "a_star.compat.inc"
33
34
#include "core/math/geometry_3d.h"
35
36
int64_t AStar3D::get_available_point_id() const {
37
if (points.has(last_free_id)) {
38
int64_t cur_new_id = last_free_id + 1;
39
while (points.has(cur_new_id)) {
40
cur_new_id++;
41
}
42
last_free_id = cur_new_id;
43
}
44
45
return last_free_id;
46
}
47
48
void AStar3D::add_point(int64_t p_id, const Vector3 &p_pos, real_t p_weight_scale) {
49
ERR_FAIL_COND_MSG(p_id < 0, vformat("Can't add a point with negative id: %d.", p_id));
50
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't add a point with weight scale less than 0.0: %f.", p_weight_scale));
51
52
Point **point_entry = points.getptr(p_id);
53
54
if (!point_entry) {
55
Point *pt = memnew(Point);
56
pt->id = p_id;
57
pt->pos = p_pos;
58
pt->weight_scale = p_weight_scale;
59
pt->prev_point = nullptr;
60
pt->open_pass = 0;
61
pt->closed_pass = 0;
62
pt->enabled = true;
63
points.insert_new(p_id, pt);
64
} else {
65
Point *found_pt = *point_entry;
66
found_pt->pos = p_pos;
67
found_pt->weight_scale = p_weight_scale;
68
}
69
}
70
71
Vector3 AStar3D::get_point_position(int64_t p_id) const {
72
Point *const *point_entry = points.getptr(p_id);
73
ERR_FAIL_COND_V_MSG(!point_entry, Vector3(), vformat("Can't get point's position. Point with id: %d doesn't exist.", p_id));
74
75
return (*point_entry)->pos;
76
}
77
78
void AStar3D::set_point_position(int64_t p_id, const Vector3 &p_pos) {
79
Point **point_entry = points.getptr(p_id);
80
ERR_FAIL_COND_MSG(!point_entry, vformat("Can't set point's position. Point with id: %d doesn't exist.", p_id));
81
82
(*point_entry)->pos = p_pos;
83
}
84
85
real_t AStar3D::get_point_weight_scale(int64_t p_id) const {
86
Point *const *point_entry = points.getptr(p_id);
87
ERR_FAIL_COND_V_MSG(!point_entry, 0, vformat("Can't get point's weight scale. Point with id: %d doesn't exist.", p_id));
88
89
return (*point_entry)->weight_scale;
90
}
91
92
void AStar3D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) {
93
Point **point_entry = points.getptr(p_id);
94
ERR_FAIL_COND_MSG(!point_entry, vformat("Can't set point's weight scale. Point with id: %d doesn't exist.", p_id));
95
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
96
97
(*point_entry)->weight_scale = p_weight_scale;
98
}
99
100
void AStar3D::remove_point(int64_t p_id) {
101
Point **point_entry = points.getptr(p_id);
102
ERR_FAIL_COND_MSG(!point_entry, vformat("Can't remove point. Point with id: %d doesn't exist.", p_id));
103
Point *p = *point_entry;
104
105
for (KeyValue<int64_t, Point *> &kv : p->neighbors) {
106
Segment s(p_id, kv.key);
107
segments.erase(s);
108
109
kv.value->neighbors.erase(p->id);
110
kv.value->unlinked_neighbours.erase(p->id);
111
}
112
113
for (KeyValue<int64_t, Point *> &kv : p->unlinked_neighbours) {
114
Segment s(p_id, kv.key);
115
segments.erase(s);
116
117
kv.value->neighbors.erase(p->id);
118
kv.value->unlinked_neighbours.erase(p->id);
119
}
120
121
memdelete(p);
122
points.erase(p_id);
123
last_free_id = p_id;
124
}
125
126
void AStar3D::connect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) {
127
ERR_FAIL_COND_MSG(p_id == p_with_id, vformat("Can't connect point with id: %d to itself.", p_id));
128
129
Point **a_entry = points.getptr(p_id);
130
ERR_FAIL_COND_MSG(!a_entry, vformat("Can't connect points. Point with id: %d doesn't exist.", p_id));
131
Point *a = *a_entry;
132
133
Point **b_entry = points.getptr(p_with_id);
134
ERR_FAIL_COND_MSG(!b_entry, vformat("Can't connect points. Point with id: %d doesn't exist.", p_with_id));
135
Point *b = *b_entry;
136
137
a->neighbors.insert(b->id, b);
138
139
if (bidirectional) {
140
b->neighbors.insert(a->id, a);
141
} else {
142
b->unlinked_neighbours.insert(a->id, a);
143
}
144
145
Segment s(p_id, p_with_id);
146
if (bidirectional) {
147
s.direction = Segment::BIDIRECTIONAL;
148
}
149
150
HashSet<Segment, Segment>::Iterator element = segments.find(s);
151
if (element) {
152
s.direction |= element->direction;
153
if (s.direction == Segment::BIDIRECTIONAL) {
154
// Both are neighbors of each other now
155
a->unlinked_neighbours.erase(b->id);
156
b->unlinked_neighbours.erase(a->id);
157
}
158
segments.remove(element);
159
}
160
161
segments.insert(s);
162
}
163
164
void AStar3D::disconnect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) {
165
Point **a_entry = points.getptr(p_id);
166
ERR_FAIL_COND_MSG(!a_entry, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_id));
167
Point *a = *a_entry;
168
169
Point **b_entry = points.getptr(p_with_id);
170
ERR_FAIL_COND_MSG(!b_entry, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_with_id));
171
Point *b = *b_entry;
172
173
Segment s(p_id, p_with_id);
174
int remove_direction = bidirectional ? (int)Segment::BIDIRECTIONAL : (int)s.direction;
175
176
HashSet<Segment, Segment>::Iterator element = segments.find(s);
177
if (element) {
178
// s is the new segment
179
// Erase the directions to be removed
180
s.direction = (element->direction & ~remove_direction);
181
182
a->neighbors.erase(b->id);
183
if (bidirectional) {
184
b->neighbors.erase(a->id);
185
if (element->direction != Segment::BIDIRECTIONAL) {
186
a->unlinked_neighbours.erase(b->id);
187
b->unlinked_neighbours.erase(a->id);
188
}
189
} else {
190
if (s.direction == Segment::NONE) {
191
b->unlinked_neighbours.erase(a->id);
192
} else {
193
a->unlinked_neighbours.insert(b->id, b);
194
}
195
}
196
197
segments.remove(element);
198
if (s.direction != Segment::NONE) {
199
segments.insert(s);
200
}
201
}
202
}
203
204
bool AStar3D::has_point(int64_t p_id) const {
205
return points.has(p_id);
206
}
207
208
PackedInt64Array AStar3D::get_point_ids() {
209
PackedInt64Array point_list;
210
211
for (KeyValue<int64_t, Point *> &kv : points) {
212
point_list.push_back(kv.key);
213
}
214
215
return point_list;
216
}
217
218
Vector<int64_t> AStar3D::get_point_connections(int64_t p_id) {
219
Point **p_entry = points.getptr(p_id);
220
ERR_FAIL_COND_V_MSG(!p_entry, Vector<int64_t>(), vformat("Can't get point's connections. Point with id: %d doesn't exist.", p_id));
221
222
Vector<int64_t> point_list;
223
224
for (KeyValue<int64_t, Point *> &kv : (*p_entry)->neighbors) {
225
point_list.push_back(kv.key);
226
}
227
228
return point_list;
229
}
230
231
bool AStar3D::are_points_connected(int64_t p_id, int64_t p_with_id, bool bidirectional) const {
232
Segment s(p_id, p_with_id);
233
const HashSet<Segment, Segment>::Iterator element = segments.find(s);
234
235
return element &&
236
(bidirectional || (element->direction & s.direction) == s.direction);
237
}
238
239
void AStar3D::clear() {
240
last_free_id = 0;
241
for (KeyValue<int64_t, Point *> &kv : points) {
242
memdelete(kv.value);
243
}
244
segments.clear();
245
points.clear();
246
}
247
248
int64_t AStar3D::get_point_count() const {
249
return points.size();
250
}
251
252
int64_t AStar3D::get_point_capacity() const {
253
return points.get_capacity();
254
}
255
256
void AStar3D::reserve_space(int64_t p_num_nodes) {
257
ERR_FAIL_COND_MSG(p_num_nodes <= 0, vformat("New capacity must be greater than 0, new was: %d.", p_num_nodes));
258
points.reserve(p_num_nodes);
259
}
260
261
int64_t AStar3D::get_closest_point(const Vector3 &p_point, bool p_include_disabled) const {
262
int64_t closest_id = -1;
263
real_t closest_dist = 1e20;
264
265
for (const KeyValue<int64_t, Point *> &kv : points) {
266
if (!p_include_disabled && !kv.value->enabled) {
267
continue; // Disabled points should not be considered.
268
}
269
270
// Keep the closest point's ID, and in case of multiple closest IDs,
271
// the smallest one (makes it deterministic).
272
real_t d = p_point.distance_squared_to(kv.value->pos);
273
int64_t id = kv.key;
274
if (d <= closest_dist) {
275
if (d == closest_dist && id > closest_id) { // Keep lowest ID.
276
continue;
277
}
278
closest_dist = d;
279
closest_id = id;
280
}
281
}
282
283
return closest_id;
284
}
285
286
Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const {
287
real_t closest_dist = 1e20;
288
Vector3 closest_point;
289
290
for (const Segment &E : segments) {
291
const Point *from_point = *points.getptr(E.key.first);
292
const Point *to_point = *points.getptr(E.key.second);
293
294
if (!(from_point->enabled && to_point->enabled)) {
295
continue;
296
}
297
298
Vector3 p = Geometry3D::get_closest_point_to_segment(p_point, from_point->pos, to_point->pos);
299
real_t d = p_point.distance_squared_to(p);
300
if (d < closest_dist) {
301
closest_point = p;
302
closest_dist = d;
303
}
304
}
305
306
return closest_point;
307
}
308
309
bool AStar3D::_solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path) {
310
last_closest_point = nullptr;
311
pass++;
312
313
if (!p_begin_point->enabled) {
314
return false;
315
}
316
if (p_begin_point == p_end_point) {
317
return true;
318
}
319
if (!p_end_point->enabled && !p_allow_partial_path) {
320
return false;
321
}
322
323
bool found_route = false;
324
325
LocalVector<Point *> open_list;
326
SortArray<Point *, SortPoints> sorter;
327
328
p_begin_point->g_score = 0;
329
p_begin_point->f_score = _estimate_cost(p_begin_point->id, p_end_point->id);
330
p_begin_point->abs_g_score = 0;
331
p_begin_point->abs_f_score = _estimate_cost(p_begin_point->id, p_end_point->id);
332
open_list.push_back(p_begin_point);
333
334
while (!open_list.is_empty()) {
335
Point *p = open_list[0]; // The currently processed point.
336
337
// Find point closer to end_point, or same distance to end_point but closer to begin_point.
338
if (last_closest_point == nullptr || last_closest_point->abs_f_score > p->abs_f_score || (last_closest_point->abs_f_score >= p->abs_f_score && last_closest_point->abs_g_score > p->abs_g_score)) {
339
last_closest_point = p;
340
}
341
342
if (p == p_end_point) {
343
found_route = true;
344
break;
345
}
346
347
sorter.pop_heap(0, open_list.size(), open_list.ptr()); // Remove the current point from the open list.
348
open_list.remove_at(open_list.size() - 1);
349
p->closed_pass = pass; // Mark the point as closed.
350
351
for (const KeyValue<int64_t, Point *> &kv : p->neighbors) {
352
Point *e = kv.value; // The neighbor point.
353
354
if (!e->enabled || e->closed_pass == pass) {
355
continue;
356
}
357
358
if (neighbor_filter_enabled) {
359
bool filtered;
360
if (GDVIRTUAL_CALL(_filter_neighbor, p->id, e->id, filtered) && filtered) {
361
continue;
362
}
363
}
364
365
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
366
367
bool new_point = false;
368
369
if (e->open_pass != pass) { // The point wasn't inside the open list.
370
e->open_pass = pass;
371
open_list.push_back(e);
372
new_point = true;
373
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
374
continue;
375
}
376
377
e->prev_point = p;
378
e->g_score = tentative_g_score;
379
e->f_score = e->g_score + _estimate_cost(e->id, p_end_point->id);
380
e->abs_g_score = tentative_g_score;
381
e->abs_f_score = e->f_score - e->g_score;
382
383
if (new_point) { // The position of the new points is already known.
384
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
385
} else {
386
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr());
387
}
388
}
389
}
390
391
return found_route;
392
}
393
394
real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
395
real_t scost;
396
if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
397
return scost;
398
}
399
400
Point **from_entry = points.getptr(p_from_id);
401
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
402
Point *from_point = *from_entry;
403
404
Point **end_entry = points.getptr(p_end_id);
405
ERR_FAIL_COND_V_MSG(!end_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
406
Point *end_point = *end_entry;
407
408
return from_point->pos.distance_to(end_point->pos);
409
}
410
411
real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
412
real_t scost;
413
if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) {
414
return scost;
415
}
416
417
Point **from_entry = points.getptr(p_from_id);
418
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
419
Point *from_point = *from_entry;
420
421
Point **to_entry = points.getptr(p_to_id);
422
ERR_FAIL_COND_V_MSG(!to_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
423
Point *to_point = *to_entry;
424
425
return from_point->pos.distance_to(to_point->pos);
426
}
427
428
Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
429
Point **a_entry = points.getptr(p_from_id);
430
ERR_FAIL_COND_V_MSG(!a_entry, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id));
431
Point *a = *a_entry;
432
433
Point **b_entry = points.getptr(p_to_id);
434
ERR_FAIL_COND_V_MSG(!b_entry, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id));
435
Point *b = *b_entry;
436
437
Point *begin_point = a;
438
Point *end_point = b;
439
440
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
441
if (!found_route) {
442
if (!p_allow_partial_path || last_closest_point == nullptr) {
443
return Vector<Vector3>();
444
}
445
446
// Use closest point instead.
447
end_point = last_closest_point;
448
}
449
450
Point *p = end_point;
451
int64_t pc = 1; // Begin point
452
while (p != begin_point) {
453
pc++;
454
p = p->prev_point;
455
}
456
457
Vector<Vector3> path;
458
path.resize(pc);
459
460
{
461
Vector3 *w = path.ptrw();
462
463
Point *p2 = end_point;
464
int64_t idx = pc - 1;
465
while (p2 != begin_point) {
466
w[idx--] = p2->pos;
467
p2 = p2->prev_point;
468
}
469
470
w[0] = p2->pos; // Assign first
471
}
472
473
return path;
474
}
475
476
Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
477
Point **a_entry = points.getptr(p_from_id);
478
ERR_FAIL_COND_V_MSG(!a_entry, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id));
479
Point *a = *a_entry;
480
481
Point **b_entry = points.getptr(p_to_id);
482
ERR_FAIL_COND_V_MSG(!b_entry, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id));
483
Point *b = *b_entry;
484
485
Point *begin_point = a;
486
Point *end_point = b;
487
488
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
489
if (!found_route) {
490
if (!p_allow_partial_path || last_closest_point == nullptr) {
491
return Vector<int64_t>();
492
}
493
494
// Use closest point instead.
495
end_point = last_closest_point;
496
}
497
498
Point *p = end_point;
499
int64_t pc = 1; // Begin point
500
while (p != begin_point) {
501
pc++;
502
p = p->prev_point;
503
}
504
505
Vector<int64_t> path;
506
path.resize(pc);
507
508
{
509
int64_t *w = path.ptrw();
510
511
p = end_point;
512
int64_t idx = pc - 1;
513
while (p != begin_point) {
514
w[idx--] = p->id;
515
p = p->prev_point;
516
}
517
518
w[0] = p->id; // Assign first
519
}
520
521
return path;
522
}
523
524
bool AStar3D::is_neighbor_filter_enabled() const {
525
return neighbor_filter_enabled;
526
}
527
528
void AStar3D::set_neighbor_filter_enabled(bool p_enabled) {
529
neighbor_filter_enabled = p_enabled;
530
}
531
532
void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) {
533
Point **p_entry = points.getptr(p_id);
534
ERR_FAIL_COND_MSG(!p_entry, vformat("Can't set if point is disabled. Point with id: %d doesn't exist.", p_id));
535
Point *p = *p_entry;
536
537
p->enabled = !p_disabled;
538
}
539
540
bool AStar3D::is_point_disabled(int64_t p_id) const {
541
Point *const *p_entry = points.getptr(p_id);
542
ERR_FAIL_COND_V_MSG(!p_entry, false, vformat("Can't get if point is disabled. Point with id: %d doesn't exist.", p_id));
543
Point *p = *p_entry;
544
545
return !p->enabled;
546
}
547
548
void AStar3D::_bind_methods() {
549
ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar3D::get_available_point_id);
550
ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar3D::add_point, DEFVAL(1.0));
551
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar3D::get_point_position);
552
ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar3D::set_point_position);
553
ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar3D::get_point_weight_scale);
554
ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar3D::set_point_weight_scale);
555
ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar3D::remove_point);
556
ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar3D::has_point);
557
ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar3D::get_point_connections);
558
ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar3D::get_point_ids);
559
560
ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar3D::set_point_disabled, DEFVAL(true));
561
ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar3D::is_point_disabled);
562
563
ClassDB::bind_method(D_METHOD("set_neighbor_filter_enabled", "enabled"), &AStar3D::set_neighbor_filter_enabled);
564
ClassDB::bind_method(D_METHOD("is_neighbor_filter_enabled"), &AStar3D::is_neighbor_filter_enabled);
565
566
ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar3D::connect_points, DEFVAL(true));
567
ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar3D::disconnect_points, DEFVAL(true));
568
ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar3D::are_points_connected, DEFVAL(true));
569
570
ClassDB::bind_method(D_METHOD("get_point_count"), &AStar3D::get_point_count);
571
ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar3D::get_point_capacity);
572
ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar3D::reserve_space);
573
ClassDB::bind_method(D_METHOD("clear"), &AStar3D::clear);
574
575
ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar3D::get_closest_point, DEFVAL(false));
576
ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar3D::get_closest_position_in_segment);
577
578
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_point_path, DEFVAL(false));
579
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_id_path, DEFVAL(false));
580
581
GDVIRTUAL_BIND(_filter_neighbor, "from_id", "neighbor_id")
582
GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
583
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
584
585
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "neighbor_filter_enabled"), "set_neighbor_filter_enabled", "is_neighbor_filter_enabled");
586
}
587
588
AStar3D::~AStar3D() {
589
clear();
590
}
591
592
/////////////////////////////////////////////////////////////
593
594
int64_t AStar2D::get_available_point_id() const {
595
return astar.get_available_point_id();
596
}
597
598
void AStar2D::add_point(int64_t p_id, const Vector2 &p_pos, real_t p_weight_scale) {
599
astar.add_point(p_id, Vector3(p_pos.x, p_pos.y, 0), p_weight_scale);
600
}
601
602
Vector2 AStar2D::get_point_position(int64_t p_id) const {
603
Vector3 p = astar.get_point_position(p_id);
604
return Vector2(p.x, p.y);
605
}
606
607
void AStar2D::set_point_position(int64_t p_id, const Vector2 &p_pos) {
608
astar.set_point_position(p_id, Vector3(p_pos.x, p_pos.y, 0));
609
}
610
611
real_t AStar2D::get_point_weight_scale(int64_t p_id) const {
612
return astar.get_point_weight_scale(p_id);
613
}
614
615
void AStar2D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) {
616
astar.set_point_weight_scale(p_id, p_weight_scale);
617
}
618
619
void AStar2D::remove_point(int64_t p_id) {
620
astar.remove_point(p_id);
621
}
622
623
bool AStar2D::has_point(int64_t p_id) const {
624
return astar.has_point(p_id);
625
}
626
627
Vector<int64_t> AStar2D::get_point_connections(int64_t p_id) {
628
return astar.get_point_connections(p_id);
629
}
630
631
PackedInt64Array AStar2D::get_point_ids() {
632
return astar.get_point_ids();
633
}
634
635
bool AStar2D::is_neighbor_filter_enabled() const {
636
return astar.neighbor_filter_enabled;
637
}
638
639
void AStar2D::set_neighbor_filter_enabled(bool p_enabled) {
640
astar.neighbor_filter_enabled = p_enabled;
641
}
642
643
void AStar2D::set_point_disabled(int64_t p_id, bool p_disabled) {
644
astar.set_point_disabled(p_id, p_disabled);
645
}
646
647
bool AStar2D::is_point_disabled(int64_t p_id) const {
648
return astar.is_point_disabled(p_id);
649
}
650
651
void AStar2D::connect_points(int64_t p_id, int64_t p_with_id, bool p_bidirectional) {
652
astar.connect_points(p_id, p_with_id, p_bidirectional);
653
}
654
655
void AStar2D::disconnect_points(int64_t p_id, int64_t p_with_id, bool p_bidirectional) {
656
astar.disconnect_points(p_id, p_with_id, p_bidirectional);
657
}
658
659
bool AStar2D::are_points_connected(int64_t p_id, int64_t p_with_id, bool p_bidirectional) const {
660
return astar.are_points_connected(p_id, p_with_id, p_bidirectional);
661
}
662
663
int64_t AStar2D::get_point_count() const {
664
return astar.get_point_count();
665
}
666
667
int64_t AStar2D::get_point_capacity() const {
668
return astar.get_point_capacity();
669
}
670
671
void AStar2D::clear() {
672
astar.clear();
673
}
674
675
void AStar2D::reserve_space(int64_t p_num_nodes) {
676
astar.reserve_space(p_num_nodes);
677
}
678
679
int64_t AStar2D::get_closest_point(const Vector2 &p_point, bool p_include_disabled) const {
680
return astar.get_closest_point(Vector3(p_point.x, p_point.y, 0), p_include_disabled);
681
}
682
683
Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const {
684
Vector3 p = astar.get_closest_position_in_segment(Vector3(p_point.x, p_point.y, 0));
685
return Vector2(p.x, p.y);
686
}
687
688
real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
689
real_t scost;
690
if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
691
return scost;
692
}
693
694
AStar3D::Point **from_entry = astar.points.getptr(p_from_id);
695
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
696
AStar3D::Point *from_point = *from_entry;
697
698
AStar3D::Point **end_entry = astar.points.getptr(p_end_id);
699
ERR_FAIL_COND_V_MSG(!end_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
700
AStar3D::Point *end_point = *end_entry;
701
702
return from_point->pos.distance_to(end_point->pos);
703
}
704
705
real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
706
real_t scost;
707
if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) {
708
return scost;
709
}
710
711
AStar3D::Point **from_entry = astar.points.getptr(p_from_id);
712
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
713
AStar3D::Point *from_point = *from_entry;
714
715
AStar3D::Point **to_entry = astar.points.getptr(p_to_id);
716
ERR_FAIL_COND_V_MSG(!to_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
717
AStar3D::Point *to_point = *to_entry;
718
719
return from_point->pos.distance_to(to_point->pos);
720
}
721
722
Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
723
AStar3D::Point **a_entry = astar.points.getptr(p_from_id);
724
ERR_FAIL_COND_V_MSG(!a_entry, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id));
725
AStar3D::Point *a = *a_entry;
726
727
AStar3D::Point **b_entry = astar.points.getptr(p_to_id);
728
ERR_FAIL_COND_V_MSG(!b_entry, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id));
729
AStar3D::Point *b = *b_entry;
730
731
AStar3D::Point *begin_point = a;
732
AStar3D::Point *end_point = b;
733
734
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
735
if (!found_route) {
736
if (!p_allow_partial_path || astar.last_closest_point == nullptr) {
737
return Vector<Vector2>();
738
}
739
740
// Use closest point instead.
741
end_point = astar.last_closest_point;
742
}
743
744
AStar3D::Point *p = end_point;
745
int64_t pc = 1; // Begin point
746
while (p != begin_point) {
747
pc++;
748
p = p->prev_point;
749
}
750
751
Vector<Vector2> path;
752
path.resize(pc);
753
754
{
755
Vector2 *w = path.ptrw();
756
757
AStar3D::Point *p2 = end_point;
758
int64_t idx = pc - 1;
759
while (p2 != begin_point) {
760
w[idx--] = Vector2(p2->pos.x, p2->pos.y);
761
p2 = p2->prev_point;
762
}
763
764
w[0] = Vector2(p2->pos.x, p2->pos.y); // Assign first
765
}
766
767
return path;
768
}
769
770
Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
771
AStar3D::Point **a_entry = astar.points.getptr(p_from_id);
772
ERR_FAIL_COND_V_MSG(!a_entry, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id));
773
AStar3D::Point *a = *a_entry;
774
775
AStar3D::Point **to_entry = astar.points.getptr(p_to_id);
776
ERR_FAIL_COND_V_MSG(!to_entry, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id));
777
AStar3D::Point *b = *to_entry;
778
779
AStar3D::Point *begin_point = a;
780
AStar3D::Point *end_point = b;
781
782
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
783
if (!found_route) {
784
if (!p_allow_partial_path || astar.last_closest_point == nullptr) {
785
return Vector<int64_t>();
786
}
787
788
// Use closest point instead.
789
end_point = astar.last_closest_point;
790
}
791
792
AStar3D::Point *p = end_point;
793
int64_t pc = 1; // Begin point
794
while (p != begin_point) {
795
pc++;
796
p = p->prev_point;
797
}
798
799
Vector<int64_t> path;
800
path.resize(pc);
801
802
{
803
int64_t *w = path.ptrw();
804
805
p = end_point;
806
int64_t idx = pc - 1;
807
while (p != begin_point) {
808
w[idx--] = p->id;
809
p = p->prev_point;
810
}
811
812
w[0] = p->id; // Assign first
813
}
814
815
return path;
816
}
817
818
bool AStar2D::_solve(AStar3D::Point *p_begin_point, AStar3D::Point *p_end_point, bool p_allow_partial_path) {
819
astar.last_closest_point = nullptr;
820
astar.pass++;
821
822
if (!p_begin_point->enabled) {
823
return false;
824
}
825
if (p_begin_point == p_end_point) {
826
return true;
827
}
828
if (!p_end_point->enabled && !p_allow_partial_path) {
829
return false;
830
}
831
832
bool found_route = false;
833
834
LocalVector<AStar3D::Point *> open_list;
835
SortArray<AStar3D::Point *, AStar3D::SortPoints> sorter;
836
837
p_begin_point->g_score = 0;
838
p_begin_point->f_score = _estimate_cost(p_begin_point->id, p_end_point->id);
839
p_begin_point->abs_g_score = 0;
840
p_begin_point->abs_f_score = _estimate_cost(p_begin_point->id, p_end_point->id);
841
open_list.push_back(p_begin_point);
842
843
while (!open_list.is_empty()) {
844
AStar3D::Point *p = open_list[0]; // The currently processed point.
845
846
// Find point closer to end_point, or same distance to end_point but closer to begin_point.
847
if (astar.last_closest_point == nullptr || astar.last_closest_point->abs_f_score > p->abs_f_score || (astar.last_closest_point->abs_f_score >= p->abs_f_score && astar.last_closest_point->abs_g_score > p->abs_g_score)) {
848
astar.last_closest_point = p;
849
}
850
851
if (p == p_end_point) {
852
found_route = true;
853
break;
854
}
855
856
sorter.pop_heap(0, open_list.size(), open_list.ptr()); // Remove the current point from the open list.
857
open_list.remove_at(open_list.size() - 1);
858
p->closed_pass = astar.pass; // Mark the point as closed.
859
860
for (KeyValue<int64_t, AStar3D::Point *> &kv : p->neighbors) {
861
AStar3D::Point *e = kv.value; // The neighbor point.
862
863
if (!e->enabled || e->closed_pass == astar.pass) {
864
continue;
865
}
866
867
if (astar.neighbor_filter_enabled) {
868
bool filtered;
869
if (GDVIRTUAL_CALL(_filter_neighbor, p->id, e->id, filtered) && filtered) {
870
continue;
871
}
872
}
873
874
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
875
876
bool new_point = false;
877
878
if (e->open_pass != astar.pass) { // The point wasn't inside the open list.
879
e->open_pass = astar.pass;
880
open_list.push_back(e);
881
new_point = true;
882
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
883
continue;
884
}
885
886
e->prev_point = p;
887
e->g_score = tentative_g_score;
888
e->f_score = e->g_score + _estimate_cost(e->id, p_end_point->id);
889
e->abs_g_score = tentative_g_score;
890
e->abs_f_score = e->f_score - e->g_score;
891
892
if (new_point) { // The position of the new points is already known.
893
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
894
} else {
895
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr());
896
}
897
}
898
}
899
900
return found_route;
901
}
902
903
void AStar2D::_bind_methods() {
904
ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar2D::get_available_point_id);
905
ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar2D::add_point, DEFVAL(1.0));
906
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar2D::get_point_position);
907
ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar2D::set_point_position);
908
ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar2D::get_point_weight_scale);
909
ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar2D::set_point_weight_scale);
910
ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar2D::remove_point);
911
ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar2D::has_point);
912
ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar2D::get_point_connections);
913
ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar2D::get_point_ids);
914
915
ClassDB::bind_method(D_METHOD("set_neighbor_filter_enabled", "enabled"), &AStar2D::set_neighbor_filter_enabled);
916
ClassDB::bind_method(D_METHOD("is_neighbor_filter_enabled"), &AStar2D::is_neighbor_filter_enabled);
917
918
ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar2D::set_point_disabled, DEFVAL(true));
919
ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar2D::is_point_disabled);
920
921
ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar2D::connect_points, DEFVAL(true));
922
ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar2D::disconnect_points, DEFVAL(true));
923
ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar2D::are_points_connected, DEFVAL(true));
924
925
ClassDB::bind_method(D_METHOD("get_point_count"), &AStar2D::get_point_count);
926
ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar2D::get_point_capacity);
927
ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar2D::reserve_space);
928
ClassDB::bind_method(D_METHOD("clear"), &AStar2D::clear);
929
930
ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar2D::get_closest_point, DEFVAL(false));
931
ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar2D::get_closest_position_in_segment);
932
933
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_point_path, DEFVAL(false));
934
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_id_path, DEFVAL(false));
935
936
GDVIRTUAL_BIND(_filter_neighbor, "from_id", "neighbor_id")
937
GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
938
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
939
940
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "neighbor_filter_enabled"), "set_neighbor_filter_enabled", "is_neighbor_filter_enabled");
941
}
942
943