# `omath::pathfinding::Astar` — Pathfinding over a navigation mesh > Header: your project’s `pathfinding/astar.hpp` > Namespace: `omath::pathfinding` > Inputs: start/end as `Vector3`, a `NavigationMesh` > Output: ordered list of waypoints `std::vector>` `Astar` exposes a single public function, `find_path`, that computes a path of 3D waypoints on a navigation mesh. Internally it reconstructs the result with `reconstruct_final_path` from a closed set keyed by `Vector3`. --- ## API ```cpp namespace omath::pathfinding { struct PathNode; // holds per-node search data (see "Expected PathNode fields") class Astar final { public: [[nodiscard]] static std::vector> find_path(const Vector3& start, const Vector3& end, const NavigationMesh& nav_mesh) noexcept; private: [[nodiscard]] static std::vector> reconstruct_final_path( const std::unordered_map, PathNode>& closed_list, const Vector3& current) noexcept; }; } // namespace omath::pathfinding ``` ### Semantics * Returns a **polyline** of 3D points from `start` to `end`. * If no path exists, the function typically returns an **empty vector** (behavior depends on implementation details; keep this contract in unit tests). --- ## What `NavigationMesh` is expected to provide The header doesn’t constrain `NavigationMesh`, but for A* it commonly needs: * **Neighborhood queries**: given a position or node key → iterable neighbors. * **Traversal cost**: `g(u,v)` (often Euclidean distance or edge weight). * **Heuristic**: `h(x,end)` (commonly straight-line distance on the mesh). * **Projection / snap**: the ability to map `start`/`end` to valid nodes/points on the mesh (if they are off-mesh). > If your `NavigationMesh` doesn’t directly expose these, `Astar::find_path` likely does the adapter work (snapping to the nearest convex polygon/portal nodes and expanding across adjacency). --- ## Expected `PathNode` fields Although not visible here, `PathNode` typically carries: * `Vector3 parent;` — predecessor position or key for backtracking * `float g;` — cost from `start` * `float h;` — heuristic to `end` * `float f;` — `g + h` `reconstruct_final_path(closed_list, current)` walks `parent` links from `current` back to the start, **reverses** the chain, and returns the path. --- ## Heuristic & optimality * Use an **admissible** heuristic (never overestimates true cost) to keep A* optimal. The usual choice is **Euclidean distance** in 3D: ```cpp h(x, goal) = (goal - x).length(); ``` * For best performance, make it **consistent** (triangle inequality holds). Euclidean distance is consistent on standard navmeshes. --- ## Complexity Let `V` be explored vertices (or portal nodes) and `E` the traversed edges. * With a binary heap open list: **O(E log V)** time, **O(V)** memory. * With a d-ary heap or pairing heap you may reduce practical constants. --- ## Typical usage ```cpp #include "omath/pathfinding/astar.hpp" #include "omath/pathfinding/navigation_mesh.hpp" using omath::Vector3; using omath::pathfinding::Astar; NavigationMesh nav = /* ... load/build mesh ... */; Vector3 start{2.5f, 0.0f, -1.0f}; Vector3 goal {40.0f, 0.0f, 12.0f}; auto path = Astar::find_path(start, goal, nav); if (!path.empty()) { // feed to your agent/renderer for (const auto& p : path) { // draw waypoint p or push to steering } } else { // handle "no path" (e.g., unreachable or disconnected mesh) } ``` --- ## Notes & recommendations * **Start/end snapping**: If `start` or `end` are outside the mesh, decide whether to snap to the nearest polygon/portal or fail early. Keep this behavior consistent and document it where `NavigationMesh` is defined. * **Numerical stability**: Prefer squared distances when only comparing (`dist2`) to avoid unnecessary `sqrt`. * **Tie-breaking**: When `f` ties are common (grid-like graphs), bias toward larger `g` or smaller `h` to reduce zig-zagging. * **Smoothing**: A* returns a polyline that may hug polygon edges. Consider: * **String pulling / Funnel algorithm** over the corridor of polygons to get a straightened path. * **Raycast smoothing** (visibility checks) to remove redundant interior points. * **Hashing `Vector3`**: Your repo defines `std::hash>`. Ensure equality/precision rules for using float keys are acceptable (or use discrete node IDs instead). --- ## Testing checklist * Start/end on the **same polygon** → direct path of 2 points. * **Disconnected components** → empty result. * **Narrow corridors** → path stays inside. * **Obstacles blocking** → no path. * **Floating-point noise** → still reconstructs a valid chain from parents. --- ## Minimal pseudo-implementation outline (for reference) ```cpp // Pseudocode only — matches the header’s intent std::vector find_path(start, goal, mesh) { auto [snode, gnode] = mesh.snap_to_nodes(start, goal); OpenSet open; // min-heap by f std::unordered_map closed; open.push({snode, g=0, h=heuristic(snode, gnode)}); parents.clear(); while (!open.empty()) { auto current = open.pop_min(); // node with lowest f if (current.pos == gnode.pos) return reconstruct_final_path(closed, current.pos); for (auto [nbr, cost] : mesh.neighbors(current.pos)) { float tentative_g = current.g + cost; if (auto it = closed.find(nbr); it == closed.end() || tentative_g < it->second.g) { closed[nbr] = { .parent = current.pos, .g = tentative_g, .h = heuristic(nbr, gnode.pos), .f = tentative_g + heuristic(nbr, gnode.pos) }; open.push(closed[nbr]); } } } return {}; // no path } ``` --- ## FAQ * **Why return `std::vector>` and not polygon IDs?** Waypoints are directly usable by agents/steering and for rendering. If you also need the corridor (polygon chain), extend the API or `PathNode` to store it. * **Does `find_path` modify the mesh?** No; it should be a read-only search over `NavigationMesh`. --- *Last updated: 31 Oct 2025*