1
0
forked from 0ad/0ad

Refactor the grid update code. Should give a significant performance boost to the simulation update.

Also fixes some bad code that could lead to hidden bugs.

Fixes #3296, thanks elexis for testing ;)

This was SVN commit r16764.
This commit is contained in:
Nicolas Auvray 2015-06-14 19:22:07 +00:00
parent 0e7f0f861b
commit 473b282265
10 changed files with 248 additions and 177 deletions

View File

@ -511,7 +511,7 @@ public:
return true; return true;
} }
void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState, void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState,
const Grid<u16>& passabilityMap, bool passabilityMapDirty, const Grid<u8>* dirtinessGrid, bool passabilityMapEntirelyDirty, const Grid<u16>& passabilityMap, const GridUpdateInformation& dirtinessInformations,
const Grid<u8>& territoryMap, bool territoryMapDirty, const Grid<u8>& territoryMap, bool territoryMapDirty,
std::map<std::string, pass_class_t> passClassMasks) std::map<std::string, pass_class_t> passClassMasks)
{ {
@ -520,13 +520,13 @@ public:
m_GameState = gameState; m_GameState = gameState;
JSContext* cx = m_ScriptInterface->GetContext(); JSContext* cx = m_ScriptInterface->GetContext();
if (passabilityMapDirty) if (dirtinessInformations.dirty)
{ {
m_PassabilityMap = passabilityMap; m_PassabilityMap = passabilityMap;
if (passabilityMapEntirelyDirty) if (dirtinessInformations.globallyDirty)
m_LongPathfinder.Reload(passClassMasks, &m_PassabilityMap); m_LongPathfinder.Reload(passClassMasks, &m_PassabilityMap);
else else
m_LongPathfinder.Update(&m_PassabilityMap, dirtinessGrid); m_LongPathfinder.Update(&m_PassabilityMap, dirtinessInformations.dirtinessGrid);
ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap); ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap);
} }
@ -1035,10 +1035,7 @@ public:
if (cmpPathfinder) if (cmpPathfinder)
passabilityMap = &cmpPathfinder->GetPassabilityGrid(); passabilityMap = &cmpPathfinder->GetPassabilityGrid();
Grid<u8> dummyDirtinessGrid; GridUpdateInformation dirtinessInformations = cmpPathfinder->GetDirtinessData();
const Grid<u8>* dirtinessGrid = &dummyDirtinessGrid;
bool passabilityMapEntirelyDirty = false;
bool passabilityMapDirty = cmpPathfinder->GetDirtinessData(dummyDirtinessGrid, passabilityMapEntirelyDirty);
// Get the territory data // Get the territory data
// Since getting the territory grid can trigger a recalculation, we check NeedUpdate first // Since getting the territory grid can trigger a recalculation, we check NeedUpdate first
@ -1058,7 +1055,7 @@ public:
passClassMasks = cmpPathfinder->GetPassabilityClasses(); passClassMasks = cmpPathfinder->GetPassabilityClasses();
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state), m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state),
*passabilityMap, passabilityMapDirty, dirtinessGrid, passabilityMapEntirelyDirty, *passabilityMap, dirtinessInformations,
*territoryMap, territoryMapDirty, *territoryMap, territoryMapDirty,
passClassMasks); passClassMasks);

View File

@ -137,6 +137,8 @@ public:
u32 m_UnitShapeNext; // next allocated id u32 m_UnitShapeNext; // next allocated id
u32 m_StaticShapeNext; u32 m_StaticShapeNext;
entity_pos_t m_MaxClearance;
bool m_PassabilityCircular; bool m_PassabilityCircular;
entity_pos_t m_WorldX0; entity_pos_t m_WorldX0;
@ -157,9 +159,9 @@ public:
m_UnitShapeNext = 1; m_UnitShapeNext = 1;
m_StaticShapeNext = 1; m_StaticShapeNext = 1;
m_Dirty = true; m_UpdateInformations.dirty = true;
m_NeedsGlobalUpdate = true; m_UpdateInformations.globallyDirty = true;
m_DirtinessGrid = NULL; m_UpdateInformations.globalRecompute = true;
m_PassabilityCircular = false; m_PassabilityCircular = false;
@ -172,7 +174,6 @@ public:
virtual void Deinit() virtual void Deinit()
{ {
SAFE_DELETE(m_DirtinessGrid);
} }
template<typename S> template<typename S>
@ -234,14 +235,16 @@ public:
ENSURE(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet ENSURE(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(x1, z1); ResetSubdivisions(x1, z1);
SAFE_DELETE(m_DirtinessGrid);
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
if (!cmpTerrain) if (!cmpTerrain)
return; return;
u16 tiles = cmpTerrain->GetTilesPerSide(); u16 tiles = cmpTerrain->GetTilesPerSide();
m_DirtinessGrid = new Grid<u8>(tiles*Pathfinding::NAVCELLS_PER_TILE, tiles*Pathfinding::NAVCELLS_PER_TILE); m_UpdateInformations.dirtinessGrid = Grid<u8>(tiles*Pathfinding::NAVCELLS_PER_TILE, tiles*Pathfinding::NAVCELLS_PER_TILE);
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
if (cmpPathfinder)
m_MaxClearance = cmpPathfinder->GetMaximumClearance();
} }
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1) void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
@ -271,10 +274,11 @@ public:
UnitShape shape = { ent, x, z, r, clearance, flags, group }; UnitShape shape = { ent, x, z, r, clearance, flags, group };
u32 id = m_UnitShapeNext++; u32 id = m_UnitShapeNext++;
m_UnitShapes[id] = shape; m_UnitShapes[id] = shape;
MakeDirtyUnit(flags, id);
m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r)); m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r));
MakeDirtyUnit(flags, id, shape);
return UNIT_INDEX_TO_TAG(id); return UNIT_INDEX_TO_TAG(id);
} }
@ -288,12 +292,13 @@ public:
StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 }; StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 };
u32 id = m_StaticShapeNext++; u32 id = m_StaticShapeNext++;
m_StaticShapes[id] = shape; m_StaticShapes[id] = shape;
MakeDirtyStatic(flags, id);
CFixedVector2D center(x, z); CFixedVector2D center(x, z);
CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2)); CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2));
m_StaticSubdivision.Add(id, center - bbHalfSize, center + bbHalfSize); m_StaticSubdivision.Add(id, center - bbHalfSize, center + bbHalfSize);
MakeDirtyStatic(flags, id, shape);
return STATIC_INDEX_TO_TAG(id); return STATIC_INDEX_TO_TAG(id);
} }
@ -324,6 +329,8 @@ public:
{ {
UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)]; UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)];
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the old shape region
m_UnitSubdivision.Move(TAG_TO_INDEX(tag), m_UnitSubdivision.Move(TAG_TO_INDEX(tag),
CFixedVector2D(shape.x - shape.r, shape.z - shape.r), CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
CFixedVector2D(shape.x + shape.r, shape.z + shape.r), CFixedVector2D(shape.x + shape.r, shape.z + shape.r),
@ -333,7 +340,7 @@ public:
shape.x = x; shape.x = x;
shape.z = z; shape.z = z;
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag)); MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the new shape region
} }
else else
{ {
@ -344,6 +351,8 @@ public:
StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)];
MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the old shape region
CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh)); CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh));
m_StaticSubdivision.Move(TAG_TO_INDEX(tag), m_StaticSubdivision.Move(TAG_TO_INDEX(tag),
@ -357,7 +366,7 @@ public:
shape.u = u; shape.u = u;
shape.v = v; shape.v = v;
MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag)); MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the new shape region
} }
} }
@ -411,7 +420,8 @@ public:
CFixedVector2D(shape.x - shape.r, shape.z - shape.r), CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
CFixedVector2D(shape.x + shape.r, shape.z + shape.r)); CFixedVector2D(shape.x + shape.r, shape.z + shape.r));
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag)); MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape);
m_UnitShapes.erase(TAG_TO_INDEX(tag)); m_UnitShapes.erase(TAG_TO_INDEX(tag));
} }
else else
@ -422,7 +432,8 @@ public:
CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize); m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize);
MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag)); MakeDirtyStatic(shape.flags, TAG_TO_INDEX(tag), shape);
m_StaticShapes.erase(TAG_TO_INDEX(tag)); m_StaticShapes.erase(TAG_TO_INDEX(tag));
} }
} }
@ -451,7 +462,7 @@ public:
virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector<entity_id_t>* out); virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector<entity_id_t>* out);
virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector<entity_id_t>* out); virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector<entity_id_t>* out);
virtual void Rasterize(Grid<u16>& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask); virtual void Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate);
virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares); virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares);
virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter); virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter);
@ -479,29 +490,21 @@ public:
void RenderSubmit(SceneCollector& collector); void RenderSubmit(SceneCollector& collector);
virtual bool GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded) virtual void UpdateInformations(GridUpdateInformation& informations)
{ {
if (!m_Dirty || !m_DirtinessGrid) // If the pathfinder wants to perform a full update, don't change that.
{ if (m_UpdateInformations.dirty && !informations.globalRecompute)
globalUpdateNeeded = false; informations = m_UpdateInformations;
return false;
}
dirtinessGrid = *m_DirtinessGrid; m_UpdateInformations.Clean();
globalUpdateNeeded = m_NeedsGlobalUpdate;
m_Dirty = false;
m_NeedsGlobalUpdate = false;
m_DirtinessGrid->reset();
return true;
} }
private: private:
// Dynamic updates for the long-range pathfinder // Dynamic updates for the long-range pathfinder
bool m_Dirty; GridUpdateInformation m_UpdateInformations;
bool m_NeedsGlobalUpdate; // These vectors might contain shapes that were deleted
Grid<u8>* m_DirtinessGrid; std::vector<u32> m_DirtyStaticShapes;
std::vector<u32> m_DirtyUnitShapes;
/** /**
* Mark all previous Rasterize()d grids as dirty, and the debug display. * Mark all previous Rasterize()d grids as dirty, and the debug display.
@ -509,8 +512,11 @@ private:
*/ */
void MakeDirtyAll() void MakeDirtyAll()
{ {
m_Dirty = true; m_UpdateInformations.dirty = true;
m_NeedsGlobalUpdate = true; m_UpdateInformations.globallyDirty = true;
m_UpdateInformations.globalRecompute = true;
m_UpdateInformations.dirtinessGrid.reset();
m_DebugOverlayDirty = true; m_DebugOverlayDirty = true;
} }
@ -525,52 +531,92 @@ private:
inline void MarkDirtinessGrid(const entity_pos_t& x, const entity_pos_t& z, const entity_pos_t& r) inline void MarkDirtinessGrid(const entity_pos_t& x, const entity_pos_t& z, const entity_pos_t& r)
{ {
if (!m_DirtinessGrid) MarkDirtinessGrid(x, z, CFixedVector2D(r, r));
return; }
inline void MarkDirtinessGrid(const entity_pos_t& x, const entity_pos_t& z, const CFixedVector2D& hbox)
{
ENSURE(m_UpdateInformations.dirtinessGrid.m_W != 0 && m_UpdateInformations.dirtinessGrid.m_H != 0);
u16 j0, j1, i0, i1; u16 j0, j1, i0, i1;
Pathfinding::NearestNavcell(x - r, z - r, i0, j0, m_DirtinessGrid->m_W, m_DirtinessGrid->m_H); Pathfinding::NearestNavcell(x - hbox.X, z - hbox.Y, i0, j0, m_UpdateInformations.dirtinessGrid.m_W, m_UpdateInformations.dirtinessGrid.m_H);
Pathfinding::NearestNavcell(x + r, z + r, i1, j1, m_DirtinessGrid->m_W, m_DirtinessGrid->m_H); Pathfinding::NearestNavcell(x + hbox.X, z + hbox.Y, i1, j1, m_UpdateInformations.dirtinessGrid.m_W, m_UpdateInformations.dirtinessGrid.m_H);
for (int j = j0; j < j1; ++j) for (int j = j0; j < j1; ++j)
for (int i = i0; i < i1; ++i) for (int i = i0; i < i1; ++i)
m_DirtinessGrid->set(i, j, 1); m_UpdateInformations.dirtinessGrid.set(i, j, 1);
} }
/** /**
* Mark all previous Rasterize()d grids as dirty, if they depend on this shape. * Mark all previous Rasterize()d grids as dirty, if they depend on this shape.
* Call this when a static shape has changed. * Call this when a static shape has changed.
*/ */
void MakeDirtyStatic(flags_t flags, u32 index) void MakeDirtyStatic(flags_t flags, u32 index, const StaticShape& shape)
{ {
m_DebugOverlayDirty = true;
if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
{ {
m_Dirty = true; m_UpdateInformations.dirty = true;
auto it = m_StaticShapes.find(index); if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), index) == m_DirtyStaticShapes.end())
if (it != m_StaticShapes.end()) m_DirtyStaticShapes.push_back(index);
MarkDirtinessGrid(it->second.x, it->second.z, std::max(it->second.hw, it->second.hh));
// All shapes overlapping the updated part of the grid should be dirtied too
CFixedVector2D center(shape.x, shape.z);
CFixedVector2D expandedBox =
Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)) +
CFixedVector2D(m_MaxClearance, m_MaxClearance);
std::vector<u32> staticsNear;
m_StaticSubdivision.GetInRange(staticsNear, center - expandedBox, center + expandedBox);
for (u32& staticId : staticsNear)
if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), staticId) == m_DirtyStaticShapes.end())
m_DirtyStaticShapes.push_back(staticId);
std::vector<u32> unitsNear;
m_UnitSubdivision.GetInRange(unitsNear, center - expandedBox, center + expandedBox);
for (u32& unitId : unitsNear)
if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), unitId) == m_DirtyUnitShapes.end())
m_DirtyUnitShapes.push_back(unitId);
MarkDirtinessGrid(shape.x, shape.z, expandedBox);
} }
m_DebugOverlayDirty = true;
} }
/** /**
* Mark all previous Rasterize()d grids as dirty, if they depend on this shape. * Mark all previous Rasterize()d grids as dirty, if they depend on this shape.
* Call this when a unit shape has changed. * Call this when a unit shape has changed.
*/ */
void MakeDirtyUnit(flags_t flags, u32 index) void MakeDirtyUnit(flags_t flags, u32 index, const UnitShape& shape)
{ {
m_DebugOverlayDirty = true;
if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) if (flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
{ {
m_Dirty = true; m_UpdateInformations.dirty = true;
auto it = m_UnitShapes.find(index); if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), index) == m_DirtyUnitShapes.end())
if (it != m_UnitShapes.end()) m_DirtyUnitShapes.push_back(index);
MarkDirtinessGrid(it->second.x, it->second.z, it->second.r);
// All shapes overlapping the updated part of the grid should be dirtied too
CFixedVector2D center(shape.x, shape.z);
entity_pos_t extendedRadius = shape.r + m_MaxClearance;
std::vector<u32> staticsNear;
m_StaticSubdivision.GetNear(staticsNear, center, extendedRadius);
for (u32& staticId : staticsNear)
if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), staticId) == m_DirtyStaticShapes.end())
m_DirtyStaticShapes.push_back(staticId);
std::vector<u32> unitsNear;
m_UnitSubdivision.GetNear(staticsNear, center, extendedRadius);
for (u32& unitId : unitsNear)
if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), unitId) == m_DirtyUnitShapes.end())
m_DirtyUnitShapes.push_back(unitId);
MarkDirtinessGrid(shape.x, shape.z, extendedRadius);
} }
m_DebugOverlayDirty = true;
} }
/** /**
@ -763,61 +809,81 @@ bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter,
return false; // didn't collide, if we got this far return false; // didn't collide, if we got this far
} }
void CCmpObstructionManager::Rasterize(Grid<u16>& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) void CCmpObstructionManager::Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate)
{ {
PROFILE3("Rasterize"); PROFILE3("Rasterize");
// Since m_DirtyID is only updated for pathfinding/foundation blocking shapes, // The update informations are only updated when pathfinding/foundation blocking shapes are modified.
// NeedUpdate+Rasterize will only be accurate for that subset of shapes.
// (If we ever want to support rasterizing more shapes, we need to improve
// the dirty-detection system too.)
ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))); ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)));
// Cells are only marked as blocked if the whole cell is strictly inside the shape. // Cells are only marked as blocked if the whole cell is strictly inside the shape.
// (That ensures the shape's geometric border is always reachable.) // (That ensures the shape's geometric border is always reachable.)
// TODO: it might be nice to rasterize with rounded corners for large 'expand' values. // Add obstructions onto the grid, for any class with (possibly zero) clearance
std::map<entity_pos_t, u16> combinedMasks;
// (This could be implemented much more efficiently.) for (const PathfinderPassability& passability : passClasses)
for (auto& pair : m_StaticShapes)
{ {
const StaticShape& shape = pair.second; if (!passability.m_HasClearance)
if (!(shape.flags & requireMask))
continue; continue;
ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh }; auto it = combinedMasks.find(passability.m_Clearance);
SimRasterize::Spans spans; if (it == combinedMasks.end())
SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE); combinedMasks[passability.m_Clearance] = passability.m_Mask;
for (SimRasterize::Span& span : spans) else
it->second |= passability.m_Mask;
}
for (auto& maskPair : combinedMasks)
{
for (auto& pair : m_StaticShapes)
{ {
i16 j = span.j; if (!fullUpdate && std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), pair.first) == m_DirtyStaticShapes.end())
if (j >= 0 && j <= grid.m_H) continue;
const StaticShape& shape = pair.second;
if (!(shape.flags & requireMask))
continue;
// TODO: it might be nice to rasterize with rounded corners for large 'expand' values.
ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh };
SimRasterize::Spans spans;
SimRasterize::RasterizeRectWithClearance(spans, square, maskPair.first, Pathfinding::NAVCELL_SIZE);
for (SimRasterize::Span& span : spans)
{ {
i16 i0 = std::max(span.i0, (i16)0); i16 j = span.j;
i16 i1 = std::min(span.i1, (i16)grid.m_W); if (j >= 0 && j <= grid.m_H)
for (i16 i = i0; i < i1; ++i) {
grid.set(i, j, grid.get(i, j) | setMask); i16 i0 = std::max(span.i0, (i16)0);
i16 i1 = std::min(span.i1, (i16)grid.m_W);
for (i16 i = i0; i < i1; ++i)
grid.set(i, j, grid.get(i, j) | maskPair.second);
}
} }
} }
for (auto& pair : m_UnitShapes)
{
if (!fullUpdate && std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), pair.first) == m_DirtyUnitShapes.end())
continue;
CFixedVector2D center(pair.second.x, pair.second.z);
if (!(pair.second.flags & requireMask))
continue;
entity_pos_t r = pair.second.r + maskPair.first;
u16 i0, j0, i1, j1;
Pathfinding::NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
Pathfinding::NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
for (u16 j = j0+1; j < j1; ++j)
for (u16 i = i0+1; i < i1; ++i)
grid.set(i, j, grid.get(i, j) | maskPair.second);
}
} }
for (auto& pair : m_UnitShapes) m_DirtyStaticShapes.clear();
{ m_DirtyUnitShapes.clear();
CFixedVector2D center(pair.second.x, pair.second.z);
if (!(pair.second.flags & requireMask))
continue;
entity_pos_t r = pair.second.r + expand;
u16 i0, j0, i1, j1;
Pathfinding::NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
Pathfinding::NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
for (u16 j = j0+1; j < j1; ++j)
for (u16 i = i0+1; i < i1; ++i)
grid.set(i, j, grid.get(i, j) | setMask);
}
} }
void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares)
@ -877,17 +943,11 @@ void CCmpObstructionManager::GetUnitsOnObstruction(const ObstructionSquare& squa
// units s.t. the RasterizeRectWithClearance of the building's shape with the // units s.t. the RasterizeRectWithClearance of the building's shape with the
// unit's clearance covers the navcell the unit is on. // unit's clearance covers the navcell the unit is on.
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return;
entity_pos_t maxClearance = cmpPathfinder->GetMaximumClearance();
std::vector<entity_id_t> unitShapes; std::vector<entity_id_t> unitShapes;
CFixedVector2D center(square.x, square.z); CFixedVector2D center(square.x, square.z);
CFixedVector2D expandedBox = CFixedVector2D expandedBox =
Geometry::GetHalfBoundingBox(square.u, square.v, CFixedVector2D(square.hw, square.hh)) + Geometry::GetHalfBoundingBox(square.u, square.v, CFixedVector2D(square.hw, square.hh)) +
CFixedVector2D(maxClearance, maxClearance); CFixedVector2D(m_MaxClearance, m_MaxClearance);
m_UnitSubdivision.GetInRange(unitShapes, center - expandedBox, center + expandedBox); m_UnitSubdivision.GetInRange(unitShapes, center - expandedBox, center + expandedBox);
std::map<entity_pos_t, SimRasterize::Spans> rasterizedRects; std::map<entity_pos_t, SimRasterize::Spans> rasterizedRects;

View File

@ -49,10 +49,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
{ {
m_MapSize = 0; m_MapSize = 0;
m_Grid = NULL; m_Grid = NULL;
m_BaseGrid = NULL; m_TerrainOnlyGrid = NULL;
m_ObstructionsDirty = true;
m_TerrainDirty = true;
m_NextAsyncTicket = 1; m_NextAsyncTicket = 1;
@ -104,7 +101,7 @@ void CCmpPathfinder::Deinit()
SetDebugOverlay(false); // cleans up memory SetDebugOverlay(false); // cleans up memory
SAFE_DELETE(m_Grid); SAFE_DELETE(m_Grid);
SAFE_DELETE(m_BaseGrid); SAFE_DELETE(m_TerrainOnlyGrid);
} }
struct SerializeLongRequest struct SerializeLongRequest
@ -170,17 +167,12 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
case MT_TerrainChanged: case MT_TerrainChanged:
case MT_WaterChanged: case MT_WaterChanged:
case MT_ObstructionMapShapeChanged: case MT_ObstructionMapShapeChanged:
{
// TODO PATHFINDER: we ought to only bother updating the dirtied region
m_TerrainDirty = true; m_TerrainDirty = true;
break; break;
}
case MT_TurnStart: case MT_TurnStart:
{
m_SameTurnMovesCount = 0; m_SameTurnMovesCount = 0;
break; break;
} }
}
} }
void CCmpPathfinder::RenderSubmit(SceneCollector& collector) void CCmpPathfinder::RenderSubmit(SceneCollector& collector)
@ -221,7 +213,9 @@ const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t
const Grid<u16>& CCmpPathfinder::GetPassabilityGrid() const Grid<u16>& CCmpPathfinder::GetPassabilityGrid()
{ {
UpdateGrid(); if (!m_Grid)
UpdateGrid();
return *m_Grid; return *m_Grid;
} }
@ -473,14 +467,13 @@ void CCmpPathfinder::UpdateGrid()
if (!cmpTerrain) if (!cmpTerrain)
return; // error return; // error
bool forceGlobalUpdate = false; m_ObstructionsDirty.Clean();
// If the terrain was resized then delete the old grid data // If the terrain was resized then delete the old grid data
if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide())
{ {
SAFE_DELETE(m_Grid); SAFE_DELETE(m_Grid);
SAFE_DELETE(m_BaseGrid); SAFE_DELETE(m_TerrainOnlyGrid);
m_TerrainDirty = true;
} }
// Initialise the terrain data when first needed // Initialise the terrain data when first needed
@ -488,25 +481,23 @@ void CCmpPathfinder::UpdateGrid()
{ {
m_MapSize = cmpTerrain->GetTilesPerSide(); m_MapSize = cmpTerrain->GetTilesPerSide();
m_Grid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_Grid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
m_BaseGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
forceGlobalUpdate = true;
m_ObstructionsDirty.dirty = true;
m_ObstructionsDirty.globallyDirty = true;
m_ObstructionsDirty.globalRecompute = true;
m_TerrainDirty = true; m_TerrainDirty = true;
} }
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (forceGlobalUpdate) cmpObstructionManager->UpdateInformations(m_ObstructionsDirty);
{
m_ObstructionsDirty = true;
m_ObstructionsGlobalUpdate = true;
}
else
m_ObstructionsDirty = cmpObstructionManager->GetDirtinessData(m_DirtinessGrid, m_ObstructionsGlobalUpdate);
if (!m_ObstructionsDirty && !m_TerrainDirty) if (!m_ObstructionsDirty.dirty && !m_TerrainDirty)
return; return;
// If the terrain has changed, recompute entirely m_Grid // If the terrain has changed, recompute m_Grid
// Else, use data from m_BaseGrid and add obstructions // Else, use data from m_TerrainOnlyGrid and add obstructions
if (m_TerrainDirty) if (m_TerrainDirty)
{ {
Grid<u16> shoreGrid = ComputeShoreGrid(); Grid<u16> shoreGrid = ComputeShoreGrid();
@ -576,39 +567,43 @@ void CCmpPathfinder::UpdateGrid()
} }
// Store the updated terrain-only grid // Store the updated terrain-only grid
*m_BaseGrid = *m_Grid; *m_TerrainOnlyGrid = *m_Grid;
m_TerrainDirty = false; m_TerrainDirty = false;
m_ObstructionsDirty.globalRecompute = true;
m_ObstructionsDirty.globallyDirty = true;
}
else if (m_ObstructionsDirty.globalRecompute)
{
ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);
memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData));
m_ObstructionsDirty.globallyDirty = true;
} }
else else
{ {
ENSURE(m_Grid->m_W == m_BaseGrid->m_W && m_Grid->m_H == m_BaseGrid->m_H); ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H);
memcpy(m_Grid->m_Data, m_BaseGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData)); ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);
for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i)
for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j)
if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1)
m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j));
} }
// TODO: tweak rasterizing conditions // Add obstructions onto the grid
// Add obstructions onto the grid, for any class with (possibly zero) clearance cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_ObstructionsDirty.globalRecompute);
for (PathfinderPassability& passability : m_PassClasses)
{
// TODO: if multiple classes have the same clearance, we should
// only bother running Rasterize once for them all
if (passability.m_HasClearance)
cmpObstructionManager->Rasterize(*m_Grid, passability.m_Clearance, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, passability.m_Mask);
}
if (m_ObstructionsGlobalUpdate) // Update the long-range pathfinder
if (m_ObstructionsDirty.globallyDirty)
m_LongPathfinder.Reload(m_PassClassMasks, m_Grid); m_LongPathfinder.Reload(m_PassClassMasks, m_Grid);
else else
m_LongPathfinder.Update(m_Grid, &m_DirtinessGrid); m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid);
} }
bool CCmpPathfinder::GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded) const GridUpdateInformation& CCmpPathfinder::GetDirtinessData() const
{ {
if (!m_ObstructionsDirty) return m_ObstructionsDirty;
return false;
dirtinessGrid = m_DirtinessGrid;
globalUpdateNeeded = m_ObstructionsGlobalUpdate;
return true;
} }
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
@ -778,8 +773,6 @@ ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I
// Test against terrain: // Test against terrain:
UpdateGrid();
ICmpObstructionManager::ObstructionSquare square; ICmpObstructionManager::ObstructionSquare square;
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id); CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square)) if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square))

View File

@ -106,13 +106,11 @@ public:
u16 m_MapSize; // tiles per side u16 m_MapSize; // tiles per side
Grid<NavcellData>* m_Grid; // terrain/passability information Grid<NavcellData>* m_Grid; // terrain/passability information
Grid<NavcellData>* m_BaseGrid; // same as m_Grid, but only with terrain, to avoid some recomputations Grid<NavcellData>* m_TerrainOnlyGrid; // same as m_Grid, but only with terrain, to avoid some recomputations
bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
// Update data, stored for the AI manager // Update data, used for clever updates and then stored for the AI manager
bool m_ObstructionsDirty; GridUpdateInformation m_ObstructionsDirty;
bool m_ObstructionsGlobalUpdate; bool m_TerrainDirty;
Grid<u8> m_DirtinessGrid;
// Interface to the long-range pathfinder. // Interface to the long-range pathfinder.
LongPathfinder m_LongPathfinder; LongPathfinder m_LongPathfinder;
@ -170,7 +168,7 @@ public:
virtual const Grid<u16>& GetPassabilityGrid(); virtual const Grid<u16>& GetPassabilityGrid();
virtual bool GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded); virtual const GridUpdateInformation& GetDirtinessData() const;
virtual Grid<u16> ComputeShoreGrid(bool expandOnWater = false); virtual Grid<u16> ComputeShoreGrid(bool expandOnWater = false);

View File

@ -20,8 +20,7 @@
#include "simulation2/system/Interface.h" #include "simulation2/system/Interface.h"
#include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Pathfinding.h"
#include "simulation2/helpers/Position.h"
#include "maths/FixedVector2D.h" #include "maths/FixedVector2D.h"
@ -217,19 +216,18 @@ public:
/** /**
* Convert the current set of shapes onto a navcell grid. * Convert the current set of shapes onto a navcell grid.
* Shapes are expanded by the clearance radius @p expand. * If @p fullUpdate is false, the function will only go through dirty shapes.
* Shapes are expanded by the @p passClasses clearances, by ORing their masks onto the @p grid.
* Only shapes with at least one of the flags from @p requireMask will be considered. * Only shapes with at least one of the flags from @p requireMask will be considered.
* @p setMask will be ORed onto the @p grid value for all navcells
* that are wholly enclosed by an expanded shape.
*/ */
virtual void Rasterize(Grid<u16>& grid, const entity_pos_t& expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) = 0; virtual void Rasterize(Grid<u16>& grid, const std::vector<PathfinderPassability>& passClasses, ICmpObstructionManager::flags_t requireMask, bool fullUpdate) = 0;
/** /**
* Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder
* to pass the information to other components if needed. (AIs, etc.) * to pass the information to other components if needed. (AIs, etc.)
* The return value is false if an update is unnecessary. * The return value is false if an update is unnecessary.
*/ */
virtual bool GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded) = 0; virtual void UpdateInformations(GridUpdateInformation& informations) = 0;
/** /**
* Standard representation for all types of shapes, for use with geometry processing code. * Standard representation for all types of shapes, for use with geometry processing code.

View File

@ -74,7 +74,7 @@ public:
* Passes the lazily-stored dirtiness data collected from * Passes the lazily-stored dirtiness data collected from
* the obstruction manager during the previous grid update. * the obstruction manager during the previous grid update.
*/ */
virtual bool GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded) = 0; virtual const GridUpdateInformation& GetDirtinessData() const = 0;
/** /**
* Get a grid representing the distance to the shore of the terrain tile. * Get a grid representing the distance to the shore of the terrain tile.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games. /* Copyright (C) 2015 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
@ -185,4 +185,29 @@ public:
size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
}; };
/**
* Structure holding grid dirtiness informations, for clever updates
*
* Note: globallyDirty should be used to know which parts of the grid have been changed after an update,
* whereas globalRecompute should be used during the update itself, to avoid unnecessary recomputations.
*/
struct GridUpdateInformation
{
bool dirty;
bool globallyDirty;
bool globalRecompute;
Grid<u8> dirtinessGrid;
/**
* Mark everything as clean
*/
void Clean()
{
dirty = false;
globallyDirty = false;
globalRecompute = false;
dirtinessGrid.reset();
}
};
#endif // INCLUDED_GRID #endif // INCLUDED_GRID

View File

@ -300,16 +300,16 @@ void HierarchicalPathfinder::Recompute(const std::map<std::string, pass_class_t>
} }
} }
void HierarchicalPathfinder::Update(Grid<NavcellData>* grid, const Grid<u8>* dirtinessGrid) void HierarchicalPathfinder::Update(Grid<NavcellData>* grid, const Grid<u8>& dirtinessGrid)
{ {
PROFILE3("Hierarchical Update"); PROFILE3("Hierarchical Update");
std::vector<std::pair<int, int> > processedChunks; std::vector<std::pair<int, int> > processedChunks;
for (int j = 0; j < dirtinessGrid->m_H; ++j) for (int j = 0; j < dirtinessGrid.m_H; ++j)
{ {
for (int i = 0; i < dirtinessGrid->m_W; ++i) for (int i = 0; i < dirtinessGrid.m_W; ++i)
{ {
if (!dirtinessGrid->get(i, j)) if (!dirtinessGrid.get(i, j))
continue; continue;
std::pair<int, int> chunkID(i / CHUNK_SIZE, j / CHUNK_SIZE); std::pair<int, int> chunkID(i / CHUNK_SIZE, j / CHUNK_SIZE);

View File

@ -80,7 +80,7 @@ public:
void SetDebugOverlay(bool enabled, const CSimContext* simContext); void SetDebugOverlay(bool enabled, const CSimContext* simContext);
void Recompute(const std::map<std::string, pass_class_t>& passClassMasks, Grid<NavcellData>* passabilityGrid); void Recompute(const std::map<std::string, pass_class_t>& passClassMasks, Grid<NavcellData>* passabilityGrid);
void Update(Grid<NavcellData>* grid, const Grid<u8>* dirtinessGrid); void Update(Grid<NavcellData>* grid, const Grid<u8>& dirtinessGrid);
RegionID Get(u16 i, u16 j, pass_class_t passClass); RegionID Get(u16 i, u16 j, pass_class_t passClass);

View File

@ -194,7 +194,7 @@ public:
m_PathfinderHier.Recompute(passClassMasks, passabilityGrid); m_PathfinderHier.Recompute(passClassMasks, passabilityGrid);
} }
void Update(Grid<NavcellData>* passabilityGrid, const Grid<u8>* dirtinessGrid) void Update(Grid<NavcellData>* passabilityGrid, const Grid<u8>& dirtinessGrid)
{ {
m_Grid = passabilityGrid; m_Grid = passabilityGrid;
ASSERT(passabilityGrid->m_H == passabilityGrid->m_W); ASSERT(passabilityGrid->m_H == passabilityGrid->m_W);