TinyRobotics
Loading...
Searching...
No Matches
AStar.h
1#pragma once
2#include <functional>
3#include <limits>
4#include <queue>
5#include <unordered_map>
6#include <vector>
7
8#include "Path.h"
9#include "TinyRobotics/coordinates/Coordinate.h"
10#include "TinyRobotics/utils/AllocatorPSRAM.h"
11
12namespace tinyrobotics {
13
14/**
15 * @class AStar
16 * @ingroup planning
17 * @brief Flexible A* pathfinding algorithm for any map implementing IMap<T> and using Coordinate<T> nodes.
18 *
19 * The AStar class implements the A* search algorithm for any map or graph that implements the IMap<T> interface.
20 * It uses Coordinate<T> as the node type and supports user-defined cost and validity callbacks for custom metrics, obstacle handling, and heuristics.
21 *
22 * Features:
23 * - Finds the optimal path from a start node to a goal node using A* search
24 * - Works with any map implementing IMap<T> (e.g., grids, navigation graphs, road networks)
25 * - Supports user-provided cost and validity callbacks for maximum flexibility
26 * - Allows passing a user reference/context pointer to callbacks (for map data, etc.)
27 * - Provides both full path reconstruction and efficient next-step queries
28 *
29 * Usage:
30 * 1. Create an AStar instance (optionally providing cost and validity callbacks).
31 * 2. Call `findPath(map, start, goal)` to get the optimal path as a Path<Coordinate<T>> object.
32 * 3. Optionally, use `nextStep(map, start, goal)` to get only the next move.
33 * 4. Customize cost and heuristic logic via the cost callback (e.g., Euclidean, Manhattan, or domain-specific).
34 *
35 * Example:
36 * @code
37 * AStar astar;
38 * astar.setCostCallback([](const Coordinate<DistanceM>& from, const Coordinate<DistanceM>& to, void*) {
39 * // Custom cost (e.g., Euclidean distance)
40 * return from.distance(to);
41 * });
42 * auto path = astar.findPath(map, start, goal);
43 * if (!path.empty()) {
44 * // Use the path
45 * }
46 * @endcode
47 *
48 * This class is suitable for embedded and desktop robotics, navigation, and any application requiring flexible, efficient pathfinding.
49 */
50
51template <typename T = DistanceM>
52class AStar {
53 public:
54 using CostCallback = std::function<float(const Coordinate<T>&,
55 const Coordinate<T>&, void* ref)>;
56 using ValidCallback = std::function<bool(const Coordinate<T>&, void* ref)>;
57
58 public:
59 /// provide a callback to determine the cost of moving from one node to
60 /// another
61 void setCostCallback(CostCallback cb) { cost_cb = cb; }
62 /// provide a callback to determine if a node is valid (e.g., not an obstacle)
63 void setValidCallback(ValidCallback cb) { valid_cb = cb; }
64 /// provide reference for callbacks (e.g., to access map data or other
65 /// context)
66 void setReference(void* reference) { ref = reference; }
67
68 /// Finds the optimal path from start to goal. Returns an empty path if no
69 /// path
71 const Coordinate<T>& goal) {
72 std::string startStr = start.toCString();
73 std::string goalStr = goal.toCString();
74 TRLogger.debug("Finding path from %s to %s", startStr.c_str(),
75 goalStr.c_str());
76 using NodeMap = std::unordered_map<
77 Coordinate<T>, Coordinate<T>, std::hash<Coordinate<T>>,
78 std::equal_to<Coordinate<T>>,
79 AllocatorPSRAM<std::pair<const Coordinate<T>, Coordinate<T>>>>;
80 NodeMap cameFrom;
81 bool found = aStarSearch(map, start, goal, &cameFrom, nullptr);
82 if (found) {
83 return reconstructPath(cameFrom, start, goal);
84 }
85 return Path<Coordinate<T>>();
86 }
87
88 /**
89 * Returns the next node on the optimal path from start to goal.
90 * If no path is found, returns start.
91 */
92 Coordinate<T> nextStep(const IMapNeighbors<T>& map, const Coordinate<T>& start,
93 const Coordinate<T>& goal) {
94 using NodeMap = std::unordered_map<
95 Coordinate<T>, Coordinate<T>, std::hash<Coordinate<T>>,
96 std::equal_to<Coordinate<T>>,
97 AllocatorPSRAM<std::pair<const Coordinate<T>, Coordinate<T>>>>;
98 NodeMap cameFrom;
99 bool found = aStarSearch(map, start, goal, &cameFrom, nullptr);
100 if (found) {
101 Path<Coordinate<T>> path = reconstructPath(cameFrom, start, goal);
102 if (path.size() >= 2) {
103 return path[1];
104 }
105 }
106 return start;
107 }
108
109 protected:
110 struct NodeRecord {
111 Coordinate<T> node;
112 float costSoFar = 0;
113 float estimatedTotalCost = 0;
114 bool operator>(const NodeRecord& other) const {
115 return estimatedTotalCost > other.estimatedTotalCost;
116 }
117 };
118
119 // Core A* search logic, fills cameFrom and optionally costSoFar, returns true
120 // if path found
121 bool aStarSearch(
122 const IMapNeighbors<T>& map, const Coordinate<T>& start, const Coordinate<T>& goal,
123 std::unordered_map<
124 Coordinate<T>, Coordinate<T>, std::hash<Coordinate<T>>,
125 std::equal_to<Coordinate<T>>,
126 AllocatorPSRAM<std::pair<const Coordinate<T>, Coordinate<T>>>>*
127 cameFrom,
128 std::unordered_map<Coordinate<T>, float, std::hash<Coordinate<T>>,
129 std::equal_to<Coordinate<T>>,
130 AllocatorPSRAM<std::pair<const Coordinate<T>, float>>>*
131 outCostSoFar) {
132 std::priority_queue<NodeRecord, std::vector<NodeRecord>,
133 std::greater<NodeRecord>>
134 openSet;
135 std::unordered_map<Coordinate<T>, float, std::hash<Coordinate<T>>,
136 std::equal_to<Coordinate<T>>,
137 AllocatorPSRAM<std::pair<const Coordinate<T>, float>>>
138 costSoFar;
139 openSet.push({start, 0.0f, cost_cb(start, goal, ref)});
140 costSoFar[start] = 0.0f;
141 if (cameFrom) cameFrom->clear();
142 while (!openSet.empty()) {
143 Coordinate<T> current = openSet.top().node;
144 if (current == goal) {
145 if (outCostSoFar) *outCostSoFar = costSoFar;
146 return true;
147 }
148 openSet.pop();
149 for (const auto& neighbor : map.getNeighbors(current)) {
150 if (!valid_cb(neighbor, ref)) continue;
151 float newCost = costSoFar[current] + cost_cb(current, neighbor, ref);
152 if (!costSoFar.count(neighbor) || newCost < costSoFar[neighbor]) {
153 costSoFar[neighbor] = newCost;
154 float priority = newCost + cost_cb(neighbor, goal, ref);
155 openSet.push({neighbor, newCost, priority});
156 if (cameFrom) (*cameFrom)[neighbor] = current;
157 }
158 }
159 }
160 if (outCostSoFar) *outCostSoFar = costSoFar;
161 return false;
162 }
163
164 protected:
165 CostCallback cost_cb = defaultCost;
166 ValidCallback valid_cb = defaultValid;
167 void* ref = this;
168
169 static float defaultCost(const Coordinate<T>& from, const Coordinate<T>& to,
170 void* ref) {
171 return from.distance(to);
172 }
173
174 static bool defaultValid(const Coordinate<T>& node, void* ref) {
175 return true; // By default, all nodes are valid
176 }
177
178 Path<Coordinate<T>> reconstructPath(
179 const std::unordered_map<
180 Coordinate<T>, Coordinate<T>, std::hash<Coordinate<T>>,
181 std::equal_to<Coordinate<T>>,
182 AllocatorPSRAM<std::pair<const Coordinate<T>, Coordinate<T>>>>&
183 cameFrom,
184 const Coordinate<T>& start, const Coordinate<T>& goal) {
185 Path<Coordinate<T>> path;
186 Coordinate<T> current = goal;
187 while (current != start) {
188 path.addWaypoint(current);
189 auto it = cameFrom.find(current);
190 if (it == cameFrom.end()) break;
191 current = it->second;
192 }
193 path.addWaypoint(start);
194 path.reverse();
195 return path;
196 }
197};
198
199} // namespace tinyrobotics
Flexible A* pathfinding algorithm for any map implementing IMap<T> and using Coordinate<T> nodes.
Definition: AStar.h:52
Coordinate< T > nextStep(const IMapNeighbors< T > &map, const Coordinate< T > &start, const Coordinate< T > &goal)
Definition: AStar.h:92
void setValidCallback(ValidCallback cb)
provide a callback to determine if a node is valid (e.g., not an obstacle)
Definition: AStar.h:63
Path< Coordinate< T > > findPath(const IMapNeighbors< T > &map, const Coordinate< T > &start, const Coordinate< T > &goal)
Definition: AStar.h:70
void setReference(void *reference)
Definition: AStar.h:66
void setCostCallback(CostCallback cb)
Definition: AStar.h:61
Custom allocator that uses ESP32's PSRAM for memory allocation.
Definition: AllocatorPSRAM.h:21
A generic 3D coordinate class for robotics, navigation, and spatial calculations.
Definition: Coordinate.h:57
Definition: AStar.h:110