1
0
forked from 0ad/0ad

Add a new spatial subdivision, based on an old patch by wraitii.

This subdivision is faster but less precise, so range queries get more
entities and are a bit slower (up to 1ms approx.), but the overall gain
on a simulation update is always positive and can reach 10ms per frame.

For now, this new subdivision is only used by the range manager,
integrating it in the obstruction manager might be sensible.

Refs #2430

This was SVN commit r16540.
This commit is contained in:
Nicolas Auvray 2015-04-14 21:33:43 +00:00
parent a0340efab8
commit 6aa99d762f
6 changed files with 263 additions and 29 deletions

View File

@ -470,9 +470,7 @@ public:
pos = cmpPosition->GetPreviousPosition2D();
else
pos = cmpPosition->GetPosition2D();
if (m_Type == STATIC)
out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
else if (m_Type == UNIT)
if (m_Type == UNIT)
out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0);
else
out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
@ -487,6 +485,14 @@ public:
return entity_pos_t::Zero();
}
virtual entity_pos_t GetSize()
{
if (m_Type == UNIT)
return m_Size0;
else
return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length();
}
virtual bool IsControlPersistent()
{
return m_ControlPersist;

View File

@ -32,7 +32,6 @@
#include "simulation2/components/ICmpVision.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Spatial.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
@ -172,10 +171,11 @@ static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutline
*/
struct EntityData
{
EntityData() : visibilities(0), retainInFog(0), owner(-1), inWorld(0), flags(1), scriptedVisibility(0) { }
EntityData() : visibilities(0), size(0), retainInFog(0), owner(-1), inWorld(0), flags(1), scriptedVisibility(0) { }
entity_pos_t x, z;
entity_pos_t visionRange;
u32 visibilities; // 2-bit visibility, per player
u32 size;
u8 retainInFog; // boolean
i8 owner;
u8 inWorld; // boolean
@ -183,7 +183,7 @@ struct EntityData
u8 scriptedVisibility; // boolean, see ComputeLosVisibility
};
cassert(sizeof(EntityData) == 24);
cassert(sizeof(EntityData) == 28);
/**
* Serialization helper template for Query
@ -236,6 +236,7 @@ struct SerializeEntityData
serialize.NumberFixed_Unbounded("z", value.z);
serialize.NumberFixed_Unbounded("vision", value.visionRange);
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
serialize.NumberU32_Unbounded("size", value.size);
serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
serialize.NumberI8_Unbounded("owner", value.owner);
serialize.NumberU8("in world", value.inWorld, 0, 1);
@ -314,7 +315,7 @@ public:
std::map<tag_t, Query> m_Queries;
EntityMap<EntityData> m_EntityData;
SpatialSubdivision m_Subdivision; // spatial index of m_EntityData
FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData
// LOS state:
static const player_id_t MAX_LOS_PLAYER_ID = 16;
@ -461,6 +462,11 @@ public:
if (cmpVisibility)
entdata.retainInFog = (cmpVisibility->GetRetainInFog() ? 1 : 0);
// Store the size
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
if (cmpObstruction)
entdata.size = cmpObstruction->GetSize().ToInt_RoundToInfinity();
// Remember this entity
m_EntityData.insert(ent, entdata);
break;
@ -482,7 +488,7 @@ public:
{
CFixedVector2D from(it->second.x, it->second.z);
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Move(ent, from, to);
m_Subdivision.Move(ent, from, to, it->second.size);
LosMove(it->second.owner, it->second.visionRange, from, to);
i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
@ -495,7 +501,7 @@ public:
else
{
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Add(ent, to);
m_Subdivision.Add(ent, to, it->second.size);
LosAdd(it->second.owner, it->second.visionRange, to);
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
}
@ -509,7 +515,7 @@ public:
if (it->second.inWorld)
{
CFixedVector2D from(it->second.x, it->second.z);
m_Subdivision.Remove(ent, from);
m_Subdivision.Remove(ent, from, it->second.size);
LosRemove(it->second.owner, it->second.visionRange, from);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
@ -559,7 +565,7 @@ public:
if (it->second.inWorld)
{
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
@ -640,7 +646,7 @@ public:
std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
std::vector<u32> oldStateRevealed = m_LosStateRevealed;
SpatialSubdivision oldSubdivision = m_Subdivision;
FastSpatialSubdivision oldSubdivision = m_Subdivision;
std::vector<std::set<entity_id_t> > oldLosTiles = m_LosTiles;
ResetDerivedData(true);
@ -671,9 +677,9 @@ public:
debug_warn(L"inconsistent los tiles");
}
SpatialSubdivision* GetSubdivision()
FastSpatialSubdivision* GetSubdivision()
{
return & m_Subdivision;
return &m_Subdivision;
}
// Reinitialise subdivisions and LOS data, based on entity data
@ -744,14 +750,12 @@ public:
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
{
// Use 8x8 tile subdivisions
// (TODO: find the optimal number instead of blindly guessing)
m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE));
m_Subdivision.Reset(x1, z1);
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (it->second.inWorld)
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z));
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z), it->second.size);
}
}
@ -1347,12 +1351,11 @@ public:
}
// render subdivision grid
float divSize = m_Subdivision.GetDivisionSize().ToFloat();
int width = m_Subdivision.GetWidth();
int height = m_Subdivision.GetHeight();
for (int x = 0; x < width; ++x)
float divSize = m_Subdivision.GetDivisionSize();
int size = m_Subdivision.GetWidth();
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < height; ++y)
for (int y = 0; y < size; ++y)
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = subdivColor;

View File

@ -52,6 +52,8 @@ public:
*/
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) = 0;
virtual entity_pos_t GetSize() = 0;
virtual entity_pos_t GetUnitRadius() = 0;
virtual bool IsControlPersistent() = 0;

View File

@ -77,7 +77,7 @@ public:
* Access the spatial subdivision kept by the range manager.
* @return pointer to spatial subdivision structure.
*/
virtual SpatialSubdivision* GetSubdivision() = 0;
virtual FastSpatialSubdivision* GetSubdivision() = 0;
/**
* Set the bounds of the world.

View File

@ -28,7 +28,6 @@
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpUnitRenderer.h"
#include "simulation2/helpers/Spatial.h"
#include "ps/CLogger.h"
#include "ps/Profiler2.h"

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -32,8 +32,6 @@
* Move/Remove are called with the same coordinates originally passed
* to Add (since this class doesn't remember which divisions an item
* occupies).
*
* (TODO: maybe an adaptive quadtree would be better than fixed sizes?)
*/
class SpatialSubdivision
{
@ -66,7 +64,6 @@ class SpatialSubdivision
uint32_t m_DivisionsW;
uint32_t m_DivisionsH;
friend struct SerializeSubDivisionGrid;
friend struct SerializeSpatialSubdivision;
public:
@ -346,4 +343,231 @@ struct SerializeSpatialSubdivision
}
};
/**
* A basic square subdivision scheme for finding entities in range
* More efficient than SpatialSubdivision, but a bit less precise
* (so the querier will get more entities to perform tests on).
*
* Items are stored in vectors in fixed-size divisions.
*
* Items have a size (min/max values of their axis-aligned bounding box).
* If that size is higher than a subdivision's size, they're stored in the "general" vector
* This means that if too many objects have a size that's big, it'll end up being slow
* We want subdivisions to be as small as possible yet contain as many items as possible.
*
* It is the caller's responsibility to ensure items are only added once, aren't removed
* unless they've been added, etc, and that Move/Remove are called with the same coordinates
* originally passed to Add (since this class doesn't remember which divisions an item
* occupies).
*
* TODO: If a unit size were to change, it would need to be updated (that doesn't happen for now)
*/
class FastSpatialSubdivision
{
private:
static const int SUBDIVISION_SIZE = 20; // bigger than most buildings and entities
std::vector<entity_id_t> m_OverSizedData;
std::vector<entity_id_t>* m_SpatialDivisionsData; // fixed size array of subdivisions
size_t m_ArrayWidth; // number of columns in m_SpatialDivisionsData
inline size_t Index(fixed position)
{
return (position / SUBDIVISION_SIZE).ToInt_RoundToZero();
}
inline size_t SubdivisionIdx(CFixedVector2D position)
{
return Index(position.X) + Index(position.Y)*m_ArrayWidth;
}
/**
* Efficiently erase from a vector by swapping with the last element and popping it.
* Returns true if the element was found and erased, else returns false.
*/
bool EraseFrom(std::vector<entity_id_t>& vector, entity_id_t item)
{
auto it = std::find(vector.begin(), vector.end(), item);
if (it == vector.end())
return false;
if ((int)vector.size() > 1)
*it = vector.back();
vector.pop_back();
return true;
}
public:
FastSpatialSubdivision() :
m_SpatialDivisionsData(NULL), m_ArrayWidth(0)
{
}
FastSpatialSubdivision(const FastSpatialSubdivision& other) :
m_SpatialDivisionsData(NULL), m_ArrayWidth(0)
{
Reset(other.m_ArrayWidth);
std::copy(&other.m_SpatialDivisionsData[0], &other.m_SpatialDivisionsData[m_ArrayWidth*m_ArrayWidth], m_SpatialDivisionsData);
}
~FastSpatialSubdivision()
{
if (m_SpatialDivisionsData)
delete[] m_SpatialDivisionsData;
}
void Reset(size_t arrayWidth)
{
if (m_SpatialDivisionsData)
SAFE_ARRAY_DELETE(m_SpatialDivisionsData);
m_ArrayWidth = arrayWidth;
m_SpatialDivisionsData = new std::vector<entity_id_t>[m_ArrayWidth*m_ArrayWidth];
m_OverSizedData.clear();
}
void Reset(fixed w, fixed h)
{
Reset(std::max(Index(w), Index(h)) + 1);
}
FastSpatialSubdivision& operator=(const FastSpatialSubdivision& other)
{
if (this != &other)
{
Reset(other.m_ArrayWidth);
std::copy(&other.m_SpatialDivisionsData[0], &other.m_SpatialDivisionsData[m_ArrayWidth*m_ArrayWidth], m_SpatialDivisionsData);
}
return *this;
}
bool operator==(const FastSpatialSubdivision& other)
{
if (m_ArrayWidth != other.m_ArrayWidth)
return false;
if (m_OverSizedData != other.m_OverSizedData)
return false;
for (size_t idx = 0; idx < m_ArrayWidth*m_ArrayWidth; ++idx)
if (m_SpatialDivisionsData[idx] != other.m_SpatialDivisionsData[idx])
return false;
return true;
}
inline bool operator!=(const FastSpatialSubdivision& rhs)
{
return !(*this == rhs);
}
/**
* Add an item.
*/
void Add(entity_id_t item, CFixedVector2D position, u32 size)
{
if (size > SUBDIVISION_SIZE)
{
if (std::find(m_OverSizedData.begin(), m_OverSizedData.end(), item) == m_OverSizedData.end())
m_OverSizedData.push_back(item);
}
else
{
std::vector<entity_id_t>& subdivision = m_SpatialDivisionsData[SubdivisionIdx(position)];
if (std::find(subdivision.begin(), subdivision.end(), item) == subdivision.end())
subdivision.push_back(item);
}
}
/**
* Remove an item.
* Position must be where we expect to find it, or we won't find it.
*/
void Remove(entity_id_t item, CFixedVector2D position, u32 size)
{
if (size > SUBDIVISION_SIZE)
EraseFrom(m_OverSizedData, item);
else
{
std::vector<entity_id_t>& subdivision = m_SpatialDivisionsData[SubdivisionIdx(position)];
EraseFrom(subdivision, item);
}
}
/**
* Equivalent to Remove() then Add(), but slightly faster.
* In particular for big objects nothing needs to be done.
*/
void Move(entity_id_t item, CFixedVector2D oldPosition, CFixedVector2D newPosition, u32 size)
{
if (size > SUBDIVISION_SIZE)
return;
if (SubdivisionIdx(newPosition) == SubdivisionIdx(oldPosition))
return;
std::vector<entity_id_t>& oldSubdivision = m_SpatialDivisionsData[SubdivisionIdx(oldPosition)];
if (EraseFrom(oldSubdivision, item))
{
std::vector<entity_id_t>& newSubdivision = m_SpatialDivisionsData[SubdivisionIdx(newPosition)];
newSubdivision.push_back(item);
}
}
/**
* Returns a sorted list of items that are either in the square or close to it.
* It's the responsibility of the querier to do proper distance checking.
*/
void GetInRange(std::vector<entity_id_t>& out, CFixedVector2D posMin, CFixedVector2D posMax)
{
size_t minX = Index(posMin.X);
size_t minY = Index(posMin.Y);
size_t maxX = Index(posMax.X) + 1;
size_t maxY = Index(posMax.Y) + 1;
// Now expand the subdivisions by one so we make sure we've got all elements potentially in range.
// Also make sure min >= 0 and max <= width
minX = minX > 0 ? minX-1 : 0;
minY = minY > 0 ? minY-1 : 0;
maxX = maxX < m_ArrayWidth ? maxX+1 : m_ArrayWidth;
maxY = maxY < m_ArrayWidth ? maxY+1 : m_ArrayWidth;
ENSURE(out.empty() && "GetInRange: out is not clean");
// Add oversized items, they can be anywhere
out.insert(out.end(), m_OverSizedData.begin(), m_OverSizedData.end());
for (size_t Y = minY; Y < maxY; ++Y)
{
for (size_t X = minX; X < maxX; ++X)
{
std::vector<entity_id_t>& subdivision = m_SpatialDivisionsData[X + Y*m_ArrayWidth];
if (!subdivision.empty())
out.insert(out.end(), subdivision.begin(), subdivision.end());
}
}
std::sort(out.begin(), out.end());
}
/**
* Returns a sorted list of items that are either in the circle or close to it.
* It's the responsibility of the querier to do proper distance checking.
*/
void GetNear(std::vector<entity_id_t>& out, CFixedVector2D pos, entity_pos_t range)
{
// Because the subdivision size is rather big wrt typical ranges,
// this square over-approximation is hopefully not too bad.
CFixedVector2D r(range, range);
GetInRange(out, pos - r, pos + r);
}
size_t GetDivisionSize() const
{
return SUBDIVISION_SIZE;
}
size_t GetWidth() const
{
return m_ArrayWidth;
}
};
#endif // INCLUDED_SPATIAL