Pyrogenesis  trunk
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
53 class TestCmpPathfinder;
54 class TestHierarchicalPathfinder;
55 #endif
56 
58 class SceneCollector;
59 
61 {
62 #ifdef TEST
63  friend class TestCmpPathfinder;
64  friend class TestHierarchicalPathfinder;
65 #endif
66 public:
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  */
150  Grid<u16> GetConnectivityGrid(pass_class_t passClass) const;
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 
164 private:
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  }
249  u16 gi, gj;
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  }
272  u16 gi, gj;
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;
287  GlobalRegionID m_NextGlobalRegionID;
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 
296 public:
297  std::vector<SOverlayLine> m_DebugOverlayLines;
298 };
299 
301 {
302 public:
304 
306  TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TERRAIN_TILE), m_PathfinderHier(pathfinderHier)
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);
315  pass_class_t passClass = m_PathfinderHier.GetPassabilityClass("default");
316 
317  for (u16 j = 0; j < height; ++j)
318  {
319  for (u16 i = 0; i < width; ++i)
320  {
321  SColor4ub color;
322 
323  HierarchicalPathfinder::RegionID rid = m_PathfinderHier.Get(i, j, passClass);
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
Definition: HierarchicalPathfinder.h:168
Definition: HierarchicalPathfinder.h:261
u8 G
Definition: SColor.h:33
HierarchicalOverlay * m_DebugOverlay
Definition: HierarchicalPathfinder.h:293
#define LOGERROR(...)
Definition: CLogger.h:36
u16 pass_class_t
Definition: Pathfinding.h:27
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
Definition: HierarchicalPathfinder.h:238
SortByCenterToPoint(u16 i, u16 j)
Definition: HierarchicalPathfinder.h:240
Definition: Pathfinding.cpp:27
RegionID(u8 ci, u8 cj, u16 r)
Definition: HierarchicalPathfinder.h:74
void Update(Grid< NavcellData > *grid, const Grid< u8 > &dirtinessGrid)
Definition: HierarchicalPathfinder.cpp:438
void FillRegionOnGrid(const RegionID &region, pass_class_t passClass, u16 value, Grid< u16 > &grid) const
Definition: HierarchicalPathfinder.cpp:808
GlobalRegionID m_NextGlobalRegionID
Definition: HierarchicalPathfinder.h:287
HierarchicalOverlay(HierarchicalPathfinder &pathfinderHier)
Definition: HierarchicalPathfinder.h:305
uint16_t u16
Definition: types.h:38
u32 DistanceTo(u16 i, u16 j) const
Definition: HierarchicalPathfinder.h:96
const Chunk & GetChunk(u8 ci, u8 cj, pass_class_t passClass) const
Definition: HierarchicalPathfinder.h:194
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
Definition: HierarchicalPathfinder.h:255
std::vector< u16 > m_RegionsID
Definition: HierarchicalPathfinder.h:171
u16 bestJ
Definition: HierarchicalPathfinder.h:258
u16 m_W
Definition: HierarchicalPathfinder.h:280
void ComputeNeighbors(EdgesMap &edges, Chunk &a, Chunk &b, bool transpose, bool opposite) const
Definition: HierarchicalPathfinder.cpp:510
u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]
Definition: HierarchicalPathfinder.h:172
Pathfinder goal.
Definition: PathGoal.h:32
u32 GlobalRegionID
Definition: HierarchicalPathfinder.h:67
bool operator()(const InterestingRegion &a, const InterestingRegion &b) const
Definition: HierarchicalPathfinder.h:264
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
u16 gj
Definition: HierarchicalPathfinder.h:249
Contains pointers to various &#39;global&#39; objects that are needed by the simulation code, to allow easy access without using real (evil) global variables.
Definition: SimContext.h:32
u8 B
Definition: SColor.h:34
uint8_t u8
Definition: types.h:37
Grid< u16 > GetConnectivityGrid(pass_class_t passClass) const
Generates the connectivity grid associated with the given pass_class.
Definition: HierarchicalPathfinder.cpp:823
This interface accepts renderable objects.
Definition: Scene.h:89
u8 m_ChunkJ
Definition: HierarchicalPathfinder.h:170
u8 A
Definition: SColor.h:35
bool operator()(const HierarchicalPathfinder::RegionID &a, const HierarchicalPathfinder::RegionID &b) const
Definition: HierarchicalPathfinder.h:241
Definition: HierarchicalPathfinder.h:300
u8 cj
Definition: HierarchicalPathfinder.h:71
pass_class_t GetPassabilityClass(const std::string &name) const
Definition: HierarchicalPathfinder.h:152
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:290
u8 m_ChunksH
Definition: HierarchicalPathfinder.h:281
HierarchicalPathfinder()
Definition: HierarchicalPathfinder.cpp:327
uint32_t u32
Definition: types.h:39
RegionID region
Definition: HierarchicalPathfinder.h:256
SortByBestToPoint(u16 i, u16 j)
Definition: HierarchicalPathfinder.h:263
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
Definition: HierarchicalPathfinder.h:60
Definition: HierarchicalPathfinder.h:69
void RenderSubmit(SceneCollector &collector)
Definition: HierarchicalPathfinder.cpp:353
Definition: SColor.h:30
void FindNearestNavcellInRegions(const std::set< RegionID, SortByCenterToPoint > &regions, u16 &iGoal, u16 &jGoal, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:740
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
bool MakeGoalReachable(u16 i0, u16 j0, PathGoal &goal, pass_class_t passClass) const
Updates goal so that it&#39;s guaranteed to be reachable from the navcell i0, j0 (which is assumed to be ...
Definition: HierarchicalPathfinder.cpp:676
void UpdateGlobalRegions(const std::map< pass_class_t, std::vector< RegionID > > &needNewGlobalRegionMap)
Definition: HierarchicalPathfinder.cpp:626
~HierarchicalPathfinder()
Definition: HierarchicalPathfinder.cpp:331
HierarchicalPathfinder & m_PathfinderHier
Definition: HierarchicalPathfinder.h:303
void SetDebugOverlay(bool enabled, const CSimContext *simContext)
Definition: HierarchicalPathfinder.cpp:336
bool operator==(const RegionID &b) const
Definition: HierarchicalPathfinder.h:90
u8 R
Definition: SColor.h:32
u8 m_ChunksW
Definition: HierarchicalPathfinder.h:281
u8 ci
Definition: HierarchicalPathfinder.h:71
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
void AddDebugEdges(pass_class_t passClass)
Debug visualisation of graph edges between regions.
Definition: HierarchicalPathfinder.cpp:588
RegionID Get(u16 i, u16 j, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:650
GlobalRegionID GetGlobalRegion(u16 i, u16 j, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:658
std::map< std::string, pass_class_t > m_PassClassMasks
Definition: HierarchicalPathfinder.h:290
Base class for texture-based terrain overlays, with an arbitrary number of texels per terrain tile...
Definition: TerrainOverlay.h:197
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
u8 m_ChunkI
Definition: HierarchicalPathfinder.h:170
u16 m_H
Definition: HierarchicalPathfinder.h:280
u16 bestI
Definition: HierarchicalPathfinder.h:257
bool operator<(const RegionID &b) const
Definition: HierarchicalPathfinder.h:76
std::map< pass_class_t, std::vector< Chunk > > m_Chunks
Definition: HierarchicalPathfinder.h:282
#define cassert(expr)
Compile-time assertion.
Definition: code_annotation.h:207
const CSimContext * m_SimContext
Definition: HierarchicalPathfinder.h:294
std::map< RegionID, std::set< RegionID > > EdgesMap
Definition: HierarchicalPathfinder.h:199
u16 gj
Definition: HierarchicalPathfinder.h:272
static const u8 CHUNK_SIZE
Definition: HierarchicalPathfinder.h:165
bool IsGoalReachable(u16 i0, u16 j0, const PathGoal &goal, pass_class_t passClass) const
Definition: HierarchicalPathfinder.cpp:710
u16 r
Definition: HierarchicalPathfinder.h:72
std::vector< SOverlayLine > m_DebugOverlayLines
Definition: HierarchicalPathfinder.h:297
std::map< pass_class_t, EdgesMap > m_Edges
Definition: HierarchicalPathfinder.h:284
std::map< pass_class_t, std::map< RegionID, GlobalRegionID > > m_GlobalRegions
Definition: HierarchicalPathfinder.h:286
void UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap &edges)
Connect a chunk&#39;s regions to their neighbors.
Definition: HierarchicalPathfinder.cpp:542