1
0
forked from 0ad/0ad

Changes the general behavior of non-pathfinding passability classes, in order to make the handling of foundation obstructions less difficult. This will allow the AI to be fixed, as reported in #3295.

Also some cleanup and comments updates.

Refs #3295.

This was SVN commit r16784.
This commit is contained in:
Nicolas Auvray 2015-06-17 20:19:53 +00:00
parent 4b8f0c9fb9
commit 1709353e2c
12 changed files with 186 additions and 126 deletions

View File

@ -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 }?
}+
}
}

View File

@ -15,6 +15,9 @@
<element>
<anyName/>
<interleave>
<element name="Obstructions">
<data type="string"/>
</element>
<optional>
<element name="MinWaterDepth">
<data type="decimal"/>

View File

@ -3,67 +3,67 @@
<!-- Sets limit on the number of same turns moves we will process -->
<!-- Setting the value to 0 disable this functionality -->
<MaxSameTurnMoves>64</MaxSameTurnMoves>
<PassabilityClasses>
<unrestricted/>
<!-- Unit pathfinding classes: -->
<!-- 'default-terrain-only' is used for wall building placement
and by the AI pathfinder; it must be kept in sync with 'default'
-->
<default>
<Obstructions>pathfinding</Obstructions>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
<Clearance>1.0</Clearance>
</default>
<default-no-clearance>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
<Clearance>0.0</Clearance>
</default-no-clearance>
<default-terrain-only>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
</default-terrain-only>
<siege-large>
<Obstructions>pathfinding</Obstructions>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
<Clearance>4.0</Clearance>
</siege-large>
<ship>
<Obstructions>pathfinding</Obstructions>
<MinWaterDepth>1</MinWaterDepth>
<Clearance>12.0</Clearance>
</ship>
<ship-small>
<Obstructions>pathfinding</Obstructions>
<MinWaterDepth>1</MinWaterDepth>
<Clearance>4.0</Clearance>
</ship-small>
<!-- Building construction classes:
<!--
Building construction classes:
* Land is used for most buildings, which must be away
from water and not on cliffs or mountains.
* Shore is used for docks, which must be near water and
land, yet shallow enough for builders to approach.
(These should not use <Clearance>, because the foundation placement
checker already does precise obstruction-shape-based checking.)
-->
<building-land>
<Obstructions>foundation</Obstructions>
<MaxWaterDepth>0</MaxWaterDepth>
<MinShoreDistance>4.0</MinShoreDistance>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
</building-land>
<building-shore>
<Obstructions>foundation</Obstructions>
<MaxShoreDistance>8.0</MaxShoreDistance>
<MaxTerrainSlope>1.25</MaxTerrainSlope>
</building-shore>
<!-- Territory growth influences: -->
<territory>
<!--
Unrestricted: only off-world limits.
Default-terrain-only: used by the AI, for wall-building
placement and for territory influence growth.
It must be kept in sync with "default".
-->
<unrestricted>
<Obstructions>none</Obstructions>
</unrestricted>
<default-terrain-only>
<Obstructions>none</Obstructions>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
</territory>
</default-terrain-only>
</PassabilityClasses>
</Pathfinder>

View File

@ -1052,7 +1052,7 @@ public:
LoadPathfinderClasses(state);
std::map<std::string, pass_class_t> passClassMasks;
if (cmpPathfinder)
passClassMasks = cmpPathfinder->GetPassabilityClasses();
passClassMasks = cmpPathfinder->GetPathfindingPassabilityClasses();
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state),
*passabilityMap, dirtinessInformations,

View File

@ -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<entity_id_t>* out);
virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector<entity_id_t>* out);
virtual void Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate);
virtual void Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& 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<ObstructionSquare>& squares);
virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& 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<u16>& 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<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate)
void CCmpObstructionManager::Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& 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<entity_pos_t, u16> 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<entity_pos_t, u16> 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<u16>& 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<ObstructionSquare>& squares)
{
PROFILE("GetObstructionsInRange");

View File

@ -200,6 +200,16 @@ std::map<std::string, pass_class_t> CCmpPathfinder::GetPassabilityClasses()
return m_PassClassMasks;
}
std::map<std::string, pass_class_t> CCmpPathfinder::GetPathfindingPassabilityClasses()
{
std::map<std::string, pass_class_t> 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;

View File

@ -141,13 +141,14 @@ public:
virtual std::map<std::string, pass_class_t> GetPassabilityClasses();
virtual std::map<std::string, pass_class_t> 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;
}

View File

@ -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<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();

View File

@ -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<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) = 0;
virtual void Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& passClasses, bool fullUpdate) = 0;
/**
* Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder

View File

@ -55,6 +55,11 @@ public:
*/
virtual std::map<std::string, pass_class_t> GetPassabilityClasses() = 0;
/**
* Get the list of pathfinding passability classes.
*/
virtual std::map<std::string, pass_class_t> GetPathfindingPassabilityClasses() = 0;
/**
* Get the tag for a given passability class name.
* Logs an error and returns something acceptable if the name is unrecognised.

View File

@ -183,7 +183,7 @@ public:
m_DebugPassClass = passClass;
}
void Reload(std::map<std::string, pass_class_t> passClassMasks, Grid<NavcellData>* passabilityGrid)
void Reload(const std::map<std::string, pass_class_t>& passClassMasks, Grid<NavcellData>* passabilityGrid)
{
m_Grid = passabilityGrid;
ASSERT(passabilityGrid->m_H == passabilityGrid->m_W);

View File

@ -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;