Index: source/simulation2/components/CCmpFootprint.cpp =================================================================== --- source/simulation2/components/CCmpFootprint.cpp +++ source/simulation2/components/CCmpFootprint.cpp @@ -135,6 +135,11 @@ Init(paramNode); } + virtual entity_pos_t GetHeight() const + { + return m_Height; + } + virtual void GetShape(EShape& shape, entity_pos_t& size0, entity_pos_t& size1, entity_pos_t& height) const { shape = m_Shape; Index: source/simulation2/components/CCmpRangeManager.cpp =================================================================== --- source/simulation2/components/CCmpRangeManager.cpp +++ source/simulation2/components/CCmpRangeManager.cpp @@ -24,6 +24,7 @@ #include "simulation2/system/EntityMap.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpFogging.h" +#include "simulation2/components/ICmpFootprint.h" #include "simulation2/components/ICmpMirage.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPosition.h" @@ -43,7 +44,9 @@ #include "renderer/Scene.h" #define LOS_TILES_RATIO 8 -#define DEBUG_RANGE_MANAGER_BOUNDS 0 +// Enables runtime tests for vision blocking computation +// Program will trigger exception when finds problem +#define DEBUG_VIS_COMP 0 /** * Representation of a range query. @@ -213,13 +216,14 @@ { EntityData() : visibilities(0), size(0), visionSharing(0), - owner(-1), flags(FlagMasks::Normal) + owner(-1), h(0), flags(FlagMasks::Normal) { } entity_pos_t x, z; - entity_pos_t visionRange; + entity_pos_t visionRange; + u16 h; // height of entity u32 visibilities; // 2-bit visibility, per player u32 size; - u16 visionSharing; // 1-bit per player + u16 visionSharing; // 1-bit per player i8 owner; u8 flags; // See the FlagMasks enum @@ -232,7 +236,7 @@ inline void SetFlag(u8 mask, bool val) { flags = val ? (flags | mask) : (flags & ~mask); } }; -cassert(sizeof(EntityData) == 24); +cassert(sizeof(EntityData) == 28); /** * Serialization helper template for Query @@ -393,6 +397,28 @@ // Lazily constructed when it's needed, to save memory in smaller games. std::vector > m_LosPlayerCounts; + + CTerrain* cTerrain; // large amount of calles so chache it + // Map used by all players and vertexes with the same structure as m_LosState + // Used in los update to determine if given tile was visible + // No need to serialise + std::vector m_Vismap; + // Map used by all players and vertexes with the same structure as m_LosState + // Used in los update to determine if given tile blocked vision + // No need to serialise + std::vector m_RelatHighMap; + // Used in los update to know if in current computation was + // vision blocked + bool m_VisionBlocked; + +#if DEBUG_VIS_COMP + // If set to 1 m_VisComputedBefore will reset values at every start of los update + // To check behaviour of multiple changes of los at the same position + #define DEBUG_CLEAR_VIS_CHECK 1 + // Holds if tile was checked by los update + std::vector m_VisComputedBefore; +#endif + // 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive) std::vector m_LosState; @@ -442,6 +468,8 @@ m_LosCircular = false; m_TerrainVerticesPerSide = 0; + + cTerrain = &GetSimContext().GetTerrain(); } virtual void Deinit() @@ -538,6 +566,11 @@ if (cmpObstruction) entdata.size = cmpObstruction->GetSize().ToInt_RoundToInfinity(); + // Store height + CmpPtr cmpFootprint(GetSimContext(), ent); + if (cmpFootprint) + entdata.h = cmpFootprint->GetHeight().ToInt_RoundToNearest(); + // Remember this entity m_EntityData.insert(ent, entdata); break; @@ -561,9 +594,9 @@ CFixedVector2D to(msgData.x, msgData.z); m_Subdivision.Move(ent, from, to, it->second.size); if (it->second.HasFlag()) - SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to); + SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to, it->second.h); else - LosMove(it->second.owner, it->second.visionRange, from, to); + LosMove(it->second.owner, it->second.visionRange, from, to, it->second.h); i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z); i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z); if (oldLosTile != newLosTile) @@ -577,9 +610,9 @@ CFixedVector2D to(msgData.x, msgData.z); m_Subdivision.Add(ent, to, it->second.size); if (it->second.HasFlag()) - SharingLosAdd(it->second.visionSharing, it->second.visionRange, to); + SharingLosAdd(it->second.visionSharing, it->second.visionRange, to, it->second.h); else - LosAdd(it->second.owner, it->second.visionRange, to); + LosAdd(it->second.owner, it->second.visionRange, to, it->second.h); AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent); } @@ -594,9 +627,9 @@ CFixedVector2D from(it->second.x, it->second.z); m_Subdivision.Remove(ent, from, it->second.size); if (it->second.HasFlag()) - SharingLosRemove(it->second.visionSharing, it->second.visionRange, from); + SharingLosRemove(it->second.visionSharing, it->second.visionRange, from, it->second.h); else - LosRemove(it->second.owner, it->second.visionRange, from); + LosRemove(it->second.owner, it->second.visionRange, from, it->second.h); RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent); } @@ -627,14 +660,14 @@ if (!it->second.HasFlag()) { CFixedVector2D pos(it->second.x, it->second.z); - LosRemove(it->second.owner, it->second.visionRange, pos); - LosAdd(msgData.to, it->second.visionRange, pos); + LosRemove(it->second.owner, it->second.visionRange, pos, it->second.h); + LosAdd(msgData.to, it->second.visionRange, pos, it->second.h); } if (it->second.HasFlag()) { - RevealShore(it->second.owner, false); - RevealShore(msgData.to, true); + RevealShore(it->second.owner, false, it->second.h); + RevealShore(msgData.to, true, it->second.h); } } @@ -696,13 +729,13 @@ CFixedVector2D pos(it->second.x, it->second.z); if (it->second.HasFlag()) { - SharingLosRemove(it->second.visionSharing, oldRange, pos); - SharingLosAdd(it->second.visionSharing, newRange, pos); + SharingLosRemove(it->second.visionSharing, oldRange, pos, it->second.h); + SharingLosAdd(it->second.visionSharing, newRange, pos, it->second.h); } else { - LosRemove(it->second.owner, oldRange, pos); - LosAdd(it->second.owner, newRange, pos); + LosRemove(it->second.owner, oldRange, pos, it->second.h); + LosAdd(it->second.owner, newRange, pos, it->second.h); } } @@ -736,9 +769,9 @@ entity_pos_t range = it->second.visionRange; CFixedVector2D pos(it->second.x, it->second.z); if (msgData.add) - LosAdd(msgData.player, range, pos); + LosAdd(msgData.player, range, pos, it->second.h); else - LosRemove(msgData.player, range, pos); + LosRemove(msgData.player, range, pos, it->second.h); } if (msgData.add) @@ -866,13 +899,13 @@ if (it->second.HasFlag()) { if (it->second.HasFlag()) - 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), it->second.h); 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), it->second.h); AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first); if (it->second.HasFlag()) - RevealShore(it->second.owner, true); + RevealShore(it->second.owner, true, it->second.h); } m_TotalInworldVertices = 0; @@ -1983,7 +2016,7 @@ } } - virtual void RevealShore(player_id_t p, bool enable) + virtual void RevealShore(player_id_t p, bool enable, u16 h) { if (p <= 0 || p > MAX_LOS_PLAYER_ID) return; @@ -2008,9 +2041,9 @@ // Maybe we could be more clever and don't add dummy strips of one tile if (enable) - LosAddStripHelper(p, i, i, j, countsData); + LosManageStripHelper(true, p, i, i, j, countsData, h, i, j); else - LosRemoveStripHelper(p, i, i, j, countsData); + LosManageStripHelper(false, p, i, i, j, countsData, h, i, j); } } @@ -2033,75 +2066,569 @@ ssize_t r = m_TerrainVerticesPerSide/2 - edgeSize + 1; // subtract a bit from the radius to ensure nice // SoD blurring around the edges of the map - return (dist2 >= r*r); } - else - { - // With a square map, the outermost edge of the map should be off-world, - // so the SoD texture blends out nicely + // With a square map, the outermost edge of the map should be off-world, + // so the SoD texture blends out nicely + return (i < edgeSize || j < edgeSize || i >= m_TerrainVerticesPerSide - edgeSize || j >= m_TerrainVerticesPerSide - edgeSize); + } - return (i < edgeSize || j < edgeSize || i >= m_TerrainVerticesPerSide-edgeSize || j >= m_TerrainVerticesPerSide-edgeSize); - } + /** + * Returns true iff both tiles (a1, b1), (a2, b2) are visible from source + * Using visibility map + */ + inline bool CheckTilesForVisibility(i32 a1, i32 b1, i32 a2, i32 b2) + { + #if DEBUG_VIS_COMP + ENSURE(b1 > -1); + ENSURE(a1 > -1); + ENSURE(b2 > -1); + ENSURE(a2 > -1); + #endif + i32 idx = (b1)*m_TerrainVerticesPerSide + (a1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + if (!m_Vismap.at(idx)) + return false; + i32 idx2 = (b2)*m_TerrainVerticesPerSide + (a2); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx2)); + #endif + return m_Vismap.at(idx2); } /** - * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive). + * Check if at least one of tiles (a1, b1), (a2, b2) + * is was higher then source + * Using blocking vision map + */ + inline bool AreTilesHigher(i32 a1, i32 b1, i32 a2, i32 b2) + { + i32 idx = (b1)*m_TerrainVerticesPerSide + (a1); + i32 idx2 = (b2)*m_TerrainVerticesPerSide + (a2); + return m_RelatHighMap.at(idx) || m_RelatHighMap.at(idx2); + } + + /** + * Returns true if tile (i, j) is visible from given starting tile (iFrom, jFrom) + * Every tile above given height (hFrom) will block vision + * This is using global visibility map and blocking vision map + * so correct computation requires to check all tiles closer to source first + * Source tile is allways marked as visible and has to be checked + * This is using m_VisionBlocked to know if there was + * blocking of vision in previous computations + * Has to be called from single thread + * @param {idxx} position of (i, j) in visibility map */ - inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts) + inline bool IsVisibleTileShowHillSide(i32 iFrom, i32 jFrom, float hFrom, i32 i, i32 j, i32 idxx) { - if (i1 < i0) - return; + // Zero: The same tile is allways visible + if (iFrom == i && jFrom == j) { + m_RelatHighMap.at(idxx) = false; + return true; + } - i32 idx0 = j*m_TerrainVerticesPerSide + i0; - i32 idx1 = j*m_TerrainVerticesPerSide + i1; - u32 &explored = m_ExploredVertices.at(owner); - for (i32 idx = idx0; idx <= idx1; ++idx) - { - // Increasing from zero to non-zero - move from unexplored/explored to visible+explored - if (counts[idx] == 0) - { - i32 i = i0 + idx - idx0; - if (!LosIsOffWorld(i, j)) - { - explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1)))); - m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1))); + // First: Check if we see it by high difference + float h2 = cTerrain->GetVertexGroundLevel(i, j); + bool blocked = hFrom < h2; + m_RelatHighMap.at(idxx) = blocked; + if (blocked) + m_VisionBlocked = true; + + // Vision was not blocked yet so abort + if (!m_VisionBlocked) + return true; + + float h3 = 0; + // Check distances + i32 iDistA = std::abs(iFrom - i); + i32 jDistA = std::abs(jFrom - j); + + #if DEBUG_VIS_COMP + ENSURE(i > 0); + ENSURE(j > 0); + ENSURE(iFrom > 0); + ENSURE(jFrom > 0); + ENSURE(i < m_TerrainVerticesPerSide); + ENSURE(j < m_TerrainVerticesPerSide); + ENSURE(iFrom < m_TerrainVerticesPerSide); + ENSURE(jFrom < m_TerrainVerticesPerSide); + #endif + + i32 idx; + // Second: Check if tile before is visible + // 1) straight vision - check tiles at line (and return right away) + // 1.1) check on i line + if (i == iFrom) { + // 1.1.a) we are down so check to up + if (j < jFrom) { + idx = (j + 1)*m_TerrainVerticesPerSide + i; + if (!m_Vismap.at(idx)) + return false; + // tile before is visible + if (!m_RelatHighMap.at(idx)) + return true; + // tile before is blocking vision + h3 = cTerrain->GetVertexGroundLevel(i, j + 1); + return h2 - h3 > 1; + } + // 1.1.b) we are up so check to down + if (j > jFrom) { + idx = (j - 1)*m_TerrainVerticesPerSide + i; + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i, j - 1); + return h2 - h3 > 1; + } + } + // 1.2) check on j line + else if (j == jFrom) { + // 1.2.a) we are left so check to right + if (i < iFrom) { + idx = j*m_TerrainVerticesPerSide + (i + 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j); + return h2 - h3 > 1; + } + // 1.2.b) we are right so check to left + if (i > iFrom) { + idx = j*m_TerrainVerticesPerSide + (i - 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j); + return h2 - h3 > 1; + } + } + // 2) check diagonals to bottom + else if (j < jFrom) { + // 2.1) check diagonals to bottom right + if (i > iFrom) { + // 2.1.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j + 1)*m_TerrainVerticesPerSide + (i - 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j + 1); + return h2 - h3 > 1; + } + // 2.1.b) we are under so check to top and top left + else if (jDistA > iDistA) { + if (!CheckTilesForVisibility(i, j + 1, i - 1, j + 1)) + return false; + // Tiles before are visible + if (!AreTilesHigher(i, j + 1, i - 1, j + 1)) + return true; + // Tiles before are blocking vision + h3 = cTerrain->GetVertexGroundLevel(i, j + 1); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j + 1); + return h2 - h3 > 1; + } + // 2.1.c) we are up so check to left and left top + else if (jDistA < iDistA) { + if (!CheckTilesForVisibility(i - 1, j, i - 1, j + 1)) + return false; + if (!AreTilesHigher(i - 1, j, i - 1, j + 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j + 1); + return h2 - h3 > 1; + } + } + // 2.2) check diagonals to bottom left + else if (i < iFrom) { + // 2.2.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j + 1)*m_TerrainVerticesPerSide + (i + 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j + 1); + return h2 - h3 > 1; + } + // 2.2.b) we are under so check to top and top right + else if (jDistA > iDistA) { + if (!CheckTilesForVisibility(i, j + 1, i + 1, j + 1)) + return false; + if (!AreTilesHigher(i, j + 1, i + 1, j + 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i, j + 1); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j + 1); + return h2 - h3 > 1; + } + // 2.2.c) we are up so check to right and right top + else if (jDistA < iDistA) { + if (!CheckTilesForVisibility(i + 1, j, i + 1, j + 1)) + return false; + if (!AreTilesHigher(i + 1, j, i + 1, j + 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j + 1); + return h2 - h3 > 1; + } + } + } + // 3) check diagonals to top + else if (j > jFrom) { + // 3.1) check diagonals to top right + if (i > iFrom) { + // 3.1.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j - 1)*m_TerrainVerticesPerSide + (i - 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j - 1); + return h2 - h3 > 1; + } + // 3.1.b) we are up so check down and down left + else if (jDistA < iDistA) { + if (!CheckTilesForVisibility(i - 1, j, i - 1, j - 1)) + return false; + if (!AreTilesHigher(i - 1, j, i - 1, j - 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j - 1); + return h2 - h3 > 1; + } + // 3.1.c) we are under so check to left and left down + else if (jDistA > iDistA) { + if (!CheckTilesForVisibility(i, j - 1, i - 1, j - 1)) + return false; + if (!AreTilesHigher(i, j - 1, i - 1, j - 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i, j - 1); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i - 1, j - 1); + return h2 - h3 > 1; + } + } + // 3.2) check diagonals to top left + else if (i < iFrom) {// + // 3.2.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j - 1)*m_TerrainVerticesPerSide + (i + 1); + if (!m_Vismap.at(idx)) + return false; + if (!m_RelatHighMap.at(idx)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j - 1); + return h2 - h3 > 1; + } + // 3.2.b) we are down so check to right and down right + else if (jDistA < iDistA) { + if (!CheckTilesForVisibility(i + 1, j, i + 1, j - 1)) + return false; + if (!AreTilesHigher(i + 1, j, i + 1, j - 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j - 1); + return h2 - h3 > 1; + } + // 3.2.c) we are up so check to right and right down + else if (jDistA > iDistA) { + if (!CheckTilesForVisibility(i, j - 1, i + 1, j - 1)) + return false; + if (!AreTilesHigher(i, j - 1, i + 1, j - 1)) + return true; + h3 = cTerrain->GetVertexGroundLevel(i, j - 1); + if (h2 - h3 <= 1) + return false; + h3 = cTerrain->GetVertexGroundLevel(i + 1, j - 1); + return h2 - h3 > 1; } + } + } + return true; + } + + /** + * Leave as backup if expended visibility checking will be too much for performance + * @see IsVisibleTileShowHillSide + */ + inline bool IsVisibleTile(i32 iFrom, i32 jFrom, float hFrom, i32 i, i32 j) + { + // Zero: The same tile is allways visible + if (iFrom == i && jFrom == j) + return true; + + // First: Check if we see it by height difference + float h2 = cTerrain->GetVertexGroundLevel(i, j); + if (hFrom < h2) + return false; - MarkVisibilityDirtyAroundTile(owner, i, j); + // Vision was not blocked yet so abort + if (!m_VisionBlocked) + return true; + + // Check distances + i32 iDistA = std::abs(iFrom - i); + i32 jDistA = std::abs(jFrom - j); + + #if DEBUG_VIS_COMP + ENSURE(i > 0); + ENSURE(j > 0); + ENSURE(iFrom > 0); + ENSURE(jFrom > 0); + ENSURE(i < m_TerrainVerticesPerSide); + ENSURE(j < m_TerrainVerticesPerSide); + ENSURE(iFrom < m_TerrainVerticesPerSide); + ENSURE(jFrom < m_TerrainVerticesPerSide); + #endif + + i32 idx; + // Second: Check if tile before is visible + // 1) straight vision - check tiles at line (and return right away) + // 1.1) check on i line + if (i == iFrom) { + // 1.1.a) we are down so check to up + if (j < jFrom) { + idx = (j + 1)*m_TerrainVerticesPerSide + i; + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 1.1.b) we are up so check to down + if (j > jFrom) { + idx = (j - 1)*m_TerrainVerticesPerSide + i; + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + } + // 1.2) check on j line + else if (j == jFrom) { + // 1.2.a) we are left so check to right + if (i < iFrom) { + idx = j*m_TerrainVerticesPerSide + (i + 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 1.2.b) we are right so check to left + if (i > iFrom) { + idx = j*m_TerrainVerticesPerSide + (i - 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + } + // 2) check diagonals to bottom + else if (j < jFrom) { + // 2.1) check diagonals to bottom right + if (i > iFrom) { + // 2.1.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j + 1)*m_TerrainVerticesPerSide + (i - 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 2.1.b) we are under so check to top and top left + else if (jDistA > iDistA) { + return CheckTilesForVisibility(i, j + 1, i - 1, j + 1); + } + // 2.1.c) we are up so check to left and left top + else if (jDistA < iDistA) { + return CheckTilesForVisibility(i - 1, j, i - 1, j + 1); + } + } + // 2.2) check diagonals to bottom left + else if (i < iFrom) { + // 2.2.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j + 1)*m_TerrainVerticesPerSide + (i + 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 2.2.b) we are under so check to top and top right + else if (jDistA > iDistA) { + return CheckTilesForVisibility(i, j + 1, i + 1, j + 1); + } + // 2.2.c) we are up so check to right and right top + else if (jDistA < iDistA) { + return CheckTilesForVisibility(i + 1, j, i + 1, j + 1); + } + } + } + // 3) check diagonals to top + else if (j > jFrom) { + // 3.1) check diagonals to top right + if (i > iFrom) { + // 3.1.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j - 1)*m_TerrainVerticesPerSide + (i - 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 3.1.b) we are up so check down and down left + else if (jDistA < iDistA) { + return CheckTilesForVisibility(i - 1, j, i - 1, j - 1); + } + // 3.1.c) we are under so check to left and left down + else if (jDistA > iDistA) { + return CheckTilesForVisibility(i, j - 1, i - 1, j - 1); + } + } + // 3.2) check diagonals to top left + else if (i < iFrom) {// + // 3.2.a) true diagonal + // - check only next diagonal tile + if (iDistA == jDistA) { + idx = (j - 1)*m_TerrainVerticesPerSide + (i + 1); + #if DEBUG_VIS_COMP + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); + } + // 3.2.b) we are down so check to right and down right + else if (jDistA < iDistA) { + return CheckTilesForVisibility(i + 1, j, i + 1, j - 1); + } + // 3.2.c) we are up so check to right and right down + else if (jDistA > iDistA) { + return CheckTilesForVisibility(i, j - 1, i + 1, j - 1); + } } + } + return true; + } - ASSERT(counts[idx] < 65535); - counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units + /** + * Check if tile is visible from given tile taking direction into account + * This is updating visibility map shared among all tiles so make sure + * this is called only from one thread + * @param iFrom position from starting tile + * @param jFrom position from starting tile + * @param hFrom height tiles higher than this will block visibility + * @param i position of tile we are checking + * @param j position of tile we are checking + * @param idx index of checking position in visibility map + * @return true iff given tile is visible + */ + inline bool IsVisibleTileMark(i32 iFrom, i32 jFrom, float hFrom, i32 i, i32 j, i32 idx) + { + #if DEBUG_VIS_COMP + if (m_VisComputedBefore.empty()) { + m_VisComputedBefore.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + ENSURE(!m_VisComputedBefore.at(1)); + } + #endif + // not visible if off world + // if is of world do not bother updating visibility map + // there will be allways false + if (LosIsOffWorld(i, j)) { + #if DEBUG_VIS_COMP + if (i > -1 && j > -1 && idx < m_VisComputedBefore.size()) { + m_VisComputedBefore.at(idx) = true; + ENSURE(m_VisComputedBefore.at(idx)); + } + #endif + return false; } + // m_Vismap.at(idx) = IsVisibleTile(iFrom, jFrom, hFrom, i, j); + m_Vismap.at(idx) = IsVisibleTileShowHillSide(iFrom, jFrom, hFrom, i, j, idx); + #if DEBUG_VIS_COMP + m_VisComputedBefore.at(idx) = true; + ENSURE(m_VisComputedBefore.at(idx)); + #endif + return m_Vismap.at(idx); } /** - * 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) + * Looking from tile (x, y) from height h + * @see IsVisibleTileMark */ - inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts) + inline void LosManageStripHelper(bool add, u8 owner, i32 i0, i32 i1, i32 j, u16* counts, float h, i32 x, i32 y) { if (i1 < i0) return; - i32 idx0 = j*m_TerrainVerticesPerSide + i0; i32 idx1 = j*m_TerrainVerticesPerSide + i1; + u32 &explored = m_ExploredVertices.at(owner); for (i32 idx = idx0; idx <= idx1; ++idx) { - ASSERT(counts[idx] > 0); - counts[idx] = (u16)(counts[idx] - 1); + i32 i = i0 + idx - idx0; + if (!IsVisibleTileMark(x, y, h, i, j, idx)) + continue; + if (add) { + // Increasing from zero to non-zero - move from unexplored/explored to visible+explored + if (counts[idx] == 0) + { + explored += !(m_LosState[idx] & (LOS_EXPLORED << (2 * (owner - 1)))); + m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2 * (owner - 1))); - // Decreasing from non-zero to zero - move from visible+explored to explored - if (counts[idx] == 0) - { - // (If LosIsOffWorld then this is a no-op, so don't bother doing the check) - m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1))); + MarkVisibilityDirtyAroundTile(owner, i, j); + } - i32 i = i0 + idx - idx0; - MarkVisibilityDirtyAroundTile(owner, i, j); + ENSURE(counts[idx] < 65535); + counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units + } + else { + if (counts[idx]) { + counts[idx] = (u16)(counts[idx] - 1); + // Decreasing from non-zero to zero - move from visible+explored to explored + if (counts[idx] == 0) + { + m_LosState[idx] &= ~(LOS_VISIBLE << (2 * (owner - 1))); + + MarkVisibilityDirtyAroundTile(owner, i, j); + } + ENSURE(counts[idx] > -1); + } } } } + /** + * Inverted function compared to LosManagerStripHelper iterating from (i, j0) to (i, j1) + * @see IsVisibleTileMark + */ + inline void LosManageStripHelperJ(bool add, u8 owner, i32 j0, i32 j1, i32 i, u16* counts, float h, i32 x, i32 y) + { + for (i32 j = j0; j <= j1; ++j) + LosManageStripHelper(add, owner, i, i, j, counts, h, x, y); + } inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j) { @@ -2132,9 +2659,10 @@ * Update the LOS state of tiles within a given circular range, * either adding or removing visibility depending on the template parameter. * Assumes owner is in the valid range. + * Provide vision blocking by terrain. + * Height (eH) is added to ground/water level. */ - template - void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos) + template void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos, u16 eH) { if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet return; @@ -2149,265 +2677,203 @@ u16* countsData = &counts[0]; - // Compute the circular region as a series of strips. - // 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 - // at once. - // 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 - // 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. - - // 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 j0clamp = std::max(j0, 1); - i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2); + // Create empty vision map if needed + if (m_Vismap.empty()) + m_Vismap.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + // Create empty blocking map if needed + if (m_RelatHighMap.empty()) + m_RelatHighMap.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + // Reset this as we are starting new detection + m_VisionBlocked = false; // 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 r2 = r.Square(); - - // Compute the integers on either side of x + i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); - i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); + i32 yfloor = (y - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); + i32 rfloor = (r - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); - // Initialise the strip (i0, i1) to a rough guess - i32 i0 = xfloor; - i32 i1 = xceil; - - for (i32 j = j0clamp; j <= j1clamp; ++j) - { - // Adjust i0 and i1 to be the outermost values that don't exceed - // the circle's radius (i.e. require dy^2 + dx^2 <= r^2). - // When moving the points inwards, clamp them to xceil+1 or xfloor-1 - // so they don't accidentally shoot off in the wrong direction forever. - - entity_pos_t dy = entity_pos_t::FromInt(j) - y; - entity_pos_t dy2 = dy.Square(); - while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2) - --i0; - while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2) - ++i0; - while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2) - ++i1; - while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2) - --i1; + #if DEBUG_VIS_COMP + if (m_VisComputedBefore.empty()) { + m_VisComputedBefore.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + ENSURE(!m_VisComputedBefore.at(1)); + } + #if DEBUG_CLEAR_VIS_CHECK + std::fill(m_VisComputedBefore.begin(), m_VisComputedBefore.end(), false); + ENSURE(m_VisComputedBefore.size() == m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + #endif + #endif + // Do nothing if of world + if (LosIsOffWorld(xfloor, yfloor)) { + #if DEBUG_VIS_COMP + i32 idxd = yfloor*m_TerrainVerticesPerSide + xfloor; + m_VisComputedBefore.at(idxd) = true; + ENSURE(m_VisComputedBefore.at(idxd)); + #endif + return; + } -#if DEBUG_RANGE_MANAGER_BOUNDS - if (i0 <= i1) - { - ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2); - ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2); + // Get height of terrain/water at entity position + float h = cTerrain->GetVertexGroundLevel(xfloor, yfloor); + CmpPtr cmpWaterManager(GetSystemEntity()); + if (cmpWaterManager) { + float wl = cmpWaterManager->GetWaterLevel(pos.X, pos.Y).ToFloat(); + if (wl > h) + h = wl; + } + // Add entity heigth + h = h + eH; + + i32 i0; + i32 i1; + i32 ifrom; + i32 ito; + i32 jfrom; + i32 jto; + // Direction of incrementation of square + i32 ipp = 1; + // Guess how many tiles we should check at squre side + // Incremented by ipp at each step + i32 ip = 0; + // incrementally make squares larger + for (i32 range = 0; range < rfloor*2 + 1; ++range) { + // This defines square with side 2*range + // and centre at entity position + i0 = xfloor - range; + i1 = xfloor + range; + i32 j = yfloor + range; + i32 j3 = yfloor - range; + + // This defines which tiles at given + // square we have to check + i32 ifrom = xfloor - ip; + i32 ito = xfloor + ip; + // we cut corners so we do not check twice + i32 jfrom = yfloor - ip + 1; + i32 jto = yfloor + ip - 1; + + if (ipp > 0) { + entity_pos_t dy = entity_pos_t::FromInt(range + 1); + entity_pos_t dy2 = dy.Square(); + // We have filled square and half length of diagonal is larger + // than vision range so invert direction + if (dy2 + dy2 > r2) + ipp = -1; + // Discard some tiles if they are out of vision range allready + while (dy2 + (entity_pos_t::FromInt(ifrom) - x).Square() > r2) + ifrom++; + while (dy2 + (entity_pos_t::FromInt(ito) - x).Square() > r2) + ito--; + while (dy2 + (entity_pos_t::FromInt(jfrom) - y).Square() > r2) + jfrom++; + while (dy2 + (entity_pos_t::FromInt(jto) - y).Square() > r2) + jto--; + } + else if (ipp < 0) { + entity_pos_t dy = entity_pos_t::FromInt(range); + entity_pos_t dy2 = dy.Square(); + // We went out of vision range so abort + if (dy2 > r2) + break; + // We might overshoot vision range so make it more looks like circle + while (dy2 + (entity_pos_t::FromInt(ifrom) - x).Square() > r2) + ifrom++; + while (dy2 + (entity_pos_t::FromInt(ito) - x).Square() > r2) + ito--; + while (dy2 + (entity_pos_t::FromInt(jfrom) - y).Square() > r2) + jfrom++; + while (dy2 + (entity_pos_t::FromInt(jto) - y).Square() > r2) + jto--; } - ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2); - ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2); -#endif + ip = ip + ipp; - // Clamp the strip to exclude the 1-tile border, - // then add or remove the strip as requested + // Keep square envelope cordinates in map i32 i0clamp = std::max(i0, 1); - i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2); - if (adding) - LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData); - else - LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData); - } - } - - /** - * Update the LOS state of tiles within a given circular range, - * by removing visibility around the 'from' position - * and then adding visibility around the 'to' position. - */ - void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to) - { - if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet - return; - - PROFILE("LosUpdateHelperIncremental"); - - std::vector& counts = m_LosPlayerCounts.at(owner); - - // Lazy initialisation of counts: - if (counts.empty()) - counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); - - u16* countsData = &counts[0]; - - // See comments in LosUpdateHelper. - // This does exactly the same, except computing the strips for - // both circles simultaneously. - // (The idea is that the circles will be heavily overlapping, - // 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 j0clamp = std::max(std::min(j0_from, j0_to), 1); - i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-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 r2 = r.Square(); - - i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); - i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); - i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity(); - i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity(); - - i32 i0_from = xfloor_from; - i32 i1_from = xceil_from; - i32 i0_to = xfloor_to; - i32 i1_to = xceil_to; - - for (i32 j = j0clamp; j <= j1clamp; ++j) - { - entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from; - entity_pos_t dy2_from = dy_from.Square(); - while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2) - --i0_from; - while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2) - ++i0_from; - while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2) - ++i1_from; - while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2) - --i1_from; - - entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to; - entity_pos_t dy2_to = dy_to.Square(); - while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2) - --i0_to; - while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2) - ++i0_to; - while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2) - ++i1_to; - while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2) - --i1_to; - -#if DEBUG_RANGE_MANAGER_BOUNDS - if (i0_from <= i1_from) - { - ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2); - ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2); - } - ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2); - ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2); - if (i0_to <= i1_to) - { - ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2); - ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2); + i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide - 2); + i32 jclamp = std::max(j, 1); + i32 j3clamp = std::min(j3, m_TerrainVerticesPerSide - 2); + // The same for side cuts + ifrom = std::max(ifrom, 1); + ito = std::min(ito, m_TerrainVerticesPerSide - 2); + jfrom = std::max(jfrom, 1); + jto = std::min(jto, m_TerrainVerticesPerSide - 2); + + // First check top line + LosManageStripHelper(adding, owner, ifrom, ito, jclamp, countsData, h, xfloor, yfloor); + // Check bottom line, special case created by clamp + if (jclamp != j3clamp) + LosManageStripHelper(adding, owner, ifrom, ito, j3clamp, countsData, h, xfloor, yfloor); + // Make sure jfrom is not greather than jto because of cutting corners + if (jfrom <= jto) { + // Now check remaining tiles at left side of square + LosManageStripHelperJ(adding, owner, jfrom, jto, i0clamp, countsData, h, xfloor, yfloor); + // At last do the check for tiles at right side of square, special case by clamp + if (i0clamp != i1clamp) + LosManageStripHelperJ(adding, owner, jfrom, jto, i1clamp, countsData, h, xfloor, yfloor); } - 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); -#endif - // Check whether this strip moved at all - 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 i0clamp_to = std::max(i0_to, 1); - i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2); - - // Check whether one strip is negative width, - // and we can just add/remove the entire other strip - if (i1clamp_from < i0clamp_from) - { - LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData); - } - else if (i1clamp_to < i0clamp_to) - { - LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData); - } - else - { - // There are four possible regions of overlap between the two strips - // (remove before add, remove after add, add before remove, add after remove). - // Process each of the regions as its own strip. - // (If this produces negative-width strips then they'll just get ignored - // which is fine.) - // (If the strips don't actually overlap (which is very rare with normal unit - // movement speeds), the region between them will be both added and removed, - // so we have to do the add first to avoid overflowing to -1 and triggering - // assertion failures.) - LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData); - LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData); - LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData); - LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData); - } - } + // We have no more tiles to check + if (ip < 1) + break; } } - void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos) + void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos, u16 eH) { if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) return; - LosUpdateHelper((u8)owner, visionRange, pos); + LosUpdateHelper((u8)owner, visionRange, pos, eH); } - void SharingLosAdd(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos) + void SharingLosAdd(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos, u16 eH) { if (visionRange.IsZero()) return; for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i) if (HasVisionSharing(visionSharing, i)) - LosAdd(i, visionRange, pos); + LosAdd(i, visionRange, pos, eH); } - void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos) + void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos, u16 eH) { if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) return; - LosUpdateHelper((u8)owner, visionRange, pos); + LosUpdateHelper((u8)owner, visionRange, pos, eH); } - void SharingLosRemove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos) + void SharingLosRemove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos, u16 eH) { if (visionRange.IsZero()) return; for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i) if (HasVisionSharing(visionSharing, i)) - LosRemove(i, visionRange, pos); + LosRemove(i, visionRange, pos, eH); } - void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to) + void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to, u16 eH) { if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) return; - if ((from - to).CompareLength(visionRange) > 0) - { - // If it's a very large move, then simply remove and add to the new position - LosUpdateHelper((u8)owner, visionRange, from); - LosUpdateHelper((u8)owner, visionRange, to); - } - else - // Otherwise use the version optimised for mostly-overlapping circles - LosUpdateHelperIncremental((u8)owner, visionRange, from, to); + LosUpdateHelper((u8)owner, visionRange, from, eH); + LosUpdateHelper((u8)owner, visionRange, to, eH); } - void SharingLosMove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to) + void SharingLosMove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to, u16 eH) { if (visionRange.IsZero()) return; for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i) if (HasVisionSharing(visionSharing, i)) - LosMove(i, visionRange, from, to); + LosMove(i, visionRange, from, to, eH); } virtual u8 GetPercentMapExplored(player_id_t player) const @@ -2420,8 +2886,8 @@ u32 exploredVertices = 0; std::vector::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_TerrainVerticesPerSide; ++j) + for (i32 i = 0; i < m_TerrainVerticesPerSide; ++i) { if (LosIsOffWorld(i, j)) continue; Index: source/simulation2/components/ICmpFootprint.h =================================================================== --- source/simulation2/components/ICmpFootprint.h +++ source/simulation2/components/ICmpFootprint.h @@ -56,6 +56,8 @@ */ JS::Value GetShape_wrapper() const; + virtual entity_pos_t GetHeight() const = 0; + /** * Pick a sensible position to place a newly-spawned entity near this footprint, * such that it won't be in an invalid (obstructed) location regardless of the spawned unit's Index: source/simulation2/components/ICmpFootprint.cpp =================================================================== --- source/simulation2/components/ICmpFootprint.cpp +++ source/simulation2/components/ICmpFootprint.cpp @@ -71,5 +71,6 @@ BEGIN_INTERFACE_WRAPPER(Footprint) DEFINE_INTERFACE_METHOD_CONST_1("PickSpawnPoint", CFixedVector3D, ICmpFootprint, PickSpawnPoint, entity_id_t) DEFINE_INTERFACE_METHOD_CONST_1("PickSpawnPointBothPass", CFixedVector3D, ICmpFootprint, PickSpawnPointBothPass, entity_id_t) +DEFINE_INTERFACE_METHOD_CONST_0("GetHeight", entity_pos_t, ICmpFootprint,GetHeight) DEFINE_INTERFACE_METHOD_CONST_0("GetShape", JS::Value, ICmpFootprint, GetShape_wrapper) END_INTERFACE_WRAPPER(Footprint) Index: source/simulation2/components/ICmpRangeManager.h =================================================================== --- source/simulation2/components/ICmpRangeManager.h +++ source/simulation2/components/ICmpRangeManager.h @@ -375,7 +375,7 @@ * will be necessary to call it the same number of times with !enabled to make the shore * fall back into the FoW. */ - virtual void RevealShore(player_id_t p, bool enable) = 0; + virtual void RevealShore(player_id_t p, bool enable, u16 h) = 0; /** * Set whether the whole map should be made visible to the given player.