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