From 7913e049505e86e7721395b6c32686967bc9ad3c Mon Sep 17 00:00:00 2001 From: Yuri Roubinsky Date: Sun, 20 Mar 2022 17:20:32 +0300 Subject: Rename `AStar` to `AStar3D` --- core/math/a_star.cpp | 144 +++++++++--------- core/math/a_star.h | 12 +- core/register_core_types.cpp | 2 +- doc/classes/AStar.xml | 329 ----------------------------------------- doc/classes/AStar2D.xml | 2 +- doc/classes/AStar3D.xml | 329 +++++++++++++++++++++++++++++++++++++++++ scene/register_scene_types.cpp | 1 + tests/core/math/test_astar.h | 14 +- 8 files changed, 417 insertions(+), 416 deletions(-) delete mode 100644 doc/classes/AStar.xml create mode 100644 doc/classes/AStar3D.xml diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 14057b96be..4212b43621 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -33,7 +33,7 @@ #include "core/math/geometry_3d.h" #include "core/object/script_language.h" -int AStar::get_available_point_id() const { +int AStar3D::get_available_point_id() const { if (points.has(last_free_id)) { int cur_new_id = last_free_id + 1; while (points.has(cur_new_id)) { @@ -45,7 +45,7 @@ int AStar::get_available_point_id() const { return last_free_id; } -void AStar::add_point(int p_id, const Vector3 &p_pos, real_t p_weight_scale) { +void AStar3D::add_point(int p_id, const Vector3 &p_pos, real_t p_weight_scale) { ERR_FAIL_COND_MSG(p_id < 0, vformat("Can't add a point with negative id: %d.", p_id)); ERR_FAIL_COND_MSG(p_weight_scale < 1, vformat("Can't add a point with weight scale less than one: %f.", p_weight_scale)); @@ -68,7 +68,7 @@ void AStar::add_point(int p_id, const Vector3 &p_pos, real_t p_weight_scale) { } } -Vector3 AStar::get_point_position(int p_id) const { +Vector3 AStar3D::get_point_position(int p_id) const { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, Vector3(), vformat("Can't get point's position. Point with id: %d doesn't exist.", p_id)); @@ -76,7 +76,7 @@ Vector3 AStar::get_point_position(int p_id) const { return p->pos; } -void AStar::set_point_position(int p_id, const Vector3 &p_pos) { +void AStar3D::set_point_position(int p_id, const Vector3 &p_pos) { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's position. Point with id: %d doesn't exist.", p_id)); @@ -84,7 +84,7 @@ void AStar::set_point_position(int p_id, const Vector3 &p_pos) { p->pos = p_pos; } -real_t AStar::get_point_weight_scale(int p_id) const { +real_t AStar3D::get_point_weight_scale(int p_id) const { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, 0, vformat("Can't get point's weight scale. Point with id: %d doesn't exist.", p_id)); @@ -92,7 +92,7 @@ real_t AStar::get_point_weight_scale(int p_id) const { return p->weight_scale; } -void AStar::set_point_weight_scale(int p_id, real_t p_weight_scale) { +void AStar3D::set_point_weight_scale(int p_id, real_t p_weight_scale) { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's weight scale. Point with id: %d doesn't exist.", p_id)); @@ -101,7 +101,7 @@ void AStar::set_point_weight_scale(int p_id, real_t p_weight_scale) { p->weight_scale = p_weight_scale; } -void AStar::remove_point(int p_id) { +void AStar3D::remove_point(int p_id) { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't remove point. Point with id: %d doesn't exist.", p_id)); @@ -127,7 +127,7 @@ void AStar::remove_point(int p_id) { last_free_id = p_id; } -void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { +void AStar3D::connect_points(int p_id, int p_with_id, bool bidirectional) { ERR_FAIL_COND_MSG(p_id == p_with_id, vformat("Can't connect point with id: %d to itself.", p_id)); Point *a; @@ -165,7 +165,7 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { segments.insert(s); } -void AStar::disconnect_points(int p_id, int p_with_id, bool bidirectional) { +void AStar3D::disconnect_points(int p_id, int p_with_id, bool bidirectional) { Point *a; bool a_exists = points.lookup(p_id, a); ERR_FAIL_COND_MSG(!a_exists, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_id)); @@ -205,11 +205,11 @@ void AStar::disconnect_points(int p_id, int p_with_id, bool bidirectional) { } } -bool AStar::has_point(int p_id) const { +bool AStar3D::has_point(int p_id) const { return points.has(p_id); } -Array AStar::get_point_ids() { +Array AStar3D::get_point_ids() { Array point_list; for (OAHashMap::Iterator it = points.iter(); it.valid; it = points.next_iter(it)) { @@ -219,7 +219,7 @@ Array AStar::get_point_ids() { return point_list; } -Vector AStar::get_point_connections(int p_id) { +Vector AStar3D::get_point_connections(int p_id) { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, Vector(), vformat("Can't get point's connections. Point with id: %d doesn't exist.", p_id)); @@ -233,7 +233,7 @@ Vector AStar::get_point_connections(int p_id) { return point_list; } -bool AStar::are_points_connected(int p_id, int p_with_id, bool bidirectional) const { +bool AStar3D::are_points_connected(int p_id, int p_with_id, bool bidirectional) const { Segment s(p_id, p_with_id); const Set::Element *element = segments.find(s); @@ -241,7 +241,7 @@ bool AStar::are_points_connected(int p_id, int p_with_id, bool bidirectional) co (bidirectional || (element->get().direction & s.direction) == s.direction); } -void AStar::clear() { +void AStar3D::clear() { last_free_id = 0; for (OAHashMap::Iterator it = points.iter(); it.valid; it = points.next_iter(it)) { memdelete(*(it.value)); @@ -250,21 +250,21 @@ void AStar::clear() { points.clear(); } -int AStar::get_point_count() const { +int AStar3D::get_point_count() const { return points.get_num_elements(); } -int AStar::get_point_capacity() const { +int AStar3D::get_point_capacity() const { return points.get_capacity(); } -void AStar::reserve_space(int p_num_nodes) { +void AStar3D::reserve_space(int p_num_nodes) { ERR_FAIL_COND_MSG(p_num_nodes <= 0, vformat("New capacity must be greater than 0, new was: %d.", p_num_nodes)); ERR_FAIL_COND_MSG((uint32_t)p_num_nodes < points.get_capacity(), vformat("New capacity must be greater than current capacity: %d, new was: %d.", points.get_capacity(), p_num_nodes)); points.reserve(p_num_nodes); } -int AStar::get_closest_point(const Vector3 &p_point, bool p_include_disabled) const { +int AStar3D::get_closest_point(const Vector3 &p_point, bool p_include_disabled) const { int closest_id = -1; real_t closest_dist = 1e20; @@ -289,7 +289,7 @@ int AStar::get_closest_point(const Vector3 &p_point, bool p_include_disabled) co return closest_id; } -Vector3 AStar::get_closest_position_in_segment(const Vector3 &p_point) const { +Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const { real_t closest_dist = 1e20; Vector3 closest_point; @@ -318,7 +318,7 @@ Vector3 AStar::get_closest_position_in_segment(const Vector3 &p_point) const { return closest_point; } -bool AStar::_solve(Point *begin_point, Point *end_point) { +bool AStar3D::_solve(Point *begin_point, Point *end_point) { pass++; if (!end_point->enabled) { @@ -380,7 +380,7 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { return found_route; } -real_t AStar::_estimate_cost(int p_from_id, int p_to_id) { +real_t AStar3D::_estimate_cost(int p_from_id, int p_to_id) { real_t scost; if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { return scost; @@ -397,7 +397,7 @@ real_t AStar::_estimate_cost(int p_from_id, int p_to_id) { return from_point->pos.distance_to(to_point->pos); } -real_t AStar::_compute_cost(int p_from_id, int p_to_id) { +real_t AStar3D::_compute_cost(int p_from_id, int p_to_id) { real_t scost; if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) { return scost; @@ -414,7 +414,7 @@ real_t AStar::_compute_cost(int p_from_id, int p_to_id) { return from_point->pos.distance_to(to_point->pos); } -Vector AStar::get_point_path(int p_from_id, int p_to_id) { +Vector AStar3D::get_point_path(int p_from_id, int p_to_id) { Point *a; bool from_exists = points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id)); @@ -463,7 +463,7 @@ Vector AStar::get_point_path(int p_from_id, int p_to_id) { return path; } -Vector AStar::get_id_path(int p_from_id, int p_to_id) { +Vector AStar3D::get_id_path(int p_from_id, int p_to_id) { Point *a; bool from_exists = points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id)); @@ -512,7 +512,7 @@ Vector AStar::get_id_path(int p_from_id, int p_to_id) { return path; } -void AStar::set_point_disabled(int p_id, bool p_disabled) { +void AStar3D::set_point_disabled(int p_id, bool p_disabled) { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set if point is disabled. Point with id: %d doesn't exist.", p_id)); @@ -520,7 +520,7 @@ void AStar::set_point_disabled(int p_id, bool p_disabled) { p->enabled = !p_disabled; } -bool AStar::is_point_disabled(int p_id) const { +bool AStar3D::is_point_disabled(int p_id) const { Point *p; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, false, vformat("Can't get if point is disabled. Point with id: %d doesn't exist.", p_id)); @@ -528,41 +528,41 @@ bool AStar::is_point_disabled(int p_id) const { return !p->enabled; } -void AStar::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar::get_available_point_id); - ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar::add_point, DEFVAL(1.0)); - ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar::get_point_position); - ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar::set_point_position); - ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar::get_point_weight_scale); - ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar::set_point_weight_scale); - ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar::remove_point); - ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar::has_point); - ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar::get_point_connections); - ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar::get_point_ids); +void AStar3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_available_point_id"), &AStar3D::get_available_point_id); + ClassDB::bind_method(D_METHOD("add_point", "id", "position", "weight_scale"), &AStar3D::add_point, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStar3D::get_point_position); + ClassDB::bind_method(D_METHOD("set_point_position", "id", "position"), &AStar3D::set_point_position); + ClassDB::bind_method(D_METHOD("get_point_weight_scale", "id"), &AStar3D::get_point_weight_scale); + ClassDB::bind_method(D_METHOD("set_point_weight_scale", "id", "weight_scale"), &AStar3D::set_point_weight_scale); + ClassDB::bind_method(D_METHOD("remove_point", "id"), &AStar3D::remove_point); + ClassDB::bind_method(D_METHOD("has_point", "id"), &AStar3D::has_point); + ClassDB::bind_method(D_METHOD("get_point_connections", "id"), &AStar3D::get_point_connections); + ClassDB::bind_method(D_METHOD("get_point_ids"), &AStar3D::get_point_ids); - ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar::set_point_disabled, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar::is_point_disabled); + ClassDB::bind_method(D_METHOD("set_point_disabled", "id", "disabled"), &AStar3D::set_point_disabled, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("is_point_disabled", "id"), &AStar3D::is_point_disabled); - ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar::connect_points, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar::disconnect_points, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar::are_points_connected, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("connect_points", "id", "to_id", "bidirectional"), &AStar3D::connect_points, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("disconnect_points", "id", "to_id", "bidirectional"), &AStar3D::disconnect_points, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("are_points_connected", "id", "to_id", "bidirectional"), &AStar3D::are_points_connected, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_point_count"), &AStar::get_point_count); - ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar::get_point_capacity); - ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar::reserve_space); - ClassDB::bind_method(D_METHOD("clear"), &AStar::clear); + ClassDB::bind_method(D_METHOD("get_point_count"), &AStar3D::get_point_count); + ClassDB::bind_method(D_METHOD("get_point_capacity"), &AStar3D::get_point_capacity); + ClassDB::bind_method(D_METHOD("reserve_space", "num_nodes"), &AStar3D::reserve_space); + ClassDB::bind_method(D_METHOD("clear"), &AStar3D::clear); - ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar::get_closest_point, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar::get_closest_position_in_segment); + ClassDB::bind_method(D_METHOD("get_closest_point", "to_position", "include_disabled"), &AStar3D::get_closest_point, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_closest_position_in_segment", "to_position"), &AStar3D::get_closest_position_in_segment); - ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStar::get_point_path); - ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStar::get_id_path); + ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStar3D::get_point_path); + ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStar3D::get_id_path); GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") } -AStar::~AStar() { +AStar3D::~AStar3D() { clear(); } @@ -660,11 +660,11 @@ real_t AStar2D::_estimate_cost(int p_from_id, int p_to_id) { return scost; } - AStar::Point *from_point; + AStar3D::Point *from_point; bool from_exists = astar.points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id)); - AStar::Point *to_point; + AStar3D::Point *to_point; bool to_exists = astar.points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id)); @@ -677,11 +677,11 @@ real_t AStar2D::_compute_cost(int p_from_id, int p_to_id) { return scost; } - AStar::Point *from_point; + AStar3D::Point *from_point; bool from_exists = astar.points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id)); - AStar::Point *to_point; + AStar3D::Point *to_point; bool to_exists = astar.points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id)); @@ -689,11 +689,11 @@ real_t AStar2D::_compute_cost(int p_from_id, int p_to_id) { } Vector AStar2D::get_point_path(int p_from_id, int p_to_id) { - AStar::Point *a; + AStar3D::Point *a; bool from_exists = astar.points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id)); - AStar::Point *b; + AStar3D::Point *b; bool to_exists = astar.points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id)); @@ -702,15 +702,15 @@ Vector AStar2D::get_point_path(int p_from_id, int p_to_id) { return ret; } - AStar::Point *begin_point = a; - AStar::Point *end_point = b; + AStar3D::Point *begin_point = a; + AStar3D::Point *end_point = b; bool found_route = _solve(begin_point, end_point); if (!found_route) { return Vector(); } - AStar::Point *p = end_point; + AStar3D::Point *p = end_point; int pc = 1; // Begin point while (p != begin_point) { pc++; @@ -723,7 +723,7 @@ Vector AStar2D::get_point_path(int p_from_id, int p_to_id) { { Vector2 *w = path.ptrw(); - AStar::Point *p2 = end_point; + AStar3D::Point *p2 = end_point; int idx = pc - 1; while (p2 != begin_point) { w[idx--] = Vector2(p2->pos.x, p2->pos.y); @@ -737,11 +737,11 @@ Vector AStar2D::get_point_path(int p_from_id, int p_to_id) { } Vector AStar2D::get_id_path(int p_from_id, int p_to_id) { - AStar::Point *a; + AStar3D::Point *a; bool from_exists = astar.points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id)); - AStar::Point *b; + AStar3D::Point *b; bool to_exists = astar.points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id)); @@ -751,15 +751,15 @@ Vector AStar2D::get_id_path(int p_from_id, int p_to_id) { return ret; } - AStar::Point *begin_point = a; - AStar::Point *end_point = b; + AStar3D::Point *begin_point = a; + AStar3D::Point *end_point = b; bool found_route = _solve(begin_point, end_point); if (!found_route) { return Vector(); } - AStar::Point *p = end_point; + AStar3D::Point *p = end_point; int pc = 1; // Begin point while (p != begin_point) { pc++; @@ -785,7 +785,7 @@ Vector AStar2D::get_id_path(int p_from_id, int p_to_id) { return path; } -bool AStar2D::_solve(AStar::Point *begin_point, AStar::Point *end_point) { +bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point) { astar.pass++; if (!end_point->enabled) { @@ -794,15 +794,15 @@ bool AStar2D::_solve(AStar::Point *begin_point, AStar::Point *end_point) { bool found_route = false; - Vector open_list; - SortArray sorter; + Vector open_list; + SortArray sorter; begin_point->g_score = 0; begin_point->f_score = _estimate_cost(begin_point->id, end_point->id); open_list.push_back(begin_point); while (!open_list.is_empty()) { - AStar::Point *p = open_list[0]; // The currently processed point + AStar3D::Point *p = open_list[0]; // The currently processed point if (p == end_point) { found_route = true; @@ -813,8 +813,8 @@ bool AStar2D::_solve(AStar::Point *begin_point, AStar::Point *end_point) { open_list.remove_at(open_list.size() - 1); p->closed_pass = astar.pass; // Mark the point as closed - for (OAHashMap::Iterator it = p->neighbours.iter(); it.valid; it = p->neighbours.next_iter(it)) { - AStar::Point *e = *(it.value); // The neighbour point + for (OAHashMap::Iterator it = p->neighbours.iter(); it.valid; it = p->neighbours.next_iter(it)) { + AStar3D::Point *e = *(it.value); // The neighbour point if (!e->enabled || e->closed_pass == astar.pass) { continue; diff --git a/core/math/a_star.h b/core/math/a_star.h index 130c202a61..bb7112fb09 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -40,8 +40,8 @@ A* pathfinding algorithm. */ -class AStar : public RefCounted { - GDCLASS(AStar, RefCounted); +class AStar3D : public RefCounted { + GDCLASS(AStar3D, RefCounted); friend class AStar2D; struct Point { @@ -156,15 +156,15 @@ public: Vector get_point_path(int p_from_id, int p_to_id); Vector get_id_path(int p_from_id, int p_to_id); - AStar() {} - ~AStar(); + AStar3D() {} + ~AStar3D(); }; class AStar2D : public RefCounted { GDCLASS(AStar2D, RefCounted); - AStar astar; + AStar3D astar; - bool _solve(AStar::Point *begin_point, AStar::Point *end_point); + bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point); protected: static void _bind_methods(); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index d05280f6a5..1a306e88fc 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -227,7 +227,7 @@ void register_core_types() { GDREGISTER_CLASS(PackedDataContainer); GDREGISTER_ABSTRACT_CLASS(PackedDataContainerRef); - GDREGISTER_CLASS(AStar); + GDREGISTER_CLASS(AStar3D); GDREGISTER_CLASS(AStar2D); GDREGISTER_CLASS(EncodedObjectAsID); GDREGISTER_CLASS(RandomNumberGenerator); diff --git a/doc/classes/AStar.xml b/doc/classes/AStar.xml deleted file mode 100644 index cb76fe8cf6..0000000000 --- a/doc/classes/AStar.xml +++ /dev/null @@ -1,329 +0,0 @@ - - - - An implementation of A* to find the shortest paths among connected points in space. - - - A* (A star) is a computer algorithm that is widely used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in three-dimensional space and Euclidean distances by default. - You must add points manually with [method add_point] and create segments manually with [method connect_points]. Then you can test if there is a path between two points with the [method are_points_connected] function, get a path containing indices by [method get_id_path], or one containing actual coordinates with [method get_point_path]. - It is also possible to use non-Euclidean distances. To do so, create a class that extends [code]AStar[/code] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example. - [codeblocks] - [gdscript] - class MyAStar: - extends AStar - - func _compute_cost(u, v): - return abs(u - v) - - func _estimate_cost(u, v): - return min(0, abs(u - v) - 1) - [/gdscript] - [csharp] - public class MyAStar : AStar - { - public override float _ComputeCost(int u, int v) - { - return Mathf.Abs(u - v); - } - public override float _EstimateCost(int u, int v) - { - return Mathf.Min(0, Mathf.Abs(u - v) - 1); - } - } - [/csharp] - [/codeblocks] - [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) <= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information. - If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path. - - - - - - - - - - Called when computing the cost between two connected points. - Note that this function is hidden in the default [code]AStar[/code] class. - - - - - - - - Called when estimating the cost between a point and the path's ending point. - Note that this function is hidden in the default [code]AStar[/code] class. - - - - - - - - - Adds a new point at the given position with the given identifier. The [code]id[/code] must be 0 or larger, and the [code]weight_scale[/code] must be 1 or larger. - The [code]weight_scale[/code] is multiplied by the result of [method _compute_cost] when determining the overall cost of traveling across a segment from a neighboring point to this point. Thus, all else being equal, the algorithm prefers points with lower [code]weight_scale[/code]s to form a path. - [codeblocks] - [gdscript] - var astar = AStar.new() - astar.add_point(1, Vector3(1, 0, 0), 4) # Adds the point (1, 0, 0) with weight_scale 4 and id 1 - [/gdscript] - [csharp] - var astar = new AStar(); - astar.AddPoint(1, new Vector3(1, 0, 0), 4); // Adds the point (1, 0, 0) with weight_scale 4 and id 1 - [/csharp] - [/codeblocks] - If there already exists a point for the given [code]id[/code], its position and weight scale are updated to the given values. - - - - - - - - - Returns whether the two given points are directly connected by a segment. If [code]bidirectional[/code] is [code]false[/code], returns whether movement from [code]id[/code] to [code]to_id[/code] is possible through this segment. - - - - - - Clears all the points and segments. - - - - - - - - - Creates a segment between the given points. If [code]bidirectional[/code] is [code]false[/code], only movement from [code]id[/code] to [code]to_id[/code] is allowed, not the reverse direction. - [codeblocks] - [gdscript] - var astar = AStar.new() - astar.add_point(1, Vector3(1, 1, 0)) - astar.add_point(2, Vector3(0, 5, 0)) - astar.connect_points(1, 2, false) - [/gdscript] - [csharp] - var astar = new AStar(); - astar.AddPoint(1, new Vector3(1, 1, 0)); - astar.AddPoint(2, new Vector3(0, 5, 0)); - astar.ConnectPoints(1, 2, false); - [/csharp] - [/codeblocks] - - - - - - - - - Deletes the segment between the given points. If [code]bidirectional[/code] is [code]false[/code], only movement from [code]id[/code] to [code]to_id[/code] is prevented, and a unidirectional segment possibly remains. - - - - - - Returns the next available point ID with no point associated to it. - - - - - - - - Returns the ID of the closest point to [code]to_position[/code], optionally taking disabled points into account. Returns [code]-1[/code] if there are no points in the points pool. - [b]Note:[/b] If several points are the closest to [code]to_position[/code], the one with the smallest ID will be returned, ensuring a deterministic result. - - - - - - - Returns the closest position to [code]to_position[/code] that resides inside a segment between two connected points. - [codeblocks] - [gdscript] - var astar = AStar.new() - astar.add_point(1, Vector3(0, 0, 0)) - astar.add_point(2, Vector3(0, 5, 0)) - astar.connect_points(1, 2) - var res = astar.get_closest_position_in_segment(Vector3(3, 3, 0)) # Returns (0, 3, 0) - [/gdscript] - [csharp] - var astar = new AStar(); - astar.AddPoint(1, new Vector3(0, 0, 0)); - astar.AddPoint(2, new Vector3(0, 5, 0)); - astar.ConnectPoints(1, 2); - Vector3 res = astar.GetClosestPositionInSegment(new Vector3(3, 3, 0)); // Returns (0, 3, 0) - [/csharp] - [/codeblocks] - The result is in the segment that goes from [code]y = 0[/code] to [code]y = 5[/code]. It's the closest position in the segment to the given point. - - - - - - - - Returns an array with the IDs of the points that form the path found by AStar between the given points. The array is ordered from the starting point to the ending point of the path. - [codeblocks] - [gdscript] - var astar = AStar.new() - astar.add_point(1, Vector3(0, 0, 0)) - astar.add_point(2, Vector3(0, 1, 0), 1) # Default weight is 1 - astar.add_point(3, Vector3(1, 1, 0)) - astar.add_point(4, Vector3(2, 0, 0)) - - astar.connect_points(1, 2, false) - astar.connect_points(2, 3, false) - astar.connect_points(4, 3, false) - astar.connect_points(1, 4, false) - - var res = astar.get_id_path(1, 3) # Returns [1, 2, 3] - [/gdscript] - [csharp] - var astar = new AStar(); - astar.AddPoint(1, new Vector3(0, 0, 0)); - astar.AddPoint(2, new Vector3(0, 1, 0), 1); // Default weight is 1 - astar.AddPoint(3, new Vector3(1, 1, 0)); - astar.AddPoint(4, new Vector3(2, 0, 0)); - astar.ConnectPoints(1, 2, false); - astar.ConnectPoints(2, 3, false); - astar.ConnectPoints(4, 3, false); - astar.ConnectPoints(1, 4, false); - int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] - [/csharp] - [/codeblocks] - If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2. - - - - - - Returns the capacity of the structure backing the points, useful in conjunction with [code]reserve_space[/code]. - - - - - - - Returns an array with the IDs of the points that form the connection with the given point. - [codeblocks] - [gdscript] - var astar = AStar.new() - astar.add_point(1, Vector3(0, 0, 0)) - astar.add_point(2, Vector3(0, 1, 0)) - astar.add_point(3, Vector3(1, 1, 0)) - astar.add_point(4, Vector3(2, 0, 0)) - - astar.connect_points(1, 2, true) - astar.connect_points(1, 3, true) - - var neighbors = astar.get_point_connections(1) # Returns [2, 3] - [/gdscript] - [csharp] - var astar = new AStar(); - astar.AddPoint(1, new Vector3(0, 0, 0)); - astar.AddPoint(2, new Vector3(0, 1, 0)); - astar.AddPoint(3, new Vector3(1, 1, 0)); - astar.AddPoint(4, new Vector3(2, 0, 0)); - astar.ConnectPoints(1, 2, true); - astar.ConnectPoints(1, 3, true); - - int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] - [/csharp] - [/codeblocks] - - - - - - Returns the number of points currently in the points pool. - - - - - - Returns an array of all point IDs. - - - - - - - - Returns an array with the points that are in the path found by AStar between the given points. The array is ordered from the starting point to the ending point of the path. - [b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty [PackedVector3Array] and will print an error message. - - - - - - - Returns the position of the point associated with the given [code]id[/code]. - - - - - - - Returns the weight scale of the point associated with the given [code]id[/code]. - - - - - - - Returns whether a point associated with the given [code]id[/code] exists. - - - - - - - Returns whether a point is disabled or not for pathfinding. By default, all points are enabled. - - - - - - - Removes the point associated with the given [code]id[/code] from the points pool. - - - - - - - Reserves space internally for [code]num_nodes[/code] points, useful if you're adding a known large number of points at once, for a grid for instance. New capacity must be greater or equals to old capacity. - - - - - - - - Disables or enables the specified point for pathfinding. Useful for making a temporary obstacle. - - - - - - - - Sets the [code]position[/code] for the point with the given [code]id[/code]. - - - - - - - - Sets the [code]weight_scale[/code] for the point with the given [code]id[/code]. The [code]weight_scale[/code] is multiplied by the result of [method _compute_cost] when determining the overall cost of traveling across a segment from a neighboring point to this point. - - - - diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml index 2dde3ad340..4b65a64389 100644 --- a/doc/classes/AStar2D.xml +++ b/doc/classes/AStar2D.xml @@ -4,7 +4,7 @@ AStar class representation that uses 2D vectors as edges. - This is a wrapper for the [AStar] class which uses 2D vectors instead of 3D vectors. + This is a wrapper for the [AStar3D] class which uses 2D vectors instead of 3D vectors. diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml new file mode 100644 index 0000000000..3087b9e363 --- /dev/null +++ b/doc/classes/AStar3D.xml @@ -0,0 +1,329 @@ + + + + An implementation of A* to find the shortest paths among connected points in space. + + + A* (A star) is a computer algorithm that is widely used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in three-dimensional space and Euclidean distances by default. + You must add points manually with [method add_point] and create segments manually with [method connect_points]. Then you can test if there is a path between two points with the [method are_points_connected] function, get a path containing indices by [method get_id_path], or one containing actual coordinates with [method get_point_path]. + It is also possible to use non-Euclidean distances. To do so, create a class that extends [code]AStar3D[/code] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example. + [codeblocks] + [gdscript] + class MyAStar: + extends AStar3D + + func _compute_cost(u, v): + return abs(u - v) + + func _estimate_cost(u, v): + return min(0, abs(u - v) - 1) + [/gdscript] + [csharp] + public class MyAStar : AStar3D + { + public override float _ComputeCost(int u, int v) + { + return Mathf.Abs(u - v); + } + public override float _EstimateCost(int u, int v) + { + return Mathf.Min(0, Mathf.Abs(u - v) - 1); + } + } + [/csharp] + [/codeblocks] + [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) <= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information. + If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path. + + + + + + + + + + Called when computing the cost between two connected points. + Note that this function is hidden in the default [code]AStar3D[/code] class. + + + + + + + + Called when estimating the cost between a point and the path's ending point. + Note that this function is hidden in the default [code]AStar3D[/code] class. + + + + + + + + + Adds a new point at the given position with the given identifier. The [code]id[/code] must be 0 or larger, and the [code]weight_scale[/code] must be 1 or larger. + The [code]weight_scale[/code] is multiplied by the result of [method _compute_cost] when determining the overall cost of traveling across a segment from a neighboring point to this point. Thus, all else being equal, the algorithm prefers points with lower [code]weight_scale[/code]s to form a path. + [codeblocks] + [gdscript] + var astar = AStar3D.new() + astar.add_point(1, Vector3(1, 0, 0), 4) # Adds the point (1, 0, 0) with weight_scale 4 and id 1 + [/gdscript] + [csharp] + var astar = new AStar3D(); + astar.AddPoint(1, new Vector3(1, 0, 0), 4); // Adds the point (1, 0, 0) with weight_scale 4 and id 1 + [/csharp] + [/codeblocks] + If there already exists a point for the given [code]id[/code], its position and weight scale are updated to the given values. + + + + + + + + + Returns whether the two given points are directly connected by a segment. If [code]bidirectional[/code] is [code]false[/code], returns whether movement from [code]id[/code] to [code]to_id[/code] is possible through this segment. + + + + + + Clears all the points and segments. + + + + + + + + + Creates a segment between the given points. If [code]bidirectional[/code] is [code]false[/code], only movement from [code]id[/code] to [code]to_id[/code] is allowed, not the reverse direction. + [codeblocks] + [gdscript] + var astar = AStar3D.new() + astar.add_point(1, Vector3(1, 1, 0)) + astar.add_point(2, Vector3(0, 5, 0)) + astar.connect_points(1, 2, false) + [/gdscript] + [csharp] + var astar = new AStar3D(); + astar.AddPoint(1, new Vector3(1, 1, 0)); + astar.AddPoint(2, new Vector3(0, 5, 0)); + astar.ConnectPoints(1, 2, false); + [/csharp] + [/codeblocks] + + + + + + + + + Deletes the segment between the given points. If [code]bidirectional[/code] is [code]false[/code], only movement from [code]id[/code] to [code]to_id[/code] is prevented, and a unidirectional segment possibly remains. + + + + + + Returns the next available point ID with no point associated to it. + + + + + + + + Returns the ID of the closest point to [code]to_position[/code], optionally taking disabled points into account. Returns [code]-1[/code] if there are no points in the points pool. + [b]Note:[/b] If several points are the closest to [code]to_position[/code], the one with the smallest ID will be returned, ensuring a deterministic result. + + + + + + + Returns the closest position to [code]to_position[/code] that resides inside a segment between two connected points. + [codeblocks] + [gdscript] + var astar = AStar3D.new() + astar.add_point(1, Vector3(0, 0, 0)) + astar.add_point(2, Vector3(0, 5, 0)) + astar.connect_points(1, 2) + var res = astar.get_closest_position_in_segment(Vector3(3, 3, 0)) # Returns (0, 3, 0) + [/gdscript] + [csharp] + var astar = new AStar3D(); + astar.AddPoint(1, new Vector3(0, 0, 0)); + astar.AddPoint(2, new Vector3(0, 5, 0)); + astar.ConnectPoints(1, 2); + Vector3 res = astar.GetClosestPositionInSegment(new Vector3(3, 3, 0)); // Returns (0, 3, 0) + [/csharp] + [/codeblocks] + The result is in the segment that goes from [code]y = 0[/code] to [code]y = 5[/code]. It's the closest position in the segment to the given point. + + + + + + + + Returns an array with the IDs of the points that form the path found by AStar3D between the given points. The array is ordered from the starting point to the ending point of the path. + [codeblocks] + [gdscript] + var astar = AStar3D.new() + astar.add_point(1, Vector3(0, 0, 0)) + astar.add_point(2, Vector3(0, 1, 0), 1) # Default weight is 1 + astar.add_point(3, Vector3(1, 1, 0)) + astar.add_point(4, Vector3(2, 0, 0)) + + astar.connect_points(1, 2, false) + astar.connect_points(2, 3, false) + astar.connect_points(4, 3, false) + astar.connect_points(1, 4, false) + + var res = astar.get_id_path(1, 3) # Returns [1, 2, 3] + [/gdscript] + [csharp] + var astar = new AStar3D(); + astar.AddPoint(1, new Vector3(0, 0, 0)); + astar.AddPoint(2, new Vector3(0, 1, 0), 1); // Default weight is 1 + astar.AddPoint(3, new Vector3(1, 1, 0)); + astar.AddPoint(4, new Vector3(2, 0, 0)); + astar.ConnectPoints(1, 2, false); + astar.ConnectPoints(2, 3, false); + astar.ConnectPoints(4, 3, false); + astar.ConnectPoints(1, 4, false); + int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] + [/csharp] + [/codeblocks] + If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2. + + + + + + Returns the capacity of the structure backing the points, useful in conjunction with [code]reserve_space[/code]. + + + + + + + Returns an array with the IDs of the points that form the connection with the given point. + [codeblocks] + [gdscript] + var astar = AStar3D.new() + astar.add_point(1, Vector3(0, 0, 0)) + astar.add_point(2, Vector3(0, 1, 0)) + astar.add_point(3, Vector3(1, 1, 0)) + astar.add_point(4, Vector3(2, 0, 0)) + + astar.connect_points(1, 2, true) + astar.connect_points(1, 3, true) + + var neighbors = astar.get_point_connections(1) # Returns [2, 3] + [/gdscript] + [csharp] + var astar = new AStar3D(); + astar.AddPoint(1, new Vector3(0, 0, 0)); + astar.AddPoint(2, new Vector3(0, 1, 0)); + astar.AddPoint(3, new Vector3(1, 1, 0)); + astar.AddPoint(4, new Vector3(2, 0, 0)); + astar.ConnectPoints(1, 2, true); + astar.ConnectPoints(1, 3, true); + + int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] + [/csharp] + [/codeblocks] + + + + + + Returns the number of points currently in the points pool. + + + + + + Returns an array of all point IDs. + + + + + + + + Returns an array with the points that are in the path found by AStar3D between the given points. The array is ordered from the starting point to the ending point of the path. + [b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty [PackedVector3Array] and will print an error message. + + + + + + + Returns the position of the point associated with the given [code]id[/code]. + + + + + + + Returns the weight scale of the point associated with the given [code]id[/code]. + + + + + + + Returns whether a point associated with the given [code]id[/code] exists. + + + + + + + Returns whether a point is disabled or not for pathfinding. By default, all points are enabled. + + + + + + + Removes the point associated with the given [code]id[/code] from the points pool. + + + + + + + Reserves space internally for [code]num_nodes[/code] points, useful if you're adding a known large number of points at once, for a grid for instance. New capacity must be greater or equals to old capacity. + + + + + + + + Disables or enables the specified point for pathfinding. Useful for making a temporary obstacle. + + + + + + + + Sets the [code]position[/code] for the point with the given [code]id[/code]. + + + + + + + + Sets the [code]weight_scale[/code] for the point with the given [code]id[/code]. The [code]weight_scale[/code] is multiplied by the result of [method _compute_cost] when determining the overall cost of traveling across a segment from a neighboring point to this point. + + + + diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index fb5d57ab9e..52d6f6e63b 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -895,6 +895,7 @@ void register_scene_types() { #ifndef DISABLE_DEPRECATED // Dropped in 4.0, near approximation. ClassDB::add_compatibility_class("AnimationTreePlayer", "AnimationTree"); + ClassDB::add_compatibility_class("AStar", "AStar3D"); ClassDB::add_compatibility_class("BitmapFont", "Font"); ClassDB::add_compatibility_class("DynamicFont", "Font"); ClassDB::add_compatibility_class("DynamicFontData", "FontData"); diff --git a/tests/core/math/test_astar.h b/tests/core/math/test_astar.h index 859172dca3..1306d3c20e 100644 --- a/tests/core/math/test_astar.h +++ b/tests/core/math/test_astar.h @@ -37,7 +37,7 @@ namespace TestAStar { -class ABCX : public AStar { +class ABCX : public AStar3D { public: enum { A, @@ -66,7 +66,7 @@ public: } }; -TEST_CASE("[AStar] ABC path") { +TEST_CASE("[AStar3D] ABC path") { ABCX abcx; Vector path = abcx.get_id_path(ABCX::A, ABCX::C); REQUIRE(path.size() == 3); @@ -75,7 +75,7 @@ TEST_CASE("[AStar] ABC path") { CHECK(path[2] == ABCX::C); } -TEST_CASE("[AStar] ABCX path") { +TEST_CASE("[AStar3D] ABCX path") { ABCX abcx; Vector path = abcx.get_id_path(ABCX::X, ABCX::C); REQUIRE(path.size() == 4); @@ -85,8 +85,8 @@ TEST_CASE("[AStar] ABCX path") { CHECK(path[3] == ABCX::C); } -TEST_CASE("[AStar] Add/Remove") { - AStar a; +TEST_CASE("[AStar3D] Add/Remove") { + AStar3D a; // Manual tests. a.add_point(1, Vector3(0, 0, 0)); @@ -213,13 +213,13 @@ TEST_CASE("[AStar] Add/Remove") { // It's been great work, cheers. \(^ ^)/ } -TEST_CASE("[Stress][AStar] Find paths") { +TEST_CASE("[Stress][AStar3D] Find paths") { // Random stress tests with Floyd-Warshall. const int N = 30; Math::seed(0); for (int test = 0; test < 1000; test++) { - AStar a; + AStar3D a; Vector3 p[N]; bool adj[N][N] = { { false } }; -- cgit v1.2.3