Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/math/a_star.cpp
9903 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 *begin_point, Point *end_point, bool p_allow_partial_path) {
310
last_closest_point = nullptr;
311
pass++;
312
313
if (!end_point->enabled && !p_allow_partial_path) {
314
return false;
315
}
316
317
bool found_route = false;
318
319
LocalVector<Point *> open_list;
320
SortArray<Point *, SortPoints> sorter;
321
322
begin_point->g_score = 0;
323
begin_point->f_score = _estimate_cost(begin_point->id, end_point->id);
324
begin_point->abs_g_score = 0;
325
begin_point->abs_f_score = _estimate_cost(begin_point->id, end_point->id);
326
open_list.push_back(begin_point);
327
328
while (!open_list.is_empty()) {
329
Point *p = open_list[0]; // The currently processed point.
330
331
// Find point closer to end_point, or same distance to end_point but closer to begin_point.
332
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)) {
333
last_closest_point = p;
334
}
335
336
if (p == end_point) {
337
found_route = true;
338
break;
339
}
340
341
sorter.pop_heap(0, open_list.size(), open_list.ptr()); // Remove the current point from the open list.
342
open_list.remove_at(open_list.size() - 1);
343
p->closed_pass = pass; // Mark the point as closed.
344
345
for (const KeyValue<int64_t, Point *> &kv : p->neighbors) {
346
Point *e = kv.value; // The neighbor point.
347
348
if (!e->enabled || e->closed_pass == pass) {
349
continue;
350
}
351
352
if (neighbor_filter_enabled) {
353
bool filtered;
354
if (GDVIRTUAL_CALL(_filter_neighbor, p->id, e->id, filtered) && filtered) {
355
continue;
356
}
357
}
358
359
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
360
361
bool new_point = false;
362
363
if (e->open_pass != pass) { // The point wasn't inside the open list.
364
e->open_pass = pass;
365
open_list.push_back(e);
366
new_point = true;
367
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
368
continue;
369
}
370
371
e->prev_point = p;
372
e->g_score = tentative_g_score;
373
e->f_score = e->g_score + _estimate_cost(e->id, end_point->id);
374
e->abs_g_score = tentative_g_score;
375
e->abs_f_score = e->f_score - e->g_score;
376
377
if (new_point) { // The position of the new points is already known.
378
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
379
} else {
380
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr());
381
}
382
}
383
}
384
385
return found_route;
386
}
387
388
real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
389
real_t scost;
390
if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
391
return scost;
392
}
393
394
Point **from_entry = points.getptr(p_from_id);
395
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
396
Point *from_point = *from_entry;
397
398
Point **end_entry = points.getptr(p_end_id);
399
ERR_FAIL_COND_V_MSG(!end_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
400
Point *end_point = *end_entry;
401
402
return from_point->pos.distance_to(end_point->pos);
403
}
404
405
real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
406
real_t scost;
407
if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) {
408
return scost;
409
}
410
411
Point **from_entry = points.getptr(p_from_id);
412
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
413
Point *from_point = *from_entry;
414
415
Point **to_entry = points.getptr(p_to_id);
416
ERR_FAIL_COND_V_MSG(!to_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
417
Point *to_point = *to_entry;
418
419
return from_point->pos.distance_to(to_point->pos);
420
}
421
422
Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
423
Point **a_entry = points.getptr(p_from_id);
424
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));
425
Point *a = *a_entry;
426
427
Point **b_entry = points.getptr(p_to_id);
428
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));
429
Point *b = *b_entry;
430
431
if (a == b) {
432
Vector<Vector3> ret;
433
ret.push_back(a->pos);
434
return ret;
435
}
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
if (a == b) {
486
Vector<int64_t> ret;
487
ret.push_back(a->id);
488
return ret;
489
}
490
491
if (!a->enabled) {
492
return Vector<int64_t>();
493
}
494
495
Point *begin_point = a;
496
Point *end_point = b;
497
498
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
499
if (!found_route) {
500
if (!p_allow_partial_path || last_closest_point == nullptr) {
501
return Vector<int64_t>();
502
}
503
504
// Use closest point instead.
505
end_point = last_closest_point;
506
}
507
508
Point *p = end_point;
509
int64_t pc = 1; // Begin point
510
while (p != begin_point) {
511
pc++;
512
p = p->prev_point;
513
}
514
515
Vector<int64_t> path;
516
path.resize(pc);
517
518
{
519
int64_t *w = path.ptrw();
520
521
p = end_point;
522
int64_t idx = pc - 1;
523
while (p != begin_point) {
524
w[idx--] = p->id;
525
p = p->prev_point;
526
}
527
528
w[0] = p->id; // Assign first
529
}
530
531
return path;
532
}
533
534
bool AStar3D::is_neighbor_filter_enabled() const {
535
return neighbor_filter_enabled;
536
}
537
538
void AStar3D::set_neighbor_filter_enabled(bool p_enabled) {
539
neighbor_filter_enabled = p_enabled;
540
}
541
542
void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) {
543
Point **p_entry = points.getptr(p_id);
544
ERR_FAIL_COND_MSG(!p_entry, vformat("Can't set if point is disabled. Point with id: %d doesn't exist.", p_id));
545
Point *p = *p_entry;
546
547
p->enabled = !p_disabled;
548
}
549
550
bool AStar3D::is_point_disabled(int64_t p_id) const {
551
Point *const *p_entry = points.getptr(p_id);
552
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));
553
Point *p = *p_entry;
554
555
return !p->enabled;
556
}
557
558
void AStar3D::_bind_methods() {
559
ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar3D::get_available_point_id);
560
ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar3D::add_point, DEFVAL(1.0));
561
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar3D::get_point_position);
562
ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar3D::set_point_position);
563
ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar3D::get_point_weight_scale);
564
ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar3D::set_point_weight_scale);
565
ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar3D::remove_point);
566
ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar3D::has_point);
567
ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar3D::get_point_connections);
568
ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar3D::get_point_ids);
569
570
ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar3D::set_point_disabled, DEFVAL(true));
571
ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar3D::is_point_disabled);
572
573
ClassDB::bind_method(D_METHOD("set_neighbor_filter_enabled", "enabled"), &AStar3D::set_neighbor_filter_enabled);
574
ClassDB::bind_method(D_METHOD("is_neighbor_filter_enabled"), &AStar3D::is_neighbor_filter_enabled);
575
576
ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar3D::connect_points, DEFVAL(true));
577
ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar3D::disconnect_points, DEFVAL(true));
578
ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar3D::are_points_connected, DEFVAL(true));
579
580
ClassDB::bind_method(D_METHOD("get_point_count"), &AStar3D::get_point_count);
581
ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar3D::get_point_capacity);
582
ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar3D::reserve_space);
583
ClassDB::bind_method(D_METHOD("clear"), &AStar3D::clear);
584
585
ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar3D::get_closest_point, DEFVAL(false));
586
ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar3D::get_closest_position_in_segment);
587
588
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_point_path, DEFVAL(false));
589
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_id_path, DEFVAL(false));
590
591
GDVIRTUAL_BIND(_filter_neighbor, "from_id", "neighbor_id")
592
GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
593
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
594
595
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "neighbor_filter_enabled"), "set_neighbor_filter_enabled", "is_neighbor_filter_enabled");
596
}
597
598
AStar3D::~AStar3D() {
599
clear();
600
}
601
602
/////////////////////////////////////////////////////////////
603
604
int64_t AStar2D::get_available_point_id() const {
605
return astar.get_available_point_id();
606
}
607
608
void AStar2D::add_point(int64_t p_id, const Vector2 &p_pos, real_t p_weight_scale) {
609
astar.add_point(p_id, Vector3(p_pos.x, p_pos.y, 0), p_weight_scale);
610
}
611
612
Vector2 AStar2D::get_point_position(int64_t p_id) const {
613
Vector3 p = astar.get_point_position(p_id);
614
return Vector2(p.x, p.y);
615
}
616
617
void AStar2D::set_point_position(int64_t p_id, const Vector2 &p_pos) {
618
astar.set_point_position(p_id, Vector3(p_pos.x, p_pos.y, 0));
619
}
620
621
real_t AStar2D::get_point_weight_scale(int64_t p_id) const {
622
return astar.get_point_weight_scale(p_id);
623
}
624
625
void AStar2D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) {
626
astar.set_point_weight_scale(p_id, p_weight_scale);
627
}
628
629
void AStar2D::remove_point(int64_t p_id) {
630
astar.remove_point(p_id);
631
}
632
633
bool AStar2D::has_point(int64_t p_id) const {
634
return astar.has_point(p_id);
635
}
636
637
Vector<int64_t> AStar2D::get_point_connections(int64_t p_id) {
638
return astar.get_point_connections(p_id);
639
}
640
641
PackedInt64Array AStar2D::get_point_ids() {
642
return astar.get_point_ids();
643
}
644
645
bool AStar2D::is_neighbor_filter_enabled() const {
646
return astar.neighbor_filter_enabled;
647
}
648
649
void AStar2D::set_neighbor_filter_enabled(bool p_enabled) {
650
astar.neighbor_filter_enabled = p_enabled;
651
}
652
653
void AStar2D::set_point_disabled(int64_t p_id, bool p_disabled) {
654
astar.set_point_disabled(p_id, p_disabled);
655
}
656
657
bool AStar2D::is_point_disabled(int64_t p_id) const {
658
return astar.is_point_disabled(p_id);
659
}
660
661
void AStar2D::connect_points(int64_t p_id, int64_t p_with_id, bool p_bidirectional) {
662
astar.connect_points(p_id, p_with_id, p_bidirectional);
663
}
664
665
void AStar2D::disconnect_points(int64_t p_id, int64_t p_with_id, bool p_bidirectional) {
666
astar.disconnect_points(p_id, p_with_id, p_bidirectional);
667
}
668
669
bool AStar2D::are_points_connected(int64_t p_id, int64_t p_with_id, bool p_bidirectional) const {
670
return astar.are_points_connected(p_id, p_with_id, p_bidirectional);
671
}
672
673
int64_t AStar2D::get_point_count() const {
674
return astar.get_point_count();
675
}
676
677
int64_t AStar2D::get_point_capacity() const {
678
return astar.get_point_capacity();
679
}
680
681
void AStar2D::clear() {
682
astar.clear();
683
}
684
685
void AStar2D::reserve_space(int64_t p_num_nodes) {
686
astar.reserve_space(p_num_nodes);
687
}
688
689
int64_t AStar2D::get_closest_point(const Vector2 &p_point, bool p_include_disabled) const {
690
return astar.get_closest_point(Vector3(p_point.x, p_point.y, 0), p_include_disabled);
691
}
692
693
Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const {
694
Vector3 p = astar.get_closest_position_in_segment(Vector3(p_point.x, p_point.y, 0));
695
return Vector2(p.x, p.y);
696
}
697
698
real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
699
real_t scost;
700
if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
701
return scost;
702
}
703
704
AStar3D::Point **from_entry = astar.points.getptr(p_from_id);
705
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
706
AStar3D::Point *from_point = *from_entry;
707
708
AStar3D::Point **end_entry = astar.points.getptr(p_end_id);
709
ERR_FAIL_COND_V_MSG(!end_entry, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
710
AStar3D::Point *end_point = *end_entry;
711
712
return from_point->pos.distance_to(end_point->pos);
713
}
714
715
real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
716
real_t scost;
717
if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) {
718
return scost;
719
}
720
721
AStar3D::Point **from_entry = astar.points.getptr(p_from_id);
722
ERR_FAIL_COND_V_MSG(!from_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id));
723
AStar3D::Point *from_point = *from_entry;
724
725
AStar3D::Point **to_entry = astar.points.getptr(p_to_id);
726
ERR_FAIL_COND_V_MSG(!to_entry, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id));
727
AStar3D::Point *to_point = *to_entry;
728
729
return from_point->pos.distance_to(to_point->pos);
730
}
731
732
Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
733
AStar3D::Point **a_entry = astar.points.getptr(p_from_id);
734
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));
735
AStar3D::Point *a = *a_entry;
736
737
AStar3D::Point **b_entry = astar.points.getptr(p_to_id);
738
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));
739
AStar3D::Point *b = *b_entry;
740
741
if (a == b) {
742
Vector<Vector2> ret = { Vector2(a->pos.x, a->pos.y) };
743
return ret;
744
}
745
746
AStar3D::Point *begin_point = a;
747
AStar3D::Point *end_point = b;
748
749
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
750
if (!found_route) {
751
if (!p_allow_partial_path || astar.last_closest_point == nullptr) {
752
return Vector<Vector2>();
753
}
754
755
// Use closest point instead.
756
end_point = astar.last_closest_point;
757
}
758
759
AStar3D::Point *p = end_point;
760
int64_t pc = 1; // Begin point
761
while (p != begin_point) {
762
pc++;
763
p = p->prev_point;
764
}
765
766
Vector<Vector2> path;
767
path.resize(pc);
768
769
{
770
Vector2 *w = path.ptrw();
771
772
AStar3D::Point *p2 = end_point;
773
int64_t idx = pc - 1;
774
while (p2 != begin_point) {
775
w[idx--] = Vector2(p2->pos.x, p2->pos.y);
776
p2 = p2->prev_point;
777
}
778
779
w[0] = Vector2(p2->pos.x, p2->pos.y); // Assign first
780
}
781
782
return path;
783
}
784
785
Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_allow_partial_path) {
786
AStar3D::Point **a_entry = astar.points.getptr(p_from_id);
787
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));
788
AStar3D::Point *a = *a_entry;
789
790
AStar3D::Point **to_entry = astar.points.getptr(p_to_id);
791
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));
792
AStar3D::Point *b = *to_entry;
793
794
if (a == b) {
795
Vector<int64_t> ret;
796
ret.push_back(a->id);
797
return ret;
798
}
799
800
if (!a->enabled) {
801
return Vector<int64_t>();
802
}
803
804
AStar3D::Point *begin_point = a;
805
AStar3D::Point *end_point = b;
806
807
bool found_route = _solve(begin_point, end_point, p_allow_partial_path);
808
if (!found_route) {
809
if (!p_allow_partial_path || astar.last_closest_point == nullptr) {
810
return Vector<int64_t>();
811
}
812
813
// Use closest point instead.
814
end_point = astar.last_closest_point;
815
}
816
817
AStar3D::Point *p = end_point;
818
int64_t pc = 1; // Begin point
819
while (p != begin_point) {
820
pc++;
821
p = p->prev_point;
822
}
823
824
Vector<int64_t> path;
825
path.resize(pc);
826
827
{
828
int64_t *w = path.ptrw();
829
830
p = end_point;
831
int64_t idx = pc - 1;
832
while (p != begin_point) {
833
w[idx--] = p->id;
834
p = p->prev_point;
835
}
836
837
w[0] = p->id; // Assign first
838
}
839
840
return path;
841
}
842
843
bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path) {
844
astar.last_closest_point = nullptr;
845
astar.pass++;
846
847
if (!end_point->enabled && !p_allow_partial_path) {
848
return false;
849
}
850
851
bool found_route = false;
852
853
LocalVector<AStar3D::Point *> open_list;
854
SortArray<AStar3D::Point *, AStar3D::SortPoints> sorter;
855
856
begin_point->g_score = 0;
857
begin_point->f_score = _estimate_cost(begin_point->id, end_point->id);
858
begin_point->abs_g_score = 0;
859
begin_point->abs_f_score = _estimate_cost(begin_point->id, end_point->id);
860
open_list.push_back(begin_point);
861
862
while (!open_list.is_empty()) {
863
AStar3D::Point *p = open_list[0]; // The currently processed point.
864
865
// Find point closer to end_point, or same distance to end_point but closer to begin_point.
866
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)) {
867
astar.last_closest_point = p;
868
}
869
870
if (p == end_point) {
871
found_route = true;
872
break;
873
}
874
875
sorter.pop_heap(0, open_list.size(), open_list.ptr()); // Remove the current point from the open list.
876
open_list.remove_at(open_list.size() - 1);
877
p->closed_pass = astar.pass; // Mark the point as closed.
878
879
for (KeyValue<int64_t, AStar3D::Point *> &kv : p->neighbors) {
880
AStar3D::Point *e = kv.value; // The neighbor point.
881
882
if (!e->enabled || e->closed_pass == astar.pass) {
883
continue;
884
}
885
886
if (astar.neighbor_filter_enabled) {
887
bool filtered;
888
if (GDVIRTUAL_CALL(_filter_neighbor, p->id, e->id, filtered) && filtered) {
889
continue;
890
}
891
}
892
893
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
894
895
bool new_point = false;
896
897
if (e->open_pass != astar.pass) { // The point wasn't inside the open list.
898
e->open_pass = astar.pass;
899
open_list.push_back(e);
900
new_point = true;
901
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
902
continue;
903
}
904
905
e->prev_point = p;
906
e->g_score = tentative_g_score;
907
e->f_score = e->g_score + _estimate_cost(e->id, end_point->id);
908
e->abs_g_score = tentative_g_score;
909
e->abs_f_score = e->f_score - e->g_score;
910
911
if (new_point) { // The position of the new points is already known.
912
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
913
} else {
914
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr());
915
}
916
}
917
}
918
919
return found_route;
920
}
921
922
void AStar2D::_bind_methods() {
923
ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar2D::get_available_point_id);
924
ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar2D::add_point, DEFVAL(1.0));
925
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar2D::get_point_position);
926
ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar2D::set_point_position);
927
ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar2D::get_point_weight_scale);
928
ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar2D::set_point_weight_scale);
929
ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar2D::remove_point);
930
ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar2D::has_point);
931
ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar2D::get_point_connections);
932
ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar2D::get_point_ids);
933
934
ClassDB::bind_method(D_METHOD("set_neighbor_filter_enabled", "enabled"), &AStar2D::set_neighbor_filter_enabled);
935
ClassDB::bind_method(D_METHOD("is_neighbor_filter_enabled"), &AStar2D::is_neighbor_filter_enabled);
936
937
ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar2D::set_point_disabled, DEFVAL(true));
938
ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar2D::is_point_disabled);
939
940
ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar2D::connect_points, DEFVAL(true));
941
ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar2D::disconnect_points, DEFVAL(true));
942
ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar2D::are_points_connected, DEFVAL(true));
943
944
ClassDB::bind_method(D_METHOD("get_point_count"), &AStar2D::get_point_count);
945
ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar2D::get_point_capacity);
946
ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar2D::reserve_space);
947
ClassDB::bind_method(D_METHOD("clear"), &AStar2D::clear);
948
949
ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar2D::get_closest_point, DEFVAL(false));
950
ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar2D::get_closest_position_in_segment);
951
952
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_point_path, DEFVAL(false));
953
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_id_path, DEFVAL(false));
954
955
GDVIRTUAL_BIND(_filter_neighbor, "from_id", "neighbor_id")
956
GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
957
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
958
959
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "neighbor_filter_enabled"), "set_neighbor_filter_enabled", "is_neighbor_filter_enabled");
960
}
961
962