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:
parent
0e7f0f861b
commit
473b282265
@ -511,7 +511,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
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,
|
||||
std::map<std::string, pass_class_t> passClassMasks)
|
||||
{
|
||||
@ -520,13 +520,13 @@ public:
|
||||
m_GameState = gameState;
|
||||
JSContext* cx = m_ScriptInterface->GetContext();
|
||||
|
||||
if (passabilityMapDirty)
|
||||
if (dirtinessInformations.dirty)
|
||||
{
|
||||
m_PassabilityMap = passabilityMap;
|
||||
if (passabilityMapEntirelyDirty)
|
||||
if (dirtinessInformations.globallyDirty)
|
||||
m_LongPathfinder.Reload(passClassMasks, &m_PassabilityMap);
|
||||
else
|
||||
m_LongPathfinder.Update(&m_PassabilityMap, dirtinessGrid);
|
||||
m_LongPathfinder.Update(&m_PassabilityMap, dirtinessInformations.dirtinessGrid);
|
||||
ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap);
|
||||
}
|
||||
|
||||
@ -1035,10 +1035,7 @@ public:
|
||||
if (cmpPathfinder)
|
||||
passabilityMap = &cmpPathfinder->GetPassabilityGrid();
|
||||
|
||||
Grid<u8> dummyDirtinessGrid;
|
||||
const Grid<u8>* dirtinessGrid = &dummyDirtinessGrid;
|
||||
bool passabilityMapEntirelyDirty = false;
|
||||
bool passabilityMapDirty = cmpPathfinder->GetDirtinessData(dummyDirtinessGrid, passabilityMapEntirelyDirty);
|
||||
GridUpdateInformation dirtinessInformations = cmpPathfinder->GetDirtinessData();
|
||||
|
||||
// Get the territory data
|
||||
// Since getting the territory grid can trigger a recalculation, we check NeedUpdate first
|
||||
@ -1058,7 +1055,7 @@ public:
|
||||
passClassMasks = cmpPathfinder->GetPassabilityClasses();
|
||||
|
||||
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state),
|
||||
*passabilityMap, passabilityMapDirty, dirtinessGrid, passabilityMapEntirelyDirty,
|
||||
*passabilityMap, dirtinessInformations,
|
||||
*territoryMap, territoryMapDirty,
|
||||
passClassMasks);
|
||||
|
||||
|
@ -137,6 +137,8 @@ public:
|
||||
u32 m_UnitShapeNext; // next allocated id
|
||||
u32 m_StaticShapeNext;
|
||||
|
||||
entity_pos_t m_MaxClearance;
|
||||
|
||||
bool m_PassabilityCircular;
|
||||
|
||||
entity_pos_t m_WorldX0;
|
||||
@ -157,9 +159,9 @@ public:
|
||||
m_UnitShapeNext = 1;
|
||||
m_StaticShapeNext = 1;
|
||||
|
||||
m_Dirty = true;
|
||||
m_NeedsGlobalUpdate = true;
|
||||
m_DirtinessGrid = NULL;
|
||||
m_UpdateInformations.dirty = true;
|
||||
m_UpdateInformations.globallyDirty = true;
|
||||
m_UpdateInformations.globalRecompute = true;
|
||||
|
||||
m_PassabilityCircular = false;
|
||||
|
||||
@ -172,7 +174,6 @@ public:
|
||||
|
||||
virtual void Deinit()
|
||||
{
|
||||
SAFE_DELETE(m_DirtinessGrid);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
@ -234,14 +235,16 @@ public:
|
||||
ENSURE(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet
|
||||
ResetSubdivisions(x1, z1);
|
||||
|
||||
SAFE_DELETE(m_DirtinessGrid);
|
||||
|
||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
|
||||
if (!cmpTerrain)
|
||||
return;
|
||||
|
||||
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)
|
||||
@ -271,10 +274,11 @@ public:
|
||||
UnitShape shape = { ent, x, z, r, clearance, flags, group };
|
||||
u32 id = m_UnitShapeNext++;
|
||||
m_UnitShapes[id] = shape;
|
||||
MakeDirtyUnit(flags, id);
|
||||
|
||||
m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r));
|
||||
|
||||
MakeDirtyUnit(flags, id, shape);
|
||||
|
||||
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 };
|
||||
u32 id = m_StaticShapeNext++;
|
||||
m_StaticShapes[id] = shape;
|
||||
MakeDirtyStatic(flags, id);
|
||||
|
||||
CFixedVector2D center(x, z);
|
||||
CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2));
|
||||
m_StaticSubdivision.Add(id, center - bbHalfSize, center + bbHalfSize);
|
||||
|
||||
MakeDirtyStatic(flags, id, shape);
|
||||
|
||||
return STATIC_INDEX_TO_TAG(id);
|
||||
}
|
||||
|
||||
@ -324,6 +329,8 @@ public:
|
||||
{
|
||||
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),
|
||||
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.z = z;
|
||||
|
||||
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag));
|
||||
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape); // dirty the new shape region
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -344,6 +351,8 @@ public:
|
||||
|
||||
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 toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh));
|
||||
m_StaticSubdivision.Move(TAG_TO_INDEX(tag),
|
||||
@ -357,7 +366,7 @@ public:
|
||||
shape.u = u;
|
||||
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));
|
||||
|
||||
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag));
|
||||
MakeDirtyUnit(shape.flags, TAG_TO_INDEX(tag), shape);
|
||||
|
||||
m_UnitShapes.erase(TAG_TO_INDEX(tag));
|
||||
}
|
||||
else
|
||||
@ -422,7 +432,8 @@ public:
|
||||
CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -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 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 GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter);
|
||||
|
||||
@ -479,29 +490,21 @@ public:
|
||||
|
||||
void RenderSubmit(SceneCollector& collector);
|
||||
|
||||
virtual bool GetDirtinessData(Grid<u8>& dirtinessGrid, bool& globalUpdateNeeded)
|
||||
virtual void UpdateInformations(GridUpdateInformation& informations)
|
||||
{
|
||||
if (!m_Dirty || !m_DirtinessGrid)
|
||||
{
|
||||
globalUpdateNeeded = false;
|
||||
return false;
|
||||
}
|
||||
// If the pathfinder wants to perform a full update, don't change that.
|
||||
if (m_UpdateInformations.dirty && !informations.globalRecompute)
|
||||
informations = m_UpdateInformations;
|
||||
|
||||
dirtinessGrid = *m_DirtinessGrid;
|
||||
globalUpdateNeeded = m_NeedsGlobalUpdate;
|
||||
|
||||
m_Dirty = false;
|
||||
m_NeedsGlobalUpdate = false;
|
||||
m_DirtinessGrid->reset();
|
||||
|
||||
return true;
|
||||
m_UpdateInformations.Clean();
|
||||
}
|
||||
|
||||
private:
|
||||
// Dynamic updates for the long-range pathfinder
|
||||
bool m_Dirty;
|
||||
bool m_NeedsGlobalUpdate;
|
||||
Grid<u8>* m_DirtinessGrid;
|
||||
GridUpdateInformation m_UpdateInformations;
|
||||
// These vectors might contain shapes that were deleted
|
||||
std::vector<u32> m_DirtyStaticShapes;
|
||||
std::vector<u32> m_DirtyUnitShapes;
|
||||
|
||||
/**
|
||||
* Mark all previous Rasterize()d grids as dirty, and the debug display.
|
||||
@ -509,8 +512,11 @@ private:
|
||||
*/
|
||||
void MakeDirtyAll()
|
||||
{
|
||||
m_Dirty = true;
|
||||
m_NeedsGlobalUpdate = true;
|
||||
m_UpdateInformations.dirty = true;
|
||||
m_UpdateInformations.globallyDirty = true;
|
||||
m_UpdateInformations.globalRecompute = true;
|
||||
m_UpdateInformations.dirtinessGrid.reset();
|
||||
|
||||
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)
|
||||
{
|
||||
if (!m_DirtinessGrid)
|
||||
return;
|
||||
MarkDirtinessGrid(x, z, CFixedVector2D(r, r));
|
||||
}
|
||||
|
||||
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;
|
||||
Pathfinding::NearestNavcell(x - r, z - r, i0, j0, m_DirtinessGrid->m_W, m_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, i0, j0, m_UpdateInformations.dirtinessGrid.m_W, m_UpdateInformations.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 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.
|
||||
* 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))
|
||||
{
|
||||
m_Dirty = true;
|
||||
m_UpdateInformations.dirty = true;
|
||||
|
||||
auto it = m_StaticShapes.find(index);
|
||||
if (it != m_StaticShapes.end())
|
||||
MarkDirtinessGrid(it->second.x, it->second.z, std::max(it->second.hw, it->second.hh));
|
||||
if (std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), index) == m_DirtyStaticShapes.end())
|
||||
m_DirtyStaticShapes.push_back(index);
|
||||
|
||||
// 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.
|
||||
* 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))
|
||||
{
|
||||
m_Dirty = true;
|
||||
m_UpdateInformations.dirty = true;
|
||||
|
||||
auto it = m_UnitShapes.find(index);
|
||||
if (it != m_UnitShapes.end())
|
||||
MarkDirtinessGrid(it->second.x, it->second.z, it->second.r);
|
||||
if (std::find(m_DirtyUnitShapes.begin(), m_DirtyUnitShapes.end(), index) == m_DirtyUnitShapes.end())
|
||||
m_DirtyUnitShapes.push_back(index);
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// Since m_DirtyID is only updated for pathfinding/foundation blocking shapes,
|
||||
// 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.)
|
||||
// The update informations are only updated when pathfinding/foundation blocking shapes are modified.
|
||||
ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)));
|
||||
|
||||
// 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.)
|
||||
|
||||
// TODO: it might be nice to rasterize with rounded corners for large 'expand' values.
|
||||
|
||||
// (This could be implemented much more efficiently.)
|
||||
|
||||
for (auto& pair : m_StaticShapes)
|
||||
// Add obstructions onto the grid, for any class with (possibly zero) clearance
|
||||
std::map<entity_pos_t, u16> combinedMasks;
|
||||
for (const PathfinderPassability& passability : passClasses)
|
||||
{
|
||||
const StaticShape& shape = pair.second;
|
||||
if (!(shape.flags & requireMask))
|
||||
if (!passability.m_HasClearance)
|
||||
continue;
|
||||
|
||||
ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh };
|
||||
SimRasterize::Spans spans;
|
||||
SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE);
|
||||
for (SimRasterize::Span& span : spans)
|
||||
auto it = combinedMasks.find(passability.m_Clearance);
|
||||
if (it == combinedMasks.end())
|
||||
combinedMasks[passability.m_Clearance] = passability.m_Mask;
|
||||
else
|
||||
it->second |= passability.m_Mask;
|
||||
}
|
||||
|
||||
for (auto& maskPair : combinedMasks)
|
||||
{
|
||||
for (auto& pair : m_StaticShapes)
|
||||
{
|
||||
i16 j = span.j;
|
||||
if (j >= 0 && j <= grid.m_H)
|
||||
if (!fullUpdate && std::find(m_DirtyStaticShapes.begin(), m_DirtyStaticShapes.end(), pair.first) == m_DirtyStaticShapes.end())
|
||||
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 i1 = std::min(span.i1, (i16)grid.m_W);
|
||||
for (i16 i = i0; i < i1; ++i)
|
||||
grid.set(i, j, grid.get(i, j) | setMask);
|
||||
i16 j = span.j;
|
||||
if (j >= 0 && j <= grid.m_H)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
m_DirtyStaticShapes.clear();
|
||||
m_DirtyUnitShapes.clear();
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
CFixedVector2D center(square.x, square.z);
|
||||
CFixedVector2D expandedBox =
|
||||
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);
|
||||
|
||||
std::map<entity_pos_t, SimRasterize::Spans> rasterizedRects;
|
||||
|
@ -49,10 +49,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
|
||||
{
|
||||
m_MapSize = 0;
|
||||
m_Grid = NULL;
|
||||
m_BaseGrid = NULL;
|
||||
|
||||
m_ObstructionsDirty = true;
|
||||
m_TerrainDirty = true;
|
||||
m_TerrainOnlyGrid = NULL;
|
||||
|
||||
m_NextAsyncTicket = 1;
|
||||
|
||||
@ -104,7 +101,7 @@ void CCmpPathfinder::Deinit()
|
||||
SetDebugOverlay(false); // cleans up memory
|
||||
|
||||
SAFE_DELETE(m_Grid);
|
||||
SAFE_DELETE(m_BaseGrid);
|
||||
SAFE_DELETE(m_TerrainOnlyGrid);
|
||||
}
|
||||
|
||||
struct SerializeLongRequest
|
||||
@ -170,17 +167,12 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
|
||||
case MT_TerrainChanged:
|
||||
case MT_WaterChanged:
|
||||
case MT_ObstructionMapShapeChanged:
|
||||
{
|
||||
// TODO PATHFINDER: we ought to only bother updating the dirtied region
|
||||
m_TerrainDirty = true;
|
||||
break;
|
||||
}
|
||||
case MT_TurnStart:
|
||||
{
|
||||
m_SameTurnMovesCount = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CCmpPathfinder::RenderSubmit(SceneCollector& collector)
|
||||
@ -221,7 +213,9 @@ const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t
|
||||
|
||||
const Grid<u16>& CCmpPathfinder::GetPassabilityGrid()
|
||||
{
|
||||
UpdateGrid();
|
||||
if (!m_Grid)
|
||||
UpdateGrid();
|
||||
|
||||
return *m_Grid;
|
||||
}
|
||||
|
||||
@ -473,14 +467,13 @@ void CCmpPathfinder::UpdateGrid()
|
||||
if (!cmpTerrain)
|
||||
return; // error
|
||||
|
||||
bool forceGlobalUpdate = false;
|
||||
m_ObstructionsDirty.Clean();
|
||||
|
||||
// If the terrain was resized then delete the old grid data
|
||||
if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide())
|
||||
{
|
||||
SAFE_DELETE(m_Grid);
|
||||
SAFE_DELETE(m_BaseGrid);
|
||||
m_TerrainDirty = true;
|
||||
SAFE_DELETE(m_TerrainOnlyGrid);
|
||||
}
|
||||
|
||||
// Initialise the terrain data when first needed
|
||||
@ -488,25 +481,23 @@ void CCmpPathfinder::UpdateGrid()
|
||||
{
|
||||
m_MapSize = cmpTerrain->GetTilesPerSide();
|
||||
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);
|
||||
forceGlobalUpdate = true;
|
||||
m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
|
||||
|
||||
m_ObstructionsDirty.dirty = true;
|
||||
m_ObstructionsDirty.globallyDirty = true;
|
||||
m_ObstructionsDirty.globalRecompute = true;
|
||||
|
||||
m_TerrainDirty = true;
|
||||
}
|
||||
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||
if (forceGlobalUpdate)
|
||||
{
|
||||
m_ObstructionsDirty = true;
|
||||
m_ObstructionsGlobalUpdate = true;
|
||||
}
|
||||
else
|
||||
m_ObstructionsDirty = cmpObstructionManager->GetDirtinessData(m_DirtinessGrid, m_ObstructionsGlobalUpdate);
|
||||
cmpObstructionManager->UpdateInformations(m_ObstructionsDirty);
|
||||
|
||||
if (!m_ObstructionsDirty && !m_TerrainDirty)
|
||||
if (!m_ObstructionsDirty.dirty && !m_TerrainDirty)
|
||||
return;
|
||||
|
||||
// If the terrain has changed, recompute entirely m_Grid
|
||||
// Else, use data from m_BaseGrid and add obstructions
|
||||
// If the terrain has changed, recompute m_Grid
|
||||
// Else, use data from m_TerrainOnlyGrid and add obstructions
|
||||
if (m_TerrainDirty)
|
||||
{
|
||||
Grid<u16> shoreGrid = ComputeShoreGrid();
|
||||
@ -576,39 +567,43 @@ void CCmpPathfinder::UpdateGrid()
|
||||
}
|
||||
|
||||
// Store the updated terrain-only grid
|
||||
*m_BaseGrid = *m_Grid;
|
||||
*m_TerrainOnlyGrid = *m_Grid;
|
||||
|
||||
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
|
||||
{
|
||||
ENSURE(m_Grid->m_W == m_BaseGrid->m_W && m_Grid->m_H == m_BaseGrid->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_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H);
|
||||
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, for any class with (possibly zero) clearance
|
||||
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);
|
||||
}
|
||||
// Add obstructions onto the grid
|
||||
cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_ObstructionsDirty.globalRecompute);
|
||||
|
||||
if (m_ObstructionsGlobalUpdate)
|
||||
// Update the long-range pathfinder
|
||||
if (m_ObstructionsDirty.globallyDirty)
|
||||
m_LongPathfinder.Reload(m_PassClassMasks, m_Grid);
|
||||
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 false;
|
||||
|
||||
dirtinessGrid = m_DirtinessGrid;
|
||||
globalUpdateNeeded = m_ObstructionsGlobalUpdate;
|
||||
return true;
|
||||
return m_ObstructionsDirty;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
@ -778,8 +773,6 @@ ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I
|
||||
|
||||
// Test against terrain:
|
||||
|
||||
UpdateGrid();
|
||||
|
||||
ICmpObstructionManager::ObstructionSquare square;
|
||||
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
|
||||
if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square))
|
||||
|
@ -106,13 +106,11 @@ public:
|
||||
|
||||
u16 m_MapSize; // tiles per side
|
||||
Grid<NavcellData>* m_Grid; // terrain/passability information
|
||||
Grid<NavcellData>* m_BaseGrid; // 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
|
||||
Grid<NavcellData>* m_TerrainOnlyGrid; // same as m_Grid, but only with terrain, to avoid some recomputations
|
||||
|
||||
// Update data, stored for the AI manager
|
||||
bool m_ObstructionsDirty;
|
||||
bool m_ObstructionsGlobalUpdate;
|
||||
Grid<u8> m_DirtinessGrid;
|
||||
// Update data, used for clever updates and then stored for the AI manager
|
||||
GridUpdateInformation m_ObstructionsDirty;
|
||||
bool m_TerrainDirty;
|
||||
|
||||
// Interface to the long-range pathfinder.
|
||||
LongPathfinder m_LongPathfinder;
|
||||
@ -170,7 +168,7 @@ public:
|
||||
|
||||
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);
|
||||
|
||||
|
@ -20,8 +20,7 @@
|
||||
|
||||
#include "simulation2/system/Interface.h"
|
||||
|
||||
#include "simulation2/helpers/Grid.h"
|
||||
#include "simulation2/helpers/Position.h"
|
||||
#include "simulation2/helpers/Pathfinding.h"
|
||||
|
||||
#include "maths/FixedVector2D.h"
|
||||
|
||||
@ -217,19 +216,18 @@ public:
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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
|
||||
* to pass the information to other components if needed. (AIs, etc.)
|
||||
* 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.
|
||||
|
@ -74,7 +74,7 @@ public:
|
||||
* Passes the lazily-stored dirtiness data collected from
|
||||
* 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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2013 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
|
||||
@ -185,4 +185,29 @@ public:
|
||||
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
|
||||
|
@ -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");
|
||||
|
||||
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;
|
||||
|
||||
std::pair<int, int> chunkID(i / CHUNK_SIZE, j / CHUNK_SIZE);
|
||||
|
@ -80,7 +80,7 @@ public:
|
||||
void SetDebugOverlay(bool enabled, const CSimContext* simContext);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -194,7 +194,7 @@ public:
|
||||
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;
|
||||
ASSERT(passabilityGrid->m_H == passabilityGrid->m_W);
|
||||
|
Loading…
Reference in New Issue
Block a user