TinyRobotics
Loading...
Searching...
No Matches
FrontierExplorer.h
1#pragma once
2#include "TinyRobotics/coordinates/Coordinate.h"
3#include "TinyRobotics/maps/IMap.h"
5
6namespace tinyrobotics {
7
8/**
9 * @enum FrontierSelectionStrategy
10 * @ingroup planning
11 * @brief Strategy for selecting the next frontier cell to explore.
12 * Used by `FrontierExplorer` to determine how the next exploration frontier is chosen.
13 */
15 RANDOM, ///< Randomly select a frontier cell
16 NEAREST, ///< Select the frontier cell closest to the robot
17 FARTHEST, ///< Select the frontier cell farthest from the robot
18 FIRST, ///< Select the first frontier cell found (e.g., in scan order)
19 LAST, ///< Select the last frontier cell found
20 SWITCH_FIRST_LAST, ///< Alternate between first and last on each call
21 CUSTOM ///< Use a user-defined callback to select the frontier
22};
23
24
25/**
26 * @class FrontierExplorer
27 * @ingroup planning
28 * @brief Generic exploration and frontier-based SLAM utility for grid or occupancy maps.
29 *
30 * Implements IFrontierExplorer for flexible autonomous exploration.
31 *
32 * @tparam T Scalar type for coordinates and map (e.g., float, DistanceM). Default: DistanceM.
33 */
34template <typename T=DistanceM>
35class FrontierExplorer : public IFrontierExplorer<T> {
36 public:
37 /**
38 * @brief Construct a FrontierExplorer with a given map (by reference).
39 * @param map The map to explore (must outlive this object).
40 */
41 FrontierExplorer(IMap<T>& map) : map_(map) {}
42
43 /**
44 * @brief Set the current position of the explorer.
45 * @param pos The current position.
46 */
47 void setCurrentPosition(const Coordinate<T>& pos) { current_pos = pos; }
48
49 /**
50 * @brief Get the current position of the explorer.
51 * @return The current position.
52 */
53 Coordinate<T> getCurrentPosition() const { return current_pos; }
54
55 /**
56 * @brief Set the strategy for selecting the next frontier cell.
57 * @param strategy The selection strategy to use.
58 */
60 strategy_ = strategy;
62 switchFirstLast = true;
63 } else {
64 switchFirstLast = false;
65 }
66 }
67
68 /**
69 * @brief Get the current frontier selection strategy.
70 * @return The current strategy.
71 */
73 if (switchFirstLast) return FrontierSelectionStrategy::SWITCH_FIRST_LAST;
74 return strategy_;
75 }
76
77 /**
78 * @brief Provides the next frontier cell to explore based on the current
79 * strategy.
80 * @param nextCell Output parameter for the selected frontier cell.
81 * @return true if a frontier was found, false otherwise.
82 */
83 bool getNextFrontier(Coordinate<T>& nextCell) {
84 // find all frontier cells
86 record_count_ = frontiers.size();
87 if (frontiers.empty()) return false;
88
89 // switch between FIRST and LAST if the strategy is SWITCH_FIRST_LAST
90 if (switchFirstLast) {
91 // switch between FIRST and LAST on each call
92 strategy_ = (strategy_ == FrontierSelectionStrategy::FIRST)
95 }
96
97 switch (strategy_) {
99 return selectRandom(nextCell);
101 return selectNearest(nextCell);
103 return selectFarthest(nextCell);
105 return selectFirst(nextCell);
107 return selectLast(nextCell);
109 return selectCustom(nextCell);
110 }
111 return false; // Should not reach here
112 }
113
114 /// Provides the number of potential frontier cells found in the last search.
115 size_t size() const { return record_count_; }
116
117 protected:
118 Coordinate<T> current_pos{};
120 bool switchFirstLast = false;
121 size_t record_count_ = 0;
122 IMap<T> & map_;
123 std::vector<Coordinate<T>> frontiers;
124 void* ref = this;
125 int (*select_callback)(std::vector<Coordinate<T>>& frontiers,
126 void* ref) = nullptr;
127
128 // --- Strategy implementations ---
129 bool selectRandom(Coordinate<T>& nextCell) {
130 int idx = random(0, frontiers.size());
131 nextCell = frontiers[idx];
132 frontiers.clear();
133 return true;
134 }
135
136 bool selectNearest(Coordinate<T>& nextCell) {
137 Coordinate<T>& result = frontiers[0];
138 float distance = current_pos.distance(result);
139 for (int i = 1; i < frontiers.size(); ++i) {
140 Coordinate<T>& tmp = frontiers[i];
141 float tmp_distance = current_pos.distance(tmp);
142 if (tmp_distance < distance) {
143 result = tmp;
144 distance = tmp_distance;
145 }
146 }
147 nextCell = result;
148 frontiers.clear();
149 return true;
150 }
151
152 bool selectFarthest(Coordinate<T>& nextCell) {
153 Coordinate<T>& result = frontiers[0];
154 float distance = current_pos.distance(result);
155 for (int i = 1; i < frontiers.size(); ++i) {
156 Coordinate<T>& tmp = frontiers[i];
157 float tmp_distance = current_pos.distance(tmp);
158 if (tmp_distance > distance) {
159 result = tmp;
160 distance = tmp_distance;
161 }
162 }
163 nextCell = result;
164 frontiers.clear();
165 return true;
166 }
167
168 bool selectFirst(Coordinate<T>& nextCell) {
169 nextCell = frontiers.front();
170 frontiers.clear();
171 return true;
172 }
173
174 bool selectLast(Coordinate<T>& nextCell) {
175 nextCell = frontiers.back();
176 frontiers.clear();
177 return true;
178 }
179
180 bool selectCustom(Coordinate<T>& nextCell) {
181 if (select_callback) {
182 int idx = select_callback(frontiers, ref);
183 if (idx >= 0 && idx < frontiers.size()) {
184 nextCell = frontiers[idx];
185 frontiers.clear();
186 return true;
187 }
188 }
189 frontiers.clear();
190 return false;
191 }
192
193 /**
194 * @brief Set a custom callback for selecting the next frontier cell.
195 * You can implement your own optimization strategy here.
196 * @param cb The callback function (returns index of selected cell).
197 */
198 void setSelectCallback(int (*cb)(std::vector<Coordinate<T>>& frontiers,
199 void* ref),
200 void* ref = nullptr) {
201 select_callback = cb;
202 if (ref != nullptr)
203 this->ref = ref; // Default reference is the FrontierExplorer instance
205 }
206
207 /**
208 * @brief Find all frontier cells in the map (cells adjacent to unknown
209 * space).
210 *
211 * A frontier cell is defined as a cell that is FREE (traversable) and has at
212 * least one neighboring cell (in any of the 8 directions) that is UNKNOWN
213 * (unexplored). This method iterates over every cell in the map, and for
214 * each FREE cell, checks all 8 neighbors. If any neighbor is UNKNOWN, the
215 * cell is added to the internal `frontiers` vector. Each cell is considered
216 * only once, so no duplicates occur.
217 *
218 * This is a key step in frontier-based exploration and SLAM, as it
219 * identifies the boundary between explored and unexplored space, guiding the
220 * robot to new areas.
221 *
222 * After calling this method, the `frontiers` vector will contain all current
223 * frontier coordinates, which can then be used by the exploration strategy to
224 * select the next goal.
225 */
226 void collectFrontiers() {
227 frontiers.clear();
228 for (int y = 0; y < map_.getYCount(); ++y) {
229 for (int x = 0; x < map_.getXCount(); ++x) {
230 CellState state;
231 if (!map_.getCell(x, y, state) || state != CellState::FREE) continue;
232 bool found = false;
233 // Check 8 neighbors for UNKNOWN
234 for (int dy = -1; dy <= 1 && !found; ++dy) {
235 for (int dx = -1; dx <= 1 && !found; ++dx) {
236 if (dx == 0 && dy == 0) continue;
237 CellState neighborState;
238 if (map_.getCell(x + dx, y + dy, neighborState) &&
239 neighborState == CellState::UNKNOWN) {
240 // return world coordinates of the frontier cell
241 frontiers.push_back(map_.toWorld(x, y));
242 found = true;
243 }
244 }
245 }
246 }
247 }
248 }
249};
250
251} // namespace tinyrobotics
A generic 3D coordinate class for robotics, navigation, and spatial calculations.
Definition: Coordinate.h:57
Generic exploration and frontier-based SLAM utility for grid or occupancy maps.
Definition: FrontierExplorer.h:35
FrontierExplorer(IMap< T > &map)
Construct a FrontierExplorer with a given map (by reference).
Definition: FrontierExplorer.h:41
void collectFrontiers()
Find all frontier cells in the map (cells adjacent to unknown space).
Definition: FrontierExplorer.h:226
size_t size() const
Provides the number of potential frontier cells found in the last search.
Definition: FrontierExplorer.h:115
bool getNextFrontier(Coordinate< T > &nextCell)
Provides the next frontier cell to explore based on the current strategy.
Definition: FrontierExplorer.h:83
void setSelectCallback(int(*cb)(std::vector< Coordinate< T > > &frontiers, void *ref), void *ref=nullptr)
Set a custom callback for selecting the next frontier cell. You can implement your own optimization s...
Definition: FrontierExplorer.h:198
Coordinate< T > getCurrentPosition() const
Get the current position of the explorer.
Definition: FrontierExplorer.h:53
void setCurrentPosition(const Coordinate< T > &pos)
Set the current position of the explorer.
Definition: FrontierExplorer.h:47
void setStrategy(FrontierSelectionStrategy strategy)
Set the strategy for selecting the next frontier cell.
Definition: FrontierExplorer.h:59
FrontierSelectionStrategy getStrategy() const
Get the current frontier selection strategy.
Definition: FrontierExplorer.h:72
Interface for frontier-based exploration utilities.
Definition: IFrontierExplorer.h:18
Abstract interface for 2D grid maps and occupancy maps in TinyRobotics.
Definition: IMap.h:79
FrontierSelectionStrategy
Strategy for selecting the next frontier cell to explore. Used by FrontierExplorer to determine how t...
Definition: FrontierExplorer.h:14
@ RANDOM
Randomly select a frontier cell.
@ FIRST
Select the first frontier cell found (e.g., in scan order)
@ CUSTOM
Use a user-defined callback to select the frontier.
@ NEAREST
Select the frontier cell closest to the robot.
@ SWITCH_FIRST_LAST
Alternate between first and last on each call.
@ FARTHEST
Select the frontier cell farthest from the robot.
@ LAST
Select the last frontier cell found.
CellState
Cell state for occupancy grid mapping (e.g., UNKNOWN, FREE, OCCUPIED).
Definition: Common.h:32