Decouple LOS resolution from terrain resolution

Introduce a LOS_TILE_SIZE, to replace usage of TERRITORY_TILE_SIZE in
the LOS code.
This makes it possible to change the resolution of LOS/Terrain without
affecting the other component.

Additional refactoring:
- LosTile has been renamed LosRegion (it's more comparable to the
hierarchical pathfinder regions/spatial subdivisions)
- LosState explicitly refers to "los vertices" instead of terrain
vertices.

Refs #5566

Differential Revision: https://code.wildfiregames.com/D3076
This was SVN commit r24980.
This commit is contained in:
wraitii 2021-03-02 16:44:40 +00:00
parent 1c9efa6fb5
commit d8ea401a95
11 changed files with 170 additions and 163 deletions

View file

@ -38,7 +38,7 @@ function InitGame(settings)
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
for (let i = 1; i < settings.PlayerData.length; ++i)
cmpRangeManager.ExploreAllTiles(i);
cmpRangeManager.ExploreMap(i);
}
// Sandbox, Very Easy, Easy, Medium, Hard, Very Hard

View file

@ -20,7 +20,6 @@
#include "LOSTexture.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "lib/config2.h"
#include "ps/CLogger.h"
@ -31,12 +30,11 @@
#include "renderer/TimeManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/helpers/Los.h"
/*
The LOS bitmap is computed with one value per map vertex, based on
The LOS bitmap is computed with one value per LOS vertex, based on
CCmpRangeManager's visibility information.
The bitmap is then blurred using an NxN filter (in particular a
@ -243,11 +241,11 @@ const CMatrix3D* CLOSTexture::GetMinimapTextureMatrix()
void CLOSTexture::ConstructTexture(int unit)
{
CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (!cmpTerrain)
CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
m_MapSize = cmpTerrain->GetVerticesPerSide();
m_MapSize = cmpRangeManager->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
@ -294,7 +292,7 @@ void CLOSTexture::ConstructTexture(int unit)
// world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex)
// onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel)
float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * TERRAIN_TILE_SIZE);
float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * LOS_TILE_SIZE);
float t = 0.5f / m_TextureSize;
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = s;
@ -320,8 +318,8 @@ void CLOSTexture::RecomputeTexture(int unit)
// If the map was resized, delete and regenerate the texture
if (m_Texture)
{
CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide())
CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide())
DeleteTexture();
}

View file

@ -99,7 +99,7 @@ private:
GLuint m_smoothFbo;
CShaderTechniquePtr m_smoothShader;
ssize_t m_MapSize; // vertexes per side
size_t m_MapSize; // vertexes per side
GLsizei m_TextureSize; // texels per side
CMatrix3D m_TextureMatrix;

View file

@ -362,8 +362,8 @@ void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D&
const CFrustum& frustum, bool culling, const CLosQuerier& los, bool losRevealAll) const
{
// Don't display objects outside the visible area
ssize_t posi = (ssize_t)(0.5f + position.X / TERRAIN_TILE_SIZE);
ssize_t posj = (ssize_t)(0.5f + position.Z / TERRAIN_TILE_SIZE);
ssize_t posi = (ssize_t)(0.5f + position.X / LOS_TILE_SIZE);
ssize_t posj = (ssize_t)(0.5f + position.Z / LOS_TILE_SIZE);
if (!losRevealAll && !los.IsVisible(posi, posj))
return;

View file

@ -816,10 +816,10 @@ void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector<SVisibilitySegmen
// Go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
// a new one at the next point.
const float terrainSize = static_cast<float>(TERRAIN_TILE_SIZE);
const float cellSize = static_cast<float>(LOS_TILE_SIZE);
bool lastVisible = losQuerier.IsExplored(
(fixed::FromFloat(m_Path[index][0].X / terrainSize)).ToInt_RoundToNearest(),
(fixed::FromFloat(m_Path[index][0].Y / terrainSize)).ToInt_RoundToNearest()
(fixed::FromFloat(m_Path[index][0].X / cellSize)).ToInt_RoundToNearest(),
(fixed::FromFloat(m_Path[index][0].Y / cellSize)).ToInt_RoundToNearest()
);
// Starting node index of the current segment
size_t curSegmentStartIndex = 0;
@ -827,8 +827,8 @@ void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector<SVisibilitySegmen
for (size_t k = 1; k < m_Path[index].size(); ++k)
{
// Grab tile indices for this coord
int i = (fixed::FromFloat(m_Path[index][k].X / terrainSize)).ToInt_RoundToNearest();
int j = (fixed::FromFloat(m_Path[index][k].Y / terrainSize)).ToInt_RoundToNearest();
int i = (fixed::FromFloat(m_Path[index][k].X / cellSize)).ToInt_RoundToNearest();
int j = (fixed::FromFloat(m_Path[index][k].Y / cellSize)).ToInt_RoundToNearest();
bool nodeVisible = losQuerier.IsExplored(i, j);
if (nodeVisible != lastVisible)

View file

@ -45,9 +45,9 @@
#include "ps/Profile.h"
#include "renderer/Scene.h"
#define LOS_TILES_RATIO 8
#define DEBUG_RANGE_MANAGER_BOUNDS 0
constexpr int LOS_REGION_RATIO = 8;
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
@ -113,7 +113,7 @@ static inline LosVisibility GetPlayerVisibility(u32 visibilities, player_id_t pl
}
/**
* Test whether the visibility is dirty for a given LoS tile and a given player
* Test whether the visibility is dirty for a given LoS region and a given player
*/
static inline bool IsVisibilityDirty(u16 dirty, player_id_t player)
{
@ -381,18 +381,18 @@ public:
// LOS state:
static const player_id_t MAX_LOS_PLAYER_ID = 16;
using LosTile = std::pair<u16, u16>;
using LosRegion = std::pair<u16, u16>;
std::array<bool, MAX_LOS_PLAYER_ID+2> m_LosRevealAll;
bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
i32 m_LosVerticesPerSide;
// Cache for visibility tracking
i32 m_LosTilesPerSide;
i32 m_LosRegionsPerSide;
bool m_GlobalVisibilityUpdate;
std::array<bool, MAX_LOS_PLAYER_ID> m_GlobalPlayerVisibilityUpdate;
Grid<u16> m_DirtyVisibility;
Grid<std::set<entity_id_t>> m_LosTiles;
Grid<std::set<entity_id_t>> m_LosRegions;
// List of entities that must be updated, regardless of the status of their tile
std::vector<entity_id_t> m_ModifiedEntities;
@ -447,7 +447,7 @@ public:
m_GlobalVisibilityUpdate = true;
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
m_LosVerticesPerSide = 0;
}
virtual void Deinit()
@ -468,14 +468,14 @@ public:
Serializer(serialize, "los reveal all", m_LosRevealAll);
serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
serialize.NumberI32_Unbounded("los verts per side", m_LosVerticesPerSide);
serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
Serializer(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
Serializer(serialize, "dirty visibility", m_DirtyVisibility);
Serializer(serialize, "modified entities", m_ModifiedEntities);
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosRegions
// since they can be recomputed from the entity data when deserializing;
// m_LosState must be serialized since it depends on the history of exploration
@ -570,12 +570,12 @@ public:
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
else
LosMove(it->second.owner, it->second.visionRange, from, to);
LosTile oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
LosTile newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
if (oldLosTile != newLosTile)
LosRegion oldLosRegion = PosToLosRegionsHelper(it->second.x, it->second.z);
LosRegion newLosRegion = PosToLosRegionsHelper(msgData.x, msgData.z);
if (oldLosRegion != newLosRegion)
{
RemoveFromTile(oldLosTile, ent);
AddToTile(newLosTile, ent);
RemoveFromRegion(oldLosRegion, ent);
AddToRegion(newLosRegion, ent);
}
}
else
@ -586,7 +586,7 @@ public:
SharingLosAdd(it->second.visionSharing, it->second.visionRange, to);
else
LosAdd(it->second.owner, it->second.visionRange, to);
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
AddToRegion(PosToLosRegionsHelper(msgData.x, msgData.z), ent);
}
it->second.SetFlag<FlagMasks::InWorld>(true);
@ -603,7 +603,7 @@ public:
SharingLosRemove(it->second.visionSharing, it->second.visionRange, from);
else
LosRemove(it->second.owner, it->second.visionRange, from);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
RemoveFromRegion(PosToLosRegionsHelper(it->second.x, it->second.z), ent);
}
it->second.SetFlag<FlagMasks::InWorld>(false);
@ -663,7 +663,7 @@ public:
if (it->second.HasFlag<FlagMasks::InWorld>())
{
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
RemoveFromRegion(PosToLosRegionsHelper(it->second.x, it->second.z), ent);
}
// This will be called after Ownership's OnDestroy, so ownership will be set
@ -769,13 +769,15 @@ public:
}
}
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1)
{
// Don't support rectangular looking maps.
ENSURE(x1-x0 == z1-z0);
m_WorldX0 = x0;
m_WorldZ0 = z0;
m_WorldX1 = x1;
m_WorldZ1 = z1;
m_TerrainVerticesPerSide = (i32)vertices;
m_LosVerticesPerSide = ((x1 - x0) / LOS_TILE_SIZE).ToInt_RoundToZero() + 1;
ResetDerivedData();
}
@ -792,7 +794,7 @@ public:
std::array<Grid<u16>, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts;
Grid<u32> oldStateRevealed = m_LosStateRevealed;
FastSpatialSubdivision oldSubdivision = m_Subdivision;
Grid<std::set<entity_id_t> > oldLosTiles = m_LosTiles;
Grid<std::set<entity_id_t> > oldLosRegions = m_LosRegions;
m_Deserializing = true;
ResetDerivedData();
@ -826,8 +828,8 @@ public:
debug_warn(L"inconsistent revealed");
if (oldSubdivision != m_Subdivision)
debug_warn(L"inconsistent subdivs");
if (oldLosTiles != m_LosTiles)
debug_warn(L"inconsistent los tiles");
if (oldLosRegions != m_LosRegions)
debug_warn(L"inconsistent los regions");
}
FastSpatialSubdivision* GetSubdivision()
@ -841,7 +843,7 @@ public:
ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(m_WorldX1, m_WorldZ1);
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
m_LosRegionsPerSide = m_LosVerticesPerSide / LOS_REGION_RATIO;
for (size_t player_id = 0; player_id < m_LosPlayerCounts.size(); ++player_id)
m_LosPlayerCounts[player_id].clear();
@ -852,24 +854,24 @@ public:
if (m_Deserializing)
{
// recalc current exploration stats.
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
for (i32 j = 0; j < m_LosVerticesPerSide; j++)
for (i32 i = 0; i < m_LosVerticesPerSide; i++)
if (!LosIsOffWorld(i, j))
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
m_ExploredVertices.at(k) += ((m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(k-1)))) > 0);
} else
m_LosState.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
m_LosState.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
m_LosStateRevealed.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
m_LosStateRevealed.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
if (!m_Deserializing)
{
m_DirtyVisibility.resize(m_LosTilesPerSide, m_LosTilesPerSide);
m_DirtyVisibility.resize(m_LosRegionsPerSide, m_LosRegionsPerSide);
}
ENSURE(m_DirtyVisibility.width() == m_LosTilesPerSide);
ENSURE(m_DirtyVisibility.height() == m_LosTilesPerSide);
ENSURE(m_DirtyVisibility.width() == m_LosRegionsPerSide);
ENSURE(m_DirtyVisibility.height() == m_LosRegionsPerSide);
m_LosTiles.resize(m_LosTilesPerSide, m_LosTilesPerSide);
m_LosRegions.resize(m_LosRegionsPerSide, m_LosRegionsPerSide);
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
if (it->second.HasFlag<FlagMasks::InWorld>())
@ -878,15 +880,15 @@ public:
SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
else
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
AddToRegion(PosToLosRegionsHelper(it->second.x, it->second.z), it->first);
if (it->second.HasFlag<FlagMasks::RevealShore>())
RevealShore(it->second.owner, true);
}
m_TotalInworldVertices = 0;
for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
m_LosStateRevealed.get(i, j) = 0;
@ -1600,9 +1602,9 @@ public:
virtual CLosQuerier GetLosQuerier(player_id_t player) const
{
if (GetLosRevealAll(player))
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_TerrainVerticesPerSide);
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_LosVerticesPerSide);
else
return CLosQuerier(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
return CLosQuerier(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
}
virtual void ActivateScriptedVisibility(entity_id_t ent, bool status)
@ -1627,8 +1629,8 @@ public:
return LosVisibility::HIDDEN;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / LOS_TILE_SIZE).ToInt_RoundToNearest();
// Reveal flag makes all positioned entities visible and all mirages useless
if (GetLosRevealAll(player))
@ -1639,7 +1641,7 @@ public:
}
// Get visible regions
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
CmpPtr<ICmpVisibility> cmpVisibility(ent);
@ -1733,7 +1735,7 @@ public:
CFixedVector2D pos = cmpPosition->GetPosition2D();
if (IsVisibilityDirty(m_DirtyVisibility[PosToLosTilesHelper(pos.X, pos.Y)], player))
if (IsVisibilityDirty(m_DirtyVisibility[PosToLosRegionsHelper(pos.X, pos.Y)], player))
return ComputeLosVisibility(ent, player);
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end())
@ -1754,8 +1756,8 @@ public:
virtual LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const
{
int i = (x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int i = (x / LOS_TILE_SIZE).ToInt_RoundToNearest();
int j = (z / LOS_TILE_SIZE).ToInt_RoundToNearest();
// Reveal flag makes all positioned entities visible and all mirages useless
if (GetLosRevealAll(player))
@ -1767,7 +1769,7 @@ public:
}
// Get visible regions
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
if (los.IsVisible(i,j))
return LosVisibility::VISIBLE;
@ -1776,47 +1778,55 @@ public:
return LosVisibility::HIDDEN;
}
LosTile PosToLosTilesHelper(u16 x, u16 z) const
size_t GetVerticesPerSide() const
{
return LosTile{ Clamp(x/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1), Clamp(z/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1) };
return m_LosVerticesPerSide;
}
LosTile PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
LosRegion LosVertexToLosRegionsHelper(u16 x, u16 z) const
{
i32 i = Clamp(
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
return LosRegion {
Clamp(x/LOS_REGION_RATIO, 0, m_LosRegionsPerSide - 1),
Clamp(z/LOS_REGION_RATIO, 0, m_LosRegionsPerSide - 1)
};
}
LosRegion PosToLosRegionsHelper(entity_pos_t x, entity_pos_t z) const
{
u16 i = Clamp(
((x/LOS_TILE_SIZE)/LOS_REGION_RATIO).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
i32 j = Clamp(
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
m_LosRegionsPerSide - 1);
u16 j = Clamp(
((z/LOS_TILE_SIZE)/LOS_REGION_RATIO).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
m_LosRegionsPerSide - 1);
return std::make_pair(i, j);
}
void AddToTile(LosTile tile, entity_id_t ent)
void AddToRegion(LosRegion region, entity_id_t ent)
{
m_LosTiles[tile].insert(ent);
m_LosRegions[region].insert(ent);
}
void RemoveFromTile(LosTile tile, entity_id_t ent)
void RemoveFromRegion(LosRegion region, entity_id_t ent)
{
std::set<entity_id_t>::const_iterator tileIt = m_LosTiles[tile].find(ent);
if (tileIt != m_LosTiles[tile].end())
m_LosTiles[tile].erase(tileIt);
std::set<entity_id_t>::const_iterator regionIt = m_LosRegions[region].find(ent);
if (regionIt != m_LosRegions[region].end())
m_LosRegions[region].erase(regionIt);
}
void UpdateVisibilityData()
{
PROFILE("UpdateVisibilityData");
for (u16 i = 0; i < m_LosTilesPerSide; ++i)
for (u16 j = 0; j < m_LosTilesPerSide; ++j)
for (u16 i = 0; i < m_LosRegionsPerSide; ++i)
for (u16 j = 0; j < m_LosRegionsPerSide; ++j)
{
LosTile pos{i, j};
LosRegion pos{i, j};
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
if (IsVisibilityDirty(m_DirtyVisibility[pos], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
for (const entity_id_t& ent : m_LosTiles[pos])
for (const entity_id_t& ent : m_LosRegions[pos])
UpdateVisibility(ent, player);
m_DirtyVisibility[pos] = 0;
@ -1936,10 +1946,10 @@ public:
return m_SharedLosMasks[player];
}
void ExploreAllTiles(player_id_t p)
void ExploreMap(player_id_t p)
{
for (u16 j = 0; j < m_TerrainVerticesPerSide; ++j)
for (u16 i = 0; i < m_TerrainVerticesPerSide; ++i)
for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
continue;
@ -1959,40 +1969,33 @@ public:
const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid();
// Territory data is stored per territory-tile (typically a multiple of terrain-tiles).
// LOS data is stored per terrain-tile vertex.
// LOS data is stored per los vertex (in reality tiles too, but it's the center that matters).
// This scales from LOS coordinates to Territory coordinates.
auto scale = [](i32 coord) -> i32 {
return (coord * LOS_TILE_SIZE + LOS_TILE_SIZE / 2) / (ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * Pathfinding::NAVCELL_SIZE_INT);
};
// For each territory-tile, if it is owned by a valid player then update the LOS
// for every vertex inside/around that tile, to mark them as explored.
// Currently this code doesn't support territory-tiles smaller than terrain-tiles
// (it will get scale==0 and break), or a non-integer multiple, so check that first
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE >= Pathfinding::NAVCELLS_PER_TILE);
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE % Pathfinding::NAVCELLS_PER_TILE == 0);
int scale = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / Pathfinding::NAVCELLS_PER_TILE;
ENSURE(grid.m_W*scale == m_TerrainVerticesPerSide-1 && grid.m_H*scale == m_TerrainVerticesPerSide-1);
for (u16 j = 0; j < grid.m_H; ++j)
for (u16 i = 0; i < grid.m_W; ++i)
for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
{
u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
// TODO: This fetches data redundantly if the los grid is smaller than the territory grid
// (but it's unlikely to matter much).
u8 p = grid.get(scale(i), scale(j)) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
if (p > 0 && p <= MAX_LOS_PLAYER_ID)
{
u32& explored = m_ExploredVertices.at(p);
for (int tj = j * scale; tj <= (j+1) * scale; ++tj)
for (int ti = i * scale; ti <= (i+1) * scale; ++ti)
{
if (LosIsOffWorld(ti, tj))
continue;
u32& losState = m_LosState.get(ti, tj);
if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1)))))
{
++explored;
losState |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
}
if (LosIsOffWorld(i, j))
continue;
u32& losState = m_LosState.get(i, j);
if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1)))))
{
++explored;
losState |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
}
}
@ -2021,10 +2024,10 @@ public:
continue;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / LOS_TILE_SIZE).ToInt_RoundToNearest();
CLosQuerier los(GetSharedLosMask(p), m_LosState, m_TerrainVerticesPerSide);
CLosQuerier los(GetSharedLosMask(p), m_LosState, m_LosVerticesPerSide);
if (!los.IsExplored(i,j) || los.IsVisible(i,j))
continue;
@ -2051,7 +2054,7 @@ public:
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
const Grid<u16>& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
ENSURE(shoreGrid.m_W == m_TerrainVerticesPerSide-1 && shoreGrid.m_H == m_TerrainVerticesPerSide-1);
ENSURE(shoreGrid.m_W == m_LosVerticesPerSide-1 && shoreGrid.m_H == m_LosVerticesPerSide-1);
Grid<u16>& counts = m_LosPlayerCounts.at(p);
ENSURE(!counts.blank());
@ -2081,10 +2084,10 @@ public:
{
// With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
ssize_t dist2 = (i - m_TerrainVerticesPerSide/2)*(i - m_TerrainVerticesPerSide/2)
+ (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/2);
ssize_t dist2 = (i - m_LosVerticesPerSide/2)*(i - m_LosVerticesPerSide/2)
+ (j - m_LosVerticesPerSide/2)*(j - m_LosVerticesPerSide/2);
ssize_t r = m_TerrainVerticesPerSide / 2 - MAP_EDGE_TILES + 1;
ssize_t r = m_LosVerticesPerSide / 2 - MAP_EDGE_TILES + 1;
// subtract a bit from the radius to ensure nice
// SoD blurring around the edges of the map
@ -2095,8 +2098,8 @@ public:
// With a square map, the outermost edge of the map should be off-world,
// so the SoD texture blends out nicely
return i < MAP_EDGE_TILES || j < MAP_EDGE_TILES ||
i >= m_TerrainVerticesPerSide - MAP_EDGE_TILES ||
j >= m_TerrainVerticesPerSide - MAP_EDGE_TILES;
i >= m_LosVerticesPerSide - MAP_EDGE_TILES ||
j >= m_LosVerticesPerSide - MAP_EDGE_TILES;
}
}
@ -2158,22 +2161,22 @@ public:
if (m_Deserializing)
return;
// Mark the LoS tiles around the updated vertex
// Mark the LoS regions around the updated vertex
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
LosTile n1 = PosToLosTilesHelper(i-1, j-1);
LosTile n2 = PosToLosTilesHelper(i-1, j);
LosTile n3 = PosToLosTilesHelper(i, j-1);
LosTile n4 = PosToLosTilesHelper(i, j);
LosRegion n1 = LosVertexToLosRegionsHelper(i-1, j-1);
LosRegion n2 = LosVertexToLosRegionsHelper(i-1, j);
LosRegion n3 = LosVertexToLosRegionsHelper(i, j-1);
LosRegion n4 = LosVertexToLosRegionsHelper(i, j);
u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner];
if (j > 0 && i > 0)
m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask;
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide)
if (n2 != n1 && j > 0 && i < m_LosVerticesPerSide)
m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask;
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0)
if (n3 != n1 && j < m_LosVerticesPerSide && i > 0)
m_DirtyVisibility[n3] |= sharedDirtyVisibilityMask;
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
if (n4 != n1 && j < m_LosVerticesPerSide && i < m_LosVerticesPerSide)
m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask;
}
@ -2185,7 +2188,7 @@ public:
template<bool adding>
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
if (m_LosVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelper");
@ -2194,7 +2197,7 @@ public:
// Lazy initialisation of counts:
if (counts.blank())
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
// Compute the circular region as a series of strips.
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
@ -2207,15 +2210,15 @@ public:
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
// (so that we never render the sharp edge of the map)
i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0 = ((pos.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1 = ((pos.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(j0, 1);
i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
i32 j1clamp = std::min(j1, m_LosVerticesPerSide-2);
// Translate world coordinates into fractional tile-space coordinates
entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t x = pos.X / LOS_TILE_SIZE;
entity_pos_t y = pos.Y / LOS_TILE_SIZE;
entity_pos_t r = visionRange / LOS_TILE_SIZE;
entity_pos_t r2 = r.Square();
// Compute the integers on either side of x
@ -2257,7 +2260,7 @@ public:
// Clamp the strip to exclude the 1-tile border,
// then add or remove the strip as requested
i32 i0clamp = std::max(i0, 1);
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
i32 i1clamp = std::min(i1, m_LosVerticesPerSide-2);
if (adding)
LosAddStripHelper(owner, i0clamp, i1clamp, j, counts);
else
@ -2272,7 +2275,7 @@ public:
*/
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
if (m_LosVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelperIncremental");
@ -2281,7 +2284,7 @@ public:
// Lazy initialisation of counts:
if (counts.blank())
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
// See comments in LosUpdateHelper.
// This does exactly the same, except computing the strips for
@ -2290,18 +2293,18 @@ public:
// so we can compute the difference between the removed/added strips
// and only have to touch tiles that have a net change.)
i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0_from = ((from.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_from = ((from.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0_to = ((to.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_to = ((to.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_LosVerticesPerSide-2);
entity_pos_t x_from = from.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t x_from = from.X / LOS_TILE_SIZE;
entity_pos_t y_from = from.Y / LOS_TILE_SIZE;
entity_pos_t x_to = to.X / LOS_TILE_SIZE;
entity_pos_t y_to = to.Y / LOS_TILE_SIZE;
entity_pos_t r = visionRange / LOS_TILE_SIZE;
entity_pos_t r2 = r.Square();
i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
@ -2359,9 +2362,9 @@ public:
if (!(i0_to == i0_from && i1_to == i1_from))
{
i32 i0clamp_from = std::max(i0_from, 1);
i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
i32 i1clamp_from = std::min(i1_from, m_LosVerticesPerSide-2);
i32 i0clamp_to = std::max(i0_to, 1);
i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
i32 i1clamp_to = std::min(i1_to, m_LosVerticesPerSide-2);
// Check whether one strip is negative width,
// and we can just add/remove the entire other strip
@ -2465,8 +2468,8 @@ public:
u32 exploredVertices = 0;
std::vector<player_id_t>::const_iterator playerIt;
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
for (i32 j = 0; j < m_LosVerticesPerSide; j++)
for (i32 i = 0; i < m_LosVerticesPerSide; i++)
{
if (LosIsOffWorld(i, j))
continue;
@ -2484,6 +2487,3 @@ public:
};
REGISTER_COMPONENT_TYPE(RangeManager)
#undef LOS_TILES_RATIO
#undef DEBUG_RANGE_MANAGER_BOUNDS

View file

@ -139,8 +139,7 @@ public:
{
cmpRangeManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(),
entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE),
entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE),
vertices);
entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE));
}
if (ReloadWater && CRenderer::IsInitialised())

View file

@ -60,7 +60,7 @@ DEFINE_INTERFACE_METHOD_CONST_1("GetEntitiesByPlayer", std::vector<entity_id_t>,
DEFINE_INTERFACE_METHOD_CONST_0("GetNonGaiaEntities", std::vector<entity_id_t>, ICmpRangeManager, GetNonGaiaEntities)
DEFINE_INTERFACE_METHOD_CONST_0("GetGaiaAndNonGaiaEntities", std::vector<entity_id_t>, ICmpRangeManager, GetGaiaAndNonGaiaEntities)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_1("ExploreAllTiles", void, ICmpRangeManager, ExploreAllTiles, player_id_t)
DEFINE_INTERFACE_METHOD_1("ExploreMap", void, ICmpRangeManager, ExploreMap, player_id_t)
DEFINE_INTERFACE_METHOD_0("ExploreTerritories", void, ICmpRangeManager, ExploreTerritories)
DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)
DEFINE_INTERFACE_METHOD_CONST_1("GetLosRevealAll", bool, ICmpRangeManager, GetLosRevealAll, player_id_t)

View file

@ -106,9 +106,8 @@ public:
* Set the bounds of the world.
* Entities should not be outside the bounds (else efficiency will suffer).
* @param x0,z0,x1,z1 Coordinates of the corners of the world
* @param vertices Number of terrain vertices per side
*/
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices) = 0;
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0;
/**
* Execute a passive query.
@ -297,9 +296,9 @@ public:
std::string GetLosVisibilityPosition_wrapper(entity_pos_t x, entity_pos_t z, player_id_t player) const;
/**
* Explore all tiles (but leave them in the FoW) for player p
* Explore the map (but leave it in the FoW) for player p
*/
virtual void ExploreAllTiles(player_id_t p) = 0;
virtual void ExploreMap(player_id_t p) = 0;
/**
* Explore the tiles inside each player's territory.
@ -357,6 +356,10 @@ public:
*/
virtual u8 GetUnionPercentMapExplored(const std::vector<player_id_t>& players) const = 0;
/**
* @return The number of LOS vertices.
*/
virtual size_t GetVerticesPerSide() const = 0;
/**
* Perform some internal consistency checks for testing/debugging.

View file

@ -143,7 +143,7 @@ public:
// This tests that the incremental computation produces the correct result
// in various edge cases
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/TERRAIN_TILE_SIZE + 1);
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
cmp->Verify();
{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
cmp->Verify();
@ -214,7 +214,7 @@ public:
test.AddMock(101, IID_Position, position2);
test.AddMock(101, IID_Obstruction, obs2);
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/TERRAIN_TILE_SIZE + 1);
cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512));
cmp->Verify();
{ CMessageCreate msg(100); cmp->HandleMessage(msg, false); }
{ CMessageCreate msg(101); cmp->HandleMessage(msg, false); }

View file

@ -22,6 +22,13 @@
// since files must include "Los.h" explicitly, and that's only done in .cpp files.
#include "Grid.h"
/**
* Computing LOS data at a very high resolution is not necessary and quite slow.
* This is the size, in meters, separating each LOS vertex.
* (Note that this also means it is the minimal meaningful resolution of any vision range change).
*/
static constexpr i32 LOS_TILE_SIZE = 4;
enum class LosState : u8
{
UNEXPLORED = 0,