LCOV - code coverage report
Current view: top level - source/simulation2/helpers - HierarchicalPathfinder.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 41 76 53.9 %
Date: 2023-01-19 00:18:29 Functions: 12 19 63.2 %

          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             : #ifndef INCLUDED_HIERPATHFINDER
      19             : #define INCLUDED_HIERPATHFINDER
      20             : 
      21             : #include "Pathfinding.h"
      22             : 
      23             : #include "ps/CLogger.h"
      24             : #include "renderer/TerrainOverlay.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             : 
      57             : class HierarchicalOverlay;
      58             : class SceneCollector;
      59             : 
      60             : class HierarchicalPathfinder
      61             : {
      62             : #ifdef TEST
      63             :     friend class TestCmpPathfinder;
      64             :     friend class TestHierarchicalPathfinder;
      65             : #endif
      66             : public:
      67             :     typedef u32 GlobalRegionID;
      68             : 
      69             :     struct RegionID
      70             :     {
      71             :         u8 ci, cj; // chunk ID
      72             :         u16 r; // unique-per-chunk local region ID
      73             : 
      74      452592 :         RegionID(u8 ci, u8 cj, u16 r) : ci(ci), cj(cj), r(r) { }
      75             : 
      76     1020729 :         bool operator<(const RegionID& b) const
      77             :         {
      78             :             // Sort by chunk ID, then by per-chunk region ID
      79     1020729 :             if (ci < b.ci)
      80      113463 :                 return true;
      81      907266 :             if (b.ci < ci)
      82      161443 :                 return false;
      83      745823 :             if (cj < b.cj)
      84      127674 :                 return true;
      85      618149 :             if (b.cj < cj)
      86      112298 :                 return false;
      87      505851 :             return r < b.r;
      88             :         }
      89             : 
      90       19644 :         bool operator==(const RegionID& b) const
      91             :         {
      92       19644 :             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        5019 :         inline u32 DistanceTo(u16 i, u16 j) const
      97             :         {
      98       10038 :             return (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) * (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) +
      99       10038 :                    (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j) * (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j);
     100             :         }
     101             : 
     102             :     };
     103             : 
     104             :     HierarchicalPathfinder();
     105             :     ~HierarchicalPathfinder();
     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           0 :     pass_class_t GetPassabilityClass(const std::string& name) const
     153             :     {
     154           0 :         auto it = m_PassClassMasks.find(name);
     155           0 :         if (it != m_PassClassMasks.end())
     156           0 :             return it->second;
     157             : 
     158           0 :         LOGERROR("Invalid passability class name '%s'", name.c_str());
     159           0 :         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         114 :     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         154 :     const Chunk& GetChunk(u8 ci, u8 cj, pass_class_t passClass) const
     195             :     {
     196         154 :         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          31 :     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          31 :         reachable.insert(from);
     216             : 
     217          31 :         const EdgesMap& edgeMap = m_Edges.at(passClass);
     218          31 :         if (edgeMap.find(from) == edgeMap.end())
     219           2 :             return;
     220             : 
     221          58 :         std::vector<RegionID> open;
     222          29 :         open.reserve(64);
     223          29 :         open.push_back(from);
     224             : 
     225         551 :         while (!open.empty())
     226             :         {
     227         261 :             RegionID curr = open.back();
     228         261 :             open.pop_back();
     229             : 
     230         921 :             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         660 :                 if (reachable.insert(region).second)
     234         232 :                     open.push_back(region);
     235             :         }
     236             :     }
     237             : 
     238             :     struct SortByCenterToPoint
     239             :     {
     240          22 :         SortByCenterToPoint(u16 i, u16 j): gi(i), gj(j) {};
     241        1477 :         bool operator()(const HierarchicalPathfinder::RegionID& a, const HierarchicalPathfinder::RegionID& b) const
     242             :         {
     243        1477 :             if (a.DistanceTo(gi, gj) < b.DistanceTo(gi, gj))
     244         502 :                 return true;
     245         975 :             if (a.DistanceTo(gi, gj) > b.DistanceTo(gi, gj))
     246         636 :                 return false;
     247         339 :             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             : 
     255             :     struct InterestingRegion {
     256             :         RegionID region;
     257             :         u16 bestI;
     258             :         u16 bestJ;
     259             :     };
     260             : 
     261             :     struct SortByBestToPoint
     262             :     {
     263          11 :         SortByBestToPoint(u16 i, u16 j): gi(i), gj(j) {};
     264           0 :         bool operator()(const InterestingRegion& a, const InterestingRegion& b) const
     265             :         {
     266           0 :             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           0 :                 return true;
     268           0 :             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           0 :                 return false;
     270           0 :             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             : 
     280             :     u16 m_W, m_H;
     281             :     u8 m_ChunksW, m_ChunksH;
     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);
     293             :     HierarchicalOverlay* m_DebugOverlay;
     294             :     const CSimContext* m_SimContext; // Used for drawing the debug lines
     295             : 
     296             : public:
     297             :     std::vector<SOverlayLine> m_DebugOverlayLines;
     298             : };
     299             : 
     300           0 : class HierarchicalOverlay : public TerrainTextureOverlay
     301             : {
     302             : public:
     303             :     HierarchicalPathfinder& m_PathfinderHier;
     304             : 
     305           0 :     HierarchicalOverlay(HierarchicalPathfinder& pathfinderHier) :
     306           0 :         TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TERRAIN_TILE), m_PathfinderHier(pathfinderHier)
     307             :     {
     308           0 :     }
     309             : 
     310           0 :     virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
     311             :     {
     312           0 :         ENSURE(h <= std::numeric_limits<u16>::max() && w <= std::numeric_limits<u16>::max());
     313           0 :         u16 height = static_cast<u16>(h);
     314           0 :         u16 width = static_cast<u16>(w);
     315           0 :         pass_class_t passClass = m_PathfinderHier.GetPassabilityClass("default");
     316             : 
     317           0 :         for (u16 j = 0; j < height; ++j)
     318             :         {
     319           0 :             for (u16 i = 0; i < width; ++i)
     320             :             {
     321           0 :                 SColor4ub color;
     322             : 
     323           0 :                 HierarchicalPathfinder::RegionID rid = m_PathfinderHier.Get(i, j, passClass);
     324           0 :                 if (rid.r == 0)
     325           0 :                     color = SColor4ub(0, 0, 0, 0);
     326           0 :                 else if (rid.r == 0xFFFF)
     327           0 :                     color = SColor4ub(255, 0, 255, 255);
     328             :                 else
     329           0 :                     color = GetColor(rid.r + rid.ci*5 + rid.cj*7, 127);
     330             : 
     331           0 :                 *data++ = color.R;
     332           0 :                 *data++ = color.G;
     333           0 :                 *data++ = color.B;
     334           0 :                 *data++ = color.A;
     335             :             }
     336             :         }
     337           0 :     }
     338             : };
     339             : 
     340             : 
     341             : #endif // INCLUDED_HIERPATHFINDER

Generated by: LCOV version 1.13