NavMap
Loading...
Searching...
No Matches
Iterators.hpp
Go to the documentation of this file.
1// Copyright 2026 Intelligent Robotics Lab
2//
3// This file is part of the project Easy Navigation (EasyNav in short)
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
29
30#ifndef NAVMAP_CORE__ITERATORS_HPP_
31#define NAVMAP_CORE__ITERATORS_HPP_
32
33#include <algorithm>
34#include <array>
35#include <cmath>
36#include <cstddef>
37#include <cstdint>
38#include <functional>
39#include <limits>
40#include <type_traits>
41#include <utility>
42#include <vector>
43
44#include "Eigen/Core"
46
47namespace navmap
48{
49
51enum class Metric
52{
55};
56
59{
60public:
62 void begin(std::size_t n)
63 {
64 if (stamp_.size() < n) {
65 stamp_.assign(n, 0);
66 dist.resize(n);
67 epoch_ = 0;
68 }
69 if (++epoch_ == 0) { // wrapped: clear the stamps once
70 std::fill(stamp_.begin(), stamp_.end(), 0);
71 epoch_ = 1;
72 }
73 stack.clear();
74 heap.clear();
75 }
76
78 bool mark(NavCelId cid)
79 {
80 if (stamp_[cid] == epoch_) {return false;}
81 stamp_[cid] = epoch_;
82 return true;
83 }
84
85 bool marked(NavCelId cid) const {return stamp_[cid] == epoch_;}
86
88 struct Front
89 {
90 float d;
93 float pivot_len;
94 Eigen::Vector3f pivot;
95 };
96
97 std::vector<float> dist;
98
99 std::vector<NavCelId> stack;
100 std::vector<Front> heap;
101 std::vector<int> nodes;
102
103private:
104 std::vector<std::uint32_t> stamp_;
105 std::uint32_t epoch_{0};
106};
107
108namespace detail
109{
110
111// Calls the visitor; true unless it returned false.
112template<class F, class ... A>
113inline bool visit(F & f, A &&... a)
114{
115 if constexpr (std::is_void_v<std::invoke_result_t<F &, A...>>) {
116 f(std::forward<A>(a)...);
117 return true;
118 } else {
119 return static_cast<bool>(f(std::forward<A>(a)...));
120 }
121}
122
123inline constexpr NavCelId kNoCel = std::numeric_limits<NavCelId>::max();
124
125inline bool boxes_overlap(const AABB & a, const AABB & b)
126{
127 return a.min.x() <= b.max.x() && a.max.x() >= b.min.x() &&
128 a.min.y() <= b.max.y() && a.max.y() >= b.min.y() &&
129 a.min.z() <= b.max.z() && a.max.z() >= b.min.z();
130}
131
132inline AABB navcel_box(const NavMap & nm, NavCelId cid)
133{
134 AABB b;
135 for (const auto v : nm.navcels[cid].v) {
136 b.expand(nm.positions.at(v));
137 }
138 return b;
139}
140
141// Even-odd rule in XY.
142inline bool in_polygon(const std::vector<Eigen::Vector2f> & poly, float x, float y)
143{
144 bool in = false;
145 for (std::size_t i = 0, j = poly.size() - 1; i < poly.size(); j = i++) {
146 const auto & a = poly[i];
147 const auto & b = poly[j];
148 if ((a.y() > y) != (b.y() > y) &&
149 x < (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()) + a.x())
150 {
151 in = !in;
152 }
153 }
154 return in;
155}
156
157inline bool seed_at(const NavMap & nm, const Eigen::Vector3f & p, NavCelId & seed)
158{
159 std::size_t s = 0;
160 Eigen::Vector3f bary, hit;
161 return nm.locate_navcel(p, s, seed, bary, &hit);
162}
163
164// Segment walk (see for_each_on_segment).
165template<class F>
167 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & a, const Eigen::Vector3f & b, F && f)
168{
169 if (seed >= nm.navcels.size()) {return false;}
170 const Eigen::Vector2f a2(a.x(), a.y());
171 const Eigen::Vector2f d2(b.x() - a.x(), b.y() - a.y());
172 auto cross = [](const Eigen::Vector2f & u, const Eigen::Vector2f & v) {
173 return u.x() * v.y() - u.y() * v.x();
174 };
175 constexpr float kEps = 1e-6f;
176 NavCelId c = seed, prev = kNoCel;
177 float t_in = 0.0f;
178 for (std::size_t steps = 0; steps <= nm.navcels.size(); ++steps) {
179 const auto & cel = nm.navcels[c];
180 Eigen::Vector2f p[3];
181 for (int k = 0; k < 3; ++k) {
182 const auto v = nm.positions.at(cel.v[k]);
183 p[k] = Eigen::Vector2f(v.x(), v.y());
184 }
185 const float area2 = cross(p[1] - p[0], p[2] - p[0]);
186 if (std::abs(area2) < 1e-12f) {return false;} // vertical in XY: cannot be walked
187 const float sgn = area2 > 0.0f ? 1.0f : -1.0f;
188 // Leaves through the edge whose inside-test reaches 0 first, beyond t_in.
189 float t_exit = std::numeric_limits<float>::infinity();
190 int e_exit = -1;
191 for (int e = 0; e < 3; ++e) {
192 const Eigen::Vector2f edge = p[(e + 1) % 3] - p[e];
193 const float ds = sgn * cross(edge, d2);
194 if (ds >= 0.0f) {continue;}
195 const float s0 = sgn * cross(edge, a2 - p[e]);
196 const float te = std::max(-s0 / ds, t_in);
197 if (cel.neighbor[e] == prev && te <= t_in + kEps) {continue;} // back where it came from
198 if (te < t_exit) {
199 t_exit = te;
200 e_exit = e;
201 }
202 }
203 if (e_exit < 0 || t_exit >= 1.0f) { // b lies in this NavCel
204 return visit(f, c, t_in, 1.0f);
205 }
206 if (t_exit - t_in > kEps || c == seed) {
207 if (!visit(f, c, t_in, t_exit)) {return false;}
208 }
209 const NavCelId next = cel.neighbor[e_exit];
210 if (next == kNoCel) {return false;} // left the surface through a border
211 prev = c;
212 c = next;
213 t_in = t_exit;
214 }
215 return false;
216}
217
218// Grows the region of NavCels at distance in [0, r_out] from the centre, connected to the seed
219// through NavCels of the region; visits those at distance >= r_in as f(cid, distance). The seed
220// (it holds the centre) is always expanded, and visited regardless of distance if with_seed.
221// GEODESIC visits by distance (non-decreasing up to one neighbour step); EUCLIDEAN breadth-first.
222template<class F>
223std::size_t grow(
224 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & center, float r_in, float r_out,
225 Metric metric, bool with_seed, TraversalScratch * scratch, F & f)
226{
227 if (seed >= nm.navcels.size() || !(r_out >= 0.0f)) {return 0;}
228 TraversalScratch local;
229 TraversalScratch & sc = scratch ? *scratch : local;
230 sc.begin(nm.navcels.size());
231 std::size_t visited = 0;
232 // Visits c if it passes the filter; false if the visitor asked to stop.
233 auto emit = [&](NavCelId c, float d) {
234 const bool forced = with_seed && c == seed;
235 if (!forced && (d < r_in || d > r_out)) {return true;}
236 ++visited;
237 return visit(f, c, d);
238 };
239
240 if (metric == Metric::EUCLIDEAN) {
241 sc.mark(seed);
242 sc.stack.push_back(seed);
243 std::size_t head = 0; // the stack is used as a FIFO queue
244 while (head < sc.stack.size()) {
245 const NavCelId c = sc.stack[head++];
246 const float d = (nm.navcel_centroid(c) - center).norm();
247 if (c != seed && d > r_out) {continue;}
248 if (!emit(c, d)) {return visited;}
249 for (const NavCelId n : nm.navcels[c].neighbor) {
250 if (n != kNoCel && sc.mark(n)) {sc.stack.push_back(n);}
251 }
252 }
253 return visited;
254 }
255
256 // GEODESIC: any-angle Dijkstra (Lazy Theta*). Each NavCel is measured in a straight line from
257 // a pivot (first the centre). A neighbour is pushed with its parent's pivot; when it is popped,
258 // a walk along the line checks that the line stays on the surface, and if it does not, the
259 // nearest settled neighbour becomes its pivot. Exact on planes; around borders and holes it
260 // bends at centroids, so it can be about one NavCel longer per bend.
261 auto cmp = [](const TraversalScratch::Front & a, const TraversalScratch::Front & b) {
262 return a.d > b.d;
263 };
264 auto push = [&](const TraversalScratch::Front & e) {
265 sc.heap.push_back(e);
266 std::push_heap(sc.heap.begin(), sc.heap.end(), cmp);
267 };
268 auto visible = [&](NavCelId from, const Eigen::Vector3f & p, const Eigen::Vector3f & q,
269 NavCelId target) {
270 NavCelId last = kNoCel;
271 const bool reached = for_each_on_segment_impl(
272 nm, from, p, q, [&](NavCelId x, float, float) {last = x;});
273 return reached && last == target;
274 };
275 push({(nm.navcel_centroid(seed) - center).norm(), seed, seed, 0.0f, center});
276 while (!sc.heap.empty()) {
277 std::pop_heap(sc.heap.begin(), sc.heap.end(), cmp);
278 TraversalScratch::Front u = sc.heap.back();
279 sc.heap.pop_back();
280 if (sc.marked(u.cid)) {continue;}
281 const Eigen::Vector3f pu = nm.navcel_centroid(u.cid);
282 if (u.cid != seed && !visible(u.pivot_cel, u.pivot, pu, u.cid)) {
283 // Not seen from the inherited pivot: through the best settled neighbour instead
284 u.d = std::numeric_limits<float>::infinity();
285 for (const NavCelId m : nm.navcels[u.cid].neighbor) {
286 if (m == kNoCel || !sc.marked(m)) {continue;}
287 const Eigen::Vector3f pm = nm.navcel_centroid(m);
288 const float dm = sc.dist[m] + (pu - pm).norm();
289 if (dm < u.d) {u = {dm, u.cid, m, sc.dist[m], pm};}
290 }
291 }
292 sc.mark(u.cid);
293 sc.dist[u.cid] = u.d;
294 if (u.cid != seed && u.d > r_out) {continue;} // corrected beyond the radius
295 if (!emit(u.cid, u.d)) {return visited;}
296 for (const NavCelId n : nm.navcels[u.cid].neighbor) {
297 if (n == kNoCel || sc.marked(n)) {continue;}
298 // Optimistic: n seen from u's pivot (checked when popped)
299 const Eigen::Vector3f pn = nm.navcel_centroid(n);
300 const float dn = u.pivot_len + (pn - u.pivot).norm();
301 if (dn <= r_out) {push({dn, n, u.pivot_cel, u.pivot_len, u.pivot});}
302 }
303 }
304 return visited;
305}
306
307} // namespace detail
308
314template<class F>
316 const NavMap & nm, const AABB & box, F && f, TraversalScratch * scratch = nullptr)
317{
318 TraversalScratch local;
319 TraversalScratch & sc = scratch ? *scratch : local;
320 std::size_t visited = 0;
321 for (const auto & s : nm.surfaces) {
322 if (s.bvh.empty()) {continue;}
323 sc.nodes.clear();
324 sc.nodes.push_back(0);
325 while (!sc.nodes.empty()) {
326 const BVHNode & node = s.bvh[sc.nodes.back()];
327 sc.nodes.pop_back();
328 if (!detail::boxes_overlap(node.box, box)) {continue;}
329 if (!node.is_leaf()) {
330 sc.nodes.push_back(node.left);
331 sc.nodes.push_back(node.right);
332 continue;
333 }
334 for (int i = 0; i < node.count; ++i) {
335 const auto cid = static_cast<NavCelId>(s.prim_indices[node.start + i]);
336 if (!detail::boxes_overlap(detail::navcel_box(nm, cid), box)) {continue;}
337 ++visited;
338 if (!detail::visit(f, cid)) {return visited;}
339 }
340 }
341 }
342 return visited;
343}
344
351template<class F>
353 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & center, float radius, F && f,
354 Metric metric = Metric::EUCLIDEAN, TraversalScratch * scratch = nullptr)
355{
356 auto g = [&](NavCelId c, float) {return detail::visit(f, c);};
357 return detail::grow(nm, seed, center, 0.0f, radius, metric, true, scratch, g);
358}
359
361template<class F>
363 const NavMap & nm, const Eigen::Vector3f & center, float radius, F && f,
364 Metric metric = Metric::EUCLIDEAN, TraversalScratch * scratch = nullptr)
365{
366 NavCelId seed;
367 if (!detail::seed_at(nm, center, seed)) {return 0;}
368 return for_each_in_disc(nm, seed, center, radius, std::forward<F>(f), metric, scratch);
369}
370
377template<class F>
379 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & center, float r_in, float r_out,
380 F && f, Metric metric = Metric::EUCLIDEAN, TraversalScratch * scratch = nullptr)
381{
382 auto g = [&](NavCelId c, float) {return detail::visit(f, c);};
383 return detail::grow(nm, seed, center, r_in, r_out, metric, false, scratch, g);
384}
385
393template<class F>
395 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & center, float max_distance, F && f,
396 TraversalScratch * scratch = nullptr)
397{
398 return detail::grow(nm, seed, center, 0.0f, max_distance, Metric::GEODESIC, true, scratch, f);
399}
400
407template<class F>
409 const NavMap & nm, NavCelId seed, const std::vector<Eigen::Vector2f> & poly, F && f,
410 TraversalScratch * scratch = nullptr)
411{
412 if (seed >= nm.navcels.size() || poly.size() < 3) {return 0;}
413 TraversalScratch local;
414 TraversalScratch & sc = scratch ? *scratch : local;
415 sc.begin(nm.navcels.size());
416 sc.mark(seed);
417 sc.stack.push_back(seed);
418 std::size_t head = 0, visited = 0;
419 while (head < sc.stack.size()) {
420 const NavCelId c = sc.stack[head++];
421 const Eigen::Vector3f p = nm.navcel_centroid(c);
422 const bool in = detail::in_polygon(poly, p.x(), p.y());
423 if (!in && c != seed) {continue;}
424 if (in) {
425 ++visited;
426 if (!detail::visit(f, c)) {return visited;}
427 }
428 for (const NavCelId n : nm.navcels[c].neighbor) {
429 if (n != detail::kNoCel && sc.mark(n)) {sc.stack.push_back(n);}
430 }
431 }
432 return visited;
433}
434
436template<class F>
438 const NavMap & nm, const Eigen::Vector3f & on_surface,
439 const std::vector<Eigen::Vector2f> & poly, F && f, TraversalScratch * scratch = nullptr)
440{
441 NavCelId seed;
442 if (!detail::seed_at(nm, on_surface, seed)) {return 0;}
443 return for_each_in_polygon(nm, seed, poly, std::forward<F>(f), scratch);
444}
445
457template<class F>
459 const NavMap & nm, NavCelId seed, const Eigen::Vector3f & a, const Eigen::Vector3f & b, F && f)
460{
461 return detail::for_each_on_segment_impl(nm, seed, a, b, std::forward<F>(f));
462}
463
465template<class F>
467 const NavMap & nm, const Eigen::Vector3f & a, const Eigen::Vector3f & b, F && f)
468{
469 NavCelId seed;
470 if (!detail::seed_at(nm, a, seed)) {return false;}
471 return for_each_on_segment(nm, seed, a, b, std::forward<F>(f));
472}
473
474} // namespace navmap
475
476#endif // NAVMAP_CORE__ITERATORS_HPP_
Core container and data structures for EasyNav navigable meshes.
Definition NavMap.hpp:561
Eigen::Vector3f navcel_centroid(NavCelId cid) const
Triangle centroid (computed on the fly).
Definition NavMap.cpp:1069
std::vector< NavCel > navcels
All triangles (global indexing).
Definition NavMap.hpp:565
bool locate_navcel(const Eigen::Vector3f &p_world, size_t &surface_idx, NavCelId &cid, Eigen::Vector3f &bary, Eigen::Vector3f *hit_pt) const
Locate the triangle under / near a world point (convenience).
Definition NavMap.hpp:1391
std::vector< Surface > surfaces
Surfaces (partitions of navcels).
Definition NavMap.hpp:566
Positions positions
Vertex positions (SoA).
Definition NavMap.hpp:563
Reusable traversal buffers. Not thread-safe: one per thread.
Definition Iterators.hpp:59
std::vector< int > nodes
BVH node stack.
Definition Iterators.hpp:101
bool marked(NavCelId cid) const
Definition Iterators.hpp:85
std::vector< NavCelId > stack
BFS frontier.
Definition Iterators.hpp:99
bool mark(NavCelId cid)
Marks cid; false if it was already marked in this traversal.
Definition Iterators.hpp:78
void begin(std::size_t n)
Starts a traversal over a map of n NavCels: every NavCel unmarked.
Definition Iterators.hpp:62
std::vector< float > dist
Per NavCel, valid once marked: its settled distance.
Definition Iterators.hpp:97
std::vector< Front > heap
Dijkstra frontier (min-heap).
Definition Iterators.hpp:100
Definition Iterators.hpp:109
bool boxes_overlap(const AABB &a, const AABB &b)
Definition Iterators.hpp:125
bool visit(F &f, A &&... a)
Definition Iterators.hpp:113
bool seed_at(const NavMap &nm, const Eigen::Vector3f &p, NavCelId &seed)
Definition Iterators.hpp:157
AABB navcel_box(const NavMap &nm, NavCelId cid)
Definition Iterators.hpp:132
bool for_each_on_segment_impl(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &a, const Eigen::Vector3f &b, F &&f)
Definition Iterators.hpp:166
std::size_t grow(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &center, float r_in, float r_out, Metric metric, bool with_seed, TraversalScratch *scratch, F &f)
Definition Iterators.hpp:223
constexpr NavCelId kNoCel
Definition Iterators.hpp:123
bool in_polygon(const std::vector< Eigen::Vector2f > &poly, float x, float y)
Definition Iterators.hpp:142
Definition Combine.hpp:39
std::size_t for_each_in_polygon(const NavMap &nm, NavCelId seed, const std::vector< Eigen::Vector2f > &poly, F &&f, TraversalScratch *scratch=nullptr)
Visits the NavCels whose centroid lies inside the XY polygon poly, connected to seed through NavCels ...
Definition Iterators.hpp:408
bool for_each_on_segment(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &a, const Eigen::Vector3f &b, F &&f)
Walks the surface along segment a -> b (in XY), from seed (the NavCel under a), visiting each NavCel ...
Definition Iterators.hpp:458
std::size_t for_each_in_disc(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &center, float radius, F &&f, Metric metric=Metric::EUCLIDEAN, TraversalScratch *scratch=nullptr)
Visits the NavCels within radius of center, connected to seed (which should contain the centre).
Definition Iterators.hpp:352
uint32_t NavCelId
Index of a triangle (NavCel) within the global mesh.
Definition NavMap.hpp:59
Metric
Distance from a centre used to grow a region.
Definition Iterators.hpp:52
@ EUCLIDEAN
3D distance from the centre to each NavCel centroid
Definition Iterators.hpp:53
@ GEODESIC
Along the surface: straight lines on it, bent only around borders.
Definition Iterators.hpp:54
std::size_t for_each_in_aabb(const NavMap &nm, const AABB &box, F &&f, TraversalScratch *scratch=nullptr)
Visits the NavCels whose bounding box overlaps box, on every surface (via the BVH).
Definition Iterators.hpp:315
std::size_t for_each_by_distance(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &center, float max_distance, F &&f, TraversalScratch *scratch=nullptr)
Visits NavCels by geodesic distance from center, up to max_distance: a spiral over the surface (neare...
Definition Iterators.hpp:394
std::size_t for_each_in_ring(const NavMap &nm, NavCelId seed, const Eigen::Vector3f &center, float r_in, float r_out, F &&f, Metric metric=Metric::EUCLIDEAN, TraversalScratch *scratch=nullptr)
Visits the NavCels at distance in [r_in, r_out] from center, connected to seed through NavCels within...
Definition Iterators.hpp:378
Axis-aligned bounding box.
Definition Geometry.hpp:165
Vec3 max
Maximum corner (x_max, y_max, z_max).
Definition Geometry.hpp:175
Vec3 min
Minimum corner (x_min, y_min, z_min).
Definition Geometry.hpp:169
void expand(const Vec3 &p)
Expand the box to include a point p.
Definition Geometry.hpp:183
Node in a per-surface bounding volume hierarchy (BVH).
Definition NavMap.hpp:455
int right
Right child index (or -1 for leaf).
Definition NavMap.hpp:458
int start
Start index in primitive array (leaf only).
Definition NavMap.hpp:459
bool is_leaf() const
Definition NavMap.hpp:461
int count
Number of primitives in leaf (0 for inner nodes).
Definition NavMap.hpp:460
int left
Left child index (or -1 for leaf).
Definition NavMap.hpp:457
AABB box
Bounding box of this node (world coordinates).
Definition NavMap.hpp:456
Eigen::Vector3f at(PointId id) const
Returns vertex id as a 3D vector.
Definition NavMap.hpp:89
Dijkstra frontier entry: distance, NavCel, and the pivot it is measured from.
Definition Iterators.hpp:89
Eigen::Vector3f pivot
Definition Iterators.hpp:94
float d
Definition Iterators.hpp:90
float pivot_len
Definition Iterators.hpp:93
NavCelId pivot_cel
Definition Iterators.hpp:92
NavCelId cid
Definition Iterators.hpp:91