Fix bug in incremental LOS computation
This was SVN commit r10446.
This commit is contained in:
parent
69feade908
commit
959b5a505c
@ -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; }
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
122
source/simulation2/components/tests/test_RangeManager.h
Normal file
122
source/simulation2/components/tests/test_RangeManager.h
Normal 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();
|
||||
}
|
||||
}
|
||||
};
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user