diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index 4752add93e..154afa6fe3 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -511,7 +511,7 @@ public: return true; } void StartComputation(const shared_ptr& gameState, - const Grid& passabilityMap, bool passabilityMapDirty, const Grid* dirtinessGrid, bool passabilityMapEntirelyDirty, + const Grid& passabilityMap, const GridUpdateInformation& dirtinessInformations, const Grid& territoryMap, bool territoryMapDirty, std::map passClassMasks) { @@ -520,13 +520,13 @@ public: m_GameState = gameState; JSContext* cx = m_ScriptInterface->GetContext(); - if (passabilityMapDirty) + if (dirtinessInformations.dirty) { m_PassabilityMap = passabilityMap; - if (passabilityMapEntirelyDirty) + if (dirtinessInformations.globallyDirty) m_LongPathfinder.Reload(passClassMasks, &m_PassabilityMap); else - m_LongPathfinder.Update(&m_PassabilityMap, dirtinessGrid); + m_LongPathfinder.Update(&m_PassabilityMap, dirtinessInformations.dirtinessGrid); ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap); } @@ -1035,10 +1035,7 @@ public: if (cmpPathfinder) passabilityMap = &cmpPathfinder->GetPassabilityGrid(); - Grid dummyDirtinessGrid; - const Grid* dirtinessGrid = &dummyDirtinessGrid; - bool passabilityMapEntirelyDirty = false; - bool passabilityMapDirty = cmpPathfinder->GetDirtinessData(dummyDirtinessGrid, passabilityMapEntirelyDirty); + GridUpdateInformation dirtinessInformations = cmpPathfinder->GetDirtinessData(); // Get the territory data // Since getting the territory grid can trigger a recalculation, we check NeedUpdate first @@ -1058,7 +1055,7 @@ public: passClassMasks = cmpPathfinder->GetPassabilityClasses(); m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state), - *passabilityMap, passabilityMapDirty, dirtinessGrid, passabilityMapEntirelyDirty, + *passabilityMap, dirtinessInformations, *territoryMap, territoryMapDirty, passClassMasks); diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index ec1dbae9ca..e50b1065af 100644 --- a/source/simulation2/components/CCmpObstructionManager.cpp +++ b/source/simulation2/components/CCmpObstructionManager.cpp @@ -137,6 +137,8 @@ public: u32 m_UnitShapeNext; // next allocated id u32 m_StaticShapeNext; + entity_pos_t m_MaxClearance; + bool m_PassabilityCircular; entity_pos_t m_WorldX0; @@ -157,9 +159,9 @@ public: m_UnitShapeNext = 1; m_StaticShapeNext = 1; - m_Dirty = true; - m_NeedsGlobalUpdate = true; - m_DirtinessGrid = NULL; + m_UpdateInformations.dirty = true; + m_UpdateInformations.globallyDirty = true; + m_UpdateInformations.globalRecompute = true; m_PassabilityCircular = false; @@ -172,7 +174,6 @@ public: virtual void Deinit() { - SAFE_DELETE(m_DirtinessGrid); } template @@ -234,14 +235,16 @@ public: ENSURE(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet ResetSubdivisions(x1, z1); - SAFE_DELETE(m_DirtinessGrid); - CmpPtr cmpTerrain(GetSystemEntity()); if (!cmpTerrain) return; u16 tiles = cmpTerrain->GetTilesPerSide(); - m_DirtinessGrid = new Grid(tiles*Pathfinding::NAVCELLS_PER_TILE, tiles*Pathfinding::NAVCELLS_PER_TILE); + m_UpdateInformations.dirtinessGrid = Grid(tiles*Pathfinding::NAVCELLS_PER_TILE, tiles*Pathfinding::NAVCELLS_PER_TILE); + + CmpPtr cmpPathfinder(GetSystemEntity()); + if (cmpPathfinder) + m_MaxClearance = cmpPathfinder->GetMaximumClearance(); } void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1) @@ -271,10 +274,11 @@ public: UnitShape shape = { ent, x, z, r, clearance, flags, group }; u32 id = m_UnitShapeNext++; m_UnitShapes[id] = shape; - MakeDirtyUnit(flags, id); m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r)); + MakeDirtyUnit(flags, id, shape); + return UNIT_INDEX_TO_TAG(id); } @@ -288,12 +292,13 @@ public: StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 }; u32 id = m_StaticShapeNext++; m_StaticShapes[id] = shape; - MakeDirtyStatic(flags, id); CFixedVector2D center(x, z); CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2)); m_StaticSubdivision.Add(id, center - bbHalfSize, center + bbHalfSize); + MakeDirtyStatic(flags, id, shape); + return STATIC_INDEX_TO_TAG(id); } @@ -324,6 +329,8 @@ public: { UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)]; + MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the old shape region + m_UnitSubdivision.Move(TAG_TO_INDEX(tag), CFixedVector2D(shape.x - shape.r, shape.z - shape.r), CFixedVector2D(shape.x + shape.r, shape.z + shape.r), @@ -333,7 +340,7 @@ public: shape.x = x; shape.z = z; - MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag)); + MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the new shape region } else { @@ -344,6 +351,8 @@ public: StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; + MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the old shape region + CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh)); m_StaticSubdivision.Move(TAG_TO_INDEX(tag), @@ -357,7 +366,7 @@ public: shape.u = u; shape.v = v; - MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag)); + MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the new shape region } } @@ -411,7 +420,8 @@ public: CFixedVector2D(shape.x - shape.r, shape.z - shape.r), CFixedVector2D(shape.x + shape.r, shape.z + shape.r)); - MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag)); + MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); + m_UnitShapes.erase(TAG_TO_INDEX(tag)); } else @@ -422,7 +432,8 @@ public: CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize); - MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag)); + MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape); + m_StaticShapes.erase(TAG_TO_INDEX(tag)); } } @@ -451,7 +462,7 @@ public: virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector* out); virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector* out); - virtual void Rasterize(Grid& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask); + virtual void Rasterize(Grid& grid, const std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate); virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector& squares); virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector& out, const IObstructionTestFilter& filter); @@ -479,29 +490,21 @@ public: void RenderSubmit(SceneCollector& collector); - virtual bool GetDirtinessData(Grid& dirtinessGrid, bool& globalUpdateNeeded) + virtual void UpdateInformations(GridUpdateInformation& informations) { - if (!m_Dirty || !m_DirtinessGrid) - { - globalUpdateNeeded = false; - return false; - } + // If the pathfinder wants to perform a full update, don't change that. + if (m_UpdateInformations.dirty && !informations.globalRecompute) + informations = m_UpdateInformations; - dirtinessGrid = *m_DirtinessGrid; - globalUpdateNeeded = m_NeedsGlobalUpdate; - - m_Dirty = false; - m_NeedsGlobalUpdate = false; - m_DirtinessGrid->reset(); - - return true; + m_UpdateInformations.Clean(); } private: // Dynamic updates for the long-range pathfinder - bool m_Dirty; - bool m_NeedsGlobalUpdate; - Grid* m_DirtinessGrid; + GridUpdateInformation m_UpdateInformations; + // These vectors might contain shapes that were deleted + std::vector m_DirtyStaticShapes; + std::vector m_DirtyUnitShapes; /** * Mark all previous Rasterize()d grids as dirty, and the debug display. @@ -509,8 +512,11 @@ private: */ void MakeDirtyAll() { - m_Dirty = true; - m_NeedsGlobalUpdate = true; + m_UpdateInformations.dirty = true; + m_UpdateInformations.globallyDirty = true; + m_UpdateInformations.globalRecompute = true; + m_UpdateInformations.dirtinessGrid.reset(); + m_DebugOverlayDirty = true; } @@ -525,52 +531,92 @@ private: inline void MarkDirtinessGrid(const entity_pos_t& x, const entity_pos_t& z, const entity_pos_t& r) { - if (!m_DirtinessGrid) - return; + MarkDirtinessGrid(x, z, CFixedVector2D(r, r)); + } + + inline void MarkDirtinessGrid(const entity_pos_t& x, const entity_pos_t& z, const CFixedVector2D& hbox) + { + ENSURE(m_UpdateInformations.dirtinessGrid.m_W != 0 && m_UpdateInformations.dirtinessGrid.m_H != 0); u16 j0, j1, i0, i1; - Pathfinding::NearestNavcell(x - r, z - r, i0, j0, m_DirtinessGrid->m_W, m_DirtinessGrid->m_H); - Pathfinding::NearestNavcell(x + r, z + r, i1, j1, m_DirtinessGrid->m_W, m_DirtinessGrid->m_H); + Pathfinding::NearestNavcell(x - hbox.X, z - hbox.Y, i0, j0, m_UpdateInformations.dirtinessGrid.m_W, m_UpdateInformations.dirtinessGrid.m_H); + Pathfinding::NearestNavcell(x + hbox.X, z + hbox.Y, i1, j1, m_UpdateInformations.dirtinessGrid.m_W, m_UpdateInformations.dirtinessGrid.m_H); for (int j = j0; j < j1; ++j) for (int i = i0; i < i1; ++i) - m_DirtinessGrid->set(i, j, 1); + m_UpdateInformations.dirtinessGrid.set(i, j, 1); } /** * Mark all previous Rasterize()d grids as dirty, if they depend on this shape. * Call this when a static shape has changed. */ - void MakeDirtyStatic(flags_t flags, u32 index) + void MakeDirtyStatic(flags_t flags, u32 index, const StaticShape& shape) { + m_DebugOverlayDirty = true; + if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) { - m_Dirty = true; + m_UpdateInformations.dirty = true; - auto it = m_StaticShapes.find(index); - if (it != m_StaticShapes.end()) - MarkDirtinessGrid(it->second.x, it->second.z, std::max(it->second.hw, it->second.hh)); + if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), index) == m_DirtyStaticShapes.end()) + m_DirtyStaticShapes.push_back(index); + + // All shapes overlapping the updated part of the grid should be dirtied too + CFixedVector2D center(shape.x, shape.z); + CFixedVector2D expandedBox = + Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)) + + CFixedVector2D(m_MaxClearance, m_MaxClearance); + + std::vector staticsNear; + m_StaticSubdivision.GetInRange(staticsNear, center - expandedBox, center + expandedBox); + for (u32& staticId : staticsNear) + if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), staticId) == m_DirtyStaticShapes.end()) + m_DirtyStaticShapes.push_back(staticId); + + std::vector unitsNear; + m_UnitSubdivision.GetInRange(unitsNear, center - expandedBox, center + expandedBox); + for (u32& unitId : unitsNear) + if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), unitId) == m_DirtyUnitShapes.end()) + m_DirtyUnitShapes.push_back(unitId); + + MarkDirtinessGrid(shape.x, shape.z, expandedBox); } - - m_DebugOverlayDirty = true; } /** * Mark all previous Rasterize()d grids as dirty, if they depend on this shape. * Call this when a unit shape has changed. */ - void MakeDirtyUnit(flags_t flags, u32 index) + void MakeDirtyUnit(flags_t flags, u32 index, const UnitShape& shape) { + m_DebugOverlayDirty = true; + if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) { - m_Dirty = true; + m_UpdateInformations.dirty = true; - auto it = m_UnitShapes.find(index); - if (it != m_UnitShapes.end()) - MarkDirtinessGrid(it->second.x, it->second.z, it->second.r); + if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), index) == m_DirtyUnitShapes.end()) + m_DirtyUnitShapes.push_back(index); + + // All shapes overlapping the updated part of the grid should be dirtied too + CFixedVector2D center(shape.x, shape.z); + entity_pos_t extendedRadius = shape.r + m_MaxClearance; + + std::vector staticsNear; + m_StaticSubdivision.GetNear(staticsNear, center, extendedRadius); + for (u32& staticId : staticsNear) + if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), staticId) == m_DirtyStaticShapes.end()) + m_DirtyStaticShapes.push_back(staticId); + + std::vector unitsNear; + m_UnitSubdivision.GetNear(staticsNear, center, extendedRadius); + for (u32& unitId : unitsNear) + if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), unitId) == m_DirtyUnitShapes.end()) + m_DirtyUnitShapes.push_back(unitId); + + MarkDirtinessGrid(shape.x, shape.z, extendedRadius); } - - m_DebugOverlayDirty = true; } /** @@ -763,61 +809,81 @@ bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, return false; // didn't collide, if we got this far } -void CCmpObstructionManager::Rasterize(Grid& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) +void CCmpObstructionManager::Rasterize(Grid& grid, const std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) { PROFILE3("Rasterize"); - // Since m_DirtyID is only updated for pathfinding/foundation blocking shapes, - // NeedUpdate+Rasterize will only be accurate for that subset of shapes. - // (If we ever want to support rasterizing more shapes, we need to improve - // the dirty-detection system too.) + // The update informations are only updated when pathfinding/foundation blocking shapes are modified. ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))); // Cells are only marked as blocked if the whole cell is strictly inside the shape. // (That ensures the shape's geometric border is always reachable.) - // TODO: it might be nice to rasterize with rounded corners for large 'expand' values. - - // (This could be implemented much more efficiently.) - - for (auto& pair : m_StaticShapes) + // Add obstructions onto the grid, for any class with (possibly zero) clearance + std::map combinedMasks; + for (const PathfinderPassability& passability : passClasses) { - const StaticShape& shape = pair.second; - if (!(shape.flags & requireMask)) + if (!passability.m_HasClearance) continue; - ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh }; - SimRasterize::Spans spans; - SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE); - for (SimRasterize::Span& span : spans) + auto it = combinedMasks.find(passability.m_Clearance); + if (it == combinedMasks.end()) + combinedMasks[passability.m_Clearance] = passability.m_Mask; + else + it->second |= passability.m_Mask; + } + + for (auto& maskPair : combinedMasks) + { + for (auto& pair : m_StaticShapes) { - i16 j = span.j; - if (j >= 0 && j <= grid.m_H) + if (!fullUpdate && std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), pair.first) == m_DirtyStaticShapes.end()) + continue; + + const StaticShape& shape = pair.second; + if (!(shape.flags & requireMask)) + continue; + + // TODO: it might be nice to rasterize with rounded corners for large 'expand' values. + ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh }; + SimRasterize::Spans spans; + SimRasterize::RasterizeRectWithClearance(spans, square, maskPair.first, Pathfinding::NAVCELL_SIZE); + for (SimRasterize::Span& span : spans) { - i16 i0 = std::max(span.i0, (i16)0); - i16 i1 = std::min(span.i1, (i16)grid.m_W); - for (i16 i = i0; i < i1; ++i) - grid.set(i, j, grid.get(i, j) | setMask); + i16 j = span.j; + if (j >= 0 && j <= grid.m_H) + { + i16 i0 = std::max(span.i0, (i16)0); + i16 i1 = std::min(span.i1, (i16)grid.m_W); + for (i16 i = i0; i < i1; ++i) + grid.set(i, j, grid.get(i, j) | maskPair.second); + } } } + + for (auto& pair : m_UnitShapes) + { + if (!fullUpdate && std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), pair.first) == m_DirtyUnitShapes.end()) + continue; + + CFixedVector2D center(pair.second.x, pair.second.z); + + if (!(pair.second.flags & requireMask)) + continue; + + entity_pos_t r = pair.second.r + maskPair.first; + + u16 i0, j0, i1, j1; + Pathfinding::NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H); + Pathfinding::NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H); + for (u16 j = j0+1; j < j1; ++j) + for (u16 i = i0+1; i < i1; ++i) + grid.set(i, j, grid.get(i, j) | maskPair.second); + } } - for (auto& pair : m_UnitShapes) - { - CFixedVector2D center(pair.second.x, pair.second.z); - - if (!(pair.second.flags & requireMask)) - continue; - - entity_pos_t r = pair.second.r + expand; - - u16 i0, j0, i1, j1; - Pathfinding::NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H); - Pathfinding::NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H); - for (u16 j = j0+1; j < j1; ++j) - for (u16 i = i0+1; i < i1; ++i) - grid.set(i, j, grid.get(i, j) | setMask); - } + m_DirtyStaticShapes.clear(); + m_DirtyUnitShapes.clear(); } void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector& squares) @@ -877,17 +943,11 @@ void CCmpObstructionManager::GetUnitsOnObstruction(const ObstructionSquare& squa // units s.t. the RasterizeRectWithClearance of the building's shape with the // unit's clearance covers the navcell the unit is on. - CmpPtr cmpPathfinder(GetSystemEntity()); - if (!cmpPathfinder) - return; - - entity_pos_t maxClearance = cmpPathfinder->GetMaximumClearance(); - std::vector unitShapes; CFixedVector2D center(square.x, square.z); CFixedVector2D expandedBox = Geometry::GetHalfBoundingBox(square.u, square.v, CFixedVector2D(square.hw, square.hh)) + - CFixedVector2D(maxClearance, maxClearance); + CFixedVector2D(m_MaxClearance, m_MaxClearance); m_UnitSubdivision.GetInRange(unitShapes, center - expandedBox, center + expandedBox); std::map rasterizedRects; diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index ae3b94eeb9..6e3d28b4f8 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -49,10 +49,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) { m_MapSize = 0; m_Grid = NULL; - m_BaseGrid = NULL; - - m_ObstructionsDirty = true; - m_TerrainDirty = true; + m_TerrainOnlyGrid = NULL; m_NextAsyncTicket = 1; @@ -104,7 +101,7 @@ void CCmpPathfinder::Deinit() SetDebugOverlay(false); // cleans up memory SAFE_DELETE(m_Grid); - SAFE_DELETE(m_BaseGrid); + SAFE_DELETE(m_TerrainOnlyGrid); } struct SerializeLongRequest @@ -170,17 +167,12 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) case MT_TerrainChanged: case MT_WaterChanged: case MT_ObstructionMapShapeChanged: - { - // TODO PATHFINDER: we ought to only bother updating the dirtied region m_TerrainDirty = true; break; - } case MT_TurnStart: - { m_SameTurnMovesCount = 0; break; } - } } void CCmpPathfinder::RenderSubmit(SceneCollector& collector) @@ -221,7 +213,9 @@ const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t const Grid& CCmpPathfinder::GetPassabilityGrid() { - UpdateGrid(); + if (!m_Grid) + UpdateGrid(); + return *m_Grid; } @@ -473,14 +467,13 @@ void CCmpPathfinder::UpdateGrid() if (!cmpTerrain) return; // error - bool forceGlobalUpdate = false; + m_ObstructionsDirty.Clean(); // If the terrain was resized then delete the old grid data if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) { SAFE_DELETE(m_Grid); - SAFE_DELETE(m_BaseGrid); - m_TerrainDirty = true; + SAFE_DELETE(m_TerrainOnlyGrid); } // Initialise the terrain data when first needed @@ -488,25 +481,23 @@ void CCmpPathfinder::UpdateGrid() { m_MapSize = cmpTerrain->GetTilesPerSide(); m_Grid = new Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); - m_BaseGrid = new Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); - forceGlobalUpdate = true; + m_TerrainOnlyGrid = new Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); + + m_ObstructionsDirty.dirty = true; + m_ObstructionsDirty.globallyDirty = true; + m_ObstructionsDirty.globalRecompute = true; + m_TerrainDirty = true; } CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); - if (forceGlobalUpdate) - { - m_ObstructionsDirty = true; - m_ObstructionsGlobalUpdate = true; - } - else - m_ObstructionsDirty = cmpObstructionManager->GetDirtinessData(m_DirtinessGrid, m_ObstructionsGlobalUpdate); + cmpObstructionManager->UpdateInformations(m_ObstructionsDirty); - if (!m_ObstructionsDirty && !m_TerrainDirty) + if (!m_ObstructionsDirty.dirty && !m_TerrainDirty) return; - // If the terrain has changed, recompute entirely m_Grid - // Else, use data from m_BaseGrid and add obstructions + // If the terrain has changed, recompute m_Grid + // Else, use data from m_TerrainOnlyGrid and add obstructions if (m_TerrainDirty) { Grid shoreGrid = ComputeShoreGrid(); @@ -576,39 +567,43 @@ void CCmpPathfinder::UpdateGrid() } // Store the updated terrain-only grid - *m_BaseGrid = *m_Grid; + *m_TerrainOnlyGrid = *m_Grid; + m_TerrainDirty = false; + m_ObstructionsDirty.globalRecompute = true; + m_ObstructionsDirty.globallyDirty = true; + } + else if (m_ObstructionsDirty.globalRecompute) + { + ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); + memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData)); + + m_ObstructionsDirty.globallyDirty = true; } else { - ENSURE(m_Grid->m_W == m_BaseGrid->m_W && m_Grid->m_H == m_BaseGrid->m_H); - memcpy(m_Grid->m_Data, m_BaseGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData)); + ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H); + ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); + + for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i) + for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j) + if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1) + m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j)); } - // TODO: tweak rasterizing conditions - // Add obstructions onto the grid, for any class with (possibly zero) clearance - for (PathfinderPassability& passability : m_PassClasses) - { - // TODO: if multiple classes have the same clearance, we should - // only bother running Rasterize once for them all - if (passability.m_HasClearance) - cmpObstructionManager->Rasterize(*m_Grid, passability.m_Clearance, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, passability.m_Mask); - } + // Add obstructions onto the grid + cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_ObstructionsDirty.globalRecompute); - if (m_ObstructionsGlobalUpdate) + // Update the long-range pathfinder + if (m_ObstructionsDirty.globallyDirty) m_LongPathfinder.Reload(m_PassClassMasks, m_Grid); else - m_LongPathfinder.Update(m_Grid, &m_DirtinessGrid); + m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid); } -bool CCmpPathfinder::GetDirtinessData(Grid& dirtinessGrid, bool& globalUpdateNeeded) +const GridUpdateInformation& CCmpPathfinder::GetDirtinessData() const { - if (!m_ObstructionsDirty) - return false; - - dirtinessGrid = m_DirtinessGrid; - globalUpdateNeeded = m_ObstructionsGlobalUpdate; - return true; + return m_ObstructionsDirty; } ////////////////////////////////////////////////////////// @@ -778,8 +773,6 @@ ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I // Test against terrain: - UpdateGrid(); - ICmpObstructionManager::ObstructionSquare square; CmpPtr cmpObstruction(GetSimContext(), id); if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square)) diff --git a/source/simulation2/components/CCmpPathfinder_Common.h b/source/simulation2/components/CCmpPathfinder_Common.h index 50702e3d15..1ce02e5b51 100644 --- a/source/simulation2/components/CCmpPathfinder_Common.h +++ b/source/simulation2/components/CCmpPathfinder_Common.h @@ -106,13 +106,11 @@ public: u16 m_MapSize; // tiles per side Grid* m_Grid; // terrain/passability information - Grid* m_BaseGrid; // same as m_Grid, but only with terrain, to avoid some recomputations - bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed + Grid* m_TerrainOnlyGrid; // same as m_Grid, but only with terrain, to avoid some recomputations - // Update data, stored for the AI manager - bool m_ObstructionsDirty; - bool m_ObstructionsGlobalUpdate; - Grid m_DirtinessGrid; + // Update data, used for clever updates and then stored for the AI manager + GridUpdateInformation m_ObstructionsDirty; + bool m_TerrainDirty; // Interface to the long-range pathfinder. LongPathfinder m_LongPathfinder; @@ -170,7 +168,7 @@ public: virtual const Grid& GetPassabilityGrid(); - virtual bool GetDirtinessData(Grid& dirtinessGrid, bool& globalUpdateNeeded); + virtual const GridUpdateInformation& GetDirtinessData() const; virtual Grid ComputeShoreGrid(bool expandOnWater = false); diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h index e4748e4e15..3909e28d15 100644 --- a/source/simulation2/components/ICmpObstructionManager.h +++ b/source/simulation2/components/ICmpObstructionManager.h @@ -20,8 +20,7 @@ #include "simulation2/system/Interface.h" -#include "simulation2/helpers/Grid.h" -#include "simulation2/helpers/Position.h" +#include "simulation2/helpers/Pathfinding.h" #include "maths/FixedVector2D.h" @@ -217,19 +216,18 @@ public: /** * Convert the current set of shapes onto a navcell grid. - * Shapes are expanded by the clearance radius @p expand. + * If @p fullUpdate is false, the function will only go through dirty shapes. + * Shapes are expanded by the @p passClasses clearances, by ORing their masks onto the @p grid. * Only shapes with at least one of the flags from @p requireMask will be considered. - * @p setMask will be ORed onto the @p grid value for all navcells - * that are wholly enclosed by an expanded shape. */ - virtual void Rasterize(Grid& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) = 0; + virtual void Rasterize(Grid& grid, const std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) = 0; /** * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder * to pass the information to other components if needed. (AIs, etc.) * The return value is false if an update is unnecessary. */ - virtual bool GetDirtinessData(Grid& dirtinessGrid, bool& globalUpdateNeeded) = 0; + virtual void UpdateInformations(GridUpdateInformation& informations) = 0; /** * Standard representation for all types of shapes, for use with geometry processing code. diff --git a/source/simulation2/components/ICmpPathfinder.h b/source/simulation2/components/ICmpPathfinder.h index 0ad964f262..183012eb78 100644 --- a/source/simulation2/components/ICmpPathfinder.h +++ b/source/simulation2/components/ICmpPathfinder.h @@ -74,7 +74,7 @@ public: * Passes the lazily-stored dirtiness data collected from * the obstruction manager during the previous grid update. */ - virtual bool GetDirtinessData(Grid& dirtinessGrid, bool& globalUpdateNeeded) = 0; + virtual const GridUpdateInformation& GetDirtinessData() const = 0; /** * Get a grid representing the distance to the shore of the terrain tile. diff --git a/source/simulation2/helpers/Grid.h b/source/simulation2/helpers/Grid.h index 6ac0a73045..7fd790d342 100644 --- a/source/simulation2/helpers/Grid.h +++ b/source/simulation2/helpers/Grid.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -185,4 +185,29 @@ public: size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated }; +/** + * Structure holding grid dirtiness informations, for clever updates + * + * Note: globallyDirty should be used to know which parts of the grid have been changed after an update, + * whereas globalRecompute should be used during the update itself, to avoid unnecessary recomputations. + */ +struct GridUpdateInformation +{ + bool dirty; + bool globallyDirty; + bool globalRecompute; + Grid dirtinessGrid; + + /** + * Mark everything as clean + */ + void Clean() + { + dirty = false; + globallyDirty = false; + globalRecompute = false; + dirtinessGrid.reset(); + } +}; + #endif // INCLUDED_GRID diff --git a/source/simulation2/helpers/HierarchicalPathfinder.cpp b/source/simulation2/helpers/HierarchicalPathfinder.cpp index cb9f53365c..c9d30e7f5d 100644 --- a/source/simulation2/helpers/HierarchicalPathfinder.cpp +++ b/source/simulation2/helpers/HierarchicalPathfinder.cpp @@ -300,16 +300,16 @@ void HierarchicalPathfinder::Recompute(const std::map } } -void HierarchicalPathfinder::Update(Grid* grid, const Grid* dirtinessGrid) +void HierarchicalPathfinder::Update(Grid* grid, const Grid& dirtinessGrid) { PROFILE3("Hierarchical Update"); std::vector > processedChunks; - for (int j = 0; j < dirtinessGrid->m_H; ++j) + for (int j = 0; j < dirtinessGrid.m_H; ++j) { - for (int i = 0; i < dirtinessGrid->m_W; ++i) + for (int i = 0; i < dirtinessGrid.m_W; ++i) { - if (!dirtinessGrid->get(i, j)) + if (!dirtinessGrid.get(i, j)) continue; std::pair chunkID(i / CHUNK_SIZE, j / CHUNK_SIZE); diff --git a/source/simulation2/helpers/HierarchicalPathfinder.h b/source/simulation2/helpers/HierarchicalPathfinder.h index 9bed1ce64e..644190e42b 100644 --- a/source/simulation2/helpers/HierarchicalPathfinder.h +++ b/source/simulation2/helpers/HierarchicalPathfinder.h @@ -80,7 +80,7 @@ public: void SetDebugOverlay(bool enabled, const CSimContext* simContext); void Recompute(const std::map& passClassMasks, Grid* passabilityGrid); - void Update(Grid* grid, const Grid* dirtinessGrid); + void Update(Grid* grid, const Grid& dirtinessGrid); RegionID Get(u16 i, u16 j, pass_class_t passClass); diff --git a/source/simulation2/helpers/LongPathfinder.h b/source/simulation2/helpers/LongPathfinder.h index bd2ee1130d..2e0060e7ce 100644 --- a/source/simulation2/helpers/LongPathfinder.h +++ b/source/simulation2/helpers/LongPathfinder.h @@ -194,7 +194,7 @@ public: m_PathfinderHier.Recompute(passClassMasks, passabilityGrid); } - void Update(Grid* passabilityGrid, const Grid* dirtinessGrid) + void Update(Grid* passabilityGrid, const Grid& dirtinessGrid) { m_Grid = passabilityGrid; ASSERT(passabilityGrid->m_H == passabilityGrid->m_W);