Adapt Atlas to the new pathfinder. Fixes #3298

This was SVN commit r16824.
This commit is contained in:
Nicolas Auvray 2015-06-29 19:59:41 +00:00
parent 6a9019564e
commit 30e5f032d8
7 changed files with 224 additions and 161 deletions

View File

@ -1024,7 +1024,7 @@ public:
LoadPathfinderClasses(state);
std::map<std::string, pass_class_t> passClassMasks;
if (cmpPathfinder)
passClassMasks = cmpPathfinder->GetPathfindingPassabilityClasses();
passClassMasks = cmpPathfinder->GetPassabilityClasses(true);
m_Worker.RunGamestateInit(scriptInterface.WriteStructuredClone(state), *passabilityMap, *territoryMap, passClassMasks);
}
@ -1076,7 +1076,7 @@ public:
LoadPathfinderClasses(state);
std::map<std::string, pass_class_t> passClassMasks;
if (cmpPathfinder)
passClassMasks = cmpPathfinder->GetPathfindingPassabilityClasses();
passClassMasks = cmpPathfinder->GetPassabilityClasses(true);
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state),
*passabilityMap, dirtinessInformations,

View File

@ -57,6 +57,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
m_NextAsyncTicket = 1;
m_DebugOverlay = false;
m_AtlasOverlay = NULL;
m_SameTurnMovesCount = 0;
@ -102,6 +103,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
void CCmpPathfinder::Deinit()
{
SetDebugOverlay(false); // cleans up memory
SAFE_DELETE(m_AtlasOverlay);
SAFE_DELETE(m_Grid);
SAFE_DELETE(m_TerrainOnlyGrid);
@ -171,6 +173,9 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
case MT_WaterChanged:
case MT_ObstructionMapShapeChanged:
m_TerrainDirty = true;
// TODO: this can be optimized by only updating a part of the terrain grid
// using the TerrainChanged message data.
MinimalTerrainUpdate();
break;
case MT_TurnStart:
m_SameTurnMovesCount = 0;
@ -186,6 +191,17 @@ void CCmpPathfinder::RenderSubmit(SceneCollector& collector)
m_LongPathfinder.HierarchicalRenderSubmit(collector);
}
void CCmpPathfinder::SetAtlasOverlay(bool enable, pass_class_t passClass)
{
if (enable)
{
if (!m_AtlasOverlay)
m_AtlasOverlay = new AtlasOverlay(this, passClass);
m_AtlasOverlay->m_PassClass = passClass;
}
else
SAFE_DELETE(m_AtlasOverlay);
}
pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name)
{
@ -203,14 +219,14 @@ std::map<std::string, pass_class_t> CCmpPathfinder::GetPassabilityClasses()
return m_PassClassMasks;
}
std::map<std::string, pass_class_t> CCmpPathfinder::GetPathfindingPassabilityClasses()
std::map<std::string, pass_class_t> CCmpPathfinder::GetPassabilityClasses(bool pathfindingClasses)
{
std::map<std::string, pass_class_t> pathfindingClasses;
std::map<std::string, pass_class_t> passabilityClasses;
for (auto& pair : m_PassClassMasks)
if (GetPassabilityFromMask(pair.second)->m_Obstructions == PathfinderPassability::PATHFINDING)
pathfindingClasses[pair.first] = pair.second;
if ((GetPassabilityFromMask(pair.second)->m_Obstructions == PathfinderPassability::PATHFINDING) == pathfindingClasses)
passabilityClasses[pair.first] = pair.second;
return pathfindingClasses;
return passabilityClasses;
}
const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t passClass) const
@ -423,58 +439,6 @@ Grid<u16> CCmpPathfinder::ComputeShoreGrid(bool expandOnWater)
return shoreGrid;
}
void CCmpPathfinder::ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid)
{
PROFILE3("terrain passability");
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
CTerrain& terrain = GetSimContext().GetTerrain();
// Compute initial terrain-dependent passability
for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j)
{
for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i)
{
// World-space coordinates for this navcell
fixed x, z;
Pathfinding::NavcellCenter(i, j, x, z);
// Terrain-tile coordinates for this navcell
int itile = i / Pathfinding::NAVCELLS_PER_TILE;
int jtile = j / Pathfinding::NAVCELLS_PER_TILE;
// Gather all the data potentially needed to determine passability:
fixed height = terrain.GetExactGroundLevelFixed(x, z);
fixed water;
if (cmpWaterManager)
water = cmpWaterManager->GetWaterLevel(x, z);
fixed depth = water - height;
//fixed slope = terrain.GetExactSlopeFixed(x, z);
// Exact slopes give kind of weird output, so just use rough tile-based slopes
fixed slope = terrain.GetSlopeFixed(itile, jtile);
// Get world-space coordinates from shoreGrid (which uses terrain tiles)
fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE);
// Compute the passability for every class for this cell:
NavcellData t = 0;
for (PathfinderPassability& passability : m_PassClasses)
{
if (!passability.IsPassable(depth, slope, shoredist))
t |= passability.m_Mask;
}
m_Grid->set(i, j, t);
}
}
}
void CCmpPathfinder::UpdateGrid()
{
PROFILE3("UpdateGrid");
@ -519,74 +483,9 @@ void CCmpPathfinder::UpdateGrid()
// Else, use data from m_TerrainOnlyGrid and add obstructions
if (m_TerrainDirty)
{
Grid<u16> shoreGrid = ComputeShoreGrid();
TerrainUpdateHelper();
ComputeTerrainPassabilityGrid(shoreGrid);
// Compute off-world passability
// WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this
const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world
NavcellData edgeMask = 0;
for (PathfinderPassability& passability : m_PassClasses)
edgeMask |= passability.m_Mask;
int w = m_Grid->m_W;
int h = m_Grid->m_H;
if (cmpObstructionManager->GetPassabilityCircular())
{
for (int j = 0; j < h; ++j)
{
for (int i = 0; i < w; ++i)
{
// Based on CCmpRangeManager::LosIsOffWorld
// but tweaked since it's tile-based instead.
// (We double all the values so we can handle half-tile coordinates.)
// This needs to be slightly tighter than the LOS circle,
// else units might get themselves lost in the SoD around the edge.
int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w)
+ (j*2 + 1 - h)*(j*2 + 1 - h);
if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize))
m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
}
}
}
else
{
for (u16 j = 0; j < h; ++j)
for (u16 i = 0; i < edgeSize; ++i)
m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
for (u16 j = 0; j < h; ++j)
for (u16 i = w-edgeSize+1; i < w; ++i)
m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
for (u16 j = 0; j < edgeSize; ++j)
for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
for (u16 j = h-edgeSize+1; j < h; ++j)
for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
}
// Expand the impassability grid, for any class with non-zero clearance,
// so that we can stop units getting too close to impassable navcells.
// Note: It's not possible to perform this expansion once for all passabilities
// with the same clearance, because the impassable cells are not necessarily the
// same for all these passabilities.
for (PathfinderPassability& passability : m_PassClasses)
{
if (passability.m_Clearance == fixed::Zero())
continue;
int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity();
ExpandImpassableCells(*m_Grid, clearance, passability.m_Mask);
}
// Store the updated terrain-only grid
*m_TerrainOnlyGrid = *m_Grid;
*m_Grid = *m_TerrainOnlyGrid;
m_TerrainDirty = false;
m_ObstructionsDirty.globalRecompute = true;
@ -615,11 +514,143 @@ void CCmpPathfinder::UpdateGrid()
// Update the long-range pathfinder
if (m_ObstructionsDirty.globallyDirty)
m_LongPathfinder.Reload(GetPathfindingPassabilityClasses(), m_Grid);
m_LongPathfinder.Reload(GetPassabilityClasses(true), m_Grid);
else
m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid);
}
void CCmpPathfinder::MinimalTerrainUpdate()
{
TerrainUpdateHelper();
}
void CCmpPathfinder::TerrainUpdateHelper()
{
PROFILE3("TerrainUpdateHelper");
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
CTerrain& terrain = GetSimContext().GetTerrain();
if (!cmpTerrain || !cmpObstructionManager)
return;
u16 terrainSize = cmpTerrain->GetTilesPerSide();
if (terrainSize == 0)
return;
if (!m_TerrainOnlyGrid || m_MapSize != terrainSize)
{
m_MapSize = terrainSize;
SAFE_DELETE(m_TerrainOnlyGrid);
m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
}
Grid<u16> shoreGrid = ComputeShoreGrid();
// Compute initial terrain-dependent passability
for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j)
{
for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i)
{
// World-space coordinates for this navcell
fixed x, z;
Pathfinding::NavcellCenter(i, j, x, z);
// Terrain-tile coordinates for this navcell
int itile = i / Pathfinding::NAVCELLS_PER_TILE;
int jtile = j / Pathfinding::NAVCELLS_PER_TILE;
// Gather all the data potentially needed to determine passability:
fixed height = terrain.GetExactGroundLevelFixed(x, z);
fixed water;
if (cmpWaterManager)
water = cmpWaterManager->GetWaterLevel(x, z);
fixed depth = water - height;
// Exact slopes give kind of weird output, so just use rough tile-based slopes
fixed slope = terrain.GetSlopeFixed(itile, jtile);
// Get world-space coordinates from shoreGrid (which uses terrain tiles)
fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE);
// Compute the passability for every class for this cell
NavcellData t = 0;
for (PathfinderPassability& passability : m_PassClasses)
if (!passability.IsPassable(depth, slope, shoredist))
t |= passability.m_Mask;
m_TerrainOnlyGrid->set(i, j, t);
}
}
// Compute off-world passability
// WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this
const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world
NavcellData edgeMask = 0;
for (PathfinderPassability& passability : m_PassClasses)
edgeMask |= passability.m_Mask;
int w = m_TerrainOnlyGrid->m_W;
int h = m_TerrainOnlyGrid->m_H;
if (cmpObstructionManager->GetPassabilityCircular())
{
for (int j = 0; j < h; ++j)
{
for (int i = 0; i < w; ++i)
{
// Based on CCmpRangeManager::LosIsOffWorld
// but tweaked since it's tile-based instead.
// (We double all the values so we can handle half-tile coordinates.)
// This needs to be slightly tighter than the LOS circle,
// else units might get themselves lost in the SoD around the edge.
int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w)
+ (j*2 + 1 - h)*(j*2 + 1 - h);
if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize))
m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
}
}
}
else
{
for (u16 j = 0; j < h; ++j)
for (u16 i = 0; i < edgeSize; ++i)
m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
for (u16 j = 0; j < h; ++j)
for (u16 i = w-edgeSize+1; i < w; ++i)
m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
for (u16 j = 0; j < edgeSize; ++j)
for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
for (u16 j = h-edgeSize+1; j < h; ++j)
for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
}
// Expand the impassability grid, for any class with non-zero clearance,
// so that we can stop units getting too close to impassable navcells.
// Note: It's not possible to perform this expansion once for all passabilities
// with the same clearance, because the impassable cells are not necessarily the
// same for all these passabilities.
for (PathfinderPassability& passability : m_PassClasses)
{
if (passability.m_Clearance == fixed::Zero())
continue;
int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity();
ExpandImpassableCells(*m_TerrainOnlyGrid, clearance, passability.m_Mask);
}
}
const GridUpdateInformation& CCmpPathfinder::GetDirtinessData() const
{
return m_ObstructionsDirty;
@ -811,15 +842,14 @@ ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I
i16 j = span.j;
// Fail if any span extends outside the grid
if (i0 < 0 || i1 > m_Grid->m_W || j < 0 || j > m_Grid->m_H)
if (i0 < 0 || i1 > m_TerrainOnlyGrid->m_W || j < 0 || j > m_TerrainOnlyGrid->m_H)
return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS;
// Fail if any span includes an impassable tile
for (i16 i = i0; i < i1; ++i)
if (!IS_PASSABLE(m_Grid->get(i, j), passClass))
if (!IS_PASSABLE(m_TerrainOnlyGrid->get(i, j), passClass))
return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS;
}
return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;
}

View File

@ -40,6 +40,7 @@
#include "simulation2/helpers/LongPathfinder.h"
class SceneCollector;
class AtlasOverlay;
#ifdef NDEBUG
#define PATHFIND_DEBUG 0
@ -124,6 +125,7 @@ public:
bool m_DebugOverlay;
std::vector<SOverlayLine> m_DebugOverlayShortPathLines;
AtlasOverlay* m_AtlasOverlay;
static std::string GetSchema()
{
@ -143,8 +145,7 @@ public:
virtual pass_class_t GetPassabilityClass(const std::string& name);
virtual std::map<std::string, pass_class_t> GetPassabilityClasses();
virtual std::map<std::string, pass_class_t> GetPathfindingPassabilityClasses();
virtual std::map<std::string, pass_class_t> GetPassabilityClasses(bool pathfindingClasses);
const PathfinderPassability* GetPassabilityFromMask(pass_class_t passClass) const;
@ -206,6 +207,8 @@ public:
m_LongPathfinder.GetDebugData(steps, time, grid);
}
virtual void SetAtlasOverlay(bool enable, pass_class_t passClass = 0);
virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass);
virtual ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass, bool onlyCenterPoint);
@ -227,9 +230,50 @@ public:
*/
virtual void UpdateGrid();
void ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid);
/**
* Updates the terrain-only grid without updating the dirtiness informations.
* Useful for fast passability updates in Atlas.
*/
void MinimalTerrainUpdate();
/**
* Regenerates the terrain-only grid.
*/
void TerrainUpdateHelper();
void RenderSubmit(SceneCollector& collector);
};
class AtlasOverlay : public TerrainTextureOverlay
{
public:
const CCmpPathfinder* m_Pathfinder;
pass_class_t m_PassClass;
AtlasOverlay(const CCmpPathfinder* pathfinder, pass_class_t passClass) :
TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TILE), m_Pathfinder(pathfinder), m_PassClass(passClass)
{
}
virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
{
// Render navcell passability, based on the terrain-only grid
u8* p = data;
for (size_t j = 0; j < h; ++j)
{
for (size_t i = 0; i < w; ++i)
{
SColor4ub color(0, 0, 0, 0);
if (!IS_PASSABLE(m_Pathfinder->m_TerrainOnlyGrid->get((int)i, (int)j), m_PassClass))
color = SColor4ub(255, 0, 0, 127);
*p++ = color.R;
*p++ = color.G;
*p++ = color.B;
*p++ = color.A;
}
}
}
};
#endif // INCLUDED_CCMPPATHFINDER_COMMON

View File

@ -95,6 +95,9 @@ public:
virtual u16 GetTilesPerSide()
{
ssize_t tiles = m_Terrain->GetTilesPerSide();
if (tiles == -1)
return 0;
ENSURE(1 <= tiles && tiles <= 65535);
return (u16)tiles;
}

View File

@ -56,9 +56,9 @@ public:
virtual std::map<std::string, pass_class_t> GetPassabilityClasses() = 0;
/**
* Get the list of pathfinding passability classes.
* Get the list of pathfinding passability classes, or all others.
*/
virtual std::map<std::string, pass_class_t> GetPathfindingPassabilityClasses() = 0;
virtual std::map<std::string, pass_class_t> GetPassabilityClasses(bool pathfindingClasses) = 0;
/**
* Get the tag for a given passability class name.
@ -186,6 +186,11 @@ public:
*/
virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid) = 0;
/**
* Sets up the pathfinder passability overlay in Atlas.
*/
virtual void SetAtlasOverlay(bool enable, pass_class_t passClass = 0) = 0;
DECLARE_INTERFACE_TYPE(Pathfinder)
};

View File

@ -137,7 +137,7 @@ QUERYHANDLER(GetTerrainPassabilityClasses)
CmpPtr<ICmpPathfinder> cmpPathfinder(*AtlasView::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
if (cmpPathfinder)
{
std::map<std::string, pass_class_t> classes = cmpPathfinder->GetPassabilityClasses();
std::map<std::string, pass_class_t> classes = cmpPathfinder->GetPassabilityClasses(false);
std::vector<std::wstring> classNames;
for (std::map<std::string, pass_class_t>::iterator it = classes.begin(); it != classes.end(); ++it)

View File

@ -236,26 +236,6 @@ void AtlasViewGame::Render()
camera.SetProjection(g_Game->GetView()->GetNear(), g_Game->GetView()->GetFar(), g_Game->GetView()->GetFOV());
camera.UpdateFrustum();
// Update the pathfinder display if necessary
if (!m_DisplayPassability.empty())
{
CmpPtr<ICmpObstructionManager> cmpObstructionManager(*GetSimulation2(), SYSTEM_ENTITY);
if (cmpObstructionManager)
{
cmpObstructionManager->SetDebugOverlay(true);
}
CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY);
if (cmpPathfinder)
{
cmpPathfinder->SetDebugOverlay(true);
// Kind of a hack to make it update the terrain grid
PathGoal goal = { PathGoal::POINT, fixed::Zero(), fixed::Zero() };
pass_class_t passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability);
cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass);
}
}
::Render();
Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas);
}
@ -315,13 +295,14 @@ void AtlasViewGame::SetParam(const std::wstring& name, const std::wstring& value
{
m_DisplayPassability = CStrW(value).ToUTF8();
CmpPtr<ICmpObstructionManager> cmpObstructionManager(*GetSimulation2(), SYSTEM_ENTITY);
if (cmpObstructionManager)
cmpObstructionManager->SetDebugOverlay(!value.empty());
CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY);
if (cmpPathfinder)
cmpPathfinder->SetDebugOverlay(!value.empty());
{
if (!value.empty())
cmpPathfinder->SetAtlasOverlay(true, cmpPathfinder->GetPassabilityClass(m_DisplayPassability));
else
cmpPathfinder->SetAtlasOverlay(false);
}
}
else if (name == L"renderpath")
{