1
0
forked from 0ad/0ad

Turn the FLOODFILL macro in to a function

Also move the "origin-handling" in to the loop.

Comments by: @sera, @vladislavbelov, @Stan
Differential Revision: https://code.wildfiregames.com/D5189
This was SVN commit r28087.
This commit is contained in:
phosit 2024-05-12 06:13:47 +00:00
parent ae639bb360
commit a4610e4ca9

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games. /* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -324,36 +324,53 @@ struct Tile
u16 x, z; u16 x, z;
}; };
// Floodfill templates that expand neighbours from a certain source onwards /**
// (posX, posZ) are the coordinates of the currently expanded tile * Queue based eight directional floodfill algorithm.
// (nx, nz) are the coordinates of the current neighbour handled *
// The user of this floodfill should use "continue" on every neighbour that * @param origin Where to start the floodfill. In the first iteration it is
// shouldn't be expanded on its own. (without continue, an infinite loop will happen) * passed as the second argument to the @see decider and the floodfill only
# define FLOODFILL(i, j, code)\ * continues when the invocation returns @c true.
do {\ * @param gridSize Tiles outside the boundary are never exteded. The
const int NUM_NEIGHBOURS = 8;\ * @see decider isn't called with thous tiles.
const int NEIGHBOURS_X[NUM_NEIGHBOURS] = {1,-1, 0, 0, 1,-1, 1,-1};\ * @param decider It is called with a tile wich was already added as the first
const int NEIGHBOURS_Z[NUM_NEIGHBOURS] = {0, 0, 1,-1, 1,-1,-1, 1};\ * argument and a neighbour as the second argument. The invocation shall
std::queue<Tile> openTiles;\ * return whether to extend the wavefront to the neighbour (if allways
openTiles.emplace(i, j);\ * @c true is returned, an infinite loop will occur). In the first iteration
while (!openTiles.empty())\ * the @see decider is invoked with a null pointer as the first argument and
{\ * @see origin as the second argument.
u16 posX = openTiles.front().x;\ */
u16 posZ = openTiles.front().z;\ template<typename Decider>
openTiles.pop();\ void Floodfill(const Tile& origin, const Tile& gridSize, Decider decider)
for (int n = 0; n < NUM_NEIGHBOURS; ++n)\ {
{\ static_assert(std::is_invocable_r_v<bool, Decider, const Tile*, const Tile&>);
u16 nx = posX + NEIGHBOURS_X[n];\
u16 nz = posZ + NEIGHBOURS_Z[n];\ constexpr std::array<std::array<int, 2>, 8> neighbours{{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1},
/* Check the bounds, underflow will cause the values to be big again */\ {-1, -1}, {1, -1}, {-1, 1}}};
if (nx >= tilesW || nz >= tilesH)\ std::queue<Tile> openTiles;
continue;\
code\ const auto emplaceIfRequested = [decider = std::move(decider), &openTiles](
openTiles.emplace(nx, nz);\ const Tile* currentTile, const Tile& neighbourTile)
}\ {
}\ if (decider(currentTile, neighbourTile))
}\ openTiles.emplace(neighbourTile);
while (false) };
emplaceIfRequested(nullptr, origin);
while (!openTiles.empty())
{
const Tile currentTile{openTiles.front()};
openTiles.pop();
for (const std::array<int, 2>& neighbour : neighbours)
{
const Tile neighbourTile{static_cast<u16>(currentTile.x + std::get<0>(neighbour)),
static_cast<u16>(currentTile.z + std::get<1>(neighbour))};
// Check the bounds, underflow will cause the values to be big again.
if (neighbourTile.x < gridSize.x && neighbourTile.z < gridSize.z)
emplaceIfRequested(&currentTile, neighbourTile);
}
}
}
/** /**
* Compute the tile indexes on the grid nearest to a given point * Compute the tile indexes on the grid nearest to a given point
@ -478,11 +495,13 @@ void CCmpTerritoryManager::CalculateTerritories()
continue; continue;
CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent); CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
u32 weight = cmpTerritoryInfluence->GetWeight(); const u32 originWeight = cmpTerritoryInfluence->GetWeight();
u32 radius = cmpTerritoryInfluence->GetRadius(); u32 radius = cmpTerritoryInfluence->GetRadius();
if (weight == 0 || radius == 0) if (originWeight == 0 || radius == 0)
continue; continue;
u32 falloff = weight * (Pathfinding::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE).ToInt_RoundToNegInfinity() / radius; const u32 relativeFalloff = originWeight *
(Pathfinding::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE)
.ToInt_RoundToNegInfinity() / radius;
CFixedVector2D pos = cmpPosition->GetPosition2D(); CFixedVector2D pos = cmpPosition->GetPosition2D();
u16 i, j; u16 i, j;
@ -491,40 +510,44 @@ void CCmpTerritoryManager::CalculateTerritories()
if (cmpTerritoryInfluence->IsRoot()) if (cmpTerritoryInfluence->IsRoot())
rootInfluenceEntities.push_back(ent); rootInfluenceEntities.push_back(ent);
// Initialise the tile under the entity
entityGrid.set(i, j, weight);
if (weight > bestWeightGrid.get(i, j))
{
bestWeightGrid.set(i, j, weight);
m_Territories->set(i, j, owner);
}
// Expand influences outwards // Expand influences outwards
FLOODFILL(i, j, Floodfill({i, j}, {tilesW, tilesH}, [&](const Tile* current, const Tile& neighbour)
u32 dg = falloff * m_CostGrid->get(nx, nz);
// diagonal neighbour -> multiply with approx sqrt(2)
if (nx != posX && nz != posZ)
dg = (dg * 362) / 256;
// Don't expand if new cost is not better than previous value for that tile
// (arranged to avoid underflow if entityGrid.get(x, z) < dg)
if (entityGrid.get(posX, posZ) <= entityGrid.get(nx, nz) + dg)
continue;
// weight of this tile = weight of predecessor - falloff from predecessor
u32 newWeight = entityGrid.get(posX, posZ) - dg;
u32 totalWeight = playerGrid.get(nx, nz) - entityGrid.get(nx, nz) + newWeight;
playerGrid.set(nx, nz, totalWeight);
entityGrid.set(nx, nz, newWeight);
// if this weight is better than the best thus far, set the owner
if (totalWeight > bestWeightGrid.get(nx, nz))
{ {
bestWeightGrid.set(nx, nz, totalWeight); const bool diagonalProgression{current && neighbour.x != current->x &&
m_Territories->set(nx, nz, owner); neighbour.z != current->z};
}
);
const u32 falloffPerTile{relativeFalloff *
m_CostGrid->get(neighbour.x, neighbour.z)};
// diagonal neighbour -> multiply with approx sqrt(2)
const u32 falloff{diagonalProgression ? (falloffPerTile * 362) / 256 :
falloffPerTile};
// Don't expand if new cost is not better than previous value for that tile
// (arranged to avoid underflow if entityGrid.get(x, z) < falloff)
if (current &&
entityGrid.get(current->x, current->z) <=
entityGrid.get(neighbour.x, neighbour.z) + falloff)
{
return false;
}
// weight of this tile = weight of predecessor - falloff from predecessor
const u32 weight{current ? entityGrid.get(current->x, current->z) - falloff :
originWeight};
const u32 totalWeight{weight + (current ?
playerGrid.get(neighbour.x, neighbour.z) -
entityGrid.get(neighbour.x, neighbour.z) : 0)};
playerGrid.set(neighbour.x, neighbour.z, totalWeight);
entityGrid.set(neighbour.x, neighbour.z, weight);
// if this weight is better than the best thus far, set the owner
if (totalWeight > bestWeightGrid.get(neighbour.x, neighbour.z))
{
bestWeightGrid.set(neighbour.x, neighbour.z, totalWeight);
m_Territories->set(neighbour.x, neighbour.z, owner);
}
return true;
});
entityGrid.reset(); entityGrid.reset();
} }
} }
@ -543,22 +566,16 @@ void CCmpTerritoryManager::CalculateTerritories()
u8 owner = (u8)cmpOwnership->GetOwner(); u8 owner = (u8)cmpOwnership->GetOwner();
if (m_Territories->get(i, j) != owner) Floodfill({i, j}, {tilesW, tilesH}, [&](const Tile*, const Tile& neighbour)
continue; {
// Don't expand non-owner tiles, or tiles that already have a connected mask
m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK); if (m_Territories->get(neighbour.x, neighbour.z) != owner)
return false;
if (m_CostGrid->get(i, j) < m_ImpassableCost) m_Territories->set(neighbour.x, neighbour.z, owner | TERRITORY_CONNECTED_MASK);
++m_TerritoryCellCounts[owner]; if (m_CostGrid->get(neighbour.x, neighbour.z) < m_ImpassableCost)
++m_TerritoryCellCounts[owner];
FLOODFILL(i, j, return true;
// Don't expand non-owner tiles, or tiles that already have a connected mask });
if (m_Territories->get(nx, nz) != owner)
continue;
m_Territories->set(nx, nz, owner | TERRITORY_CONNECTED_MASK);
if (m_CostGrid->get(nx, nz) < m_ImpassableCost)
++m_TerritoryCellCounts[owner];
);
} }
// Then recomputes the blinking tiles // Then recomputes the blinking tiles
@ -750,21 +767,22 @@ std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_
// use a flood-fill algorithm that fills up to the borders and remembers the owners // use a flood-fill algorithm that fills up to the borders and remembers the owners
Grid<bool> markerGrid(tilesW, tilesH); Grid<bool> markerGrid(tilesW, tilesH);
markerGrid.set(i, j, true);
FLOODFILL(i, j, Floodfill({i, j}, {tilesW, tilesH}, [&](const Tile*, const Tile& neighbour)
if (markerGrid.get(nx, nz))
continue;
// mark the tile as visited in any case
markerGrid.set(nx, nz, true);
int owner = m_Territories->get(nx, nz) & TERRITORY_PLAYER_MASK;
if (owner != thisOwner)
{ {
if (owner == 0 || !filterConnected || (m_Territories->get(nx, nz) & TERRITORY_CONNECTED_MASK) != 0) if (markerGrid.get(neighbour.x, neighbour.z))
ret[owner]++; // add player to the neighbour list when requested return false;
continue; // don't expand non-owner tiles further // mark the tile as visited in any case
} markerGrid.set(neighbour.x, neighbour.z, true);
); int owner = m_Territories->get(neighbour.x, neighbour.z) & TERRITORY_PLAYER_MASK;
if (owner != thisOwner)
{
if (owner == 0 || !filterConnected || (m_Territories->get(neighbour.x, neighbour.z) & TERRITORY_CONNECTED_MASK) != 0)
ret[owner]++; // add player to the neighbour list when requested
return false; // don't expand non-owner tiles further
}
return true;
});
return ret; return ret;
} }
@ -794,25 +812,20 @@ void CCmpTerritoryManager::SetTerritoryBlinking(entity_pos_t x, entity_pos_t z,
player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
u8 bitmask = m_Territories->get(i, j); Floodfill({i, j}, {tilesW, tilesH}, [&](const Tile*, const Tile& neighbour)
u8 blinking = bitmask & TERRITORY_BLINKING_MASK; {
if (enable && !blinking) const u8 bitmask{m_Territories->get(neighbour.x, neighbour.z)};
m_Territories->set(i, j, bitmask | TERRITORY_BLINKING_MASK); if ((bitmask & TERRITORY_PLAYER_MASK) != thisOwner)
else if (!enable && blinking) return false;
m_Territories->set(i, j, bitmask & ~TERRITORY_BLINKING_MASK); const bool blinking{(bitmask & TERRITORY_BLINKING_MASK) != 0};
if (enable != blinking)
FLOODFILL(i, j, {
bitmask = m_Territories->get(nx, nz); m_Territories->set(neighbour.x, neighbour.z, enable ?
if ((bitmask & TERRITORY_PLAYER_MASK) != thisOwner) bitmask | TERRITORY_BLINKING_MASK : bitmask & ~TERRITORY_BLINKING_MASK);
continue; return true;
blinking = bitmask & TERRITORY_BLINKING_MASK; }
if (enable && !blinking) return false;
m_Territories->set(nx, nz, bitmask | TERRITORY_BLINKING_MASK); });
else if (!enable && blinking)
m_Territories->set(nx, nz, bitmask & ~TERRITORY_BLINKING_MASK);
else
continue;
);
++m_DirtyBlinkingID; ++m_DirtyBlinkingID;
m_BoundaryLinesDirty = true; m_BoundaryLinesDirty = true;
} }
@ -868,5 +881,3 @@ void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h)
} }
} }
} }
#undef FLOODFILL