summaryrefslogtreecommitdiffhomepage
path: root/src/search-astar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/search-astar.cpp')
-rw-r--r--src/search-astar.cpp372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/search-astar.cpp b/src/search-astar.cpp
new file mode 100644
index 00000000..eb2812a2
--- /dev/null
+++ b/src/search-astar.cpp
@@ -0,0 +1,372 @@
+#include "search-astar.hpp"
+#include "search-bbox.hpp"
+#include "search-constants.hpp"
+#include "search-cache.hpp"
+#include "compat/format.hpp"
+#include "compat/vector-wrapper.hpp"
+#include "compat/heap.hpp"
+#include "object.hpp"
+#include "world.hpp"
+#include "point.hpp"
+#include <cstdio>
+#include <Corrade/Containers/GrowableArray.h>
+#include <Corrade/Containers/StaticArray.h> // todo remove
+#include <Magnum/Math/Vector2.h>
+#include <Magnum/Math/Functions.h>
+#include <Magnum/Timeline.h>
+
+namespace floormat {
+
+struct astar::visited
+{
+ uint32_t dist = (uint32_t)-1;
+ uint32_t prev = (uint32_t)-1;
+ point pt;
+};
+
+using visited = astar::visited;
+using Search::bbox;
+using Search::div_size;
+using Search::div_factor;
+using Search::min_size;
+
+namespace {
+
+constexpr bbox<Int> bbox_from_pos1(point pt, Vector2ui size)
+{
+ auto center = Vector2i(pt.local()) * iTILE_SIZE2 + Vector2i(pt.offset());
+ auto top_left = center - Vector2i(size / 2);
+ auto bottom_right = top_left + Vector2i(size);
+ return { top_left, bottom_right };
+}
+
+constexpr bbox<Int> bbox_from_pos2(point pt, point from, Vector2ui size)
+{
+ constexpr auto chunk_size = iTILE_SIZE2 * (int)TILE_MAX_DIM;
+ auto nchunks = from.chunk() - pt.chunk();
+ auto chunk_pixels = nchunks * chunk_size;
+
+ auto bb0_ = bbox_from_pos1(from, size);
+ auto bb0 = bbox<Int>{ bb0_.min + chunk_pixels, bb0_.max + chunk_pixels };
+ auto bb = bbox_from_pos1(pt, size);
+
+ auto min = Math::min(bb0.min, bb.min);
+ auto max = Math::max(bb0.max, bb.max);
+
+ return { min, max };
+}
+
+static_assert(bbox_from_pos1({{}, {0, 0}, {15, 35}}, {10, 20}) == bbox<Int>{{10, 25}, {20, 45}});
+static_assert(bbox_from_pos2({{{1, 1}, {1, 15}, 0}, {1, -1}},
+ {{{1, 2}, {1, 0}, 0}, {1, -1}},
+ {256, 256}) == bbox<Int>{{-63, 831}, {193, 1151}});
+
+constexpr auto directions = []() constexpr
+{
+ struct pair { Vector2i dir; uint32_t len; };
+ constexpr auto len1 = div_size;
+ constexpr auto len2 = (uint32_t)(len1.length() + 0.5f); // NOLINT
+ std::array<pair, 8> array = {{
+ { { -1, -1 }, len2 },
+ { { 1, 1 }, len2 },
+ { { -1, 1 }, len2 },
+ { { 1, -1 }, len2 },
+ { { -1, 0 }, len1.x() },
+ { { 0, -1 }, len1.y() },
+ { { 1, 0 }, len1.x() },
+ { { 0, 1 }, len1.y() },
+ }};
+ for (auto& [vec, len] : array)
+ {
+ vec *= div_size;
+ vec += Vector2i(1);
+ }
+#if 0
+ for (auto i = 0uz; i < array.size(); i++)
+ for (auto j = 0uz; j < i; j++)
+ fm_assert(array[i].dir != array[j].dir);
+#endif
+ return array;
+}();
+
+struct heap_comparator
+{
+ const Array<visited>& nodes; // NOLINT
+ inline heap_comparator(const Array<visited>& nodes) : nodes{nodes} {}
+ inline bool operator()(uint32_t a, uint32_t b) const { return nodes[b].dist < nodes[a].dist; }
+};
+
+inline uint32_t distance(point a, point b)
+{
+ Vector2i dist;
+ dist += (a.coord() - b.coord())*iTILE_SIZE2;
+ dist += Vector2i(a.offset()) - Vector2i(b.offset());
+ return (uint32_t)Math::ceil(Math::sqrt(Vector2(dist).dot()));
+}
+
+inline uint32_t distance_l2(point a, point b)
+{
+ Vector2i dist;
+ dist += (a.coord() - b.coord())*iTILE_SIZE2;
+ dist += Vector2i(a.offset()) - Vector2i(b.offset());
+ return (uint32_t)Math::abs(dist).sum();
+}
+
+void set_result_from_idx(path_search_result& result, const Array<visited>& nodes,
+ point to, const uint32_t idx)
+{
+ fm_debug_assert(idx != (uint32_t)-1);
+
+ auto& path = result.raw_path().vec;
+ path.clear();
+
+ const auto& to_node = nodes[idx];
+ if (result.is_found() && to != to_node.pt)
+ path.push_back(to);
+ result.set_cost(to_node.dist);
+
+ auto i = idx;
+ do {
+ const auto& node = nodes[i];
+ path.push_back(node.pt);
+ i = node.prev;
+ } while (i != (uint32_t)-1);
+
+ std::reverse(path.begin(), path.end());
+}
+
+} // namespace
+
+astar::astar()
+{
+ reserve(initial_capacity);
+}
+
+astar::~astar() noexcept = default;
+
+void astar::reserve(size_t capacity)
+{
+ arrayReserve(nodes, capacity);
+ arrayReserve(Q, capacity);
+}
+
+void astar::clear()
+{
+ arrayResize(nodes, 0);
+ arrayResize(Q, 0);
+}
+
+void astar::add_to_heap(uint32_t id)
+{
+ arrayAppend(Q, id);
+ Heap::push_heap(Q.begin(), Q.end(), heap_comparator{nodes});
+}
+
+uint32_t astar::pop_from_heap()
+{
+ Heap::pop_heap(Q.begin(), Q.end(), heap_comparator{nodes});
+ const auto id = Q.back();
+ arrayRemoveSuffix(Q);
+ return id;
+}
+
+path_search_result astar::Dijkstra(world& w, const point from, const point to,
+ object_id own_id, uint32_t max_dist, Vector2ub own_size_,
+ int debug, const pred& p)
+{
+#ifdef FM_NO_DEBUG
+ (void)debug;
+#endif
+
+ Timeline timeline; timeline.start();
+
+ clear();
+ _cache->allocate(from, max_dist);
+
+ const auto own_size = Math::max(Vector2ui(own_size_), min_size);
+ constexpr auto goal_thres = (uint32_t)(div_size.length() + 1.5f);
+
+ if (from.coord().z() != to.coord().z()) [[unlikely]]
+ return {};
+
+ // todo try removing this eventually
+ if (from.coord().z() != 0) [[unlikely]]
+ return {};
+
+ if (!path_search::is_passable(w, *_cache, from.coord(), from.offset(), own_size, own_id, p))
+ return {};
+
+ if (!path_search::is_passable(w, *_cache, to.coord(), to.offset(), own_size, own_id, p))
+ return {};
+
+ constexpr int8_t div_min = -div_factor*2, div_max = div_factor*2;
+
+ for (int8_t y = div_min; y <= div_max; y++)
+ for (int8_t x = div_min; x <= div_max; x++)
+ {
+ constexpr auto min_dist = (uint32_t)((TILE_SIZE2*2.f).length() + 1.f);
+ auto off = Vector2i(x, y) * div_size;
+ auto pt = object::normalize_coords({from.coord(), {}}, off);
+ auto bb = bbox<float>(bbox_from_pos2(from, pt, own_size));
+ auto dist = distance(from, pt) + min_dist;
+
+ if (path_search::is_passable(w, *_cache, from.chunk3(), bb, own_id, p))
+ {
+ auto idx = (uint32_t)nodes.size();
+ _cache->add_index(pt, idx);
+ arrayAppend(nodes, {.dist = dist, .prev = (uint32_t)-1, .pt = pt, });
+ add_to_heap(idx);
+ }
+ }
+
+ auto closest_dist = (uint32_t)-1;
+ uint32_t closest_idx = (uint32_t)-1;
+ auto goal_idx = (uint32_t)-1;
+
+ while (!Q.isEmpty())
+ {
+ const auto cur_idx = pop_from_heap();
+ point cur_pt;
+ uint32_t cur_dist;
+ { auto& n = nodes[cur_idx];
+ cur_pt = n.pt;
+ cur_dist = n.dist;
+ }
+
+ if (cur_dist >= max_dist) [[unlikely]]
+ continue;
+
+ if (auto d = distance(cur_pt, to); d < closest_dist) [[unlikely]]
+ {
+ closest_dist = d;
+ closest_idx = cur_idx;
+
+#ifndef FM_NO_DEBUG
+ if (debug >= 2) [[unlikely]]
+ DBG_nospace << "closest node"
+ << " px:" << closest_dist << " path:" << cur_dist
+ << " pos:" << cur_pt;
+#endif
+ }
+
+ if (auto dist_to_goal = distance_l2(cur_pt, to); dist_to_goal < goal_thres) [[unlikely]]
+ {
+ auto dist = cur_dist + dist_to_goal;
+ if (auto bb = bbox<float>(bbox_from_pos2(to, cur_pt, own_size));
+ path_search::is_passable(w, *_cache, to.chunk3(), bb, own_id, p))
+ {
+ goal_idx = cur_idx;
+ max_dist = dist;
+ continue; // path can only get longer
+ }
+ }
+
+ for (auto [vec, len] : directions)
+ {
+ const auto dist = cur_dist + len;
+ if (dist >= max_dist)
+ continue;
+
+ const auto new_pt = object::normalize_coords(cur_pt, vec);
+ auto chunk_idx = _cache->get_chunk_index(Vector2i(new_pt.chunk()));
+ auto tile_idx = _cache->get_tile_index(Vector2i(new_pt.local()), new_pt.offset());
+ auto new_idx = _cache->lookup_index(chunk_idx, tile_idx);
+
+ if (new_idx != (uint32_t)-1)
+ {
+ if (nodes[new_idx].dist <= dist)
+ continue;
+ }
+
+ if (auto bb = bbox<float>(bbox_from_pos2(new_pt, cur_pt, own_size));
+ !path_search::is_passable(w, *_cache, new_pt.chunk3(), bb, own_id, p))
+ continue;
+
+ if (new_idx == (uint32_t)-1)
+ {
+ const auto sz = nodes.size();
+ new_idx = (uint32_t)sz;
+ _cache->add_index(chunk_idx, tile_idx, new_idx);
+ auto new_node = visited {
+ .dist = dist, .prev = cur_idx,
+ .pt = new_pt,
+ };
+ arrayAppend(nodes, new_node);
+ }
+ else
+ {
+ auto& n = nodes[new_idx];
+ n.dist = dist;
+ n.prev = cur_idx;
+ }
+
+#ifndef FM_NO_DEBUG
+ if (debug >= 3) [[unlikely]]
+ DBG_nospace << " path:" << dist
+ << " pos:" << Vector3i(new_pt.coord())
+ << ";" << new_pt.offset();
+#endif
+
+ add_to_heap(new_idx);
+ }
+ }
+
+ //fm_debug_assert(nodes.size() == indexes.size());
+
+ path_search_result result;
+
+ if (goal_idx != (uint32_t)-1)
+ {
+ result.set_found(true);
+ result.set_distance(0);
+ set_result_from_idx(result, nodes, to, goal_idx);
+ }
+ else if (closest_idx != (uint32_t)-1)
+ {
+ result.set_found(false);
+ result.set_distance(closest_dist);
+ set_result_from_idx(result, nodes, to, closest_idx);
+ }
+
+ result.set_time(timeline.currentFrameTime());
+
+#ifndef FM_NO_DEBUG
+ if (debug >= 1) [[unlikely]]
+ {
+ auto d0_ =
+ Vector2i(Math::abs(from.coord() - to.coord())) * iTILE_SIZE2
+ + Vector2i(Math::abs(Vector2i(from.offset()) - Vector2i(to.offset())));
+ auto d0 = (uint32_t)d0_.length();
+ char buf[128];
+ size_t len = 0;
+ const auto time = (uint32_t)Math::ceil(result.time() * 1e3f);
+ if (goal_idx != (uint32_t)-1)
+ {
+ auto d = nodes[goal_idx].dist;
+ len = snformat(buf, "Dijkstra: found in {} ms "
+ "len:{} len0:{} ratio:{:.4}\n"_cf,
+ time, d, d0,
+ d > 0 && d0 > 0 ? (float)d/(float)d0 : 1);
+ }
+ else if (closest_idx != (uint32_t)-1)
+ {
+ const auto& closest = nodes[closest_idx];
+ fm_assert(closest.dist != 0 && closest.dist != (uint32_t)-1);
+ len = snformat(buf, "Dijkstra: no path found in {} ms "
+ "closest:{} len:{} len0:{} ratio:{:.4}\n"_cf,
+ time, closest_dist, closest.dist, d0,
+ d0 > 0 ? (float)closest.dist/(float)d0 : 1);
+ }
+ if (len)
+ {
+ len = Math::min(len, std::size(buf)-1);
+ std::fwrite(buf, len, 1, stdout);
+ std::fflush(stdout);
+ }
+ }
+#endif
+
+ return result;
+}
+
+} // namespace floormat