1
0
forked from 0ad/0ad

Fix bug in incremental LOS computation

This was SVN commit r10446.
This commit is contained in:
Ykkrosh 2011-10-28 13:15:33 +00:00
parent 69feade908
commit 959b5a505c
5 changed files with 242 additions and 12 deletions

View File

@ -117,6 +117,7 @@ public:
CFixed() : value(0) { }
static CFixed Zero() { return CFixed(0); }
static CFixed Epsilon() { return CFixed(1); }
static CFixed Pi();
T GetInternalValue() const { return value; }

View File

@ -36,6 +36,8 @@
#include "ps/Profile.h"
#include "renderer/Scene.h"
#define DEBUG_RANGE_MANAGER_BOUNDS 0
/**
* Representation of a range query.
*/
@ -396,6 +398,10 @@ public:
if (it->second.inWorld)
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
// This will be called after Ownership's OnDestroy, so ownership will be set
// to -1 already and we don't have to do a LosRemove here
ENSURE(it->second.owner == -1);
m_EntityData.erase(it);
break;
@ -427,6 +433,45 @@ public:
ResetDerivedData(false);
}
virtual void Verify()
{
// Ignore if map not initialised yet
if (m_WorldX1.IsZero())
return;
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
// does not affect the incrementally-computed state
std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
std::vector<u32> oldStateRevealed = m_LosStateRevealed;
SpatialSubdivision<entity_id_t> oldSubdivision = m_Subdivision;
ResetDerivedData(true);
if (oldPlayerCounts != m_LosPlayerCounts)
{
for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
{
debug_printf(L"%d: ", i);
for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
debug_printf(L"%d ", oldPlayerCounts[i][j]);
debug_printf(L"\n");
}
for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
{
debug_printf(L"%d: ", i);
for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
debug_printf(L"%d ", m_LosPlayerCounts[i][j]);
debug_printf(L"\n");
}
debug_warn(L"inconsistent player counts");
}
if (oldStateRevealed != m_LosStateRevealed)
debug_warn(L"inconsistent revealed");
if (oldSubdivision != m_Subdivision)
debug_warn(L"inconsistent subdivs");
}
// Reinitialise subdivisions and LOS data, based on entity data
void ResetDerivedData(bool skipLosState)
{
@ -1055,8 +1100,8 @@ public:
entity_pos_t r2 = r.Square();
// Compute the integers on either side of x
i32 xfloor = x.ToInt_RoundToNegInfinity();
i32 xceil = x.ToInt_RoundToInfinity();
i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
// Initialise the strip (i0, i1) to a rough guess
i32 i0 = xfloor;
@ -1073,13 +1118,23 @@ public:
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)
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)
while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
--i1;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0 <= i1)
{
ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
}
ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
#endif
// Clamp the strip to exclude the 1-tile border,
// then add or remove the strip as requested
i32 i0clamp = std::max(i0, 1);
@ -1132,10 +1187,10 @@ public:
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 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 i0_from = xfloor_from;
i32 i1_from = xceil_from;
@ -1148,24 +1203,41 @@ public:
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)
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)
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)
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)
while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
--i1_to;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0_from <= i1_from)
{
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
}
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
if (i0_to <= i1_to)
{
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
}
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
#endif
// Check whether this strip moved at all
if (!(i0_to == i0_from && i1_to == i1_from))
{

View File

@ -295,6 +295,12 @@ public:
*/
virtual i32 GetPercentMapExplored(player_id_t player) = 0;
/**
* Perform some internal consistency checks for testing/debugging.
*/
virtual void Verify() = 0;
DECLARE_INTERFACE_TYPE(RangeManager)
};

View File

@ -0,0 +1,122 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpVision.h"
#include "maths/Random.h"
#include <boost/random/uniform_real.hpp>
class MockVision : public ICmpVision
{
public:
DEFAULT_MOCK_COMPONENT()
virtual entity_pos_t GetRange() { return entity_pos_t::FromInt(66); }
virtual bool GetRetainInFog() { return false; }
virtual bool GetAlwaysVisible() { return false; }
};
class MockPosition : public ICmpPosition
{
public:
DEFAULT_MOCK_COMPONENT()
virtual bool IsInWorld() { return true; }
virtual void MoveOutOfWorld() { }
virtual void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }
virtual void JumpTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }
virtual void SetHeightOffset(entity_pos_t UNUSED(dy)) { }
virtual entity_pos_t GetHeightOffset() { return entity_pos_t::Zero(); }
virtual void SetHeightFixed(entity_pos_t UNUSED(y)) { }
virtual bool IsFloating() { return false; }
virtual CFixedVector3D GetPosition() { return CFixedVector3D(); }
virtual CFixedVector2D GetPosition2D() { return CFixedVector2D(); }
virtual void TurnTo(entity_angle_t UNUSED(y)) { }
virtual void SetYRotation(entity_angle_t UNUSED(y)) { }
virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { }
virtual CFixedVector3D GetRotation() { return CFixedVector3D(); }
virtual fixed GetDistanceTravelled() { return fixed::Zero(); }
virtual void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) { x = z = rotY = 0; }
virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset), bool UNUSED(forceFloating)) { return CMatrix3D(); }
};
class TestCmpRangeManager : public CxxTest::TestSuite
{
public:
void setUp()
{
CXeromyces::Startup();
}
void tearDown()
{
CXeromyces::Terminate();
}
void test_basic()
{
ComponentTestHelper test;
ICmpRangeManager* cmp = test.Add<ICmpRangeManager>(CID_RangeManager, "");
MockVision vision;
test.AddMock(100, IID_Vision, vision);
MockPosition position;
test.AddMock(100, IID_Position, position);
// This tests that the incremental computation produces the correct result
// in various edge cases
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/CELL_SIZE + 1);
cmp->Verify();
cmp->HandleMessage(CMessageCreate(100), false);
cmp->Verify();
cmp->HandleMessage(CMessageOwnershipChanged(100, -1, 1), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromDouble(257.95), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromInt(253), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_angle_t::Zero()), false);
cmp->Verify();
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_angle_t::Zero()), false);
cmp->Verify();
WELL512 rng;
for (size_t i = 0; i < 1024; ++i)
{
double x = boost::uniform_real<>(0.0, 512.0)(rng);
double z = boost::uniform_real<>(0.0, 512.0)(rng);
cmp->HandleMessage(CMessagePositionChanged(100, true, entity_pos_t::FromDouble(x), entity_pos_t::FromDouble(z), entity_angle_t::Zero()), false);
cmp->Verify();
}
}
};

View File

@ -43,6 +43,35 @@ public:
{
}
/**
* Equivalence test (ignoring order of items within each subdivision)
*/
bool operator==(const SpatialSubdivision& rhs)
{
if (m_DivisionSize != rhs.m_DivisionSize || m_DivisionsW != rhs.m_DivisionsW || m_DivisionsH != rhs.m_DivisionsH)
return false;
for (u32 j = 0; j < m_DivisionsH; ++j)
{
for (u32 i = 0; i < m_DivisionsW; ++i)
{
std::vector<T> div1 = m_Divisions.at(i + j*m_DivisionsW);
std::vector<T> div2 = rhs.m_Divisions.at(i + j*m_DivisionsW);
std::sort(div1.begin(), div1.end());
std::sort(div2.begin(), div2.end());
if (div1 != div2)
return false;
}
}
return true;
}
bool operator!=(const SpatialSubdivision& rhs)
{
return !(*this == rhs);
}
void Reset(entity_pos_t maxX, entity_pos_t maxZ, entity_pos_t divisionSize)
{
m_DivisionSize = divisionSize;