LCOV - code coverage report
Current view: top level - source/simulation2/helpers - LongPathfinder.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 12 482 2.5 %
Date: 2023-01-19 00:18:29 Functions: 3 38 7.9 %

          Line data    Source code
       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             : #include "precompiled.h"
      19             : 
      20             : #include "LongPathfinder.h"
      21             : 
      22             : #include "lib/bits.h"
      23             : #include "ps/Profile.h"
      24             : 
      25             : #include "Geometry.h"
      26             : #include "HierarchicalPathfinder.h"
      27             : 
      28             : #include <mutex>
      29             : 
      30             : namespace
      31             : {
      32             : static std::mutex g_DebugMutex;
      33             : }
      34             : 
      35             : /**
      36             :  * Jump point cache.
      37             :  *
      38             :  * The JPS algorithm wants to efficiently either find the first jump point
      39             :  * in some direction from some cell (not counting the cell itself),
      40             :  * if it is reachable without crossing any impassable cells;
      41             :  * or know that there is no such reachable jump point.
      42             :  * The jump point is always on a passable cell.
      43             :  * We cache that data to allow fast lookups, which helps performance
      44             :  * significantly (especially on sparse maps).
      45             :  * Recalculation might be expensive but the underlying passability data
      46             :  * changes relatively rarely.
      47             :  *
      48             :  * To allow the algorithm to detect goal cells, we want to treat them as
      49             :  * jump points too. (That means the algorithm will push those cells onto
      50             :  * its open queue, and will eventually pop a goal cell and realise it's done.)
      51             :  * (Goals might be circles/squares/etc, not just a single cell.)
      52             :  * But the goal generally changes for every path request, so we can't cache
      53             :  * it like the normal jump points.
      54             :  * Instead, if there's no jump point from some cell then we'll cache the
      55             :  * first impassable cell as an 'obstruction jump point'
      56             :  * (with a flag to distinguish from a real jump point), and then the caller
      57             :  * can test whether the goal includes a cell that's closer than the first
      58             :  * (obstruction or real) jump point,
      59             :  * and treat the goal cell as a jump point in that case.
      60             :  *
      61             :  * We only ever need to find the jump point relative to a passable cell;
      62             :  * the cache is allowed to return bogus values for impassable cells.
      63             :  */
      64           0 : class JumpPointCache
      65             : {
      66             :     /**
      67             :      * Simple space-inefficient row storage.
      68             :      */
      69           0 :     struct RowRaw
      70             :     {
      71             :         std::vector<u16> data;
      72             : 
      73           0 :         size_t GetMemoryUsage() const
      74             :         {
      75           0 :             return data.capacity() * sizeof(u16);
      76             :         }
      77             : 
      78           0 :         RowRaw(int length)
      79           0 :         {
      80           0 :             data.resize(length);
      81           0 :         }
      82             : 
      83             :         /**
      84             :          * Set cells x0 <= x < x1 to have jump point x1.
      85             :          */
      86           0 :         void SetRange(int x0, int x1, bool obstruction)
      87             :         {
      88           0 :             ENSURE(0 <= x0 && x0 <= x1 && x1 < (int)data.size());
      89           0 :             for (int x = x0; x < x1; ++x)
      90           0 :                 data[x] = (x1 << 1) | (obstruction ? 1 : 0);
      91           0 :         }
      92             : 
      93             :         /**
      94             :          * Returns the coordinate of the next jump point xp (where x < xp),
      95             :          * and whether it's an obstruction point or jump point.
      96             :          */
      97           0 :         void Get(int x, int& xp, bool& obstruction) const
      98             :         {
      99           0 :             ENSURE(0 <= x && x < (int)data.size());
     100           0 :             xp = data[x] >> 1;
     101           0 :             obstruction = data[x] & 1;
     102           0 :         }
     103             : 
     104           0 :         void Finish() { }
     105             :     };
     106             : 
     107             :     struct RowTree
     108             :     {
     109             :         /**
     110             :          * Represents an interval [u15 x0, u16 x1)
     111             :          * with a boolean obstruction flag,
     112             :          * packed into a single u32.
     113             :          */
     114             :         struct Interval
     115             :         {
     116             :             Interval() : data(0) { }
     117             : 
     118             :             Interval(int x0, int x1, bool obstruction)
     119             :             {
     120             :                 ENSURE(0 <= x0 && x0 < 0x8000);
     121             :                 ENSURE(0 <= x1 && x1 < 0x10000);
     122             :                 data = ((u32)x0 << 17) | (u32)(obstruction ? 0x10000 : 0) | (u32)x1;
     123             :             }
     124             : 
     125             :             int x0() { return data >> 17; }
     126             :             int x1() { return data & 0xFFFF; }
     127             :             bool obstruction() { return (data & 0x10000) != 0; }
     128             : 
     129             :             u32 data;
     130             :         };
     131             : 
     132             :         std::vector<Interval> data;
     133             : 
     134             :         size_t GetMemoryUsage() const
     135             :         {
     136             :             return data.capacity() * sizeof(Interval);
     137             :         }
     138             : 
     139             :         RowTree(int UNUSED(length))
     140             :         {
     141             :         }
     142             : 
     143             :         void SetRange(int x0, int x1, bool obstruction)
     144             :         {
     145             :             ENSURE(0 <= x0 && x0 <= x1);
     146             :             data.emplace_back(x0, x1, obstruction);
     147             :         }
     148             : 
     149             :         /**
     150             :          * Recursive helper function for Finish().
     151             :          * Given two ranges [x0, pivot) and [pivot, x1) in the sorted array 'data',
     152             :          * the pivot element is added onto the binary tree (stored flattened in an
     153             :          * array), and then each range is split into two sub-ranges with a pivot in
     154             :          * the middle (to ensure the tree remains balanced) and ConstructTree recurses.
     155             :          */
     156             :         void ConstructTree(std::vector<Interval>& tree, size_t x0, size_t pivot, size_t x1, size_t idx_tree)
     157             :         {
     158             :             ENSURE(x0 < data.size());
     159             :             ENSURE(x1 <= data.size());
     160             :             ENSURE(x0 <= pivot);
     161             :             ENSURE(pivot < x1);
     162             :             ENSURE(idx_tree < tree.size());
     163             : 
     164             :             tree[idx_tree] = data[pivot];
     165             : 
     166             :             if (x0 < pivot)
     167             :                 ConstructTree(tree, x0, (x0 + pivot) / 2, pivot, (idx_tree << 1) + 1);
     168             :             if (pivot + 1 < x1)
     169             :                 ConstructTree(tree, pivot + 1, (pivot + x1) / 2, x1, (idx_tree << 1) + 2);
     170             :         }
     171             : 
     172             :         void Finish()
     173             :         {
     174             :             // Convert the sorted interval list into a balanced binary tree
     175             : 
     176             :             std::vector<Interval> tree;
     177             : 
     178             :             if (!data.empty())
     179             :             {
     180             :                 size_t depth = ceil_log2(data.size() + 1);
     181             :                 tree.resize((1 << depth) - 1);
     182             :                 ConstructTree(tree, 0, data.size() / 2, data.size(), 0);
     183             :             }
     184             : 
     185             :             data.swap(tree);
     186             :         }
     187             : 
     188             :         void Get(int x, int& xp, bool& obstruction) const
     189             :         {
     190             :             // Search the binary tree for an interval which contains x
     191             :             int i = 0;
     192             :             while (true)
     193             :             {
     194             :                 ENSURE(i < (int)data.size());
     195             :                 Interval interval = data[i];
     196             :                 if (x < interval.x0())
     197             :                     i = (i << 1) + 1;
     198             :                 else if (x >= interval.x1())
     199             :                     i = (i << 1) + 2;
     200             :                 else
     201             :                 {
     202             :                     ENSURE(interval.x0() <= x && x < interval.x1());
     203             :                     xp = interval.x1();
     204             :                     obstruction = interval.obstruction();
     205             :                     return;
     206             :                 }
     207             :             }
     208             :         }
     209             :     };
     210             : 
     211             :     // Pick one of the row implementations
     212             :     typedef RowRaw Row;
     213             : 
     214             : public:
     215             :     int m_Width;
     216             :     int m_Height;
     217             :     std::vector<Row> m_JumpPointsRight;
     218             :     std::vector<Row> m_JumpPointsLeft;
     219             :     std::vector<Row> m_JumpPointsUp;
     220             :     std::vector<Row> m_JumpPointsDown;
     221             : 
     222             :     /**
     223             :      * Compute the cached obstruction/jump points for each cell,
     224             :      * in a single direction. By default the code assumes the rightwards
     225             :      * (+i) direction; set 'transpose' to switch to upwards (+j),
     226             :      * and/or set 'mirror' to reverse the direction.
     227             :      */
     228           0 :     void ComputeRows(std::vector<Row>& rows,
     229             :         const Grid<NavcellData>& terrain, pass_class_t passClass,
     230             :         bool transpose, bool mirror)
     231             :     {
     232           0 :         int w = terrain.m_W;
     233           0 :         int h = terrain.m_H;
     234             : 
     235           0 :         if (transpose)
     236           0 :             std::swap(w, h);
     237             : 
     238             :         // Check the terrain passability, adjusted for transpose/mirror
     239             : #define TERRAIN_IS_PASSABLE(i, j) \
     240             :     IS_PASSABLE( \
     241             :         mirror \
     242             :         ? (transpose ? terrain.get((j), w-1-(i)) : terrain.get(w-1-(i), (j))) \
     243             :         : (transpose ? terrain.get((j), (i)) : terrain.get((i), (j))) \
     244             :     , passClass)
     245             : 
     246           0 :         rows.reserve(h);
     247           0 :         for (int j = 0; j < h; ++j)
     248           0 :             rows.emplace_back(w);
     249             : 
     250           0 :         for (int j = 1; j < h - 1; ++j)
     251             :         {
     252             :             // Find the first passable cell.
     253             :             // Then, find the next jump/obstruction point after that cell,
     254             :             // and store that point for the passable range up to that cell,
     255             :             // then repeat.
     256             : 
     257           0 :             int i = 0;
     258           0 :             while (i < w)
     259             :             {
     260             :                 // Restart the 'while' loop until we reach a passable cell
     261           0 :                 if (!TERRAIN_IS_PASSABLE(i, j))
     262             :                 {
     263           0 :                     ++i;
     264           0 :                     continue;
     265             :                 }
     266             : 
     267             :                 // i is now a passable cell; find the next jump/obstruction point.
     268             :                 // (We assume the map is surrounded by impassable cells, so we don't
     269             :                 // need to explicitly check for world bounds here.)
     270             : 
     271           0 :                 int i0 = i;
     272             :                 while (true)
     273             :                 {
     274           0 :                     ++i;
     275             : 
     276             :                     // Check if we hit an obstructed tile
     277           0 :                     if (!TERRAIN_IS_PASSABLE(i, j))
     278             :                     {
     279           0 :                         rows[j].SetRange(i0, i, true);
     280           0 :                         break;
     281             :                     }
     282             : 
     283             :                     // Check if we reached a jump point
     284           0 :                     if ((!TERRAIN_IS_PASSABLE(i - 1, j - 1) && TERRAIN_IS_PASSABLE(i, j - 1)) ||
     285           0 :                         (!TERRAIN_IS_PASSABLE(i - 1, j + 1) && TERRAIN_IS_PASSABLE(i, j + 1)))
     286             :                     {
     287           0 :                         rows[j].SetRange(i0, i, false);
     288           0 :                         break;
     289             :                     }
     290             :                 }
     291             :             }
     292             : 
     293           0 :             rows[j].Finish();
     294             :         }
     295             : #undef TERRAIN_IS_PASSABLE
     296           0 :     }
     297             : 
     298           0 :     void reset(const Grid<NavcellData>* terrain, pass_class_t passClass)
     299             :     {
     300           0 :         PROFILE2("JumpPointCache reset");
     301           0 :         TIMER(L"JumpPointCache reset");
     302             : 
     303           0 :         m_Width = terrain->m_W;
     304           0 :         m_Height = terrain->m_H;
     305             : 
     306           0 :         ComputeRows(m_JumpPointsRight, *terrain, passClass, false, false);
     307           0 :         ComputeRows(m_JumpPointsLeft, *terrain, passClass, false, true);
     308           0 :         ComputeRows(m_JumpPointsUp, *terrain, passClass, true, false);
     309           0 :         ComputeRows(m_JumpPointsDown, *terrain, passClass, true, true);
     310           0 :     }
     311             : 
     312           0 :     size_t GetMemoryUsage() const
     313             :     {
     314           0 :         size_t bytes = 0;
     315           0 :         for (int i = 0; i < m_Width; ++i)
     316             :         {
     317           0 :             bytes += m_JumpPointsUp[i].GetMemoryUsage();
     318           0 :             bytes += m_JumpPointsDown[i].GetMemoryUsage();
     319             :         }
     320           0 :         for (int j = 0; j < m_Height; ++j)
     321             :         {
     322           0 :             bytes += m_JumpPointsRight[j].GetMemoryUsage();
     323           0 :             bytes += m_JumpPointsLeft[j].GetMemoryUsage();
     324             :         }
     325           0 :         return bytes;
     326             :     }
     327             : 
     328             :     /**
     329             :      * Returns the next jump point (or goal point) to explore,
     330             :      * at (ip, j) where i < ip.
     331             :      * Returns i if there is no such point.
     332             :      */
     333           0 :     int GetJumpPointRight(int i, int j, const PathGoal& goal) const
     334             :     {
     335             :         int ip;
     336             :         bool obstruction;
     337           0 :         m_JumpPointsRight[j].Get(i, ip, obstruction);
     338             :         // Adjust ip to be a goal cell, if there is one closer than the jump point;
     339             :         // and then return the new ip if there is a goal,
     340             :         // or the old ip if there is a (non-obstruction) jump point
     341           0 :         if (goal.NavcellRectContainsGoal(i + 1, j, ip - 1, j, &ip, NULL) || !obstruction)
     342           0 :             return ip;
     343           0 :         return i;
     344             :     }
     345             : 
     346           0 :     int GetJumpPointLeft(int i, int j, const PathGoal& goal) const
     347             :     {
     348             :         int mip; // mirrored value, because m_JumpPointsLeft is generated from a mirrored map
     349             :         bool obstruction;
     350           0 :         m_JumpPointsLeft[j].Get(m_Width - 1 - i, mip, obstruction);
     351           0 :         int ip = m_Width - 1 - mip;
     352           0 :         if (goal.NavcellRectContainsGoal(i - 1, j, ip + 1, j, &ip, NULL) || !obstruction)
     353           0 :             return ip;
     354           0 :         return i;
     355             :     }
     356             : 
     357           0 :     int GetJumpPointUp(int i, int j, const PathGoal& goal) const
     358             :     {
     359             :         int jp;
     360             :         bool obstruction;
     361           0 :         m_JumpPointsUp[i].Get(j, jp, obstruction);
     362           0 :         if (goal.NavcellRectContainsGoal(i, j + 1, i, jp - 1, NULL, &jp) || !obstruction)
     363           0 :             return jp;
     364           0 :         return j;
     365             :     }
     366             : 
     367           0 :     int GetJumpPointDown(int i, int j, const PathGoal& goal) const
     368             :     {
     369             :         int mjp; // mirrored value
     370             :         bool obstruction;
     371           0 :         m_JumpPointsDown[i].Get(m_Height - 1 - j, mjp, obstruction);
     372           0 :         int jp = m_Height - 1 - mjp;
     373           0 :         if (goal.NavcellRectContainsGoal(i, j - 1, i, jp + 1, NULL, &jp) || !obstruction)
     374           0 :             return jp;
     375           0 :         return j;
     376             :     }
     377             : };
     378             : 
     379             : //////////////////////////////////////////////////////////
     380             : 
     381           3 : LongPathfinder::LongPathfinder() :
     382             :     m_UseJPSCache(false),
     383           3 :     m_Grid(NULL), m_GridSize(0)
     384             : {
     385           3 : }
     386             : 
     387             : #define PASSABLE(i, j) IS_PASSABLE(state.terrain->get(i, j), state.passClass)
     388             : 
     389             : // Calculate heuristic cost from tile i,j to goal
     390             : // (This ought to be an underestimate for correctness)
     391           0 : PathCost LongPathfinder::CalculateHeuristic(int i, int j, int iGoal, int jGoal) const
     392             : {
     393           0 :     int di = abs(i - iGoal);
     394           0 :     int dj = abs(j - jGoal);
     395           0 :     int diag = std::min(di, dj);
     396           0 :     return PathCost(di - diag + dj - diag, diag);
     397             : }
     398             : 
     399             : // Do the A* processing for a neighbour tile i,j.
     400           0 : void LongPathfinder::ProcessNeighbour(int pi, int pj, int i, int j, PathCost pg, PathfinderState& state) const
     401             : {
     402             :     // Reject impassable tiles
     403           0 :     if (!PASSABLE(i, j))
     404           0 :         return;
     405             : 
     406           0 :     PathfindTile& n = state.tiles->get(i, j);
     407             : 
     408           0 :     if (n.IsClosed())
     409           0 :         return;
     410             : 
     411           0 :     PathCost dg;
     412           0 :     if (pi == i)
     413           0 :         dg = PathCost::horizvert(abs(pj - j));
     414           0 :     else if (pj == j)
     415           0 :         dg = PathCost::horizvert(abs(pi - i));
     416             :     else
     417             :     {
     418           0 :         ASSERT(abs((int)pi - (int)i) == abs((int)pj - (int)j)); // must be 45 degrees
     419           0 :         dg = PathCost::diag(abs((int)pi - (int)i));
     420             :     }
     421             : 
     422           0 :     PathCost g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
     423             : 
     424           0 :     PathCost h = CalculateHeuristic(i, j, state.iGoal, state.jGoal);
     425             : 
     426             :     // If this is a new tile, compute the heuristic distance
     427           0 :     if (n.IsUnexplored())
     428             :     {
     429             :         // Remember the best tile we've seen so far, in case we never actually reach the target
     430           0 :         if (h < state.hBest)
     431             :         {
     432           0 :             state.hBest = h;
     433           0 :             state.iBest = i;
     434           0 :             state.jBest = j;
     435             :         }
     436             :     }
     437             :     else
     438             :     {
     439             :         // If we've already seen this tile, and the new path to this tile does not have a
     440             :         // better cost, then stop now
     441           0 :         if (g >= n.GetCost())
     442           0 :             return;
     443             : 
     444             :         // Otherwise, we have a better path.
     445             : 
     446             :         // If we've already added this tile to the open list:
     447           0 :         if (n.IsOpen())
     448             :         {
     449             :             // This is a better path, so replace the old one with the new cost/parent
     450           0 :             PathCost gprev = n.GetCost();
     451           0 :             n.SetCost(g);
     452           0 :             n.SetPred(pi, pj, i, j);
     453           0 :             state.open.promote(TileID(i, j), gprev + h, g + h, h);
     454           0 :             return;
     455             :         }
     456             :     }
     457             : 
     458             :     // Add it to the open list:
     459           0 :     n.SetStatusOpen();
     460           0 :     n.SetCost(g);
     461           0 :     n.SetPred(pi, pj, i, j);
     462           0 :     PriorityQueue::Item t = { TileID(i, j), g + h, h };
     463           0 :     state.open.push(t);
     464             : }
     465             : 
     466             : /*
     467             :  * In the JPS algorithm, after a tile is taken off the open queue,
     468             :  * we don't process every adjacent neighbour (as in standard A*).
     469             :  * Instead we only move in a subset of directions (depending on the
     470             :  * direction from the predecessor); and instead of moving by a single
     471             :  * cell, we move up to the next jump point in that direction.
     472             :  * The AddJumped... functions do this by calling ProcessNeighbour
     473             :  * on the jump point (if any) in a certain direction.
     474             :  * The HasJumped... functions return whether there is any jump point
     475             :  * in that direction.
     476             :  */
     477             : 
     478             : // JPS functions scan navcells towards one direction
     479             : // OnTheWay tests whether we are scanning towards the right direction, to avoid useless scans
     480           0 : inline bool OnTheWay(int i, int j, int di, int dj, int iGoal, int jGoal)
     481             : {
     482           0 :     if (dj != 0)
     483             :     {
     484             :         // We're not moving towards the goal
     485           0 :         if ((jGoal - j) * dj < 0)
     486           0 :             return false;
     487             :     }
     488           0 :     else if (j != jGoal)
     489           0 :         return false;
     490             : 
     491           0 :     if (di != 0)
     492             :     {
     493             :         // We're not moving towards the goal
     494           0 :         if ((iGoal - i) * di < 0)
     495           0 :             return false;
     496             :     }
     497           0 :     else if (i != iGoal)
     498           0 :         return false;
     499             : 
     500           0 :     return true;
     501             : }
     502             : 
     503             : 
     504           0 : void LongPathfinder::AddJumpedHoriz(int i, int j, int di, PathCost g, PathfinderState& state, bool detectGoal) const
     505             : {
     506           0 :     if (m_UseJPSCache)
     507             :     {
     508             :         int jump;
     509           0 :         if (di > 0)
     510           0 :             jump = state.jpc->GetJumpPointRight(i, j, state.goal);
     511             :         else
     512           0 :             jump = state.jpc->GetJumpPointLeft(i, j, state.goal);
     513             : 
     514           0 :         if (jump != i)
     515           0 :             ProcessNeighbour(i, j, jump, j, g, state);
     516             :     }
     517             :     else
     518             :     {
     519           0 :         ASSERT(di == 1 || di == -1);
     520           0 :         int ni = i + di;
     521             :         while (true)
     522             :         {
     523           0 :             if (!PASSABLE(ni, j))
     524           0 :                 break;
     525             : 
     526           0 :             if (detectGoal && state.goal.NavcellContainsGoal(ni, j))
     527             :             {
     528           0 :                 state.open.clear();
     529           0 :                 ProcessNeighbour(i, j, ni, j, g, state);
     530           0 :                 break;
     531             :             }
     532             : 
     533           0 :             if  ((!PASSABLE(ni - di, j - 1) && PASSABLE(ni, j - 1)) ||
     534           0 :                 (!PASSABLE(ni - di, j + 1) && PASSABLE(ni, j + 1)))
     535             :             {
     536           0 :                 ProcessNeighbour(i, j, ni, j, g, state);
     537           0 :                 break;
     538             :             }
     539             : 
     540           0 :             ni += di;
     541             :         }
     542             :     }
     543           0 : }
     544             : 
     545             : // Returns the i-coordinate of the jump point if it exists, else returns i
     546           0 : int LongPathfinder::HasJumpedHoriz(int i, int j, int di, PathfinderState& state, bool detectGoal) const
     547             : {
     548           0 :     if (m_UseJPSCache)
     549             :     {
     550             :         int jump;
     551           0 :         if (di > 0)
     552           0 :             jump = state.jpc->GetJumpPointRight(i, j, state.goal);
     553             :         else
     554           0 :             jump = state.jpc->GetJumpPointLeft(i, j, state.goal);
     555             : 
     556           0 :         return jump;
     557             :     }
     558             :     else
     559             :     {
     560           0 :         ASSERT(di == 1 || di == -1);
     561           0 :         int ni = i + di;
     562             :         while (true)
     563             :         {
     564           0 :             if (!PASSABLE(ni, j))
     565           0 :                 return i;
     566             : 
     567           0 :             if (detectGoal && state.goal.NavcellContainsGoal(ni, j))
     568             :             {
     569           0 :                 state.open.clear();
     570           0 :                 return ni;
     571             :             }
     572             : 
     573           0 :             if  ((!PASSABLE(ni - di, j - 1) && PASSABLE(ni, j - 1)) ||
     574           0 :                 (!PASSABLE(ni - di, j + 1) && PASSABLE(ni, j + 1)))
     575           0 :                 return ni;
     576             : 
     577           0 :             ni += di;
     578             :         }
     579             :     }
     580             : }
     581             : 
     582           0 : void LongPathfinder::AddJumpedVert(int i, int j, int dj, PathCost g, PathfinderState& state, bool detectGoal) const
     583             : {
     584           0 :     if (m_UseJPSCache)
     585             :     {
     586             :         int jump;
     587           0 :         if (dj > 0)
     588           0 :             jump = state.jpc->GetJumpPointUp(i, j, state.goal);
     589             :         else
     590           0 :             jump = state.jpc->GetJumpPointDown(i, j, state.goal);
     591             : 
     592           0 :         if (jump != j)
     593           0 :             ProcessNeighbour(i, j, i, jump, g, state);
     594             :     }
     595             :     else
     596             :     {
     597           0 :         ASSERT(dj == 1 || dj == -1);
     598           0 :         int nj = j + dj;
     599             :         while (true)
     600             :         {
     601           0 :             if (!PASSABLE(i, nj))
     602           0 :                 break;
     603             : 
     604           0 :             if (detectGoal && state.goal.NavcellContainsGoal(i, nj))
     605             :             {
     606           0 :                 state.open.clear();
     607           0 :                 ProcessNeighbour(i, j, i, nj, g, state);
     608           0 :                 break;
     609             :             }
     610             : 
     611           0 :             if  ((!PASSABLE(i - 1, nj - dj) && PASSABLE(i - 1, nj)) ||
     612           0 :                 (!PASSABLE(i + 1, nj - dj) && PASSABLE(i + 1, nj)))
     613             :             {
     614           0 :                 ProcessNeighbour(i, j, i, nj, g, state);
     615           0 :                 break;
     616             :             }
     617             : 
     618           0 :             nj += dj;
     619             :         }
     620             :     }
     621           0 : }
     622             : 
     623             : // Returns the j-coordinate of the jump point if it exists, else returns j
     624           0 : int LongPathfinder::HasJumpedVert(int i, int j, int dj, PathfinderState& state, bool detectGoal) const
     625             : {
     626           0 :     if (m_UseJPSCache)
     627             :     {
     628             :         int jump;
     629           0 :         if (dj > 0)
     630           0 :             jump = state.jpc->GetJumpPointUp(i, j, state.goal);
     631             :         else
     632           0 :             jump = state.jpc->GetJumpPointDown(i, j, state.goal);
     633             : 
     634           0 :         return jump;
     635             :     }
     636             :     else
     637             :     {
     638           0 :         ASSERT(dj == 1 || dj == -1);
     639           0 :         int nj = j + dj;
     640             :         while (true)
     641             :         {
     642           0 :             if (!PASSABLE(i, nj))
     643           0 :                 return j;
     644             : 
     645           0 :             if (detectGoal && state.goal.NavcellContainsGoal(i, nj))
     646             :             {
     647           0 :                 state.open.clear();
     648           0 :                 return nj;
     649             :             }
     650             : 
     651           0 :             if  ((!PASSABLE(i - 1, nj - dj) && PASSABLE(i - 1, nj)) ||
     652           0 :                 (!PASSABLE(i + 1, nj - dj) && PASSABLE(i + 1, nj)))
     653           0 :                 return nj;
     654             : 
     655           0 :             nj += dj;
     656             :         }
     657             :     }
     658             : }
     659             : 
     660             : /*
     661             :  * We never cache diagonal jump points - they're usually so frequent that
     662             :  * a linear search is about as cheap and avoids the setup cost and memory cost.
     663             :  */
     664           0 : void LongPathfinder::AddJumpedDiag(int i, int j, int di, int dj, PathCost g, PathfinderState& state) const
     665             : {
     666             :     //  ProcessNeighbour(i, j, i + di, j + dj, g, state);
     667             :     //  return;
     668             : 
     669           0 :     ASSERT(di == 1 || di == -1);
     670           0 :     ASSERT(dj == 1 || dj == -1);
     671             : 
     672           0 :     int ni = i + di;
     673           0 :     int nj = j + dj;
     674           0 :     bool detectGoal = OnTheWay(i, j, di, dj, state.iGoal, state.jGoal);
     675             :     while (true)
     676             :     {
     677             :         // Stop if we hit an obstructed cell
     678           0 :         if (!PASSABLE(ni, nj))
     679           0 :             return;
     680             : 
     681             :         // Stop if moving onto this cell caused us to
     682             :         // touch the corner of an obstructed cell
     683           0 :         if (!PASSABLE(ni - di, nj) || !PASSABLE(ni, nj - dj))
     684           0 :             return;
     685             : 
     686             :         // Process this cell if it's at the goal
     687           0 :         if (detectGoal && state.goal.NavcellContainsGoal(ni, nj))
     688             :         {
     689           0 :             state.open.clear();
     690           0 :             ProcessNeighbour(i, j, ni, nj, g, state);
     691           0 :             return;
     692             :         }
     693             : 
     694           0 :         int fi = HasJumpedHoriz(ni, nj, di, state, detectGoal && OnTheWay(ni, nj, di, 0, state.iGoal, state.jGoal));
     695           0 :         int fj = HasJumpedVert(ni, nj, dj, state, detectGoal && OnTheWay(ni, nj, 0, dj, state.iGoal, state.jGoal));
     696           0 :         if (fi != ni || fj != nj)
     697             :         {
     698           0 :             ProcessNeighbour(i, j, ni, nj, g, state);
     699           0 :             g += PathCost::diag(abs(ni - i));
     700             : 
     701           0 :             if (fi != ni)
     702           0 :                 ProcessNeighbour(ni, nj, fi, nj, g, state);
     703             : 
     704           0 :             if (fj != nj)
     705           0 :                 ProcessNeighbour(ni, nj, ni, fj, g, state);
     706             : 
     707           0 :             return;
     708             :         }
     709             : 
     710           0 :         ni += di;
     711           0 :         nj += dj;
     712           0 :     }
     713             : }
     714             : 
     715           0 : void LongPathfinder::ComputeJPSPath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, WaypointPath& path) const
     716             : {
     717           0 :     PROFILE2("ComputePathJPS");
     718           0 :     PathfinderState state = { 0 };
     719             : 
     720           0 :     if (m_UseJPSCache)
     721             :     {
     722             :         // Needs to lock for construction, or several threads might try doing that at the same time.
     723             :         static std::mutex JPCMutex;
     724           0 :         std::unique_lock<std::mutex> lock(JPCMutex);
     725           0 :         std::map<pass_class_t, std::shared_ptr<JumpPointCache>>::const_iterator it = m_JumpPointCache.find(passClass);
     726           0 :         if (it != m_JumpPointCache.end())
     727           0 :             state.jpc = it->second.get();
     728             : 
     729           0 :         if (!state.jpc)
     730             :         {
     731           0 :             m_JumpPointCache[passClass] = std::make_shared<JumpPointCache>();
     732           0 :             m_JumpPointCache[passClass]->reset(m_Grid, passClass);
     733           0 :             debug_printf("PATHFINDER: JPC memory: %d kB\n", (int)state.jpc->GetMemoryUsage() / 1024);
     734           0 :             state.jpc = m_JumpPointCache[passClass].get();
     735             :         }
     736             :     }
     737             : 
     738             :     // Convert the start coordinates to tile indexes
     739             :     u16 i0, j0;
     740           0 :     Pathfinding::NearestNavcell(x0, z0, i0, j0, m_GridSize, m_GridSize);
     741             : 
     742           0 :     if (!IS_PASSABLE(m_Grid->get(i0, j0), passClass))
     743             :     {
     744             :         // The JPS pathfinder requires units to be on passable tiles
     745             :         // (otherwise it might crash), so handle the supposedly-invalid
     746             :         // state specially
     747           0 :         hierPath.FindNearestPassableNavcell(i0, j0, passClass);
     748             :     }
     749             : 
     750           0 :     state.goal = origGoal;
     751             : 
     752             :     // Make the goal reachable. This includes shortening the path if the goal is in a non-passable
     753             :     // region, transforming non-point goals to reachable point goals, etc.
     754           0 :     hierPath.MakeGoalReachable(i0, j0, state.goal, passClass);
     755             : 
     756           0 :     ENSURE(state.goal.type == PathGoal::POINT);
     757             : 
     758             :     // If we're already at the goal tile, then move directly to the exact goal coordinates
     759           0 :     if (state.goal.NavcellContainsGoal(i0, j0))
     760             :     {
     761           0 :         path.m_Waypoints.emplace_back(Waypoint{ state.goal.x, state.goal.z });
     762           0 :         return;
     763             :     }
     764             : 
     765           0 :     Pathfinding::NearestNavcell(state.goal.x, state.goal.z, state.iGoal, state.jGoal, m_GridSize, m_GridSize);
     766             : 
     767           0 :     ENSURE((state.goal.x / Pathfinding::NAVCELL_SIZE).ToInt_RoundToNegInfinity() == state.iGoal);
     768           0 :     ENSURE((state.goal.z / Pathfinding::NAVCELL_SIZE).ToInt_RoundToNegInfinity() == state.jGoal);
     769             : 
     770           0 :     state.passClass = passClass;
     771             : 
     772           0 :     state.steps = 0;
     773             : 
     774           0 :     state.tiles = new PathfindTileGrid(m_Grid->m_W, m_Grid->m_H);
     775           0 :     state.terrain = m_Grid;
     776             : 
     777           0 :     state.iBest = i0;
     778           0 :     state.jBest = j0;
     779           0 :     state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal);
     780             : 
     781           0 :     PriorityQueue::Item start = { TileID(i0, j0), PathCost() };
     782           0 :     state.open.push(start);
     783           0 :     state.tiles->get(i0, j0).SetStatusOpen();
     784           0 :     state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0);
     785           0 :     state.tiles->get(i0, j0).SetCost(PathCost());
     786             : 
     787             :     while (true)
     788             :     {
     789           0 :         ++state.steps;
     790             : 
     791             :         // If we ran out of tiles to examine, give up
     792           0 :         if (state.open.empty())
     793           0 :             break;
     794             : 
     795             :         // Move best tile from open to closed
     796           0 :         PriorityQueue::Item curr = state.open.pop();
     797           0 :         u16 i = curr.id.i();
     798           0 :         u16 j = curr.id.j();
     799           0 :         state.tiles->get(i, j).SetStatusClosed();
     800             : 
     801             :         // If we've reached the destination, stop
     802           0 :         if (state.goal.NavcellContainsGoal(i, j))
     803             :         {
     804           0 :             state.iBest = i;
     805           0 :             state.jBest = j;
     806           0 :             state.hBest = PathCost();
     807           0 :             break;
     808             :         }
     809             : 
     810           0 :         PathfindTile tile = state.tiles->get(i, j);
     811           0 :         PathCost g = tile.GetCost();
     812             : 
     813             :         // Get the direction of the predecessor tile from this tile
     814           0 :         int dpi = tile.GetPredDI();
     815           0 :         int dpj = tile.GetPredDJ();
     816           0 :         dpi = (dpi < 0 ? -1 : dpi > 0 ? 1 : 0);
     817           0 :         dpj = (dpj < 0 ? -1 : dpj > 0 ? 1 : 0);
     818             : 
     819           0 :         if (dpi != 0 && dpj == 0)
     820             :         {
     821             :             // Moving horizontally from predecessor
     822           0 :             if (!PASSABLE(i + dpi, j - 1))
     823             :             {
     824           0 :                 AddJumpedDiag(i, j, -dpi, -1, g, state);
     825           0 :                 AddJumpedVert(i, j, -1, g, state, OnTheWay(i, j, 0, -1, state.iGoal, state.jGoal));
     826             :             }
     827           0 :             if (!PASSABLE(i + dpi, j + 1))
     828             :             {
     829           0 :                 AddJumpedDiag(i, j, -dpi, +1, g, state);
     830           0 :                 AddJumpedVert(i, j, +1, g, state, OnTheWay(i, j, 0, +1, state.iGoal, state.jGoal));
     831             :             }
     832           0 :             AddJumpedHoriz(i, j, -dpi, g, state, OnTheWay(i, j, -dpi, 0, state.iGoal, state.jGoal));
     833             :         }
     834           0 :         else if (dpi == 0 && dpj != 0)
     835             :         {
     836             :             // Moving vertically from predecessor
     837           0 :             if (!PASSABLE(i - 1, j + dpj))
     838             :             {
     839           0 :                 AddJumpedDiag(i, j, -1, -dpj, g, state);
     840           0 :                 AddJumpedHoriz(i, j, -1, g, state,OnTheWay(i, j, -1, 0, state.iGoal, state.jGoal));
     841             :             }
     842           0 :             if (!PASSABLE(i + 1, j + dpj))
     843             :             {
     844           0 :                 AddJumpedDiag(i, j, +1, -dpj, g, state);
     845           0 :                 AddJumpedHoriz(i, j, +1, g, state,OnTheWay(i, j, +1, 0, state.iGoal, state.jGoal));
     846             :             }
     847           0 :             AddJumpedVert(i, j, -dpj, g, state, OnTheWay(i, j, 0, -dpj, state.iGoal, state.jGoal));
     848             :         }
     849           0 :         else if (dpi != 0 && dpj != 0)
     850             :         {
     851             :             // Moving diagonally from predecessor
     852           0 :             AddJumpedHoriz(i, j, -dpi, g, state, OnTheWay(i, j, -dpi, 0, state.iGoal, state.jGoal));
     853           0 :             AddJumpedVert(i, j, -dpj, g, state, OnTheWay(i, j, 0, -dpj, state.iGoal, state.jGoal));
     854           0 :             AddJumpedDiag(i, j, -dpi, -dpj, g, state);
     855             :         }
     856             :         else
     857             :         {
     858             :             // No predecessor, i.e. the start tile
     859             :             // Start searching in every direction
     860             : 
     861             :             // XXX - check passability?
     862             : 
     863           0 :             bool passl = PASSABLE(i - 1, j);
     864           0 :             bool passr = PASSABLE(i + 1, j);
     865           0 :             bool passd = PASSABLE(i, j - 1);
     866           0 :             bool passu = PASSABLE(i, j + 1);
     867             : 
     868           0 :             if (passl && passd)
     869           0 :                 ProcessNeighbour(i, j, i-1, j-1, g, state);
     870           0 :             if (passr && passd)
     871           0 :                 ProcessNeighbour(i, j, i+1, j-1, g, state);
     872           0 :             if (passl && passu)
     873           0 :                 ProcessNeighbour(i, j, i-1, j+1, g, state);
     874           0 :             if (passr && passu)
     875           0 :                 ProcessNeighbour(i, j, i+1, j+1, g, state);
     876           0 :             if (passl)
     877           0 :                 ProcessNeighbour(i, j, i-1, j, g, state);
     878           0 :             if (passr)
     879           0 :                 ProcessNeighbour(i, j, i+1, j, g, state);
     880           0 :             if (passd)
     881           0 :                 ProcessNeighbour(i, j, i, j-1, g, state);
     882           0 :             if (passu)
     883           0 :                 ProcessNeighbour(i, j, i, j+1, g, state);
     884             :         }
     885           0 :     }
     886             : 
     887             :     // Reconstruct the path (in reverse)
     888           0 :     u16 ip = state.iBest, jp = state.jBest;
     889           0 :     while (ip != i0 || jp != j0)
     890             :     {
     891           0 :         PathfindTile& n = state.tiles->get(ip, jp);
     892           0 :         entity_pos_t x, z;
     893           0 :         Pathfinding::NavcellCenter(ip, jp, x, z);
     894           0 :         path.m_Waypoints.emplace_back(Waypoint{ x, z });
     895             : 
     896             :         // Follow the predecessor link
     897           0 :         ip = n.GetPredI(ip);
     898           0 :         jp = n.GetPredJ(jp);
     899             :     }
     900             :     // The last waypoint is slightly incorrect (it's not the goal but the center
     901             :     // of the navcell of the goal), so replace it
     902           0 :     if (!path.m_Waypoints.empty())
     903           0 :         path.m_Waypoints.front() = { state.goal.x, state.goal.z };
     904             : 
     905           0 :     ImprovePathWaypoints(path, passClass, origGoal.maxdist, x0, z0);
     906             : 
     907             :     // Save this grid for debug display
     908           0 :     if (m_Debug.Overlay)
     909             :     {
     910           0 :         std::lock_guard<std::mutex> lock(g_DebugMutex);
     911           0 :         delete m_Debug.Grid;
     912           0 :         m_Debug.Grid = state.tiles;
     913           0 :         m_Debug.Steps = state.steps;
     914           0 :         m_Debug.Goal = state.goal;
     915             :     }
     916             :     else
     917           0 :         SAFE_DELETE(state.tiles);
     918             : }
     919             : 
     920             : #undef PASSABLE
     921             : 
     922           0 : void LongPathfinder::ImprovePathWaypoints(WaypointPath& path, pass_class_t passClass, entity_pos_t maxDist, entity_pos_t x0, entity_pos_t z0) const
     923             : {
     924           0 :     if (path.m_Waypoints.empty())
     925           0 :         return;
     926             : 
     927           0 :     if (maxDist > fixed::Zero())
     928             :     {
     929           0 :         CFixedVector2D start(x0, z0);
     930           0 :         CFixedVector2D first(path.m_Waypoints.back().x, path.m_Waypoints.back().z);
     931           0 :         CFixedVector2D offset = first - start;
     932           0 :         if (offset.CompareLength(maxDist) > 0)
     933             :         {
     934           0 :             offset.Normalize(maxDist);
     935           0 :             path.m_Waypoints.emplace_back(Waypoint{ (start + offset).X, (start + offset).Y });
     936             :         }
     937             :     }
     938             : 
     939           0 :     if (path.m_Waypoints.size() < 2)
     940           0 :         return;
     941             : 
     942           0 :     std::vector<Waypoint>& waypoints = path.m_Waypoints;
     943           0 :     std::vector<Waypoint> newWaypoints;
     944             : 
     945           0 :     CFixedVector2D prev(waypoints.front().x, waypoints.front().z);
     946           0 :     newWaypoints.push_back(waypoints.front());
     947           0 :     for (size_t k = 1; k < waypoints.size() - 1; ++k)
     948             :     {
     949           0 :         CFixedVector2D ahead(waypoints[k + 1].x, waypoints[k + 1].z);
     950           0 :         CFixedVector2D curr(waypoints[k].x, waypoints[k].z);
     951             : 
     952           0 :         if (maxDist > fixed::Zero() && (curr - prev).CompareLength(maxDist) > 0)
     953             :         {
     954             :             // We are too far away from the previous waypoint, so create one in
     955             :             // between and continue with the improvement of the path
     956           0 :             prev = prev + (curr - prev) / 2;
     957           0 :             newWaypoints.emplace_back(Waypoint{ prev.X, prev.Y });
     958             :         }
     959             : 
     960             :         // If we're mostly straight, don't even bother.
     961           0 :         if ((ahead - curr).Perpendicular().Dot(curr - prev).Absolute() <= fixed::Epsilon() * 100)
     962           0 :             continue;
     963             : 
     964           0 :         if (!Pathfinding::CheckLineMovement(prev.X, prev.Y, ahead.X, ahead.Y, passClass, *m_Grid))
     965             :         {
     966           0 :             prev = CFixedVector2D(waypoints[k].x, waypoints[k].z);
     967           0 :             newWaypoints.push_back(waypoints[k]);
     968             :         }
     969             :     }
     970           0 :     newWaypoints.push_back(waypoints.back());
     971           0 :     path.m_Waypoints.swap(newWaypoints);
     972             : }
     973             : 
     974           0 : void LongPathfinder::GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid) const
     975             : {
     976           0 :     steps = m_Debug.Steps;
     977           0 :     time = m_Debug.Time;
     978             : 
     979           0 :     if (!m_Debug.Grid)
     980           0 :         return;
     981             : 
     982           0 :     std::lock_guard<std::mutex> lock(g_DebugMutex);
     983             : 
     984             :     u16 iGoal, jGoal;
     985           0 :     Pathfinding::NearestNavcell(m_Debug.Goal.x, m_Debug.Goal.z, iGoal, jGoal, m_GridSize, m_GridSize);
     986             : 
     987           0 :     grid = Grid<u8>(m_Debug.Grid->m_W, m_Debug.Grid->m_H);
     988           0 :     for (u16 j = 0; j < grid.m_H; ++j)
     989             :     {
     990           0 :         for (u16 i = 0; i < grid.m_W; ++i)
     991             :         {
     992           0 :             if (i == iGoal && j == jGoal)
     993           0 :                 continue;
     994           0 :             PathfindTile t = m_Debug.Grid->get(i, j);
     995           0 :             grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0));
     996             :         }
     997             :     }
     998             : }
     999             : 
    1000           0 : void LongPathfinder::ComputePath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal,
    1001             :     pass_class_t passClass, WaypointPath& path) const
    1002             : {
    1003           0 :     if (!m_Grid)
    1004             :     {
    1005           0 :         LOGERROR("The pathfinder grid hasn't been setup yet, aborting ComputeJPSPath");
    1006           0 :         return;
    1007             :     }
    1008             : 
    1009           0 :     ComputeJPSPath(hierPath, x0, z0, origGoal, passClass, path);
    1010             : }
    1011           0 : void LongPathfinder::ComputePath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal,
    1012             :     pass_class_t passClass, std::vector<CircularRegion> excludedRegions, WaypointPath& path)
    1013             : {
    1014           0 :     GenerateSpecialMap(passClass, excludedRegions);
    1015           0 :     ComputeJPSPath(hierPath, x0, z0, origGoal, SPECIAL_PASS_CLASS, path);
    1016           0 : }
    1017             : 
    1018           0 : inline bool InRegion(u16 i, u16 j, CircularRegion region)
    1019             : {
    1020           0 :     fixed cellX = Pathfinding::NAVCELL_SIZE * i;
    1021           0 :     fixed cellZ = Pathfinding::NAVCELL_SIZE * j;
    1022             : 
    1023           0 :     return CFixedVector2D(cellX - region.x, cellZ - region.z).CompareLength(region.r) <= 0;
    1024             : }
    1025             : 
    1026           0 : void LongPathfinder::GenerateSpecialMap(pass_class_t passClass, std::vector<CircularRegion> excludedRegions)
    1027             : {
    1028           0 :     for (u16 j = 0; j < m_Grid->m_H; ++j)
    1029             :     {
    1030           0 :         for (u16 i = 0; i < m_Grid->m_W; ++i)
    1031             :         {
    1032           0 :             NavcellData n = m_Grid->get(i, j);
    1033           0 :             if (!IS_PASSABLE(n, passClass))
    1034             :             {
    1035           0 :                 n |= SPECIAL_PASS_CLASS;
    1036           0 :                 m_Grid->set(i, j, n);
    1037           0 :                 continue;
    1038             :             }
    1039             : 
    1040           0 :             for (CircularRegion& region : excludedRegions)
    1041             :             {
    1042           0 :                 if (!InRegion(i, j, region))
    1043           0 :                     continue;
    1044             : 
    1045           0 :                 n |= SPECIAL_PASS_CLASS;
    1046           0 :                 break;
    1047             :             }
    1048           0 :             m_Grid->set(i, j, n);
    1049             :         }
    1050             :     }
    1051           0 : }
    1052             : 
    1053             : /**
    1054             :  * Terrain overlay for pathfinder debugging.
    1055             :  * Renders a representation of the most recent pathfinding operation.
    1056             :  */
    1057           0 : class LongOverlay : public TerrainTextureOverlay
    1058             : {
    1059             : public:
    1060             :     LongPathfinder& m_Pathfinder;
    1061             : 
    1062           0 :     LongOverlay(LongPathfinder& pathfinder) :
    1063           0 :     TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TERRAIN_TILE), m_Pathfinder(pathfinder)
    1064             :     {
    1065           0 :     }
    1066             : 
    1067           0 :     virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
    1068             :     {
    1069             :         // Grab the debug data for the most recently generated path
    1070             :         u32 steps;
    1071             :         double time;
    1072           0 :         Grid<u8> debugGrid;
    1073           0 :         m_Pathfinder.GetDebugData(steps, time, debugGrid);
    1074             : 
    1075             :         // Render navcell passability
    1076           0 :         u8* p = data;
    1077           0 :         for (size_t j = 0; j < h; ++j)
    1078             :         {
    1079           0 :             for (size_t i = 0; i < w; ++i)
    1080             :             {
    1081           0 :                 SColor4ub color(0, 0, 0, 0);
    1082           0 :                 if (!IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_Debug.PassClass))
    1083           0 :                     color = SColor4ub(255, 0, 0, 127);
    1084             : 
    1085           0 :                 if (debugGrid.m_W && debugGrid.m_H)
    1086             :                 {
    1087           0 :                     u8 n = debugGrid.get((int)i, (int)j);
    1088             : 
    1089           0 :                     if (n == 1)
    1090           0 :                         color = SColor4ub(255, 255, 0, 127);
    1091           0 :                     else if (n == 2)
    1092           0 :                         color = SColor4ub(0, 255, 0, 127);
    1093             : 
    1094           0 :                     if (m_Pathfinder.m_Debug.Goal.NavcellContainsGoal(i, j))
    1095           0 :                         color = SColor4ub(0, 0, 255, 127);
    1096             :                 }
    1097             : 
    1098           0 :                 *p++ = color.R;
    1099           0 :                 *p++ = color.G;
    1100           0 :                 *p++ = color.B;
    1101           0 :                 *p++ = color.A;
    1102             :             }
    1103             :         }
    1104             : 
    1105             :         // Render the most recently generated path
    1106           0 :         if (m_Pathfinder.m_Debug.Path && !m_Pathfinder.m_Debug.Path->m_Waypoints.empty())
    1107             :         {
    1108           0 :             std::vector<Waypoint>& waypoints = m_Pathfinder.m_Debug.Path->m_Waypoints;
    1109           0 :             u16 ip = 0, jp = 0;
    1110           0 :             for (size_t k = 0; k < waypoints.size(); ++k)
    1111             :             {
    1112             :                 u16 i, j;
    1113           0 :                 Pathfinding::NearestNavcell(waypoints[k].x, waypoints[k].z, i, j, m_Pathfinder.m_GridSize, m_Pathfinder.m_GridSize);
    1114           0 :                 if (k == 0)
    1115             :                 {
    1116           0 :                     ip = i;
    1117           0 :                     jp = j;
    1118             :                 }
    1119             :                 else
    1120             :                 {
    1121           0 :                     bool firstCell = true;
    1122           0 :                     do
    1123             :                     {
    1124           0 :                         if (data[(jp*w + ip)*4+3] == 0)
    1125             :                         {
    1126           0 :                             data[(jp*w + ip)*4+0] = 0xFF;
    1127           0 :                             data[(jp*w + ip)*4+1] = 0xFF;
    1128           0 :                             data[(jp*w + ip)*4+2] = 0xFF;
    1129           0 :                             data[(jp*w + ip)*4+3] = firstCell ? 0xA0 : 0x60;
    1130             :                         }
    1131           0 :                         ip = ip < i ? ip+1 : ip > i ? ip-1 : ip;
    1132           0 :                         jp = jp < j ? jp+1 : jp > j ? jp-1 : jp;
    1133           0 :                         firstCell = false;
    1134             :                     }
    1135           0 :                     while (ip != i || jp != j);
    1136             :                 }
    1137             :             }
    1138             :         }
    1139           0 :     }
    1140             : };
    1141             : 
    1142             : // These two functions must come below LongOverlay's definition.
    1143           3 : void LongPathfinder::SetDebugOverlay(bool enabled)
    1144             : {
    1145           3 :     if (enabled && !m_Debug.Overlay)
    1146           0 :         m_Debug.Overlay = new LongOverlay(*this);
    1147           3 :     else if (!enabled && m_Debug.Overlay)
    1148           0 :         SAFE_DELETE(m_Debug.Overlay);
    1149           3 : }
    1150             : 
    1151           6 : LongPathfinder::~LongPathfinder()
    1152             : {
    1153           3 :     SAFE_DELETE(m_Debug.Overlay);
    1154           3 :     SAFE_DELETE(m_Debug.Grid);
    1155           3 :     SAFE_DELETE(m_Debug.Path);
    1156           3 : }

Generated by: LCOV version 1.13