diff --git a/README.md b/README.md index 3e1cda9..e770918 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # astar -Fast and easy to use header only 2D astar algorithm library in C++20. +Fast and easy to use standalone header only 2D astar algorithm library in C++20. I made it for learning how the astar algorithm works, try to make the fastest, tested and configurable as possible for my needs (future games and works). @@ -24,6 +24,20 @@ It is an [astar algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm), t * [x] Debug mode in template argument and lambda function * [x] Support direct access and not access to the map * [x] Unit tests and benchmarks +* [ ] Working CI (WIP) + +### Heuristic function + +You can set the heuristic function to calculate the distance between two points and return the cost. + +| Heuristic | C++ Function | Description | +|-----------|--------------|-------------| +| euclidean | AStar::Heuristic::euclidean | Default | +| manhattan | AStar::Heuristic::manhattan | | +| octagonal | AStar::Heuristic::octagonal | | +| chebyshev | AStar::Heuristic::chebyshev | | +| euclideanNoSQR | AStar::Heuristic::euclideanNoSQR | | +| dijkstra | AStar::Heuristic::dijkstra | Always return 0 | # How to use it diff --git a/include/astar/astar.hpp b/include/astar/astar.hpp index 9aa8816..39d2e13 100644 --- a/include/astar/astar.hpp +++ b/include/astar/astar.hpp @@ -18,22 +18,22 @@ #include #include -template -concept ArithmeticType = std::is_arithmetic::value; +template +concept ArithmeticType = std::is_arithmetic::value; -template -concept IntegerType = std::is_integral::value; +template +concept IntegerType = std::is_integral::value; -template -concept FloatingPointType = std::is_floating_point::value; +template +concept FloatingPointType = std::is_floating_point::value; namespace AStar { -template +template class Vec2 { public: Vec2() = default; - Vec2(T x_, T y_) : x(x_), y(y_) {} + Vec2(CoordinateType x_, CoordinateType y_) : x(x_), y(y_) {} bool operator==(const Vec2& pos) const noexcept { return (x == pos.x && y == pos.y); } Vec2 operator=(const Vec2& pos) noexcept { @@ -49,25 +49,25 @@ class Vec2 { size_t operator()(const Vec2& pos) const noexcept { return std::hash()(pos.x ^ (pos.y << 4)); } }; - T x = 0; - T y = 0; + CoordinateType x = 0; + CoordinateType y = 0; }; typedef Vec2 Vec2i; -template +template class Node { public: explicit Node() : pos(Vec2i(0, 0)), parentNode(nullptr) {} explicit Node(const Vec2i& pos, Node* parent = nullptr) : pos(pos), parentNode(parent) {} - explicit Node(const Vec2i& pos, const T pathCost, const T heuristicCost, Node* parent = nullptr) + explicit Node(const Vec2i& pos, const CoordinateType pathCost, const CoordinateType heuristicCost, Node* parent = nullptr) : pathCost(pathCost), heuristicCost(heuristicCost), pos(pos), parentNode(parent) {} - inline T getTotalCost() const noexcept { return pathCost + heuristicCost; } + inline CoordinateType getTotalCost() const noexcept { return pathCost + heuristicCost; } struct hash { size_t operator()(const Node* node) const noexcept { return std::hash()(node->pos.x ^ (node->pos.y << 4)); } }; - T pathCost = 0; - T heuristicCost = 0; + CoordinateType pathCost = 0; + CoordinateType heuristicCost = 0; Vec2i pos = {0, 0}; Node* parentNode = nullptr; }; @@ -109,7 +109,7 @@ static constexpr uint32_t dijkstra([[maybe_unused]] const Vec2i& source, } }; // namespace Heuristic -template +template class AStarVirtual { public: explicit AStarVirtual() @@ -117,8 +117,8 @@ class AStarVirtual { _directionsCount(4), _heuristicWeight(10), _mouvemementCost(10), - _debugCurrentNode([](Node*) {}), - _debugOpenNode([](Node*) {}) { + _debugCurrentNode([](Node*) {}), + _debugOpenNode([](Node*) {}) { _directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}}; } void setHeuristic(const std::function& heuristic) { @@ -143,23 +143,23 @@ class AStarVirtual { std::vector& getDirections() noexcept { return _directions; } - void setDebugCurrentNode(const std::function*)>& debugCurrentNode) noexcept { _debugCurrentNode = debugCurrentNode; } - void setDebugOpenNode(const std::function*)>& debugOpenNode) noexcept { _debugOpenNode = debugOpenNode; } + void setDebugCurrentNode(const std::function*)>& debugCurrentNode) noexcept { _debugCurrentNode = debugCurrentNode; } + void setDebugOpenNode(const std::function*)>& debugOpenNode) noexcept { _debugOpenNode = debugOpenNode; } protected: std::function _heuristicFunction; std::vector _directions; size_t _directionsCount; - T _heuristicWeight; + CoordinateType _heuristicWeight; size_t _mouvemementCost = 10; // Only used if enableDebug is true - std::function*)> _debugCurrentNode; - std::function*)> _debugOpenNode; + std::function*)> _debugCurrentNode; + std::function*)> _debugOpenNode; }; -template -class AStar final : public AStarVirtual { +template +class AStar final : public AStarVirtual { public: explicit AStar() {} @@ -168,23 +168,23 @@ class AStar final : public AStarVirtual { return {}; } - Node* currentNode = nullptr; + Node* currentNode = nullptr; - auto compareFn = [](const Node* a, const Node* b) { return a->getTotalCost() > b->getTotalCost(); }; - std::priority_queue*, std::vector*>, decltype(compareFn)> openNodeVecPQueue = - std::priority_queue*, std::vector*>, decltype(compareFn)>(compareFn); + auto compareFn = [](const Node* a, const Node* b) { return a->getTotalCost() > b->getTotalCost(); }; + std::priority_queue*, std::vector*>, decltype(compareFn)> openNodeVecPQueue = + std::priority_queue*, std::vector*>, decltype(compareFn)>(compareFn); - std::unordered_map*, Vec2i::hash> openNodeMap; - std::unordered_map*, Vec2i::hash> closedNodeMap; + std::unordered_map*, Vec2i::hash> openNodeMap; + std::unordered_map*, Vec2i::hash> closedNodeMap; - openNodeVecPQueue.push(new Node(source)); + openNodeVecPQueue.push(new Node(source)); openNodeMap.insert({source, openNodeVecPQueue.top()}); while (!openNodeVecPQueue.empty()) { currentNode = openNodeVecPQueue.top(); if constexpr (enableDebug) { - AStarVirtual::_debugCurrentNode(currentNode); + AStarVirtual::_debugCurrentNode(currentNode); } if (currentNode->pos == target) { @@ -195,8 +195,8 @@ class AStar final : public AStarVirtual { openNodeMap.erase(currentNode->pos); closedNodeMap.insert({currentNode->pos, currentNode}); - for (size_t i = 0; i < AStarVirtual::_directionsCount; ++i) { - Vec2i newPos = currentNode->pos + AStarVirtual::_directions[i]; + for (size_t i = 0; i < AStarVirtual::_directionsCount; ++i) { + Vec2i newPos = currentNode->pos + AStarVirtual::_directions[i]; if (_obstacles.contains(newPos)) { continue; @@ -210,14 +210,14 @@ class AStar final : public AStarVirtual { continue; } - T nextCost = currentNode->pathCost + AStarVirtual::_mouvemementCost; - Node* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr; + CoordinateType nextCost = currentNode->pathCost + AStarVirtual::_mouvemementCost; + Node* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr; if (nextNode == nullptr) { - nextNode = new Node(newPos, currentNode); + nextNode = new Node(newPos, currentNode); nextNode->pathCost = nextCost; - nextNode->heuristicCost = static_cast(AStarVirtual::_heuristicFunction( - nextNode->pos, target, AStarVirtual::_heuristicWeight)); + nextNode->heuristicCost = static_cast(AStarVirtual::_heuristicFunction( + nextNode->pos, target, AStarVirtual::_heuristicWeight)); openNodeVecPQueue.push(nextNode); openNodeMap.insert({nextNode->pos, nextNode}); } else if (nextCost < nextNode->pathCost) { @@ -226,7 +226,7 @@ class AStar final : public AStarVirtual { } if constexpr (enableDebug) { - AStarVirtual::_debugOpenNode(nextNode); + AStarVirtual::_debugOpenNode(nextNode); } } } @@ -264,33 +264,33 @@ class AStar final : public AStarVirtual { }; // Fast AStar are faster than normal AStar but use more ram and direct access to the map -template -class AStarFast final : public AStarVirtual { +template +class AStarFast final : public AStarVirtual { public: - explicit AStarFast() : _isObstacleFunction([](U value) { return value == 1; }) {} + explicit AStarFast() : _isObstacleFunction([](MapElementType value) { return value == 1; }) {} // Same as AStar::findPath() but use direct access to the map - std::vector findPath(const Vec2i& source, const Vec2i& target, const std::vector& map, const Vec2i& worldSize) { + std::vector findPath(const Vec2i& source, const Vec2i& target, const std::vector& map, const Vec2i& worldSize) { if (target.x < 0 || target.x >= worldSize.x || target.y < 0 || target.y >= worldSize.y) { return {}; } - Node* currentNode = nullptr; + Node* currentNode = nullptr; - auto compareFn = [](const Node* a, const Node* b) { return a->getTotalCost() > b->getTotalCost(); }; - std::priority_queue*, std::vector*>, decltype(compareFn)> openNodeVecPQueue = - std::priority_queue*, std::vector*>, decltype(compareFn)>(compareFn); - std::unordered_map*, Vec2i::hash> openNodeMap; - std::unordered_map*, Vec2i::hash> closedNodeMap; + auto compareFn = [](const Node* a, const Node* b) { return a->getTotalCost() > b->getTotalCost(); }; + std::priority_queue*, std::vector*>, decltype(compareFn)> openNodeVecPQueue = + std::priority_queue*, std::vector*>, decltype(compareFn)>(compareFn); + std::unordered_map*, Vec2i::hash> openNodeMap; + std::unordered_map*, Vec2i::hash> closedNodeMap; - openNodeVecPQueue.push(new Node(source)); + openNodeVecPQueue.push(new Node(source)); openNodeMap.insert({source, openNodeVecPQueue.top()}); while (!openNodeVecPQueue.empty()) { currentNode = openNodeVecPQueue.top(); if constexpr (enableDebug) { - AStarVirtual::_debugCurrentNode(currentNode); + AStarVirtual::_debugCurrentNode(currentNode); } if (currentNode->pos == target) { @@ -301,8 +301,8 @@ class AStarFast final : public AStarVirtual { openNodeMap.erase(currentNode->pos); closedNodeMap.insert({currentNode->pos, currentNode}); - for (size_t i = 0; i < AStarVirtual::_directionsCount; ++i) { - Vec2i newPos = currentNode->pos + AStarVirtual::_directions[i]; + for (size_t i = 0; i < AStarVirtual::_directionsCount; ++i) { + Vec2i newPos = currentNode->pos + AStarVirtual::_directions[i]; if (_isObstacleFunction(map[newPos.x + newPos.y * worldSize.x])) { continue; @@ -316,13 +316,13 @@ class AStarFast final : public AStarVirtual { continue; } - T nextCost = currentNode->pathCost + AStarVirtual::_mouvemementCost; - Node* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr; + CoordinateType nextCost = currentNode->pathCost + AStarVirtual::_mouvemementCost; + Node* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr; if (nextNode == nullptr) { - nextNode = new Node(newPos, currentNode); + nextNode = new Node(newPos, currentNode); nextNode->pathCost = nextCost; - nextNode->heuristicCost = static_cast(AStarVirtual::_heuristicFunction( - nextNode->pos, target, AStarVirtual::_heuristicWeight)); + nextNode->heuristicCost = static_cast(AStarVirtual::_heuristicFunction( + nextNode->pos, target, AStarVirtual::_heuristicWeight)); openNodeVecPQueue.push(nextNode); openNodeMap.insert({nextNode->pos, nextNode}); } else if (nextCost < nextNode->pathCost) [[likely]] { @@ -331,7 +331,7 @@ class AStarFast final : public AStarVirtual { } if constexpr (enableDebug) { - AStarVirtual::_debugOpenNode(nextNode); + AStarVirtual::_debugOpenNode(nextNode); } } } @@ -355,11 +355,11 @@ class AStarFast final : public AStarVirtual { return path; } - void setObstacle(const std::function& isObstacleFunction) noexcept { _isObstacleFunction = isObstacleFunction; } - std::function& getObstacle() noexcept { return _isObstacleFunction; } + void setObstacle(const std::function& isObstacleFunction) noexcept { _isObstacleFunction = isObstacleFunction; } + std::function& getObstacle() noexcept { return _isObstacleFunction; } private: - std::function _isObstacleFunction; + std::function _isObstacleFunction; }; } // namespace AStar diff --git a/test/source/benchmark/astar_bench.cpp b/test/source/benchmark/astar_bench.cpp index 056e803..7bb49e4 100644 --- a/test/source/benchmark/astar_bench.cpp +++ b/test/source/benchmark/astar_bench.cpp @@ -18,7 +18,7 @@ static void DoSetup([[maybe_unused]] const benchmark::State& state) {} static void DoTeardown([[maybe_unused]] const benchmark::State& state) {} -template +template static void astar_bench(benchmark::State& state) { auto range = state.range(0); @@ -47,7 +47,7 @@ static void astar_bench(benchmark::State& state) { std::vector blocks = std::vector(mapWidth * mapHeight, 0); benchmark::DoNotOptimize(blocks); - AStar::AStar pathFinder; + AStar::AStar pathFinder; benchmark::DoNotOptimize(pathFinder); pathFinder.setWorldSize({mapWidth, mapHeight}); pathFinder.setHeuristic(AStar::Heuristic::euclidean); @@ -104,7 +104,7 @@ BENCHMARK(astar_bench) ->UseRealTime() ->Repetitions(repetitions); -template +template static void astar_bench_fast(benchmark::State& state) { auto range = state.range(0); @@ -133,7 +133,7 @@ static void astar_bench_fast(benchmark::State& state) { std::vector blocks = std::vector(mapWidth * mapHeight, 0); benchmark::DoNotOptimize(blocks); - AStar::AStarFast pathFinder; + AStar::AStarFast pathFinder; benchmark::DoNotOptimize(pathFinder); pathFinder.setHeuristic(AStar::Heuristic::euclidean); pathFinder.setDiagonalMovement(true);