Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpRangeManager.cpp
Show All 37 Lines | |||||
#include "simulation2/helpers/Spatial.h" | #include "simulation2/helpers/Spatial.h" | ||||
#include "graphics/Overlay.h" | #include "graphics/Overlay.h" | ||||
#include "graphics/Terrain.h" | #include "graphics/Terrain.h" | ||||
#include "lib/timer.h" | #include "lib/timer.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/Profile.h" | #include "ps/Profile.h" | ||||
#include "renderer/Scene.h" | #include "renderer/Scene.h" | ||||
#define LOS_TILES_RATIO 8 | constexpr int LOS_REGION_RATIO = 8; | ||||
Stan: constexpr | |||||
#define DEBUG_RANGE_MANAGER_BOUNDS 0 | constexpr int DEBUG_RANGE_MANAGER_BOUNDS = 0; | ||||
/** | /** | ||||
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players) | * Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players) | ||||
* into a 32-bit mask for quick set-membership tests. | * into a 32-bit mask for quick set-membership tests. | ||||
*/ | */ | ||||
static inline u32 CalcOwnerMask(player_id_t owner) | static inline u32 CalcOwnerMask(player_id_t owner) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 311 Lines • ▼ Show 20 Lines | public: | ||||
EntityMap<EntityData> m_EntityData; | EntityMap<EntityData> m_EntityData; | ||||
FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData | FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData | ||||
std::vector<entity_id_t> m_SubdivisionResults; | std::vector<entity_id_t> m_SubdivisionResults; | ||||
// LOS state: | // LOS state: | ||||
static const player_id_t MAX_LOS_PLAYER_ID = 16; | 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; | std::array<bool, MAX_LOS_PLAYER_ID+2> m_LosRevealAll; | ||||
bool m_LosCircular; | bool m_LosCircular; | ||||
i32 m_TerrainVerticesPerSide; | i32 m_LosVerticesPerSide; | ||||
// Cache for visibility tracking | // Cache for visibility tracking | ||||
i32 m_LosTilesPerSide; | i32 m_LosRegionsPerSide; | ||||
bool m_GlobalVisibilityUpdate; | bool m_GlobalVisibilityUpdate; | ||||
std::array<bool, MAX_LOS_PLAYER_ID> m_GlobalPlayerVisibilityUpdate; | std::array<bool, MAX_LOS_PLAYER_ID> m_GlobalPlayerVisibilityUpdate; | ||||
Grid<u16> m_DirtyVisibility; | 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 | // List of entities that must be updated, regardless of the status of their tile | ||||
std::vector<entity_id_t> m_ModifiedEntities; | std::vector<entity_id_t> m_ModifiedEntities; | ||||
// Counts of units seeing vertex, per vertex, per player (starting with player 0). | // Counts of units seeing vertex, per vertex, per player (starting with player 0). | ||||
// Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers | // Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers | ||||
// of units in a very small area. | // of units in a very small area. | ||||
// (Note we use vertexes, not tiles, to better match the renderer.) | // (Note we use vertexes, not tiles, to better match the renderer.) | ||||
// Lazily constructed when it's needed, to save memory in smaller games. | // Lazily constructed when it's needed, to save memory in smaller games. | ||||
Show All 38 Lines | virtual void Init(const CParamNode& UNUSED(paramNode)) | ||||
// The whole map should be visible to Gaia by default, else e.g. animals | // The whole map should be visible to Gaia by default, else e.g. animals | ||||
// will get confused when trying to run from enemies | // will get confused when trying to run from enemies | ||||
m_LosRevealAll[0] = true; | m_LosRevealAll[0] = true; | ||||
m_GlobalVisibilityUpdate = true; | m_GlobalVisibilityUpdate = true; | ||||
m_LosCircular = false; | m_LosCircular = false; | ||||
m_TerrainVerticesPerSide = 0; | m_LosVerticesPerSide = 0; | ||||
} | } | ||||
virtual void Deinit() | virtual void Deinit() | ||||
{ | { | ||||
} | } | ||||
template<typename S> | template<typename S> | ||||
void SerializeCommon(S& serialize) | void SerializeCommon(S& serialize) | ||||
{ | { | ||||
serialize.NumberFixed_Unbounded("world x0", m_WorldX0); | serialize.NumberFixed_Unbounded("world x0", m_WorldX0); | ||||
serialize.NumberFixed_Unbounded("world z0", m_WorldZ0); | serialize.NumberFixed_Unbounded("world z0", m_WorldZ0); | ||||
serialize.NumberFixed_Unbounded("world x1", m_WorldX1); | serialize.NumberFixed_Unbounded("world x1", m_WorldX1); | ||||
serialize.NumberFixed_Unbounded("world z1", m_WorldZ1); | serialize.NumberFixed_Unbounded("world z1", m_WorldZ1); | ||||
serialize.NumberU32_Unbounded("query next", m_QueryNext); | serialize.NumberU32_Unbounded("query next", m_QueryNext); | ||||
SerializeMap<SerializeU32_Unbounded, SerializeQuery>()(serialize, "queries", m_Queries, GetSimContext()); | SerializeMap<SerializeU32_Unbounded, SerializeQuery>()(serialize, "queries", m_Queries, GetSimContext()); | ||||
SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData); | SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData); | ||||
SerializeArray<SerializeBool>()(serialize, "los reveal all", m_LosRevealAll); | SerializeArray<SerializeBool>()(serialize, "los reveal all", m_LosRevealAll); | ||||
serialize.Bool("los circular", m_LosCircular); | 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); | serialize.Bool("global visibility update", m_GlobalVisibilityUpdate); | ||||
SerializeArray<SerializeBool>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate); | SerializeArray<SerializeBool>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate); | ||||
SerializedGridCompressed<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility); | SerializedGridCompressed<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility); | ||||
SerializeVector<SerializeU32_Unbounded>()(serialize, "modified entities", m_ModifiedEntities); | SerializeVector<SerializeU32_Unbounded>()(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; | // since they can be recomputed from the entity data when deserializing; | ||||
// m_LosState must be serialized since it depends on the history of exploration | // m_LosState must be serialized since it depends on the history of exploration | ||||
SerializedGridCompressed<SerializeU32_Unbounded>()(serialize, "los state", m_LosState); | SerializedGridCompressed<SerializeU32_Unbounded>()(serialize, "los state", m_LosState); | ||||
SerializeArray<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks); | SerializeArray<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks); | ||||
SerializeArray<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks); | SerializeArray<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | case MT_PositionChanged: | ||||
{ | { | ||||
CFixedVector2D from(it->second.x, it->second.z); | CFixedVector2D from(it->second.x, it->second.z); | ||||
CFixedVector2D to(msgData.x, msgData.z); | CFixedVector2D to(msgData.x, msgData.z); | ||||
m_Subdivision.Move(ent, from, to, it->second.size); | m_Subdivision.Move(ent, from, to, it->second.size); | ||||
if (it->second.HasFlag<FlagMasks::SharedVision>()) | if (it->second.HasFlag<FlagMasks::SharedVision>()) | ||||
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to); | SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to); | ||||
else | else | ||||
LosMove(it->second.owner, it->second.visionRange, from, to); | LosMove(it->second.owner, it->second.visionRange, from, to); | ||||
LosTile oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z); | LosRegion oldLosRegion = PosToLosRegionsHelper(it->second.x, it->second.z); | ||||
LosTile newLosTile = PosToLosTilesHelper(msgData.x, msgData.z); | LosRegion newLosRegion = PosToLosRegionsHelper(msgData.x, msgData.z); | ||||
if (oldLosTile != newLosTile) | if (oldLosRegion != newLosRegion) | ||||
{ | { | ||||
RemoveFromTile(oldLosTile, ent); | RemoveFromTile(oldLosRegion, ent); | ||||
AddToTile(newLosTile, ent); | AddToTile(newLosRegion, ent); | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
CFixedVector2D to(msgData.x, msgData.z); | CFixedVector2D to(msgData.x, msgData.z); | ||||
m_Subdivision.Add(ent, to, it->second.size); | m_Subdivision.Add(ent, to, it->second.size); | ||||
if (it->second.HasFlag<FlagMasks::SharedVision>()) | if (it->second.HasFlag<FlagMasks::SharedVision>()) | ||||
SharingLosAdd(it->second.visionSharing, it->second.visionRange, to); | SharingLosAdd(it->second.visionSharing, it->second.visionRange, to); | ||||
else | else | ||||
LosAdd(it->second.owner, it->second.visionRange, to); | LosAdd(it->second.owner, it->second.visionRange, to); | ||||
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent); | AddToTile(PosToLosRegionsHelper(msgData.x, msgData.z), ent); | ||||
} | } | ||||
it->second.SetFlag<FlagMasks::InWorld>(true); | it->second.SetFlag<FlagMasks::InWorld>(true); | ||||
it->second.x = msgData.x; | it->second.x = msgData.x; | ||||
it->second.z = msgData.z; | it->second.z = msgData.z; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (it->second.HasFlag<FlagMasks::InWorld>()) | if (it->second.HasFlag<FlagMasks::InWorld>()) | ||||
{ | { | ||||
CFixedVector2D from(it->second.x, it->second.z); | CFixedVector2D from(it->second.x, it->second.z); | ||||
m_Subdivision.Remove(ent, from, it->second.size); | m_Subdivision.Remove(ent, from, it->second.size); | ||||
if (it->second.HasFlag<FlagMasks::SharedVision>()) | if (it->second.HasFlag<FlagMasks::SharedVision>()) | ||||
SharingLosRemove(it->second.visionSharing, it->second.visionRange, from); | SharingLosRemove(it->second.visionSharing, it->second.visionRange, from); | ||||
else | else | ||||
LosRemove(it->second.owner, it->second.visionRange, from); | LosRemove(it->second.owner, it->second.visionRange, from); | ||||
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent); | RemoveFromTile(PosToLosRegionsHelper(it->second.x, it->second.z), ent); | ||||
} | } | ||||
it->second.SetFlag<FlagMasks::InWorld>(false); | it->second.SetFlag<FlagMasks::InWorld>(false); | ||||
it->second.x = entity_pos_t::Zero(); | it->second.x = entity_pos_t::Zero(); | ||||
it->second.z = entity_pos_t::Zero(); | it->second.z = entity_pos_t::Zero(); | ||||
} | } | ||||
RequestVisibilityUpdate(ent); | RequestVisibilityUpdate(ent); | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | case MT_Destroy: | ||||
// Ignore if we're not already tracking this entity | // Ignore if we're not already tracking this entity | ||||
if (it == m_EntityData.end()) | if (it == m_EntityData.end()) | ||||
break; | break; | ||||
if (it->second.HasFlag<FlagMasks::InWorld>()) | if (it->second.HasFlag<FlagMasks::InWorld>()) | ||||
{ | { | ||||
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size); | m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size); | ||||
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent); | RemoveFromTile(PosToLosRegionsHelper(it->second.x, it->second.z), ent); | ||||
} | } | ||||
// This will be called after Ownership's OnDestroy, so ownership will be set | // This will be called after Ownership's OnDestroy, so ownership will be set | ||||
// to -1 already and we don't have to do a LosRemove here | // to -1 already and we don't have to do a LosRemove here | ||||
ENSURE(it->second.owner == -1); | ENSURE(it->second.owner == -1); | ||||
m_EntityData.erase(it); | m_EntityData.erase(it); | ||||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | ||||
RenderSubmit(msgData.collector); | RenderSubmit(msgData.collector); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
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. | |||||
Not Done Inline Actionswhy ? Silier: why ?
As far as I know there are rectangular maps in 0ad. | |||||
Done Inline ActionsNote that I do support square maps. I don't think we have actually rectangular maps? wraitii: Note that I do support square maps.
I don't think we have actually rectangular maps? | |||||
Not Done Inline ActionsBut is there a reason we can't support them? E.g. unnecessary code complexity? Freagarach: But is there a reason we can't support them? E.g. unnecessary code complexity? | |||||
Done Inline ActionsMostly, there's other code that assumes a square map, and I'm not sure the rest of this file works for oblong maps, so I'd rather crash early than let things run. I can remove this though. wraitii: Mostly, there's other code that assumes a square map, and I'm not sure the rest of this file… | |||||
Not Done Inline ActionsWell don't remove if it crashes later, I'd say ^^ Freagarach: Well don't remove if it crashes later, I'd say ^^ | |||||
ENSURE(x1-x0 == z1-z0); | |||||
m_WorldX0 = x0; | m_WorldX0 = x0; | ||||
m_WorldZ0 = z0; | m_WorldZ0 = z0; | ||||
m_WorldX1 = x1; | m_WorldX1 = x1; | ||||
m_WorldZ1 = z1; | m_WorldZ1 = z1; | ||||
m_TerrainVerticesPerSide = (i32)vertices; | m_LosVerticesPerSide = ((x1 - x0) / LOS_TILE_SIZE).ToInt_RoundToZero() + 1; | ||||
ResetDerivedData(); | ResetDerivedData(); | ||||
} | } | ||||
virtual void Verify() | virtual void Verify() | ||||
{ | { | ||||
// Ignore if map not initialised yet | // Ignore if map not initialised yet | ||||
if (m_WorldX1.IsZero()) | if (m_WorldX1.IsZero()) | ||||
return; | return; | ||||
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch) | // Check that calling ResetDerivedData (i.e. recomputing all the state from scratch) | ||||
// does not affect the incrementally-computed state | // does not affect the incrementally-computed state | ||||
std::array<Grid<u16>, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts; | std::array<Grid<u16>, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts; | ||||
Grid<u32> oldStateRevealed = m_LosStateRevealed; | Grid<u32> oldStateRevealed = m_LosStateRevealed; | ||||
FastSpatialSubdivision oldSubdivision = m_Subdivision; | 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; | m_Deserializing = true; | ||||
ResetDerivedData(); | ResetDerivedData(); | ||||
m_Deserializing = false; | m_Deserializing = false; | ||||
if (oldPlayerCounts != m_LosPlayerCounts) | if (oldPlayerCounts != m_LosPlayerCounts) | ||||
{ | { | ||||
for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id) | for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id) | ||||
Show All 17 Lines | if (oldPlayerCounts != m_LosPlayerCounts) | ||||
} | } | ||||
} | } | ||||
debug_warn(L"inconsistent player counts"); | debug_warn(L"inconsistent player counts"); | ||||
} | } | ||||
if (oldStateRevealed != m_LosStateRevealed) | if (oldStateRevealed != m_LosStateRevealed) | ||||
debug_warn(L"inconsistent revealed"); | debug_warn(L"inconsistent revealed"); | ||||
if (oldSubdivision != m_Subdivision) | if (oldSubdivision != m_Subdivision) | ||||
debug_warn(L"inconsistent subdivs"); | debug_warn(L"inconsistent subdivs"); | ||||
if (oldLosTiles != m_LosTiles) | if (oldLosRegions != m_LosRegions) | ||||
debug_warn(L"inconsistent los tiles"); | debug_warn(L"inconsistent los tiles"); | ||||
} | } | ||||
FastSpatialSubdivision* GetSubdivision() | FastSpatialSubdivision* GetSubdivision() | ||||
{ | { | ||||
return &m_Subdivision; | return &m_Subdivision; | ||||
} | } | ||||
// Reinitialise subdivisions and LOS data, based on entity data | // Reinitialise subdivisions and LOS data, based on entity data | ||||
void ResetDerivedData() | void ResetDerivedData() | ||||
{ | { | ||||
ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet | ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet | ||||
ResetSubdivisions(m_WorldX1, m_WorldZ1); | 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) | for (size_t player_id = 0; player_id < m_LosPlayerCounts.size(); ++player_id) | ||||
m_LosPlayerCounts[player_id].clear(); | m_LosPlayerCounts[player_id].clear(); | ||||
m_ExploredVertices.clear(); | m_ExploredVertices.clear(); | ||||
m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0); | m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0); | ||||
if (m_Deserializing) | if (m_Deserializing) | ||||
{ | { | ||||
// recalc current exploration stats. | // recalc current exploration stats. | ||||
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++) | for (i32 j = 0; j < m_LosVerticesPerSide; j++) | ||||
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++) | for (i32 i = 0; i < m_LosVerticesPerSide; i++) | ||||
if (!LosIsOffWorld(i, j)) | if (!LosIsOffWorld(i, j)) | ||||
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k) | 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); | m_ExploredVertices.at(k) += ((m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(k-1)))) > 0); | ||||
} else | } 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) | 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.width() == m_LosRegionsPerSide); | ||||
ENSURE(m_DirtyVisibility.height() == m_LosTilesPerSide); | 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) | for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) | ||||
if (it->second.HasFlag<FlagMasks::InWorld>()) | if (it->second.HasFlag<FlagMasks::InWorld>()) | ||||
{ | { | ||||
if (it->second.HasFlag<FlagMasks::SharedVision>()) | if (it->second.HasFlag<FlagMasks::SharedVision>()) | ||||
SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); | SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); | ||||
else | else | ||||
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); | LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); | ||||
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first); | AddToTile(PosToLosRegionsHelper(it->second.x, it->second.z), it->first); | ||||
if (it->second.HasFlag<FlagMasks::RevealShore>()) | if (it->second.HasFlag<FlagMasks::RevealShore>()) | ||||
RevealShore(it->second.owner, true); | RevealShore(it->second.owner, true); | ||||
} | } | ||||
m_TotalInworldVertices = 0; | m_TotalInworldVertices = 0; | ||||
for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j) | for (ssize_t j = 0; j < m_LosVerticesPerSide; ++j) | ||||
StanUnsubmitted Not Done Inline Actionsi32 Stan: i32 | |||||
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i) | for (ssize_t i = 0; i < m_LosVerticesPerSide; ++i) | ||||
{ | { | ||||
if (LosIsOffWorld(i,j)) | if (LosIsOffWorld(i,j)) | ||||
m_LosStateRevealed.get(i, j) = 0; | m_LosStateRevealed.get(i, j) = 0; | ||||
else | else | ||||
{ | { | ||||
m_LosStateRevealed.get(i, j) = 0xFFFFFFFFu; | m_LosStateRevealed.get(i, j) = 0xFFFFFFFFu; | ||||
m_TotalInworldVertices++; | m_TotalInworldVertices++; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 695 Lines • ▼ Show 20 Lines | |||||
// **************************************************************** | // **************************************************************** | ||||
// LOS implementation: | // LOS implementation: | ||||
virtual CLosQuerier GetLosQuerier(player_id_t player) const | virtual CLosQuerier GetLosQuerier(player_id_t player) const | ||||
{ | { | ||||
if (GetLosRevealAll(player)) | if (GetLosRevealAll(player)) | ||||
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_TerrainVerticesPerSide); | return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_LosVerticesPerSide); | ||||
else | 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) | virtual void ActivateScriptedVisibility(entity_id_t ent, bool status) | ||||
{ | { | ||||
EntityMap<EntityData>::iterator it = m_EntityData.find(ent); | EntityMap<EntityData>::iterator it = m_EntityData.find(ent); | ||||
if (it != m_EntityData.end()) | if (it != m_EntityData.end()) | ||||
it->second.SetFlag<FlagMasks::ScriptedVisibility>(status); | it->second.SetFlag<FlagMasks::ScriptedVisibility>(status); | ||||
} | } | ||||
LosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const | LosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const | ||||
{ | { | ||||
// Entities not with positions in the world are never visible | // Entities not with positions in the world are never visible | ||||
if (ent.GetId() == INVALID_ENTITY) | if (ent.GetId() == INVALID_ENTITY) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
CmpPtr<ICmpPosition> cmpPosition(ent); | CmpPtr<ICmpPosition> cmpPosition(ent); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
// Mirage entities, whatever the situation, are visible for one specific player | // Mirage entities, whatever the situation, are visible for one specific player | ||||
CmpPtr<ICmpMirage> cmpMirage(ent); | CmpPtr<ICmpMirage> cmpMirage(ent); | ||||
if (cmpMirage && cmpMirage->GetPlayer() != player) | if (cmpMirage && cmpMirage->GetPlayer() != player) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | CFixedVector2D pos = cmpPosition->GetPosition2D(); | ||||
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest(); | ||||
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int j = (pos.Y / LOS_TILE_SIZE).ToInt_RoundToNearest(); | ||||
// Reveal flag makes all positioned entities visible and all mirages useless | // Reveal flag makes all positioned entities visible and all mirages useless | ||||
if (GetLosRevealAll(player)) | if (GetLosRevealAll(player)) | ||||
{ | { | ||||
if (LosIsOffWorld(i, j) || cmpMirage) | if (LosIsOffWorld(i, j) || cmpMirage) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
return LosVisibility::VISIBLE; | return LosVisibility::VISIBLE; | ||||
} | } | ||||
// Get visible regions | // Get visible regions | ||||
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide); | CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide); | ||||
CmpPtr<ICmpVisibility> cmpVisibility(ent); | CmpPtr<ICmpVisibility> cmpVisibility(ent); | ||||
// Possibly ask the scripted Visibility component | // Possibly ask the scripted Visibility component | ||||
EntityMap<EntityData>::const_iterator it = m_EntityData.find(ent.GetId()); | EntityMap<EntityData>::const_iterator it = m_EntityData.find(ent.GetId()); | ||||
if (it != m_EntityData.end()) | if (it != m_EntityData.end()) | ||||
{ | { | ||||
if (it->second.HasFlag<FlagMasks::ScriptedVisibility>() && cmpVisibility) | if (it->second.HasFlag<FlagMasks::ScriptedVisibility>() && cmpVisibility) | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
// Gaia and observers do not have a visibility cache | // Gaia and observers do not have a visibility cache | ||||
if (player <= 0) | if (player <= 0) | ||||
return ComputeLosVisibility(ent, player); | return ComputeLosVisibility(ent, player); | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | 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); | return ComputeLosVisibility(ent, player); | ||||
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end()) | if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end()) | ||||
return ComputeLosVisibility(ent, player); | return ComputeLosVisibility(ent, player); | ||||
EntityMap<EntityData>::const_iterator it = m_EntityData.find(entId); | EntityMap<EntityData>::const_iterator it = m_EntityData.find(entId); | ||||
if (it == m_EntityData.end()) | if (it == m_EntityData.end()) | ||||
return ComputeLosVisibility(ent, player); | return ComputeLosVisibility(ent, player); | ||||
return static_cast<LosVisibility>(GetPlayerVisibility(it->second.visibilities, player)); | return static_cast<LosVisibility>(GetPlayerVisibility(it->second.visibilities, player)); | ||||
} | } | ||||
virtual LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const | virtual LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const | ||||
{ | { | ||||
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent); | CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent); | ||||
return GetLosVisibility(handle, player); | return GetLosVisibility(handle, player); | ||||
} | } | ||||
virtual LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const | 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 i = (x / LOS_TILE_SIZE).ToInt_RoundToNearest(); | ||||
int j = (z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int j = (z / LOS_TILE_SIZE).ToInt_RoundToNearest(); | ||||
// Reveal flag makes all positioned entities visible and all mirages useless | // Reveal flag makes all positioned entities visible and all mirages useless | ||||
if (GetLosRevealAll(player)) | if (GetLosRevealAll(player)) | ||||
{ | { | ||||
if (LosIsOffWorld(i, j)) | if (LosIsOffWorld(i, j)) | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
else | else | ||||
return LosVisibility::VISIBLE; | return LosVisibility::VISIBLE; | ||||
} | } | ||||
// Get visible regions | // Get visible regions | ||||
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide); | CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide); | ||||
if (los.IsVisible(i,j)) | if (los.IsVisible(i,j)) | ||||
return LosVisibility::VISIBLE; | return LosVisibility::VISIBLE; | ||||
if (los.IsExplored(i,j)) | if (los.IsExplored(i,j)) | ||||
return LosVisibility::FOGGED; | return LosVisibility::FOGGED; | ||||
return LosVisibility::HIDDEN; | return LosVisibility::HIDDEN; | ||||
} | } | ||||
LosTile PosToLosTilesHelper(u16 x, u16 z) const | size_t GetVerticesPerSide() const | ||||
StanUnsubmitted Not Done Inline Actionsi32? Stan: i32? | |||||
{ | { | ||||
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( | return LosRegion { | ||||
Not Done Inline ActionsIsn't that supposed to be u16? Stan: Isn't that supposed to be u16? | |||||
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(), | 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, | 0, | ||||
m_LosTilesPerSide - 1); | m_LosRegionsPerSide - 1); | ||||
i32 j = Clamp( | u16 j = Clamp( | ||||
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(), | ((z/LOS_TILE_SIZE)/LOS_REGION_RATIO).ToInt_RoundToZero(), | ||||
0, | 0, | ||||
m_LosTilesPerSide - 1); | m_LosRegionsPerSide - 1); | ||||
return std::make_pair(i, j); | return std::make_pair(i, j); | ||||
} | } | ||||
void AddToTile(LosTile tile, entity_id_t ent) | void AddToTile(LosRegion tile, entity_id_t ent) | ||||
{ | { | ||||
m_LosTiles[tile].insert(ent); | m_LosRegions[tile].insert(ent); | ||||
} | } | ||||
void RemoveFromTile(LosTile tile, entity_id_t ent) | void RemoveFromTile(LosRegion tile, entity_id_t ent) | ||||
{ | { | ||||
std::set<entity_id_t>::const_iterator tileIt = m_LosTiles[tile].find(ent); | std::set<entity_id_t>::const_iterator tileIt = m_LosRegions[tile].find(ent); | ||||
if (tileIt != m_LosTiles[tile].end()) | if (tileIt != m_LosRegions[tile].end()) | ||||
m_LosTiles[tile].erase(tileIt); | m_LosRegions[tile].erase(tileIt); | ||||
} | } | ||||
void UpdateVisibilityData() | void UpdateVisibilityData() | ||||
{ | { | ||||
PROFILE("UpdateVisibilityData"); | PROFILE("UpdateVisibilityData"); | ||||
for (u16 i = 0; i < m_LosTilesPerSide; ++i) | for (u16 i = 0; i < m_LosRegionsPerSide; ++i) | ||||
for (u16 j = 0; j < m_LosTilesPerSide; ++j) | 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) | 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) | 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); | UpdateVisibility(ent, player); | ||||
m_DirtyVisibility[pos] = 0; | m_DirtyVisibility[pos] = 0; | ||||
} | } | ||||
std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), false); | std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), false); | ||||
m_GlobalVisibilityUpdate = false; | m_GlobalVisibilityUpdate = false; | ||||
▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | |||||
virtual u32 GetSharedLosMask(player_id_t player) const | virtual u32 GetSharedLosMask(player_id_t player) const | ||||
{ | { | ||||
return m_SharedLosMasks[player]; | return m_SharedLosMasks[player]; | ||||
} | } | ||||
void ExploreAllTiles(player_id_t p) | void ExploreAllTiles(player_id_t p) | ||||
{ | { | ||||
for (u16 j = 0; j < m_TerrainVerticesPerSide; ++j) | for (u16 j = 0; j < m_LosVerticesPerSide; ++j) | ||||
StanUnsubmitted Not Done Inline Actionsi32 too Stan: i32 too | |||||
for (u16 i = 0; i < m_TerrainVerticesPerSide; ++i) | for (u16 i = 0; i < m_LosVerticesPerSide; ++i) | ||||
{ | { | ||||
if (LosIsOffWorld(i,j)) | if (LosIsOffWorld(i,j)) | ||||
continue; | continue; | ||||
u32 &explored = m_ExploredVertices.at(p); | u32 &explored = m_ExploredVertices.at(p); | ||||
explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(p-1)))); | explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(p-1)))); | ||||
m_LosState.get(i, j) |= ((u32)LosState::EXPLORED << (2*(p-1))); | m_LosState.get(i, j) |= ((u32)LosState::EXPLORED << (2*(p-1))); | ||||
} | } | ||||
SeeExploredEntities(p); | SeeExploredEntities(p); | ||||
} | } | ||||
virtual void ExploreTerritories() | virtual void ExploreTerritories() | ||||
{ | { | ||||
PROFILE3("ExploreTerritories"); | PROFILE3("ExploreTerritories"); | ||||
CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity()); | CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity()); | ||||
const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid(); | const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid(); | ||||
// Territory data is stored per territory-tile (typically a multiple of terrain-tiles). | // 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 tile. | ||||
auto scale = [](i32 coord) { return coord * LOS_TILE_SIZE / (ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE*Pathfinding::NAVCELLS_PER_TILE); }; | |||||
// For each territory-tile, if it is owned by a valid player then update the LOS | // 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. | // for every vertex inside/around that tile, to mark them as explored. | ||||
for (u16 j = 0; j < m_LosVerticesPerSide; ++j) | |||||
StanUnsubmitted Not Done Inline Actionsi32 Stan: i32 | |||||
// Currently this code doesn't support territory-tiles smaller than terrain-tiles | for (u16 i = 0; i < m_LosVerticesPerSide; ++i) | ||||
// (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) | |||||
{ | { | ||||
u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK; | // This does slightly redundant work is 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) | if (p > 0 && p <= MAX_LOS_PLAYER_ID) | ||||
{ | { | ||||
u32& explored = m_ExploredVertices.at(p); | 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(i, j)) | ||||
{ | |||||
if (LosIsOffWorld(ti, tj)) | |||||
continue; | continue; | ||||
u32& losState = m_LosState.get(ti, tj); | u32& losState = m_LosState.get(i, j); | ||||
if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1))))) | if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1))))) | ||||
{ | { | ||||
++explored; | ++explored; | ||||
losState |= ((u32)LosState::EXPLORED << (2*(p-1))); | losState |= ((u32)LosState::EXPLORED << (2*(p-1))); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p) | for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p) | ||||
SeeExploredEntities(p); | SeeExploredEntities(p); | ||||
} | } | ||||
/** | /** | ||||
* Force any entity in explored territory to appear for player p. | * Force any entity in explored territory to appear for player p. | ||||
* This is useful for miraging entities inside the territory borders at the beginning of a game, | * This is useful for miraging entities inside the territory borders at the beginning of a game, | ||||
Show All 10 Lines | void SeeExploredEntities(player_id_t p) const | ||||
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) | for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) | ||||
{ | { | ||||
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first); | CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | if (!cmpPosition || !cmpPosition->IsInWorld()) | ||||
continue; | continue; | ||||
CFixedVector2D pos = cmpPosition->GetPosition2D(); | CFixedVector2D pos = cmpPosition->GetPosition2D(); | ||||
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest(); | ||||
int j = (pos.Y / (int)TERRAIN_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)) | if (!los.IsExplored(i,j) || los.IsVisible(i,j)) | ||||
continue; | continue; | ||||
CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), it->first); | CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), it->first); | ||||
if (cmpFogging) | if (cmpFogging) | ||||
miragableEntities.push_back(it->first); | miragableEntities.push_back(it->first); | ||||
} | } | ||||
Show All 10 Lines | virtual void RevealShore(player_id_t p, bool enable) | ||||
if (p <= 0 || p > MAX_LOS_PLAYER_ID) | if (p <= 0 || p > MAX_LOS_PLAYER_ID) | ||||
return; | return; | ||||
// Maximum distance to the shore | // Maximum distance to the shore | ||||
const u16 maxdist = 10; | const u16 maxdist = 10; | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
const Grid<u16>& shoreGrid = cmpPathfinder->ComputeShoreGrid(true); | 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); | Grid<u16>& counts = m_LosPlayerCounts.at(p); | ||||
ENSURE(!counts.blank()); | ENSURE(!counts.blank()); | ||||
for (u16 j = 0; j < shoreGrid.m_H; ++j) | for (u16 j = 0; j < shoreGrid.m_H; ++j) | ||||
for (u16 i = 0; i < shoreGrid.m_W; ++i) | for (u16 i = 0; i < shoreGrid.m_W; ++i) | ||||
{ | { | ||||
u16 shoredist = shoreGrid.get(i, j); | u16 shoredist = shoreGrid.get(i, j); | ||||
Show All 13 Lines | |||||
* (i.e. outside the range of a circular map) | * (i.e. outside the range of a circular map) | ||||
*/ | */ | ||||
inline bool LosIsOffWorld(ssize_t i, ssize_t j) const | inline bool LosIsOffWorld(ssize_t i, ssize_t j) const | ||||
{ | { | ||||
if (m_LosCircular) | if (m_LosCircular) | ||||
{ | { | ||||
// With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2: | // 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) | ssize_t dist2 = (i - m_LosVerticesPerSide/2)*(i - m_LosVerticesPerSide/2) | ||||
+ (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/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 | // subtract a bit from the radius to ensure nice | ||||
// SoD blurring around the edges of the map | // SoD blurring around the edges of the map | ||||
return (dist2 >= r*r); | return (dist2 >= r*r); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// With a square map, the outermost edge of the map should be off-world, | // With a square map, the outermost edge of the map should be off-world, | ||||
// so the SoD texture blends out nicely | // so the SoD texture blends out nicely | ||||
return i < MAP_EDGE_TILES || j < MAP_EDGE_TILES || | return i < MAP_EDGE_TILES || j < MAP_EDGE_TILES || | ||||
i >= m_TerrainVerticesPerSide - MAP_EDGE_TILES || | i >= m_LosVerticesPerSide - MAP_EDGE_TILES || | ||||
j >= m_TerrainVerticesPerSide - MAP_EDGE_TILES; | j >= m_LosVerticesPerSide - MAP_EDGE_TILES; | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive). | * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive). | ||||
*/ | */ | ||||
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts) | inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | |||||
inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j) | inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j) | ||||
{ | { | ||||
// If we're still in the deserializing process, we must not modify m_DirtyVisibility | // If we're still in the deserializing process, we must not modify m_DirtyVisibility | ||||
if (m_Deserializing) | if (m_Deserializing) | ||||
return; | return; | ||||
// Mark the LoS tiles around the updated vertex | // Mark the LoS tiles around the updated vertex | ||||
// 1: left-up, 2: right-up, 3: left-down, 4: right-down | // 1: left-up, 2: right-up, 3: left-down, 4: right-down | ||||
LosTile n1 = PosToLosTilesHelper(i-1, j-1); | LosRegion n1 = LosVertexToLosRegionsHelper(i-1, j-1); | ||||
LosTile n2 = PosToLosTilesHelper(i-1, j); | LosRegion n2 = LosVertexToLosRegionsHelper(i-1, j); | ||||
LosTile n3 = PosToLosTilesHelper(i, j-1); | LosRegion n3 = LosVertexToLosRegionsHelper(i, j-1); | ||||
LosTile n4 = PosToLosTilesHelper(i, j); | LosRegion n4 = LosVertexToLosRegionsHelper(i, j); | ||||
u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner]; | u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner]; | ||||
if (j > 0 && i > 0) | if (j > 0 && i > 0) | ||||
m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask; | m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask; | ||||
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide) | if (n2 != n1 && j > 0 && i < m_LosVerticesPerSide) | ||||
m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask; | m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask; | ||||
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0) | if (n3 != n1 && j < m_LosVerticesPerSide && i > 0) | ||||
m_DirtyVisibility[n3] |= sharedDirtyVisibilityMask; | 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; | m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask; | ||||
} | } | ||||
/** | /** | ||||
* Update the LOS state of tiles within a given circular range, | * Update the LOS state of tiles within a given circular range, | ||||
* either adding or removing visibility depending on the template parameter. | * either adding or removing visibility depending on the template parameter. | ||||
* Assumes owner is in the valid range. | * Assumes owner is in the valid range. | ||||
*/ | */ | ||||
template<bool adding> | template<bool adding> | ||||
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos) | 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; | return; | ||||
PROFILE("LosUpdateHelper"); | PROFILE("LosUpdateHelper"); | ||||
Grid<u16>& counts = m_LosPlayerCounts.at(owner); | Grid<u16>& counts = m_LosPlayerCounts.at(owner); | ||||
// Lazy initialisation of counts: | // Lazy initialisation of counts: | ||||
if (counts.blank()) | if (counts.blank()) | ||||
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide); | counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide); | ||||
// Compute the circular region as a series of strips. | // Compute the circular region as a series of strips. | ||||
// Rather than quantise pos to vertexes, we do more precise sub-tile computations | // Rather than quantise pos to vertexes, we do more precise sub-tile computations | ||||
// to get smoother behaviour as a unit moves rather than jumping a whole tile | // to get smoother behaviour as a unit moves rather than jumping a whole tile | ||||
// at once. | // at once. | ||||
// To avoid the cost of sqrt when computing the outline of the circle, | // To avoid the cost of sqrt when computing the outline of the circle, | ||||
// we loop from the bottom to the top and estimate the width of the current | // we loop from the bottom to the top and estimate the width of the current | ||||
// strip based on the previous strip, then adjust each end of the strip | // strip based on the previous strip, then adjust each end of the strip | ||||
// inwards or outwards until it's the widest that still falls within the circle. | // inwards or outwards until it's the widest that still falls within the circle. | ||||
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map | // 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) | // (so that we never render the sharp edge of the map) | ||||
i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity(); | i32 j0 = ((pos.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity(); | ||||
i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(); | i32 j1 = ((pos.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity(); | ||||
i32 j0clamp = std::max(j0, 1); | 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 | // Translate world coordinates into fractional tile-space coordinates | ||||
entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE; | entity_pos_t x = pos.X / LOS_TILE_SIZE; | ||||
entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE; | entity_pos_t y = pos.Y / LOS_TILE_SIZE; | ||||
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE; | entity_pos_t r = visionRange / LOS_TILE_SIZE; | ||||
entity_pos_t r2 = r.Square(); | entity_pos_t r2 = r.Square(); | ||||
// Compute the integers on either side of x | // Compute the integers on either side of x | ||||
i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | ||||
i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | ||||
// Initialise the strip (i0, i1) to a rough guess | // Initialise the strip (i0, i1) to a rough guess | ||||
i32 i0 = xfloor; | i32 i0 = xfloor; | ||||
Show All 25 Lines | #if DEBUG_RANGE_MANAGER_BOUNDS | ||||
} | } | ||||
ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2); | ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2); | ||||
ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2); | ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2); | ||||
#endif | #endif | ||||
// Clamp the strip to exclude the 1-tile border, | // Clamp the strip to exclude the 1-tile border, | ||||
// then add or remove the strip as requested | // then add or remove the strip as requested | ||||
i32 i0clamp = std::max(i0, 1); | i32 i0clamp = std::max(i0, 1); | ||||
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2); | i32 i1clamp = std::min(i1, m_LosVerticesPerSide-2); | ||||
if (adding) | if (adding) | ||||
LosAddStripHelper(owner, i0clamp, i1clamp, j, counts); | LosAddStripHelper(owner, i0clamp, i1clamp, j, counts); | ||||
else | else | ||||
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts); | LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Update the LOS state of tiles within a given circular range, | * Update the LOS state of tiles within a given circular range, | ||||
* by removing visibility around the 'from' position | * by removing visibility around the 'from' position | ||||
* and then adding visibility around the 'to' position. | * and then adding visibility around the 'to' position. | ||||
*/ | */ | ||||
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to) | 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; | return; | ||||
PROFILE("LosUpdateHelperIncremental"); | PROFILE("LosUpdateHelperIncremental"); | ||||
Grid<u16>& counts = m_LosPlayerCounts.at(owner); | Grid<u16>& counts = m_LosPlayerCounts.at(owner); | ||||
// Lazy initialisation of counts: | // Lazy initialisation of counts: | ||||
if (counts.blank()) | if (counts.blank()) | ||||
counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide); | counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide); | ||||
// See comments in LosUpdateHelper. | // See comments in LosUpdateHelper. | ||||
// This does exactly the same, except computing the strips for | // This does exactly the same, except computing the strips for | ||||
// both circles simultaneously. | // both circles simultaneously. | ||||
// (The idea is that the circles will be heavily overlapping, | // (The idea is that the circles will be heavily overlapping, | ||||
// so we can compute the difference between the removed/added strips | // so we can compute the difference between the removed/added strips | ||||
// and only have to touch tiles that have a net change.) | // and only have to touch tiles that have a net change.) | ||||
i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity(); | i32 j0_from = ((from.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity(); | ||||
i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(); | i32 j1_from = ((from.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity(); | ||||
i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity(); | i32 j0_to = ((to.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity(); | ||||
i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(); | i32 j1_to = ((to.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity(); | ||||
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1); | 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 x_from = from.X / LOS_TILE_SIZE; | ||||
entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE; | entity_pos_t y_from = from.Y / LOS_TILE_SIZE; | ||||
entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE; | entity_pos_t x_to = to.X / LOS_TILE_SIZE; | ||||
entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE; | entity_pos_t y_to = to.Y / LOS_TILE_SIZE; | ||||
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE; | entity_pos_t r = visionRange / LOS_TILE_SIZE; | ||||
entity_pos_t r2 = r.Square(); | entity_pos_t r2 = r.Square(); | ||||
i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | ||||
i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | ||||
i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); | ||||
i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); | ||||
i32 i0_from = xfloor_from; | i32 i0_from = xfloor_from; | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | #if DEBUG_RANGE_MANAGER_BOUNDS | ||||
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2); | ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2); | ||||
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2); | ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2); | ||||
#endif | #endif | ||||
// Check whether this strip moved at all | // Check whether this strip moved at all | ||||
if (!(i0_to == i0_from && i1_to == i1_from)) | if (!(i0_to == i0_from && i1_to == i1_from)) | ||||
{ | { | ||||
i32 i0clamp_from = std::max(i0_from, 1); | 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 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, | // Check whether one strip is negative width, | ||||
// and we can just add/remove the entire other strip | // and we can just add/remove the entire other strip | ||||
if (i1clamp_from < i0clamp_from) | if (i1clamp_from < i0clamp_from) | ||||
{ | { | ||||
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts); | LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts); | ||||
} | } | ||||
else if (i1clamp_to < i0clamp_to) | else if (i1clamp_to < i0clamp_to) | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | virtual u8 GetPercentMapExplored(player_id_t player) const | ||||
return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices; | return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices; | ||||
} | } | ||||
virtual u8 GetUnionPercentMapExplored(const std::vector<player_id_t>& players) const | virtual u8 GetUnionPercentMapExplored(const std::vector<player_id_t>& players) const | ||||
{ | { | ||||
u32 exploredVertices = 0; | u32 exploredVertices = 0; | ||||
std::vector<player_id_t>::const_iterator playerIt; | std::vector<player_id_t>::const_iterator playerIt; | ||||
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++) | for (i32 j = 0; j < m_LosVerticesPerSide; j++) | ||||
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++) | for (i32 i = 0; i < m_LosVerticesPerSide; i++) | ||||
{ | { | ||||
if (LosIsOffWorld(i, j)) | if (LosIsOffWorld(i, j)) | ||||
continue; | continue; | ||||
for (playerIt = players.begin(); playerIt != players.end(); ++playerIt) | for (playerIt = players.begin(); playerIt != players.end(); ++playerIt) | ||||
if (m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*((*playerIt)-1)))) | if (m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*((*playerIt)-1)))) | ||||
{ | { | ||||
exploredVertices += 1; | exploredVertices += 1; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
return exploredVertices * 100 / m_TotalInworldVertices; | return exploredVertices * 100 / m_TotalInworldVertices; | ||||
} | } | ||||
}; | }; | ||||
REGISTER_COMPONENT_TYPE(RangeManager) | REGISTER_COMPONENT_TYPE(RangeManager) | ||||
#undef LOS_TILES_RATIO | |||||
#undef DEBUG_RANGE_MANAGER_BOUNDS |
Wildfire Games · Phabricator
constexpr