Index: ps/trunk/source/simulation2/components/tests/test_HierPathfinder.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_HierPathfinder.h +++ ps/trunk/source/simulation2/components/tests/test_HierPathfinder.h @@ -77,7 +77,7 @@ TS_ASSERT(i == 89 && j == 34); for (auto& chunk : hierPath.m_Chunks[PASS_1]) - TS_ASSERT(chunk.m_NumRegions == 1); + TS_ASSERT(chunk.m_RegionsID.size() == 1); // number of connected regions: 4 in the middle, 2 in the corners. TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(120, 120, PASS_1)].size() == 4); @@ -440,4 +440,41 @@ TS_ASSERT(abs(euclidian(goal.x.ToInt_RoundToNegInfinity(), goal.z.ToInt_RoundToNegInfinity(), pi, pj)-20) < 1.5f); TS_ASSERT(abs(euclidian(goal.x.ToInt_RoundToNegInfinity(), goal.z.ToInt_RoundToNegInfinity(), oi, oj) - euclidian(pi, pj, oi, oj)) < 22.0f); } + + void test_regions_flood_fill() + { + // Partial test of region inner flood filling. + // This highlights that internal region IDs can become higher than the number of regions. + pathClassMask = std::map { + { "1", 1 }, + { "2", 2 }, + }; + nonPathClassMask = std::map { + { "3", 4 } + }; + + // 0 is passable, 1 is not. + // i is vertical, j is horizontal; +#define _ 0 +#define X 1 + NavcellData gridDef[5][5] = { + {X,_,X,_,_}, + {_,_,X,X,_}, + {X,_,X,_,_}, + {_,_,X,X,_}, + {X,_,X,_,_} + }; +#undef _ +#undef X + HierarchicalPathfinder hierPath; + Grid grid(5, 5); + Grid dirtyGrid(5, 5); + for (size_t i = 0; i < 5; ++i) + for (size_t j = 0; j < 5; ++j) + grid.set(i, j, gridDef[i][j]); + hierPath.Recompute(&grid, nonPathClassMask, pathClassMask); + + TS_ASSERT_EQUALS(hierPath.m_Chunks[pathClassMask["1"]][0].m_RegionsID.size(), 2); + TS_ASSERT_EQUALS(hierPath.m_Chunks[pathClassMask["1"]][0].m_RegionsID.back(), 4); + } }; Index: ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h =================================================================== --- ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h +++ ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h @@ -27,7 +27,7 @@ /** * Hierarchical pathfinder. * - * It doesn't find shortest paths, but deals with connectivity. + * Deals with connectivity (can point A reach point B?). * * The navcell-grid representation of the map is split into fixed-size chunks. * Within a chunk, each maximal set of adjacently-connected passable navcells @@ -132,15 +132,15 @@ private: static const u8 CHUNK_SIZE = 96; // number of navcells per side - // TODO PATHFINDER: figure out best number. Probably 64 < n < 128 + // TODO: figure out best number. Probably 64 < n < 128 struct Chunk { u8 m_ChunkI, m_ChunkJ; // chunk ID - u16 m_NumRegions; // number of local region IDs (starting from 1) + std::vector m_RegionsID; // IDs of local regions, 0 (impassable) excluded u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]; // local region ID per navcell - cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_NumRegions with a checkerboard pattern + cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_RegionsID with a checkerboard pattern void InitRegions(int ci, int cj, Grid* grid, pass_class_t passClass); @@ -155,7 +155,7 @@ #ifdef TEST bool operator==(const Chunk& b) const { - return (m_ChunkI == b.m_ChunkI && m_ChunkJ == b.m_ChunkJ && m_NumRegions == b.m_NumRegions && memcmp(&m_Regions, &b.m_Regions, sizeof(u16) * CHUNK_SIZE * CHUNK_SIZE) == 0); + 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); } #endif }; @@ -183,7 +183,7 @@ void FillRegionOnGrid(const RegionID& region, pass_class_t passClass, u16 value, Grid& grid) const; u16 m_W, m_H; - u16 m_ChunksW, m_ChunksH; + u8 m_ChunksW, m_ChunksH; std::map > m_Chunks; std::map m_Edges; Index: ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.cpp +++ ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -110,14 +110,14 @@ } } - // Directly point the root ID - m_NumRegions = 0; + // Mark connected regions as being the same ID (i.e. the lowest) + m_RegionsID.clear(); for (u16 i = 1; i < regionID+1; ++i) { - if (connect[i] == i) - ++m_NumRegions; - else + if (connect[i] != i) connect[i] = RootID(i, connect); + else + m_RegionsID.push_back(connect[i]); } // Replace IDs by the root ID @@ -361,12 +361,12 @@ m_W = grid->m_W; m_H = grid->m_H; + ENSURE((grid->m_W + CHUNK_SIZE - 1) / CHUNK_SIZE < 256 && (grid->m_H + CHUNK_SIZE - 1) / CHUNK_SIZE < 256); // else the u8 Chunk::m_ChunkI will overflow + // Divide grid into chunks with round-to-positive-infinity m_ChunksW = (grid->m_W + CHUNK_SIZE - 1) / CHUNK_SIZE; m_ChunksH = (grid->m_H + CHUNK_SIZE - 1) / CHUNK_SIZE; - ENSURE(m_ChunksW < 256 && m_ChunksH < 256); // else the u8 Chunk::m_ChunkI will overflow - m_Chunks.clear(); m_Edges.clear(); @@ -728,11 +728,8 @@ { // Construct a set of all regions of all chunks for this pass class for (const Chunk& chunk : m_Chunks.at(passClass)) - { - // region 0 is impassable tiles - for (int r = 1; r <= chunk.m_NumRegions; ++r) + for (int r : chunk.m_RegionsID) regions.insert(RegionID(chunk.m_ChunkI, chunk.m_ChunkJ, r)); - } } void HierarchicalPathfinder::FillRegionOnGrid(const RegionID& region, pass_class_t passClass, u16 value, Grid& grid) const