Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpPathfinder_Tile.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2010 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 /**
19  * @file
20  * Tile-based algorithm for CCmpPathfinder.
21  * This is a fairly naive algorithm and could probably be improved substantially
22  * (hopefully without needing to change the interface much).
23  */
24 
25 #include "precompiled.h"
26 
27 #include "CCmpPathfinder_Common.h"
28 
29 #include "ps/Profile.h"
32 
34 
35 #define PATHFIND_STATS 0
36 
37 #define USE_DIAGONAL_MOVEMENT 1
38 
39 // Heuristic cost to move between adjacent tiles.
40 // This should be similar to DEFAULT_MOVE_COST.
41 const u32 g_CostPerTile = 256;
42 
43 /**
44  * Tile data for A* computation.
45  * (We store an array of one of these per terrain tile, so it ought to be optimised for size)
46  */
48 {
49 public:
50  enum {
54  };
55 
56  bool IsUnexplored() { return status == STATUS_UNEXPLORED; }
57  bool IsOpen() { return status == STATUS_OPEN; }
58  bool IsClosed() { return status == STATUS_CLOSED; }
61 
62  // Get pi,pj coords of predecessor to this tile on best path, given i,j coords of this tile
63  u16 GetPredI(u16 i) { return (u16)(i + dpi); }
64  u16 GetPredJ(u16 j) { return (u16)(j + dpj); }
65  // Set the pi,pj coords of predecessor, given i,j coords of this tile
66  void SetPred(u16 pi_, u16 pj_, u16 i, u16 j)
67  {
68  dpi = (i8)((int)pi_ - (int)i);
69  dpj = (i8)((int)pj_ - (int)j);
70 #if PATHFIND_DEBUG
71  // predecessor must be adjacent
72  ENSURE(pi_-i == -1 || pi_-i == 0 || pi_-i == 1);
73  ENSURE(pj_-j == -1 || pj_-j == 0 || pj_-j == 1);
74 #endif
75  }
76 
77 private:
78  u8 status; // this only needs 2 bits
79  i8 dpi, dpj; // these only really need 2 bits in total
80 public:
81  u32 cost; // g (cost to this tile)
82  u32 h; // h (heuristic cost to goal) (TODO: is it really better for performance to store this instead of recomputing?)
83 
84 #if PATHFIND_DEBUG
85  u32 GetStep() { return step; }
86  void SetStep(u32 s) { step = s; }
87 private:
88  u32 step; // step at which this tile was last processed (for debug rendering)
89 #else
90  u32 GetStep() { return 0; }
91  void SetStep(u32) { }
92 #endif
93 
94 };
95 
96 /**
97  * Terrain overlay for pathfinder debugging.
98  * Renders a representation of the most recent pathfinding operation.
99  */
101 {
103 public:
105 
107  : TerrainOverlay(pathfinder.GetSimContext()), m_Pathfinder(pathfinder)
108  {
109  }
110 
111  virtual void StartRender()
112  {
114  }
115 
116  virtual void ProcessTile(ssize_t i, ssize_t j)
117  {
119  RenderTile(CColor(1, 0, 0, 0.6f), false);
120 
122  {
123  PathfindTile& n = m_Pathfinder.m_DebugGrid->get((int)i, (int)j);
124 
125  float c = clamp((float)n.GetStep() / (float)m_Pathfinder.m_DebugSteps, 0.f, 1.f);
126 
127  if (n.IsOpen())
128  RenderTile(CColor(1, 1, c, 0.6f), false);
129  else if (n.IsClosed())
130  RenderTile(CColor(0, 1, c, 0.6f), false);
131  }
132  }
133 
134  virtual void EndRender()
135  {
137  {
138  std::vector<ICmpPathfinder::Waypoint>& wp = m_Pathfinder.m_DebugPath->m_Waypoints;
139  for (size_t n = 0; n < wp.size(); ++n)
140  {
141  u16 i, j;
142  m_Pathfinder.NearestTile(wp[n].x, wp[n].z, i, j);
143  RenderTileOutline(CColor(1, 1, 1, 1), 2, false, i, j);
144  }
145  }
146  }
147 };
148 
150 {
151  if (enabled && !m_DebugOverlay)
152  {
153  m_DebugOverlay = new PathfinderOverlay(*this);
154  }
155  else if (!enabled && m_DebugOverlay)
156  {
157  delete m_DebugOverlay;
158  m_DebugOverlay = NULL;
159  }
160 }
161 
162 void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass)
163 {
164  if (!m_DebugOverlay)
165  return;
166 
167  delete m_DebugGrid;
168  m_DebugGrid = NULL;
169  delete m_DebugPath;
170  m_DebugPath = new Path();
171  ComputePath(x0, z0, goal, passClass, costClass, *m_DebugPath);
172  m_DebugPassClass = passClass;
173 }
174 
176 {
177  delete m_DebugGrid;
178  m_DebugGrid = NULL;
179  delete m_DebugPath;
180  m_DebugPath = NULL;
181 }
182 
183 
184 //////////////////////////////////////////////////////////
185 
186 
188 {
189  u32 steps; // number of algorithm iterations
190 
191  u16 iGoal, jGoal; // goal tile
192  u16 rGoal; // radius of goal (around tile center)
193 
195  std::vector<u32> moveCosts;
196 
198  // (there's no explicit closed list; it's encoded in PathfindTile)
199 
202 
203  bool ignoreImpassable; // allows us to escape if stuck in patches of impassability
204 
205  u32 hBest; // heuristic of closest discovered tile to goal
206  u16 iBest, jBest; // closest tile
207 
208 #if PATHFIND_STATS
209  // Performance debug counters
210  size_t numProcessed;
211  size_t numImproveOpen;
212  size_t numImproveClosed;
213  size_t numAddToOpen;
214  size_t sumOpenSize;
215 #endif
216 };
217 
218 static bool AtGoal(u16 i, u16 j, const ICmpPathfinder::Goal& goal)
219 {
220  // Allow tiles slightly more than sqrt(2) from the actual goal,
221  // i.e. adjacent diagonally to the target tile
223 
224  entity_pos_t x, z;
225  CCmpPathfinder::TileCenter(i, j, x, z);
227  return (dist < tolerance);
228 }
229 
230 // Calculate heuristic cost from tile i,j to destination
231 // (This ought to be an underestimate for correctness)
232 static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal)
233 {
234 #if USE_DIAGONAL_MOVEMENT
236  CFixedVector2D goal (fixed::FromInt(iGoal), fixed::FromInt(jGoal));
237  fixed dist = (pos - goal).Length();
238  // TODO: the heuristic could match the costs better - it's not really Euclidean movement
239 
240  fixed rdist = dist - fixed::FromInt(rGoal);
241  rdist = rdist.Absolute();
242 
243  // To avoid overflows on large distances we have to convert to int before multiplying
244  // by the full tile cost, which means we lose some accuracy over short distances,
245  // so do a partial multiplication here.
246  // (This will overflow if sqrt(2)*tilesPerSide*premul >= 32768, so
247  // premul=32 means max tilesPerSide=724)
248  const int premul = 32;
249  cassert(g_CostPerTile % premul == 0);
250  return (rdist * premul).ToInt_RoundToZero() * (g_CostPerTile / premul);
251 
252 #else
253  return (abs((int)i - (int)iGoal) + abs((int)j - (int)jGoal)) * g_CostPerTile;
254 #endif
255 }
256 
257 // Calculate movement cost from predecessor tile pi,pj to tile i,j
258 static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, PathfindTileGrid* tempGrid, u32 tileCost)
259 {
260  u32 dg = tileCost;
261 
262 #if USE_DIAGONAL_MOVEMENT
263  // XXX: Probably a terrible hack:
264  // For simplicity, we only consider horizontally/vertically adjacent neighbours, but
265  // units can move along arbitrary lines. That results in ugly square paths, so we want
266  // to prefer diagonal paths.
267  // Instead of solving this nicely, I'll just special-case 45-degree and 30-degree lines
268  // by checking the three predecessor tiles (which'll be in the closed set and therefore
269  // likely to be reasonably stable) and reducing the cost, and use a Euclidean heuristic.
270  // At least this makes paths look a bit nicer for now...
271 
272  PathfindTile& p = tempGrid->get(pi, pj);
273  u16 ppi = p.GetPredI(pi);
274  u16 ppj = p.GetPredJ(pj);
275  if (ppi != i && ppj != j)
276  dg = (dg << 16) / 92682; // dg*sqrt(2)/2
277  else
278  {
279  PathfindTile& pp = tempGrid->get(ppi, ppj);
280  int di = abs(i - pp.GetPredI(ppi));
281  int dj = abs(j - pp.GetPredJ(ppj));
282  if ((di == 1 && dj == 2) || (di == 2 && dj == 1))
283  dg = (dg << 16) / 79742; // dg*(sqrt(5)-sqrt(2))
284  }
285 #endif
286 
287  return dg;
288 }
289 
290 // Do the A* processing for a neighbour tile i,j.
291 static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderState& state)
292 {
293 #if PATHFIND_STATS
294  state.numProcessed++;
295 #endif
296 
297  // Reject impassable tiles
298  TerrainTile tileTag = state.terrain->get(i, j);
299  if (!IS_PASSABLE(tileTag, state.passClass) && !state.ignoreImpassable)
300  return;
301 
302  u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles, state.moveCosts.at(GET_COST_CLASS(tileTag)));
303 
304  u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
305 
306  PathfindTile& n = state.tiles->get(i, j);
307 
308  // If this is a new tile, compute the heuristic distance
309  if (n.IsUnexplored())
310  {
311  n.h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal);
312  // Remember the best tile we've seen so far, in case we never actually reach the target
313  if (n.h < state.hBest)
314  {
315  state.hBest = n.h;
316  state.iBest = i;
317  state.jBest = j;
318  }
319  }
320  else
321  {
322  // If we've already seen this tile, and the new path to this tile does not have a
323  // better cost, then stop now
324  if (g >= n.cost)
325  return;
326 
327  // Otherwise, we have a better path.
328 
329  // If we've already added this tile to the open list:
330  if (n.IsOpen())
331  {
332  // This is a better path, so replace the old one with the new cost/parent
333  n.cost = g;
334  n.SetPred(pi, pj, i, j);
335  n.SetStep(state.steps);
336  state.open.promote(std::make_pair(i, j), g + n.h);
337 #if PATHFIND_STATS
338  state.numImproveOpen++;
339 #endif
340  return;
341  }
342 
343  // If we've already found the 'best' path to this tile:
344  if (n.IsClosed())
345  {
346  // This is a better path (possible when we use inadmissible heuristics), so reopen it
347 #if PATHFIND_STATS
348  state.numImproveClosed++;
349 #endif
350  // (fall through)
351  }
352  }
353 
354  // Add it to the open list:
355  n.SetStatusOpen();
356  n.cost = g;
357  n.SetPred(pi, pj, i, j);
358  n.SetStep(state.steps);
359  PriorityQueue::Item t = { std::make_pair(i, j), g + n.h };
360  state.open.push(t);
361 #if PATHFIND_STATS
362  state.numAddToOpen++;
363 #endif
364 }
365 
366 void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& path)
367 {
368  UpdateGrid();
369 
370  PROFILE3("ComputePath");
371 
372  PathfinderState state = { 0 };
373 
374  // Convert the start/end coordinates to tile indexes
375  u16 i0, j0;
376  NearestTile(x0, z0, i0, j0);
377  NearestTile(goal.x, goal.z, state.iGoal, state.jGoal);
378 
379  // If we're already at the goal tile, then move directly to the exact goal coordinates
380  if (AtGoal(i0, j0, goal))
381  {
382  Waypoint w = { goal.x, goal.z };
383  path.m_Waypoints.push_back(w);
384  return;
385  }
386 
387  // If the target is a circle, we want to aim for the edge of it (so e.g. if we're inside
388  // a large circle then the heuristics will aim us directly outwards);
389  // otherwise just aim at the center point. (We'll never try moving outwards to a square shape.)
390  if (goal.type == Goal::CIRCLE)
391  state.rGoal = (u16)(goal.hw / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero();
392  else
393  state.rGoal = 0;
394 
395  state.passClass = passClass;
396  state.moveCosts = m_MoveCosts.at(costClass);
397 
398  state.steps = 0;
399 
401  state.terrain = m_Grid;
402 
403  state.iBest = i0;
404  state.jBest = j0;
405  state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal, state.rGoal);
406 
407  PriorityQueue::Item start = { std::make_pair(i0, j0), 0 };
408  state.open.push(start);
409  state.tiles->get(i0, j0).SetStatusOpen();
410  state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0);
411  state.tiles->get(i0, j0).cost = 0;
412 
413  // To prevent units getting very stuck, if they start on an impassable tile
414  // surrounded entirely by impassable tiles, we ignore the impassability
415  state.ignoreImpassable = !IS_PASSABLE(state.terrain->get(i0, j0), state.passClass);
416 
417  while (1)
418  {
419  ++state.steps;
420 
421  // Hack to avoid spending ages computing giant paths, particularly when
422  // the destination is unreachable
423  if (state.steps > 40000)
424  break;
425 
426  // If we ran out of tiles to examine, give up
427  if (state.open.empty())
428  break;
429 
430 #if PATHFIND_STATS
431  state.sumOpenSize += state.open.size();
432 #endif
433 
434  // Move best tile from open to closed
435  PriorityQueue::Item curr = state.open.pop();
436  u16 i = curr.id.first;
437  u16 j = curr.id.second;
438  state.tiles->get(i, j).SetStatusClosed();
439 
440  // If we've reached the destination, stop
441  if (AtGoal(i, j, goal))
442  {
443  state.iBest = i;
444  state.jBest = j;
445  state.hBest = 0;
446  break;
447  }
448 
449  // As soon as we find an escape route from the impassable terrain,
450  // take it and forbid any further use of impassable tiles
451  if (state.ignoreImpassable)
452  {
453  if (i > 0 && IS_PASSABLE(state.terrain->get(i-1, j), state.passClass))
454  state.ignoreImpassable = false;
455  else if (i < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i+1, j), state.passClass))
456  state.ignoreImpassable = false;
457  else if (j > 0 && IS_PASSABLE(state.terrain->get(i, j-1), state.passClass))
458  state.ignoreImpassable = false;
459  else if (j < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i, j+1), state.passClass))
460  state.ignoreImpassable = false;
461  }
462 
463  u32 g = state.tiles->get(i, j).cost;
464  if (i > 0)
465  ProcessNeighbour(i, j, (u16)(i-1), j, g, state);
466  if (i < m_MapSize-1)
467  ProcessNeighbour(i, j, (u16)(i+1), j, g, state);
468  if (j > 0)
469  ProcessNeighbour(i, j, i, (u16)(j-1), g, state);
470  if (j < m_MapSize-1)
471  ProcessNeighbour(i, j, i, (u16)(j+1), g, state);
472  }
473 
474  // Reconstruct the path (in reverse)
475  u16 ip = state.iBest, jp = state.jBest;
476  while (ip != i0 || jp != j0)
477  {
478  PathfindTile& n = state.tiles->get(ip, jp);
479  entity_pos_t x, z;
480  TileCenter(ip, jp, x, z);
481  Waypoint w = { x, z };
482  path.m_Waypoints.push_back(w);
483 
484  // Follow the predecessor link
485  ip = n.GetPredI(ip);
486  jp = n.GetPredJ(jp);
487  }
488 
489  // Save this grid for debug display
490  delete m_DebugGrid;
491  m_DebugGrid = state.tiles;
492  m_DebugSteps = state.steps;
493 
494  PROFILE2_ATTR("from: (%d, %d)", i0, j0);
495  PROFILE2_ATTR("to: (%d, %d)", state.iGoal, state.jGoal);
496  PROFILE2_ATTR("reached: (%d, %d)", state.iBest, state.jBest);
497  PROFILE2_ATTR("steps: %u", state.steps);
498 
499 #if PATHFIND_STATS
500  printf("PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen);
501 #endif
502 }
#define u8
Definition: types.h:39
A simple fixed-point number class.
Definition: Fixed.h:115
std::vector< u32 > moveCosts
const u32 g_CostPerTile
virtual void StartRender()
Override to perform processing at the start of the overlay rendering, before the ProcessTile calls...
Declares CCmpPathfinder, whose implementation is split into multiple source files, and provides common code needed for more than one of those files.
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal &goal, pass_class_t passClass, cost_class_t costClass)
If the debug overlay is enabled, render the path that will computed by ComputePath.
Implementation of ICmpPathfinder.
void UpdateGrid()
Regenerates the grid based on the current obstruction list, if necessary.
Similar to Grid, except optimised for sparse usage (the grid is subdivided into buckets whose content...
Definition: Grid.h:112
void SetPred(u16 pi_, u16 pj_, u16 i, u16 j)
static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal &goal)
PathfinderOverlay(CCmpPathfinder &pathfinder)
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
virtual void ResetDebugPath()
virtual void EndRender()
Override to perform processing at the end of the overlay rendering, after the ProcessTile calls...
Definition: Overlay.h:34
void NearestTile(entity_pos_t x, entity_pos_t z, u16 &i, u16 &j)
Returns the tile containing the given position.
Priority queue implemented as a binary heap.
Definition: PriorityQueue.h:61
CFixed Absolute() const
Definition: Fixed.h:284
std::vector< std::vector< u32 > > m_MoveCosts
Base class for (relatively) simple drawing of data onto terrain tiles, intended for debugging purpose...
T & get(int i, int j)
Definition: Grid.h:163
Grid< TerrainTile > * m_Grid
static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderState &state)
void RenderTile(const CColor &colour, bool draw_hidden)
Draw a filled quad on top of the current tile.
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
Terrain overlay for pathfinder debugging.
static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal)
Definition: path.h:75
virtual void ProcessTile(ssize_t i, ssize_t j)
Override to perform processing of each tile.
PathfindTileGrid * m_DebugGrid
Tile data for A* computation.
void promote(ID id, R newrank)
Definition: PriorityQueue.h:86
void RenderTileOutline(const CColor &colour, int line_width, bool draw_hidden)
Draw an outlined quad on top of the current tile.
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal &goal, pass_class_t passClass, cost_class_t costClass, Path &ret)
Grid< TerrainTile > * terrain
virtual void SetDebugOverlay(bool enabled)
Toggle the storage and rendering of debug info.
CCmpPathfinder & m_Pathfinder
SparseGrid< PathfindTile > PathfindTileGrid
#define i8
Definition: types.h:34
intptr_t ssize_t
Definition: wposix_types.h:82
static CFixed FromInt(int n)
Definition: Fixed.h:136
#define IS_PASSABLE(item, classmask)
#define PROFILE2_ATTR
Associates a string (with printf-style formatting) with the current region or event.
Definition: Profiler2.h:461
PathfindTileGrid * tiles
PathfinderOverlay * m_DebugOverlay
#define u16
Definition: types.h:40
enum ICmpPathfinder::Goal::Type type
static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, PathfindTileGrid *tempGrid, u32 tileCost)
#define PROFILE3(name)
Definition: Profile.h:201
#define u32
Definition: types.h:41
static void TileCenter(u16 i, u16 j, entity_pos_t &x, entity_pos_t &z)
Returns the position of the center of the given tile.
static bool AtGoal(u16 i, u16 j, const ICmpPathfinder::Goal &goal)
T & get(int i, int j) const
Definition: Grid.h:93
#define GET_COST_CLASS(item)
u16 TerrainTile
#define cassert(expr)
Compile-time assertion.
static float Length(const SVec3 v)
Definition: mikktspace.cpp:112
ICmpPathfinder::pass_class_t passClass
PriorityQueueHeap< std::pair< u16, u16 >, u32 > PriorityQueue
void push(const Item &item)
Definition: PriorityQueue.h:70
NONCOPYABLE(PathfinderOverlay)
T clamp(T value, T min, T max)
Definition: MathUtil.h:32
static enum @41 state
pass_class_t m_DebugPassClass