diff --git a/binaries/data/mods/public/simulation/data/pathfinder.rnc b/binaries/data/mods/public/simulation/data/pathfinder.rnc index 58f0a24541..dabb1d36b3 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.rnc +++ b/binaries/data/mods/public/simulation/data/pathfinder.rnc @@ -1,19 +1,20 @@ -namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" -## -# NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file -# and use a converter tool like trang to generate the Relax NG XML (.rng) file -## - -element Pathfinder { - element MaxSameTurnMoves { xsd:nonNegativeInteger } & - element PassabilityClasses { - element * { - element MinWaterDepth { xsd:decimal }? & # TODO: fixed type - element MaxWaterDepth { xsd:decimal }? & - element MaxTerrainSlope { xsd:decimal }? & - element MinShoreDistance { xsd:decimal }? & - element MaxShoreDistance { xsd:decimal }? & - element Clearance { xsd:decimal }? - }+ - } -} +namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" +## +# NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file +# and use a converter tool like trang to generate the Relax NG XML (.rng) file +## + +element Pathfinder { + element MaxSameTurnMoves { xsd:nonNegativeInteger } & + element PassabilityClasses { + element * { + element Obstructions { xsd:string } & + element MinWaterDepth { xsd:decimal }? & # TODO: fixed type + element MaxWaterDepth { xsd:decimal }? & + element MaxTerrainSlope { xsd:decimal }? & + element MinShoreDistance { xsd:decimal }? & + element MaxShoreDistance { xsd:decimal }? & + element Clearance { xsd:decimal }? + }+ + } +} diff --git a/binaries/data/mods/public/simulation/data/pathfinder.rng b/binaries/data/mods/public/simulation/data/pathfinder.rng index a654335fb1..81b57f1555 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.rng +++ b/binaries/data/mods/public/simulation/data/pathfinder.rng @@ -15,6 +15,9 @@ + + + diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml index 35e9797330..ae59924ec1 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.xml +++ b/binaries/data/mods/public/simulation/data/pathfinder.xml @@ -3,67 +3,67 @@ 64 - + - - - + pathfinding 2 1.0 1.0 - - 2 - 1.0 - 0.0 - - - 2 - 1.0 - + pathfinding 2 1.0 4.0 + pathfinding 1 12.0 + pathfinding 1 4.0 - + foundation 0 4.0 1.0 + foundation 8.0 1.25 - - + + + none + + + none 2 1.0 - + diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index 154afa6fe3..351277e93c 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -1052,7 +1052,7 @@ public: LoadPathfinderClasses(state); std::map passClassMasks; if (cmpPathfinder) - passClassMasks = cmpPathfinder->GetPassabilityClasses(); + passClassMasks = cmpPathfinder->GetPathfindingPassabilityClasses(); m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state), *passabilityMap, dirtinessInformations, diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index 0f154dbcf0..91e9bb050f 100644 --- a/source/simulation2/components/CCmpObstructionManager.cpp +++ b/source/simulation2/components/CCmpObstructionManager.cpp @@ -462,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 std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate); + virtual void Rasterize(Grid& grid, const std::vector& passClasses, 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); @@ -635,6 +635,8 @@ private: { return (m_WorldX0 <= p.X && p.X <= m_WorldX1 && m_WorldZ0 <= p.Y && p.Y <= m_WorldZ1); } + + void RasterizeHelper(Grid& grid, ICmpObstructionManager::flags_t requireMask, bool fullUpdate, u16 appliedMask, entity_pos_t clearance = fixed::Zero()); }; REGISTER_COMPONENT_TYPE(ObstructionManager) @@ -810,83 +812,100 @@ bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, return false; // didn't collide, if we got this far } -void CCmpObstructionManager::Rasterize(Grid& grid, const std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) +void CCmpObstructionManager::Rasterize(Grid& grid, const std::vector& passClasses, bool fullUpdate) { PROFILE3("Rasterize"); - // 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.) - // Add obstructions onto the grid, for any class with (possibly zero) clearance - std::map combinedMasks; + // Pass classes will get shapes rasterized on them depending on their Obstruction value. + // Classes with another value than "pathfinding" should not use Clearance. + + std::map pathfindingMasks; + u16 foundationMask = 0; for (const PathfinderPassability& passability : passClasses) { - if (!passability.m_HasClearance) + switch (passability.m_Obstructions) + { + case PathfinderPassability::PATHFINDING: + { + auto it = pathfindingMasks.find(passability.m_Clearance); + if (it == pathfindingMasks.end()) + pathfindingMasks[passability.m_Clearance] = passability.m_Mask; + else + it->second |= passability.m_Mask; + break; + } + case PathfinderPassability::FOUNDATION: + foundationMask |= passability.m_Mask; + break; + default: continue; - - 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) - { - 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 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); } } + // FLAG_BLOCK_PATHFINDING and FLAG_BLOCK_FOUNDATION are the only flags taken into account by MakeDirty* functions, + // so they should be the only ones rasterized using with the help of m_Dirty*Shapes vectors. + + for (auto& maskPair : pathfindingMasks) + RasterizeHelper(grid, FLAG_BLOCK_PATHFINDING, fullUpdate, maskPair.second, maskPair.first); + + RasterizeHelper(grid, FLAG_BLOCK_FOUNDATION, fullUpdate, foundationMask); + m_DirtyStaticShapes.clear(); m_DirtyUnitShapes.clear(); } +void CCmpObstructionManager::RasterizeHelper(Grid& grid, ICmpObstructionManager::flags_t requireMask, bool fullUpdate, u16 appliedMask, entity_pos_t clearance) +{ + for (auto& pair : m_StaticShapes) + { + 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, clearance, Pathfinding::NAVCELL_SIZE); + for (SimRasterize::Span& span : spans) + { + 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) | appliedMask); + } + } + } + + 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 + clearance; + + 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) | appliedMask); + } +} + 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) { PROFILE("GetObstructionsInRange"); diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index 6e3d28b4f8..67cdb180b7 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -200,6 +200,16 @@ std::map CCmpPathfinder::GetPassabilityClasses() return m_PassClassMasks; } +std::map CCmpPathfinder::GetPathfindingPassabilityClasses() +{ + std::map pathfindingClasses; + for (auto& pair : m_PassClassMasks) + if (GetPassabilityFromMask(pair.second)->m_Obstructions == PathfinderPassability::PATHFINDING) + pathfindingClasses[pair.first] = pair.second; + + return pathfindingClasses; +} + const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t passClass) const { for (const PathfinderPassability& passability : m_PassClasses) @@ -556,7 +566,7 @@ void CCmpPathfinder::UpdateGrid() // so that we can stop units getting too close to impassable navcells for (PathfinderPassability& passability : m_PassClasses) { - if (!passability.m_HasClearance) + if (passability.m_Clearance != fixed::Zero()) continue; // TODO: if multiple classes have the same clearance, we should @@ -592,11 +602,11 @@ void CCmpPathfinder::UpdateGrid() } // Add obstructions onto the grid - cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_ObstructionsDirty.globalRecompute); + cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_ObstructionsDirty.globalRecompute); // Update the long-range pathfinder if (m_ObstructionsDirty.globallyDirty) - m_LongPathfinder.Reload(m_PassClassMasks, m_Grid); + m_LongPathfinder.Reload(GetPathfindingPassabilityClasses(), m_Grid); else m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid); } @@ -780,7 +790,7 @@ ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I entity_pos_t expand; const PathfinderPassability* passability = GetPassabilityFromMask(passClass); - if (passability && passability->m_HasClearance) + if (passability) expand = passability->m_Clearance; SimRasterize::Spans spans; diff --git a/source/simulation2/components/CCmpPathfinder_Common.h b/source/simulation2/components/CCmpPathfinder_Common.h index 1ce02e5b51..8b794d216b 100644 --- a/source/simulation2/components/CCmpPathfinder_Common.h +++ b/source/simulation2/components/CCmpPathfinder_Common.h @@ -141,13 +141,14 @@ public: virtual std::map GetPassabilityClasses(); + virtual std::map GetPathfindingPassabilityClasses(); + const PathfinderPassability* GetPassabilityFromMask(pass_class_t passClass) const; virtual entity_pos_t GetClearance(pass_class_t passClass) const { const PathfinderPassability* passability = GetPassabilityFromMask(passClass); - - if (!passability->m_HasClearance) + if (!passability) return fixed::Zero(); return passability->m_Clearance; @@ -158,10 +159,8 @@ public: entity_pos_t max = fixed::Zero(); for (const PathfinderPassability& passability : m_PassClasses) - { - if (passability.m_HasClearance && passability.m_Clearance > max) + if (passability.m_Clearance > max) max = passability.m_Clearance; - } return max; } diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 1a03f6bad6..2846518a20 100644 --- a/source/simulation2/components/CCmpTerritoryManager.cpp +++ b/source/simulation2/components/CCmpTerritoryManager.cpp @@ -328,7 +328,7 @@ void CCmpTerritoryManager::CalculateCostGrid() if (!cmpPathfinder) return; - pass_class_t passClassTerritory = cmpPathfinder->GetPassabilityClass("territory"); + pass_class_t passClassTerritory = cmpPathfinder->GetPassabilityClass("default-terrain-only"); pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); const Grid& passGrid = cmpPathfinder->GetPassabilityGrid(); diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h index 3909e28d15..d9d1452087 100644 --- a/source/simulation2/components/ICmpObstructionManager.h +++ b/source/simulation2/components/ICmpObstructionManager.h @@ -215,12 +215,11 @@ public: }; /** - * Convert the current set of shapes onto a navcell grid. + * Convert the current set of shapes onto a navcell grid, for all passability classes contained in @p passClasses. * 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. */ - virtual void Rasterize(Grid& grid, const std::vector& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) = 0; + virtual void Rasterize(Grid& grid, const std::vector& passClasses, bool fullUpdate) = 0; /** * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder diff --git a/source/simulation2/components/ICmpPathfinder.h b/source/simulation2/components/ICmpPathfinder.h index 183012eb78..c1074260b4 100644 --- a/source/simulation2/components/ICmpPathfinder.h +++ b/source/simulation2/components/ICmpPathfinder.h @@ -55,6 +55,11 @@ public: */ virtual std::map GetPassabilityClasses() = 0; + /** + * Get the list of pathfinding passability classes. + */ + virtual std::map GetPathfindingPassabilityClasses() = 0; + /** * Get the tag for a given passability class name. * Logs an error and returns something acceptable if the name is unrecognised. diff --git a/source/simulation2/helpers/LongPathfinder.h b/source/simulation2/helpers/LongPathfinder.h index 2e0060e7ce..327c917754 100644 --- a/source/simulation2/helpers/LongPathfinder.h +++ b/source/simulation2/helpers/LongPathfinder.h @@ -183,7 +183,7 @@ public: m_DebugPassClass = passClass; } - void Reload(std::map passClassMasks, Grid* passabilityGrid) + void Reload(const std::map& passClassMasks, Grid* passabilityGrid) { m_Grid = passabilityGrid; ASSERT(passabilityGrid->m_H == passabilityGrid->m_W); diff --git a/source/simulation2/helpers/Pathfinding.h b/source/simulation2/helpers/Pathfinding.h index ca530b89b7..89f1509f0b 100644 --- a/source/simulation2/helpers/Pathfinding.h +++ b/source/simulation2/helpers/Pathfinding.h @@ -163,10 +163,13 @@ namespace Pathfinding * Passability is determined by water depth, terrain slope, forestness, buildingness. * We need at least one bit per class per tile to represent passability. * - * We use a separate bit to indicate building obstructions (instead of folding it into - * the class passabilities) so that it can be ignored when doing the accurate short paths. - * We use another bit to indicate tiles near obstructions that block construction, - * for the AI to plan safe building spots. + * Not all pass classes are used for actual pathfinding. The pathfinder calls + * CCmpObstructionManager's Rasterize() to add shapes onto the passability grid. + * Which shapes are rasterized depend on the value of the m_Obstructions of each passability + * class. + * + * Passabilities not used for unit pathfinding should not use the Clearance attribute, and + * will get a zero clearance value. */ class PathfinderPassability { @@ -201,7 +204,6 @@ public: if (node.GetChild("Clearance").IsOk()) { - m_HasClearance = true; m_Clearance = node.GetChild("Clearance").ToFixed(); if (!(m_Clearance % Pathfinding::NAVCELL_SIZE).IsZero()) @@ -214,10 +216,25 @@ public: } } else - { - m_HasClearance = false; m_Clearance = fixed::Zero(); + + if (node.GetChild("Obstructions").IsOk()) + { + std::wstring obstructions = node.GetChild("Obstructions").ToString(); + if (obstructions == L"none") + m_Obstructions = NONE; + else if (obstructions == L"pathfinding") + m_Obstructions = PATHFINDING; + else if (obstructions == L"foundation") + m_Obstructions = FOUNDATION; + else + { + LOGERROR("Invalid value for Obstructions in pathfinder.xml for pass class %d", mask); + m_Obstructions = NONE; + } } + else + m_Obstructions = NONE; } bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist) @@ -227,9 +244,16 @@ public: pass_class_t m_Mask; - bool m_HasClearance; // whether static obstructions are impassable fixed m_Clearance; // min distance from static obstructions + enum ObstructionHandling + { + NONE, + PATHFINDING, + FOUNDATION + }; + ObstructionHandling m_Obstructions; + private: fixed m_MinDepth; fixed m_MaxDepth;