1
0
forked from 0ad/0ad

Optimise LOS visibility updates

This was SVN commit r8613.
This commit is contained in:
Ykkrosh 2010-11-15 00:36:15 +00:00
parent bfbd47ffec
commit 4a1bc8e11a

View File

@ -876,25 +876,46 @@ private:
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
* amount is +1 or -1.
*/
inline void LosUpdateStripHelper(u8 owner, ssize_t i0, ssize_t i1, ssize_t j, int amount, std::vector<u16>& counts)
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
{
for (ssize_t i = i0; i <= i1; ++i)
{
ssize_t idx = j*m_TerrainVerticesPerSide + i;
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
for (i32 idx = idx0; idx <= idx1; ++idx)
{
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
if (counts[idx] == 0 && amount > 0)
if (counts[idx] == 0)
{
i32 i = i0 + idx - idx0;
if (!LosIsOffWorld(i, j))
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
counts[idx] += amount;
counts[idx] += 1;
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
{
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
for (i32 idx = idx0; idx <= idx1; ++idx)
{
counts[idx] -= 1;
// Decreasing from non-zero to zero - move from visible+explored to explored
if (counts[idx] == 0 && amount < 0)
if (counts[idx] == 0)
{
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
@ -903,10 +924,12 @@ private:
}
/**
* Update the LOS state of tiles within a given circular range.
* Update the LOS state of tiles within a given circular range,
* either adding or removing visibility depending on the template parameter.
* Assumes owner is in the valid range.
*/
inline void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos, int amount)
template<bool adding>
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
@ -923,6 +946,10 @@ private:
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
// to get smoother behaviour as a unit moves rather than jumping a whole tile
// at once.
// To avoid the cost of sqrt when computing the outline of the circle,
// we loop from the bottom to the top and estimate the width of the current
// strip based on the previous strip, then adjust each end of the strip
// inwards or outwards until it's the widest that still falls within the circle.
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
// (so that we never render the sharp edge of the map)
@ -931,21 +958,153 @@ private:
i32 j0clamp = std::max(j0, 1);
i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
entity_pos_t xscale = pos.X / (int)CELL_SIZE;
entity_pos_t yscale = pos.Y / (int)CELL_SIZE;
entity_pos_t rsquared = (visionRange / (int)CELL_SIZE).Square();
// Translate world coordinates into fractional tile-space coordinates
entity_pos_t x = pos.X / (int)CELL_SIZE;
entity_pos_t y = pos.Y / (int)CELL_SIZE;
entity_pos_t r = visionRange / (int)CELL_SIZE;
entity_pos_t r2 = r.Square();
// Compute the integers on either side of x
i32 xfloor = x.ToInt_RoundToNegInfinity();
i32 xceil = x.ToInt_RoundToInfinity();
// Initialise the strip (i0, i1) to a rough guess
i32 i0 = xfloor;
i32 i1 = xceil;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
// Compute values such that (i - x)^2 + (j - y)^2 <= r^2
// (TODO: is this sqrt slow? can we optimise it?)
entity_pos_t di = (rsquared - (entity_pos_t::FromInt(j) - yscale).Square()).Sqrt();
i32 i0 = (xscale - di).ToInt_RoundToInfinity();
i32 i1 = (xscale + di).ToInt_RoundToNegInfinity();
// Adjust i0 and i1 to be the outermost values that don't exceed
// the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
// When moving the points inwards, clamp them to xceil+1 or xfloor-1
// so they don't accidentally shoot off in the wrong direction forever.
entity_pos_t dy = entity_pos_t::FromInt(j) - y;
entity_pos_t dy2 = dy.Square();
while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
--i0;
while (i0 <= xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
++i0;
while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
++i1;
while (i1 >= xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
--i1;
// Clamp the strip to exclude the 1-tile border,
// then add or remove the strip as requested
i32 i0clamp = std::max(i0, 1);
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
LosUpdateStripHelper(owner, i0clamp, i1clamp, j, amount, counts);
if (adding)
LosAddStripHelper(owner, i0clamp, i1clamp, j, counts.data());
else
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts.data());
}
}
/**
* Update the LOS state of tiles within a given circular range,
* by removing visibility around the 'from' position
* and then adding visibility around the 'to' position.
*/
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelperIncremental");
std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
// See comments in LosUpdateHelper.
// This does exactly the same, except computing the strips for
// both circles simultaneously.
// (The idea is that the circles will be heavily overlapping,
// so we can compute the difference between the removed/added strips
// and only have to touch tiles that have a net change.)
i32 j0_from = ((from.Y - visionRange)/(int)CELL_SIZE).ToInt_RoundToInfinity();
i32 j1_from = ((from.Y + visionRange)/(int)CELL_SIZE).ToInt_RoundToNegInfinity();
i32 j0_to = ((to.Y - visionRange)/(int)CELL_SIZE).ToInt_RoundToInfinity();
i32 j1_to = ((to.Y + visionRange)/(int)CELL_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
entity_pos_t x_from = from.X / (int)CELL_SIZE;
entity_pos_t y_from = from.Y / (int)CELL_SIZE;
entity_pos_t x_to = to.X / (int)CELL_SIZE;
entity_pos_t y_to = to.Y / (int)CELL_SIZE;
entity_pos_t r = visionRange / (int)CELL_SIZE;
entity_pos_t r2 = r.Square();
i32 xfloor_from = x_from.ToInt_RoundToNegInfinity();
i32 xceil_from = x_from.ToInt_RoundToInfinity();
i32 xfloor_to = x_to.ToInt_RoundToNegInfinity();
i32 xceil_to = x_to.ToInt_RoundToInfinity();
i32 i0_from = xfloor_from;
i32 i1_from = xceil_from;
i32 i0_to = xfloor_to;
i32 i1_to = xceil_to;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
entity_pos_t dy2_from = dy_from.Square();
while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
--i0_from;
while (i0_from <= xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
++i0_from;
while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
++i1_from;
while (i1_from >= xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
--i1_from;
entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
entity_pos_t dy2_to = dy_to.Square();
while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
--i0_to;
while (i0_to <= xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
++i0_to;
while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
++i1_to;
while (i1_to >= xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
--i1_to;
// Check whether this strip moved at all
if (!(i0_to == i0_from && i1_to == i1_from))
{
i32 i0clamp_from = std::max(i0_from, 1);
i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
i32 i0clamp_to = std::max(i0_to, 1);
i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
// Check whether one strip is negative width,
// and we can just add/remove the entire other strip
if (i1clamp_from < i0clamp_from)
{
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts.data());
}
else if (i1clamp_to < i0clamp_to)
{
LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, counts.data());
}
else
{
// There are four possible regions of overlap between the two strips
// (remove before add, remove after add, add before remove, add after remove).
// Process each of the regions as its own strip.
// (If this produces negative-width strips then they'll just get ignored
// which is fine.)
LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, counts.data());
LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, counts.data());
LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, counts.data());
LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, counts.data());
}
}
}
}
@ -954,7 +1113,7 @@ private:
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper(owner, visionRange, pos, 1);
LosUpdateHelper<true>(owner, visionRange, pos);
}
void LosRemove(i8 owner, entity_pos_t visionRange, CFixedVector2D pos)
@ -962,7 +1121,7 @@ private:
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper(owner, visionRange, pos, -1);
LosUpdateHelper<false>(owner, visionRange, pos);
}
void LosMove(i8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
@ -970,9 +1129,19 @@ private:
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
// TODO: we could optimise this by only modifying tiles that changed
LosRemove(owner, visionRange, from);
LosAdd(owner, visionRange, to);
if ((from - to).CompareLength(visionRange) > 0)
{
// If it's a very large move, then simply remove and add to the new position
LosUpdateHelper<false>(owner, visionRange, from);
LosUpdateHelper<true>(owner, visionRange, to);
}
else
{
// Otherwise use the version optimised for mostly-overlapping circles
LosUpdateHelperIncremental(owner, visionRange, from, to);
}
}
};