Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
HierarchicalPathfinder.h
Go to the documentation of this file.
1/* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#ifndef INCLUDED_HIERPATHFINDER
19#define INCLUDED_HIERPATHFINDER
20
21#include "Pathfinding.h"
22
23#include "ps/CLogger.h"
25#include "Render.h"
26#include "graphics/SColor.h"
27
28#include <map>
29#include <set>
30
31/**
32 * Hierarchical pathfinder.
33 *
34 * Deals with connectivity (can point A reach point B?).
35 *
36 * The navcell-grid representation of the map is split into fixed-size chunks.
37 * Within a chunk, each maximal set of adjacently-connected passable navcells
38 * is defined as a region.
39 * Each region is a vertex in the hierarchical pathfinder's graph.
40 * When two regions in adjacent chunks are connected by passable navcells,
41 * the graph contains an edge between the corresponding two vertexes.
42 * By design, there can never be an edge between two regions in the same chunk.
43 *
44 * Those fixed-size chunks are used to efficiently compute "global regions" by effectively flood-filling.
45 * Those can then be used to immediately determine if two reachables points are connected.
46 *
47 * The main use of this class is to convert an arbitrary PathGoal to a reachable navcell.
48 * This happens in MakeGoalReachable.
49 *
50 */
51
52#ifdef TEST
53class TestCmpPathfinder;
54class TestHierarchicalPathfinder;
55#endif
56
58class SceneCollector;
59
61{
62#ifdef TEST
63 friend class TestCmpPathfinder;
64 friend class TestHierarchicalPathfinder;
65#endif
66public:
68
69 struct RegionID
70 {
71 u8 ci, cj; // chunk ID
72 u16 r; // unique-per-chunk local region ID
73
74 RegionID(u8 ci, u8 cj, u16 r) : ci(ci), cj(cj), r(r) { }
75
76 bool operator<(const RegionID& b) const
77 {
78 // Sort by chunk ID, then by per-chunk region ID
79 if (ci < b.ci)
80 return true;
81 if (b.ci < ci)
82 return false;
83 if (cj < b.cj)
84 return true;
85 if (b.cj < cj)
86 return false;
87 return r < b.r;
88 }
89
90 bool operator==(const RegionID& b) const
91 {
92 return ((ci == b.ci) && (cj == b.cj) && (r == b.r));
93 }
94
95 // Returns the distance from the center to the point (i, j)
96 inline u32 DistanceTo(u16 i, u16 j) const
97 {
98 return (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) * (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) +
99 (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j) * (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j);
100 }
101
102 };
103
106
107 void SetDebugOverlay(bool enabled, const CSimContext* simContext);
108
109 // Non-pathfinding grids will never be recomputed on calling HierarchicalPathfinder::Update
110 void Recompute(Grid<NavcellData>* passabilityGrid,
111 const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks,
112 const std::map<std::string, pass_class_t>& pathfindingPassClassMasks);
113
114 void Update(Grid<NavcellData>* grid, const Grid<u8>& dirtinessGrid);
115
116 RegionID Get(u16 i, u16 j, pass_class_t passClass) const;
117
118 GlobalRegionID GetGlobalRegion(u16 i, u16 j, pass_class_t passClass) const;
119 GlobalRegionID GetGlobalRegion(RegionID region, pass_class_t passClass) const;
120
121 /**
122 * Updates @p goal so that it's guaranteed to be reachable from the navcell
123 * @p i0, @p j0 (which is assumed to be on a passable navcell).
124 *
125 * If the goal is not reachable, it is replaced with a point goal nearest to
126 * the goal center.
127 *
128 * In the case of a non-point reachable goal, it is replaced with a point goal
129 * at the reachable navcell of the goal which is nearest to the starting navcell.
130 *
131 * @returns true if the goal was reachable, false otherwise.
132 */
133 bool MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass) const;
134
135 /**
136 * @return true if the goal is reachable from navcell i0, j0.
137 * (similar to MakeGoalReachable but only checking for reachability).
138 */
139 bool IsGoalReachable(u16 i0, u16 j0, const PathGoal& goal, pass_class_t passClass) const;
140
141 /**
142 * Updates @p i, @p j (which is assumed to be an impassable navcell)
143 * to the nearest passable navcell.
144 */
145 void FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass) const;
146
147 /**
148 * Generates the connectivity grid associated with the given pass_class
149 */
151
152 pass_class_t GetPassabilityClass(const std::string& name) const
153 {
154 auto it = m_PassClassMasks.find(name);
155 if (it != m_PassClassMasks.end())
156 return it->second;
157
158 LOGERROR("Invalid passability class name '%s'", name.c_str());
159 return 0;
160 }
161
162 void RenderSubmit(SceneCollector& collector);
163
164private:
165 static const u8 CHUNK_SIZE = 96; // number of navcells per side
166 // TODO: figure out best number. Probably 64 < n < 128
167
168 struct Chunk
169 {
170 u8 m_ChunkI, m_ChunkJ; // chunk ID
171 std::vector<u16> m_RegionsID; // IDs of local regions, 0 (impassable) excluded
172 u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]; // local region ID per navcell
173
174 cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_RegionsID with a checkerboard pattern
175
176 void InitRegions(int ci, int cj, Grid<NavcellData>* grid, pass_class_t passClass);
177
178 RegionID Get(int i, int j) const;
179
180 void RegionCenter(u16 r, int& i, int& j) const;
181
182 void RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const;
183
184 bool RegionNearestNavcellInGoal(u16 r, u16 i0, u16 j0, const PathGoal& goal, u16& iOut, u16& jOut, u32& dist2Best) const;
185
186#ifdef TEST
187 bool operator==(const Chunk& b) const
188 {
189 return (m_ChunkI == b.m_ChunkI && m_ChunkJ == b.m_ChunkJ && m_RegionsID.size() == b.m_RegionsID.size() && memcmp(&m_Regions, &b.m_Regions, sizeof(u16) * CHUNK_SIZE * CHUNK_SIZE) == 0);
190 }
191#endif
192 };
193
194 const Chunk& GetChunk(u8 ci, u8 cj, pass_class_t passClass) const
195 {
196 return m_Chunks.at(passClass).at(cj * m_ChunksW + ci);
197 }
198
199 typedef std::map<RegionID, std::set<RegionID> > EdgesMap;
200
201 void ComputeNeighbors(EdgesMap& edges, Chunk& a, Chunk& b, bool transpose, bool opposite) const;
202 void RecomputeAllEdges(pass_class_t passClass, EdgesMap& edges);
203 void UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges);
204
205 void UpdateGlobalRegions(const std::map<pass_class_t, std::vector<RegionID> >& needNewGlobalRegionMap);
206
207 /**
208 * Returns all reachable regions, optionally ordered in a specific manner.
209 */
210 template<typename Ordering>
211 void FindReachableRegions(RegionID from, std::set<RegionID, Ordering>& reachable, pass_class_t passClass) const
212 {
213 // Flood-fill the region graph, starting at 'from',
214 // collecting all the regions that are reachable via edges
215 reachable.insert(from);
216
217 const EdgesMap& edgeMap = m_Edges.at(passClass);
218 if (edgeMap.find(from) == edgeMap.end())
219 return;
220
221 std::vector<RegionID> open;
222 open.reserve(64);
223 open.push_back(from);
224
225 while (!open.empty())
226 {
227 RegionID curr = open.back();
228 open.pop_back();
229
230 for (const RegionID& region : edgeMap.at(curr))
231 // Add to the reachable set; if this is the first time we added
232 // it then also add it to the open list
233 if (reachable.insert(region).second)
234 open.push_back(region);
235 }
236 }
237
239 {
240 SortByCenterToPoint(u16 i, u16 j): gi(i), gj(j) {};
242 {
243 if (a.DistanceTo(gi, gj) < b.DistanceTo(gi, gj))
244 return true;
245 if (a.DistanceTo(gi, gj) > b.DistanceTo(gi, gj))
246 return false;
247 return a.r < b.r;
248 }
250 };
251
252 void FindNearestNavcellInRegions(const std::set<RegionID, SortByCenterToPoint>& regions,
253 u16& iGoal, u16& jGoal, pass_class_t passClass) const;
254
259 };
260
262 {
263 SortByBestToPoint(u16 i, u16 j): gi(i), gj(j) {};
264 bool operator()(const InterestingRegion& a, const InterestingRegion& b) const
265 {
266 if ((a.bestI - gi) * (a.bestI - gi) + (a.bestJ - gj) * (a.bestJ - gj) < (b.bestI - gi) * (b.bestI - gi) + (b.bestJ - gj) * (b.bestJ - gj))
267 return true;
268 if ((a.bestI - gi) * (a.bestI - gi) + (a.bestJ - gj) * (a.bestJ - gj) > (b.bestI - gi) * (b.bestI - gi) + (b.bestJ - gj) * (b.bestJ - gj))
269 return false;
270 return a.region.r < b.region.r;
271 }
273 };
274
275 // Returns the region along with the best cell for optimisation.
276 void FindGoalRegionsAndBestNavcells(u16 i0, u16 j0, u16 gi, u16 gj, const PathGoal& goal, std::set<InterestingRegion, SortByBestToPoint>& regions, pass_class_t passClass) const;
277
278 void FillRegionOnGrid(const RegionID& region, pass_class_t passClass, u16 value, Grid<u16>& grid) const;
279
282 std::map<pass_class_t, std::vector<Chunk> > m_Chunks;
283
284 std::map<pass_class_t, EdgesMap> m_Edges;
285
286 std::map<pass_class_t, std::map<RegionID, GlobalRegionID> > m_GlobalRegions;
288
289 // Passability classes for which grids will be updated when calling Update
290 std::map<std::string, pass_class_t> m_PassClassMasks;
291
292 void AddDebugEdges(pass_class_t passClass);
294 const CSimContext* m_SimContext; // Used for drawing the debug lines
295
296public:
297 std::vector<SOverlayLine> m_DebugOverlayLines;
298};
299
301{
302public:
304
307 {
308 }
309
310 virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
311 {
312 ENSURE(h <= std::numeric_limits<u16>::max() && w <= std::numeric_limits<u16>::max());
313 u16 height = static_cast<u16>(h);
314 u16 width = static_cast<u16>(w);
316
317 for (u16 j = 0; j < height; ++j)
318 {
319 for (u16 i = 0; i < width; ++i)
320 {
321 SColor4ub color;
322
324 if (rid.r == 0)
325 color = SColor4ub(0, 0, 0, 0);
326 else if (rid.r == 0xFFFF)
327 color = SColor4ub(255, 0, 255, 255);
328 else
329 color = GetColor(rid.r + rid.ci*5 + rid.cj*7, 127);
330
331 *data++ = color.R;
332 *data++ = color.G;
333 *data++ = color.B;
334 *data++ = color.A;
335 }
336 }
337 }
338};
339
340
341#endif // INCLUDED_HIERPATHFINDER
#define LOGERROR(...)
Definition: CLogger.h:37
bool operator==(const FCDJointWeightPair &a, const FCDJointWeightPair &b)
Definition: GeomReindex.cpp:59
u16 pass_class_t
Definition: Pathfinding.h:29
Contains pointers to various 'global' objects that are needed by the simulation code,...
Definition: SimContext.h:33
Definition: HierarchicalPathfinder.h:301
HierarchicalOverlay(HierarchicalPathfinder &pathfinderHier)
Definition: HierarchicalPathfinder.h:305
virtual void BuildTextureRGBA(u8 *data, size_t w, size_t h)
Called each frame to generate the texture to render on the terrain.
Definition: HierarchicalPathfinder.h:310
HierarchicalPathfinder & m_PathfinderHier
Definition: HierarchicalPathfinder.h:303
Definition: HierarchicalPathfinder.h:61
void FindNearestNavcellInRegions(const std::set< RegionID, SortByCenterToPoint > &regions, u16 &iGoal, u16 &jGoal, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:740
u8 m_ChunksH
Definition: HierarchicalPathfinder.h:281
const CSimContext * m_SimContext
Definition: HierarchicalPathfinder.h:294
std::map< pass_class_t, EdgesMap > m_Edges
Definition: HierarchicalPathfinder.h:284
void SetDebugOverlay(bool enabled, const CSimContext *simContext)
Definition: HierarchicalPathfinder.cpp:336
void Update(Grid< NavcellData > *grid, const Grid< u8 > &dirtinessGrid)
Definition: HierarchicalPathfinder.cpp:438
GlobalRegionID m_NextGlobalRegionID
Definition: HierarchicalPathfinder.h:287
u16 m_H
Definition: HierarchicalPathfinder.h:280
HierarchicalOverlay * m_DebugOverlay
Definition: HierarchicalPathfinder.h:293
std::map< std::string, pass_class_t > m_PassClassMasks
Definition: HierarchicalPathfinder.h:290
~HierarchicalPathfinder()
Definition: HierarchicalPathfinder.cpp:331
void FindNearestPassableNavcell(u16 &i, u16 &j, pass_class_t passClass) const
Updates i, j (which is assumed to be an impassable navcell) to the nearest passable navcell.
Definition: HierarchicalPathfinder.cpp:728
void FillRegionOnGrid(const RegionID &region, pass_class_t passClass, u16 value, Grid< u16 > &grid) const
Definition: HierarchicalPathfinder.cpp:808
u8 m_ChunksW
Definition: HierarchicalPathfinder.h:281
Grid< u16 > GetConnectivityGrid(pass_class_t passClass) const
Generates the connectivity grid associated with the given pass_class.
Definition: HierarchicalPathfinder.cpp:823
HierarchicalPathfinder()
Definition: HierarchicalPathfinder.cpp:327
static const u8 CHUNK_SIZE
Definition: HierarchicalPathfinder.h:165
bool MakeGoalReachable(u16 i0, u16 j0, PathGoal &goal, pass_class_t passClass) const
Updates goal so that it's guaranteed to be reachable from the navcell i0, j0 (which is assumed to be ...
Definition: HierarchicalPathfinder.cpp:676
std::map< pass_class_t, std::map< RegionID, GlobalRegionID > > m_GlobalRegions
Definition: HierarchicalPathfinder.h:286
pass_class_t GetPassabilityClass(const std::string &name) const
Definition: HierarchicalPathfinder.h:152
void ComputeNeighbors(EdgesMap &edges, Chunk &a, Chunk &b, bool transpose, bool opposite) const
Definition: HierarchicalPathfinder.cpp:510
u32 GlobalRegionID
Definition: HierarchicalPathfinder.h:67
void FindReachableRegions(RegionID from, std::set< RegionID, Ordering > &reachable, pass_class_t passClass) const
Returns all reachable regions, optionally ordered in a specific manner.
Definition: HierarchicalPathfinder.h:211
void RecomputeAllEdges(pass_class_t passClass, EdgesMap &edges)
Find edges between regions in all chunks, in an optimised manner (only look at top/left)
Definition: HierarchicalPathfinder.cpp:564
void RenderSubmit(SceneCollector &collector)
Definition: HierarchicalPathfinder.cpp:353
std::vector< SOverlayLine > m_DebugOverlayLines
Definition: HierarchicalPathfinder.h:297
RegionID Get(u16 i, u16 j, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:650
void AddDebugEdges(pass_class_t passClass)
Debug visualisation of graph edges between regions.
Definition: HierarchicalPathfinder.cpp:588
bool IsGoalReachable(u16 i0, u16 j0, const PathGoal &goal, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:710
void UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap &edges)
Connect a chunk's regions to their neighbors.
Definition: HierarchicalPathfinder.cpp:542
const Chunk & GetChunk(u8 ci, u8 cj, pass_class_t passClass) const
Definition: HierarchicalPathfinder.h:194
u16 m_W
Definition: HierarchicalPathfinder.h:280
GlobalRegionID GetGlobalRegion(u16 i, u16 j, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:658
std::map< RegionID, std::set< RegionID > > EdgesMap
Definition: HierarchicalPathfinder.h:199
std::map< pass_class_t, std::vector< Chunk > > m_Chunks
Definition: HierarchicalPathfinder.h:282
void UpdateGlobalRegions(const std::map< pass_class_t, std::vector< RegionID > > &needNewGlobalRegionMap)
Definition: HierarchicalPathfinder.cpp:626
void Recompute(Grid< NavcellData > *passabilityGrid, const std::map< std::string, pass_class_t > &nonPathfindingPassClassMasks, const std::map< std::string, pass_class_t > &pathfindingPassClassMasks)
Definition: HierarchicalPathfinder.cpp:362
void FindGoalRegionsAndBestNavcells(u16 i0, u16 j0, u16 gi, u16 gj, const PathGoal &goal, std::set< InterestingRegion, SortByBestToPoint > &regions, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:775
Pathfinder goal.
Definition: PathGoal.h:33
This interface accepts renderable objects.
Definition: Scene.h:90
Base class for texture-based terrain overlays, with an arbitrary number of texels per terrain tile,...
Definition: TerrainOverlay.h:198
SColor4ub GetColor(size_t idx, u8 alpha) const
Returns an arbitrary color, for subclasses that want to distinguish different integers visually.
Definition: TerrainOverlay.cpp:373
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:277
Definition: Pathfinding.cpp:28
constexpr int NAVCELLS_PER_TERRAIN_TILE
The terrain grid is coarser, and it is often convenient to convert from one to the other.
Definition: Pathfinding.h:150
Definition: HierarchicalPathfinder.h:169
void RegionNavcellNearest(u16 r, int iGoal, int jGoal, int &iBest, int &jBest, u32 &dist2Best) const
Returns the global navcell coords, and the squared distance to the goal navcell, of whichever navcell...
Definition: HierarchicalPathfinder.cpp:182
cassert(CHUNK_SIZE *CHUNK_SIZE/2< 65536)
u8 m_ChunkJ
Definition: HierarchicalPathfinder.h:170
void InitRegions(int ci, int cj, Grid< NavcellData > *grid, pass_class_t passClass)
Definition: HierarchicalPathfinder.cpp:37
u8 m_ChunkI
Definition: HierarchicalPathfinder.h:170
std::vector< u16 > m_RegionsID
Definition: HierarchicalPathfinder.h:171
u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]
Definition: HierarchicalPathfinder.h:172
RegionID Get(int i, int j) const
Returns a RegionID for the given global navcell coords (which must be inside this chunk);.
Definition: HierarchicalPathfinder.cpp:136
bool RegionNearestNavcellInGoal(u16 r, u16 i0, u16 j0, const PathGoal &goal, u16 &iOut, u16 &jOut, u32 &dist2Best) const
Gives the global navcell coords, and the squared distance to the (i0,j0) navcell, of whichever navcel...
Definition: HierarchicalPathfinder.cpp:214
void RegionCenter(u16 r, int &i, int &j) const
Return the global navcell coords that correspond roughly to the center of the given region in this ch...
Definition: HierarchicalPathfinder.cpp:147
Definition: HierarchicalPathfinder.h:255
u16 bestI
Definition: HierarchicalPathfinder.h:257
RegionID region
Definition: HierarchicalPathfinder.h:256
u16 bestJ
Definition: HierarchicalPathfinder.h:258
Definition: HierarchicalPathfinder.h:70
u8 ci
Definition: HierarchicalPathfinder.h:71
u8 cj
Definition: HierarchicalPathfinder.h:71
bool operator<(const RegionID &b) const
Definition: HierarchicalPathfinder.h:76
u32 DistanceTo(u16 i, u16 j) const
Definition: HierarchicalPathfinder.h:96
u16 r
Definition: HierarchicalPathfinder.h:72
bool operator==(const RegionID &b) const
Definition: HierarchicalPathfinder.h:90
RegionID(u8 ci, u8 cj, u16 r)
Definition: HierarchicalPathfinder.h:74
Definition: HierarchicalPathfinder.h:262
u16 gj
Definition: HierarchicalPathfinder.h:272
u16 gi
Definition: HierarchicalPathfinder.h:272
bool operator()(const InterestingRegion &a, const InterestingRegion &b) const
Definition: HierarchicalPathfinder.h:264
SortByBestToPoint(u16 i, u16 j)
Definition: HierarchicalPathfinder.h:263
Definition: HierarchicalPathfinder.h:239
SortByCenterToPoint(u16 i, u16 j)
Definition: HierarchicalPathfinder.h:240
u16 gi
Definition: HierarchicalPathfinder.h:249
u16 gj
Definition: HierarchicalPathfinder.h:249
bool operator()(const HierarchicalPathfinder::RegionID &a, const HierarchicalPathfinder::RegionID &b) const
Definition: HierarchicalPathfinder.h:241
Definition: SColor.h:31
u8 R
Definition: SColor.h:32
u8 B
Definition: SColor.h:34
u8 G
Definition: SColor.h:33
u8 A
Definition: SColor.h:35
uint8_t u8
Definition: types.h:37
uint16_t u16
Definition: types.h:38
uint32_t u32
Definition: types.h:39