From 924d1219a7ab288ecd0d2cf8608c054c1ea73215 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Thu, 23 Sep 2010 12:13:13 +0000 Subject: [PATCH] # Add fog-of-war. Move Vision component to C++, for more efficient FoW processing. Disable FoW on demo maps. This was SVN commit r8160. --- .../mods/public/gui/session_new/session.xml | 7 +- .../public/maps/scenarios/Combat_demo.xml | 4 +- .../maps/scenarios/Combat_demo_(huge).xml | 4 +- .../maps/scenarios/Pathfinding_demo.xml | 4 +- .../scenarios/Pathfinding_terrain_demo.xml | 4 +- .../mods/public/maps/scenarios/Units_demo.xml | 4 +- .../public/simulation/components/Vision.js | 22 -- .../components/interfaces/Vision.js | 1 - .../mods/public/simulation/helpers/Setup.js | 7 + .../simulation/templates/template_gaia.xml | 1 + source/gui/MiniMap.cpp | 69 ++--- source/gui/scripting/ScriptFunctions.cpp | 16 +- source/maths/Fixed.h | 13 + source/maths/tests/test_Fixed.h | 16 ++ source/renderer/PatchRData.cpp | 65 ++--- source/renderer/TerrainRenderer.cpp | 50 ++-- source/simulation2/MessageTypes.h | 7 + source/simulation2/TypeList.h | 3 + source/simulation2/components/CCmpMinimap.cpp | 6 +- .../components/CCmpRangeManager.cpp | 243 ++++++++++++++++-- .../components/CCmpTemplateManager.cpp | 14 +- source/simulation2/components/CCmpTerrain.cpp | 3 +- source/simulation2/components/CCmpVision.cpp | 76 ++++++ .../components/CCmpVisualActor.cpp | 21 +- source/simulation2/components/ICmpMinimap.h | 4 +- .../components/ICmpRangeManager.cpp | 1 + .../simulation2/components/ICmpRangeManager.h | 103 +++++++- source/simulation2/components/ICmpVision.cpp | 27 ++ source/simulation2/components/ICmpVision.h | 38 +++ source/simulation2/helpers/Selection.cpp | 13 +- source/simulation2/helpers/Selection.h | 3 +- source/simulation2/system/SimContext.cpp | 9 + source/simulation2/system/SimContext.h | 6 + .../tests/test_CmpTemplateManager.h | 17 +- .../tools/atlas/GameInterface/ActorViewer.cpp | 11 + .../GameInterface/Handlers/MapHandlers.cpp | 6 + 36 files changed, 725 insertions(+), 173 deletions(-) delete mode 100644 binaries/data/mods/public/simulation/components/Vision.js delete mode 100644 binaries/data/mods/public/simulation/components/interfaces/Vision.js create mode 100644 source/simulation2/components/CCmpVision.cpp create mode 100644 source/simulation2/components/ICmpVision.cpp create mode 100644 source/simulation2/components/ICmpVision.h diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml index 2c723b9c2d..12718c7120 100644 --- a/binaries/data/mods/public/gui/session_new/session.xml +++ b/binaries/data/mods/public/gui/session_new/session.xml @@ -77,7 +77,7 @@ /> - diff --git a/binaries/data/mods/public/maps/scenarios/Combat_demo.xml b/binaries/data/mods/public/maps/scenarios/Combat_demo.xml index da92e38c22..c44309944f 100755 --- a/binaries/data/mods/public/maps/scenarios/Combat_demo.xml +++ b/binaries/data/mods/public/maps/scenarios/Combat_demo.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e1d41bffabe5eee7d05ad583c0dfef8bde2dcf1107033660a76eaf04ada2f86 -size 10975 +oid sha256:f61f903eb1565d9bbe72ad5e05153d0bb1ae9eedc4c28f1155323adca626c619 +size 10995 diff --git a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml index b9bac2805a..9861444120 100755 --- a/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml +++ b/binaries/data/mods/public/maps/scenarios/Combat_demo_(huge).xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03b18839389266b6cd660a99b987e71763df07f570b062404c8eae3e46adc055 -size 2683 +oid sha256:8ae3781db402ea2fb04996c998713e3b3b6744c1c4ffb25f48fd4ef30324542b +size 2703 diff --git a/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml b/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml index 2a108261a3..ec2334387c 100644 --- a/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml +++ b/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83896add80e4ae91e7d9508b7d3c88f8a156f7edf527592d405d59f7132e23f6 -size 18204 +oid sha256:5657b71b257c6445e7c4c89002a8a418d4b8e7148608fe42751880550f274cce +size 18224 diff --git a/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml b/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml index 3c2557db61..caf7196848 100644 --- a/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml +++ b/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db0294ad024f5a08bb6441328532e4177a3cecb5881355ab19a82898cfcc1154 -size 7674 +oid sha256:11768719c63f14b4450657c6c37b817e1962e734515e9329adbf191d4c39a54a +size 7694 diff --git a/binaries/data/mods/public/maps/scenarios/Units_demo.xml b/binaries/data/mods/public/maps/scenarios/Units_demo.xml index fd7c423c2a..ff9b3ee434 100644 --- a/binaries/data/mods/public/maps/scenarios/Units_demo.xml +++ b/binaries/data/mods/public/maps/scenarios/Units_demo.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4254e278f9379fd2fdcf8cdfc8aba3cdef56179755035c8a1295d0875a6981e7 -size 2257 +oid sha256:08e09ebf02494ac877823a08bb372879af40b0ac7ccfada6266de21585444c90 +size 2277 diff --git a/binaries/data/mods/public/simulation/components/Vision.js b/binaries/data/mods/public/simulation/components/Vision.js deleted file mode 100644 index 1dd9329f85..0000000000 --- a/binaries/data/mods/public/simulation/components/Vision.js +++ /dev/null @@ -1,22 +0,0 @@ -function Vision() {} - -Vision.prototype.Schema = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - -/* - * TODO: this all needs to be designed and implemented - */ - -Vision.prototype.GetRange = function() -{ - return +this.template.Range; -}; - -Engine.RegisterComponentType(IID_Vision, "Vision", Vision); diff --git a/binaries/data/mods/public/simulation/components/interfaces/Vision.js b/binaries/data/mods/public/simulation/components/interfaces/Vision.js deleted file mode 100644 index 9477507015..0000000000 --- a/binaries/data/mods/public/simulation/components/interfaces/Vision.js +++ /dev/null @@ -1 +0,0 @@ -Engine.RegisterInterface("Vision"); diff --git a/binaries/data/mods/public/simulation/helpers/Setup.js b/binaries/data/mods/public/simulation/helpers/Setup.js index 95177b4d5a..b4dff7446c 100644 --- a/binaries/data/mods/public/simulation/helpers/Setup.js +++ b/binaries/data/mods/public/simulation/helpers/Setup.js @@ -12,6 +12,13 @@ function LoadMapSettings(settings) cmpUnitAI.SetStance(settings.DefaultStance); } } + + if (settings.RevealMap) + { + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (cmpRangeManager) + cmpRangeManager.SetLosRevealAll(true); + } } Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings); diff --git a/binaries/data/mods/public/simulation/templates/template_gaia.xml b/binaries/data/mods/public/simulation/templates/template_gaia.xml index 6b1eb349a0..9102795053 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia.xml @@ -5,6 +5,7 @@ Gaia + 0 true diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp index 266a4d8080..2357e6cbfc 100644 --- a/source/gui/MiniMap.cpp +++ b/source/gui/MiniMap.cpp @@ -39,6 +39,7 @@ #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" +#include "simulation2/components/ICmpRangeManager.h" bool g_GameRestarted = false; @@ -246,9 +247,7 @@ void CMiniMap::Draw() if(m_TerrainDirty) RebuildTerrainTexture(); -// CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); -// if(losMgr->m_LOSSetting != LOS_SETTING_ALL_VISIBLE) -// RebuildLOSTexture(); + RebuildLOSTexture(); } const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize); @@ -366,16 +365,24 @@ void CMiniMap::Draw() std::vector vertexArray; vertexArray.reserve(ents.size()); + CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); + debug_assert(!cmpRangeManager.null()); + for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { MinimapUnitVertex v; ICmpMinimap* cmpMinimap = static_cast(it->second); - if (cmpMinimap->GetRenderData(v.r, v.g, v.b, v.x, v.y)) + entity_pos_t posX, posZ; + if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { - v.a = 255; - v.x = x + v.x*sx; - v.y = y - v.y*sy; - vertexArray.push_back(v); + ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetPlayerID()); + if (vis != ICmpRangeManager::VIS_HIDDEN) + { + v.a = 255; + v.x = x + posX.ToFloat()*sx; + v.y = y - posZ.ToFloat()*sy; + vertexArray.push_back(v); + } } } @@ -421,7 +428,7 @@ void CMiniMap::CreateTextures() glGenTextures(1, &m_LOSTexture); g_Renderer.BindTexture(0, m_LOSTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); - m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)]; + m_LOSData = new u8[m_MapSize * m_MapSize]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); @@ -493,41 +500,39 @@ void CMiniMap::RebuildTerrainTexture() void CMiniMap::RebuildLOSTexture() { - PROFILE_START("rebuild minimap: los"); + PROFILE("rebuild minimap: los"); - ssize_t x = 0; - ssize_t y = 0; - ssize_t w = m_MapSize - 1; - ssize_t h = m_MapSize - 1; + ssize_t w = m_MapSize; + ssize_t h = m_MapSize; - memset(m_LOSData, 0, w*h); + CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (cmpRangeManager.null() || cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID())) + { + memset(m_LOSData, 0, w*h); + } + else + { + ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID()); - // TODO: ought to do something like: -/* - CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); - CPlayer* player = g_Game->GetLocalPlayer(); + u8 *dataPtr = m_LOSData; - for(ssize_t j = 0; j < h; j++) + for (ssize_t j = 0; j < h; j++) { - u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x; - for(ssize_t i = 0; i < w; i++) + for (ssize_t i = 0; i < w; i++) { - ELOSStatus status = losMgr->GetStatus(i, j, player); - if(status == LOS_UNEXPLORED) - *dataPtr++ = 0xff; - else if(status == LOS_EXPLORED) - *dataPtr++ = (u8) (0xff * 0.3f); - else + if (los.IsVisible(i, j)) *dataPtr++ = 0; + else if (los.IsExplored(i, j)) + *dataPtr++ = 76; + else + *dataPtr++ = 255; } } -*/ + } // Upload the texture g_Renderer.BindTexture(0, m_LOSTexture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData); - - PROFILE_END("rebuild minimap: los"); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData); } void CMiniMap::Destroy() diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 016c920864..670cdd988d 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -36,6 +36,7 @@ #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpGuiInterface.h" +#include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/helpers/Selection.h" @@ -119,7 +120,7 @@ void PostNetworkCommand(void* cbdata, CScriptVal cmd) std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y) { - return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y); + return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID()); } std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player) @@ -304,6 +305,18 @@ CScriptVal LoadMapData(void* cbdata, std::wstring name) return reader.GetScriptData(guiManager->GetScriptInterface()).get(); } +void SetRevealMap(void* UNUSED(cbdata), bool enabled) +{ + if (!g_Game) + return; + + CSimulation2* sim = g_Game->GetSimulation2(); + debug_assert(sim); + CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); + if (!cmpRangeManager.null()) + cmpRangeManager->SetLosRevealAll(enabled); +} + } // namespace void GuiScriptingInit(ScriptInterface& scriptInterface) @@ -343,4 +356,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) scriptInterface.RegisterFunction("RestartInAtlas"); scriptInterface.RegisterFunction("AtlasIsAvailable"); scriptInterface.RegisterFunction("LoadMapData"); + scriptInterface.RegisterFunction("SetRevealMap"); } diff --git a/source/maths/Fixed.h b/source/maths/Fixed.h index f195c031d3..5d8b8297ca 100644 --- a/source/maths/Fixed.h +++ b/source/maths/Fixed.h @@ -176,6 +176,11 @@ public: return value >> fract_bits; } + int ToInt_RoundToNearest() const // (ties to infinity) + { + return (value + fract_pow2/2) >> fract_bits; + } + /// Returns the shortest string such that FromString will parse to the correct value. CStr8 ToString() const; @@ -280,6 +285,14 @@ public: return CFixed((T)t); } + /** + * Multiply the value by itself. Might overflow. + */ + CFixed Square() const + { + return (*this).Multiply(*this); + } + /** * Compute this*m/d. Must not have d == 0. Won't overflow if the result can be represented as a CFixed. */ diff --git a/source/maths/tests/test_Fixed.h b/source/maths/tests/test_Fixed.h index 9dca55646b..780a4a3e7e 100644 --- a/source/maths/tests/test_Fixed.h +++ b/source/maths/tests/test_Fixed.h @@ -189,6 +189,22 @@ public: TS_ASSERT_EQUALS(fixed::FromFloat(-2.99f).ToInt_RoundToNegInfinity(), -3); } + void test_RoundToNearest() + { + TS_ASSERT_EQUALS(fixed::FromFloat(10.f).ToInt_RoundToNearest(), 10); + TS_ASSERT_EQUALS(fixed::FromFloat(10.1f).ToInt_RoundToNearest(), 10); + TS_ASSERT_EQUALS(fixed::FromFloat(10.5f).ToInt_RoundToNearest(), 11); + TS_ASSERT_EQUALS(fixed::FromFloat(10.99f).ToInt_RoundToNearest(), 11); + TS_ASSERT_EQUALS(fixed::FromFloat(0.1f).ToInt_RoundToNearest(), 0); + TS_ASSERT_EQUALS(fixed::FromFloat(0.0f).ToInt_RoundToNearest(), 0); + TS_ASSERT_EQUALS(fixed::FromFloat(-0.1f).ToInt_RoundToNearest(), 0); + TS_ASSERT_EQUALS(fixed::FromFloat(-0.99f).ToInt_RoundToNearest(), -1); + TS_ASSERT_EQUALS(fixed::FromFloat(-1.0f).ToInt_RoundToNearest(), -1); + TS_ASSERT_EQUALS(fixed::FromFloat(-2.0f).ToInt_RoundToNearest(), -2); + TS_ASSERT_EQUALS(fixed::FromFloat(-2.5f).ToInt_RoundToNearest(), -2); + TS_ASSERT_EQUALS(fixed::FromFloat(-2.99f).ToInt_RoundToNearest(), -3); + } + // TODO: test all the arithmetic operators void test_Mod() diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp index 4d1664bf5a..ce7dd9ba6d 100644 --- a/source/renderer/PatchRData.cpp +++ b/source/renderer/PatchRData.cpp @@ -33,6 +33,7 @@ #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpRangeManager.h" const ssize_t BlendOffsets[8][2] = { { 0, -1 }, @@ -431,47 +432,11 @@ void CPatchRData::Update() ssize_t pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; - ssize_t mapSize=terrain->GetVerticesPerSide(); ssize_t vsize=PATCH_SIZE+1; SColor4ub baseColour = terrain->GetBaseColour(); - /* - if (g_Game) // XXX: need to implement this for new sim system - { - CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); - - // this is very similar to BuildVertices(), but just for color - for (ssize_t j=0;j= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) - { - ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); - if(s==LOS_EXPLORED && losMod.R > 178) - losMod = SColor4ub(178, 178, 178, 255); - else if(s==LOS_UNEXPLORED && losMod.R > 0) - losMod = SColor4ub(0, 0, 0, 255); - } - } - - m_Vertices[v].m_LOSColor = losMod; - } - } - } - else - */ + CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (cmpRangeManager.null() || cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID())) { for (ssize_t j = 0; j < vsize; ++j) { @@ -481,7 +446,31 @@ void CPatchRData::Update() m_Vertices[v].m_LOSColor = baseColour; } } + } + else + { + ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID()); + // this is very similar to BuildVertices(), but just for color + for (ssize_t j = 0; j < vsize; j++) + { + for (ssize_t i = 0; i < vsize; i++) + { + ssize_t ix = px * PATCH_SIZE + i; + ssize_t iz = pz * PATCH_SIZE + j; + ssize_t v = (j * vsize) + i; + + SColor4ub losMod; + if (los.IsVisible(ix, iz)) + losMod = baseColour; + else if (los.IsExplored(ix, iz)) + losMod = SColor4ub(178, 178, 178, 255); + else + losMod = SColor4ub(0, 0, 0, 255); + + m_Vertices[v].m_LOSColor = losMod; + } + } } // upload base vertices into their vertex buffer diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index fc7ab0fb67..f313123342 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -38,6 +38,7 @@ #include "ps/World.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpRangeManager.h" #include "renderer/PatchRData.h" #include "renderer/Renderer.h" @@ -434,9 +435,11 @@ void TerrainRenderer::RenderWater() } } CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); -// int mapSize = terrain->GetVerticesPerSide(); -// CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); + CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + debug_assert(!cmpRangeManager.null()); + ICmpRangeManager::CLosQuerier los = cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID()); + bool losRevealAll = cmpRangeManager->GetLosRevealAll(g_Game->GetPlayerID()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); @@ -566,50 +569,35 @@ void TerrainRenderer::RenderWater() // is any corner of the tile below the water height? if not, no point rendering it bool shouldRender = false; - for(int j=0; j<4; j++) + for (int j = 0; j < 4; j++) { float terrainHeight = terrain->GetVertexGroundLevel(x + DX[j], z + DZ[j]); - if( terrainHeight < WaterMgr->m_WaterHeight ) + if (terrainHeight < WaterMgr->m_WaterHeight) { shouldRender = true; break; } } - if(!shouldRender) - { + if (!shouldRender) continue; - } for (int j=0; j<4; j++) { - int ix = x + DX[j]; - int iz = z + DZ[j]; + ssize_t ix = x + DX[j]; + ssize_t iz = z + DZ[j]; float vertX = ix * CELL_SIZE; float vertZ = iz * CELL_SIZE; float terrainHeight = terrain->GetVertexGroundLevel(ix, iz); - float losMod = 1.0f; - /* - if (false) // XXX: need to implement this for new sim system - { - for(size_t k=0; k<4; k++) - { - ssize_t tx = ix - DX[k]; - ssize_t tz = iz - DZ[k]; - - if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) - { - ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); - if(s == LOS_EXPLORED && losMod > 0.7f) - losMod = 0.7f; - else if(s==LOS_UNEXPLORED && losMod > 0.0f) - losMod = 0.0f; - } - } - } - */ + float losMod; + if (losRevealAll || los.IsVisible(ix, iz)) + losMod = 1.0f; + else if (los.IsExplored(ix, iz)) + losMod = 0.7f; + else + losMod = 0.0f; if (fancy) { @@ -672,5 +660,3 @@ void TerrainRenderer::RenderWater() glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } - - diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h index 61bc7bc949..4a131bb88c 100644 --- a/source/simulation2/MessageTypes.h +++ b/source/simulation2/MessageTypes.h @@ -115,6 +115,9 @@ public: fixed turnLength; }; +/** + * Prepare for rendering a new frame (set up model positions etc). + */ class CMessageInterpolate : public CMessage { public: @@ -129,6 +132,10 @@ public: float offset; // range [0, 1] (inclusive); fractional time of current frame between previous/next simulation turns }; +/** + * Add renderable objects to the scene collector. + * Called after CMessageInterpolate. + */ class CMessageRenderSubmit : public CMessage { public: diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 05654de0eb..c12313cbbe 100644 --- a/source/simulation2/TypeList.h +++ b/source/simulation2/TypeList.h @@ -118,6 +118,9 @@ COMPONENT(Terrain) INTERFACE(UnitMotion) COMPONENT(UnitMotion) // must be after Obstruction +INTERFACE(Vision) +COMPONENT(Vision) + INTERFACE(Visual) COMPONENT(VisualActor) diff --git a/source/simulation2/components/CCmpMinimap.cpp b/source/simulation2/components/CCmpMinimap.cpp index f32f6b43ba..5ae4d08c4c 100644 --- a/source/simulation2/components/CCmpMinimap.cpp +++ b/source/simulation2/components/CCmpMinimap.cpp @@ -160,7 +160,7 @@ public: } } - virtual bool GetRenderData(u8& r, u8& g, u8& b, float& x, float& z) + virtual bool GetRenderData(u8& r, u8& g, u8& b, entity_pos_t& x, entity_pos_t& z) { if (!m_Active) return false; @@ -168,8 +168,8 @@ public: r = m_R; g = m_G; b = m_B; - x = m_X.ToFloat(); - z = m_Z.ToFloat(); + x = m_X; + z = m_Z; return true; } }; diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp index d905373277..9c9fb39d50 100644 --- a/source/simulation2/components/CCmpRangeManager.cpp +++ b/source/simulation2/components/CCmpRangeManager.cpp @@ -21,6 +21,7 @@ #include "ICmpRangeManager.h" #include "ICmpPosition.h" +#include "ICmpVision.h" #include "simulation2/MessageTypes.h" #include "simulation2/helpers/Render.h" #include "simulation2/helpers/Spatial.h" @@ -49,11 +50,11 @@ struct Query /** * Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players) - * into a 31-bit mask for quick set-membership tests. + * into a 32-bit mask for quick set-membership tests. */ static u32 CalcOwnerMask(i32 owner) { - if (owner >= -1 && owner < 30) + if (owner >= -1 && owner < 31) return 1 << (1+owner); else return 0; // owner was invalid @@ -64,13 +65,15 @@ static u32 CalcOwnerMask(i32 owner) */ struct EntityData { - EntityData() : ownerMask(CalcOwnerMask(-1)), inWorld(0) { } + EntityData() : retainInFog(0), owner(-1), inWorld(0) { } entity_pos_t x, z; - u32 ownerMask : 31; - u32 inWorld : 1; + entity_pos_t visionRange; + u8 retainInFog; // boolean + i8 owner; + u8 inWorld; // boolean }; -cassert(sizeof(EntityData) == 12); +cassert(sizeof(EntityData) == 16); /** * Functor for sorting entities by distance from a source point. @@ -101,12 +104,13 @@ private: }; /** - * Basic range manager implementation. + * Range manager implementation. * Maintains a list of all entities (and their positions and owners), which is used for * queries. * - * TODO: Ideally this would use a quadtree or something for more efficient spatial queries, - * since it's about O(n^2) in the total number of entities on the map. + * LOS implementation is based on the model described in GPG2. + * (TODO: would be nice to make it cleverer, so e.g. mountains and walls + * can block vision) */ class CCmpRangeManager : public ICmpRangeManager { @@ -131,10 +135,27 @@ public: SpatialSubdivision m_Subdivision; + // Range query state: tag_t m_QueryNext; // next allocated id std::map m_Queries; std::map m_EntityData; + // LOS state: + + bool m_LosRevealAll; + ssize_t m_TerrainVerticesPerSide; + + // 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 + // of units in a very small area. + // (Note we use vertexes, not tiles, to better match the renderer.) + // Lazily constructed when it's needed, to save memory in smaller games. + std::vector > m_LosPlayerCounts; + + // 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive) + std::vector m_LosState; + static const int MAX_LOS_PLAYER_ID = 16; + static std::string GetSchema() { return ""; @@ -150,6 +171,9 @@ public: // Initialise with bogus values (these will get replaced when // SetBounds is called) ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1)); + + m_LosRevealAll = false; + m_TerrainVerticesPerSide = 0; } virtual void Deinit(const CSimContext& UNUSED(context)) @@ -189,6 +213,14 @@ public: // use the default-constructed EntityData here EntityData entdata; + // Store the LOS data, if any + CmpPtr cmpVision(GetSimContext(), ent); + if (!cmpVision.null()) + { + entdata.visionRange = cmpVision->GetRange(); + entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0); + } + // Remember this entity m_EntityData.insert(std::make_pair(ent, entdata)); @@ -208,9 +240,18 @@ public: if (msgData.inWorld) { if (it->second.inWorld) - m_Subdivision.Move(ent, CFixedVector2D(it->second.x, it->second.z), CFixedVector2D(msgData.x, msgData.z)); + { + CFixedVector2D from(it->second.x, it->second.z); + CFixedVector2D to(msgData.x, msgData.z); + m_Subdivision.Move(ent, from, to); + LosMove(it->second.owner, it->second.visionRange, from, to); + } else - m_Subdivision.Add(ent, CFixedVector2D(msgData.x, msgData.z)); + { + CFixedVector2D to(msgData.x, msgData.z); + m_Subdivision.Add(ent, to); + LosAdd(it->second.owner, it->second.visionRange, to); + } it->second.inWorld = 1; it->second.x = msgData.x; @@ -219,7 +260,11 @@ public: else { if (it->second.inWorld) - m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z)); + { + CFixedVector2D from(it->second.x, it->second.z); + m_Subdivision.Remove(ent, from); + LosRemove(it->second.owner, it->second.visionRange, from); + } it->second.inWorld = 0; it->second.x = entity_pos_t::Zero(); @@ -239,7 +284,14 @@ public: if (it == m_EntityData.end()) break; - it->second.ownerMask = CalcOwnerMask(msgData.to); + if (it->second.inWorld) + { + CFixedVector2D pos(it->second.x, it->second.z); + LosRemove(it->second.owner, it->second.visionRange, pos); + LosAdd(msgData.to, it->second.visionRange, pos); + } + + it->second.owner = msgData.to; break; } @@ -276,10 +328,19 @@ public: } } - virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) + virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices) { debug_assert(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet ResetSubdivisions(x1, z1); + + m_TerrainVerticesPerSide = vertices; + m_LosPlayerCounts.clear(); + m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1); + m_LosState.clear(); + m_LosState.resize(vertices*vertices); + + for (std::map::iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); } void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1) @@ -482,7 +543,7 @@ private: debug_assert(it != m_EntityData.end()); // Quick filter to ignore entities with the wrong owner - if (!(it->second.ownerMask & q.ownersMask)) + if (!(CalcOwnerMask(it->second.owner) & q.ownersMask)) continue; // Restrict based on precise location @@ -573,6 +634,158 @@ private: for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i) collector.Submit(&m_DebugOverlayLines[i]); } + + + // LOS implementation: + + virtual CLosQuerier GetLosQuerier(int player) + { + return CLosQuerier(player, m_LosState, m_TerrainVerticesPerSide); + } + + virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player) + { + // (We can't use m_EntityData since this needs to handle LOCAL entities too) + + CmpPtr cmpPosition(GetSimContext(), ent); + if (cmpPosition.null() || !cmpPosition->IsInWorld()) + return VIS_HIDDEN; + + if (m_LosRevealAll) + return VIS_VISIBLE; + + CFixedVector2D pos = cmpPosition->GetPosition2D(); + + CLosQuerier los(player, m_LosState, m_TerrainVerticesPerSide); + + int i = (pos.X / (int)CELL_SIZE).ToInt_RoundToNearest(); + int j = (pos.Y / (int)CELL_SIZE).ToInt_RoundToNearest(); + + if (los.IsVisible(i, j)) + return VIS_VISIBLE; + + if (los.IsExplored(i, j)) + { + CmpPtr cmpVision(GetSimContext(), ent); + if (!cmpVision.null() && cmpVision->GetRetainInFog()) + return VIS_FOGGED; + } + + return VIS_HIDDEN; + } + + virtual void SetLosRevealAll(bool enabled) + { + // Eventually we might want this to be a per-player flag (which is why + // GetLosRevealAll takes a player argument), but currently it's just a + // global setting since I can't quite work out where per-player would be useful + + m_LosRevealAll = enabled; + } + + virtual bool GetLosRevealAll(int UNUSED(player)) + { + return m_LosRevealAll; + } + + /** + * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive). + * amount is +1 or -1. + */ + inline void LosUpdateStripHelper(u8 owner, ssize_t i0, ssize_t i1, ssize_t j, int amount, std::vector& counts) + { + for (ssize_t i = i0; i <= i1; ++i) + { + ssize_t idx = j*m_TerrainVerticesPerSide + i; + + // Increasing from zero to non-zero - move from unexplored/explored to visible+explored + if (counts[idx] == 0 && amount > 0) + { + m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1))); + } + + counts[idx] += amount; + + // Decreasing from non-zero to zero - move from visible+explored to explored + if (counts[idx] == 0 && amount < 0) + { + m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1))); + } + } + } + + /** + * Update the LOS state of tiles within a given circular range. + * Assumes owner is in the valid range. + */ + inline void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos, int amount) + { + if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet + return; + + PROFILE("LosUpdateHelper"); + + std::vector& counts = m_LosPlayerCounts.at(owner); + + // Lazy initialisation of counts: + if (counts.empty()) + counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); + + // 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. + + // 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) + ssize_t j0 = ((pos.Y - visionRange)/(int)CELL_SIZE).ToInt_RoundToInfinity(); + ssize_t j1 = ((pos.Y + visionRange)/(int)CELL_SIZE).ToInt_RoundToNegInfinity(); + ssize_t j0clamp = std::max(j0, (ssize_t)1); + ssize_t j1clamp = std::min(j1, m_TerrainVerticesPerSide-2); + + entity_pos_t xscale = pos.X / (int)CELL_SIZE; + entity_pos_t yscale = pos.Y / (int)CELL_SIZE; + entity_pos_t rsquared = (visionRange / (int)CELL_SIZE).Square(); + + for (ssize_t j = j0clamp; j <= j1clamp; ++j) + { + // Compute values such that (i - x)^2 + (j - y)^2 <= r^2 + // (TODO: is this sqrt slow? can we optimise it?) + entity_pos_t di = (rsquared - (entity_pos_t::FromInt(j) - yscale).Square()).Sqrt(); + ssize_t i0 = (xscale - di).ToInt_RoundToInfinity(); + ssize_t i1 = (xscale + di).ToInt_RoundToNegInfinity(); + + ssize_t i0clamp = std::max(i0, (ssize_t)1); + ssize_t i1clamp = std::min(i1, m_TerrainVerticesPerSide-2); + LosUpdateStripHelper(owner, i0clamp, i1clamp, j, amount, counts); + } + } + + void LosAdd(i8 owner, entity_pos_t visionRange, CFixedVector2D pos) + { + if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) + return; + + LosUpdateHelper(owner, visionRange, pos, 1); + } + + void LosRemove(i8 owner, entity_pos_t visionRange, CFixedVector2D pos) + { + if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) + return; + + LosUpdateHelper(owner, visionRange, pos, -1); + } + + void LosMove(i8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to) + { + if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID) + return; + + // TODO: we could optimise this by only modifying tiles that changed + LosRemove(owner, visionRange, from); + LosAdd(owner, visionRange, to); + } }; REGISTER_COMPONENT_TYPE(RangeManager) diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index c562cbbdee..0fae9a8fdf 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -433,9 +433,15 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i if (out.GetChild("Entity").GetChild("Obstruction").IsOk()) CParamNode::LoadXMLString(out, ""); - // Corpses should include decay components and un-inactivate them + if (!corpse) + { + // Previews should always be visible in fog-of-war + CParamNode::LoadXMLString(out, "0true"); + } + if (corpse) { + // Corpses should include decay components and un-inactivate them if (out.GetChild("Entity").GetChild("Decay").IsOk()) CParamNode::LoadXMLString(out, ""); } @@ -459,6 +465,7 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode permittedComponentTypes.insert("Decay"); permittedComponentTypes.insert("Cost"); permittedComponentTypes.insert("Sound"); + permittedComponentTypes.insert("Vision"); CParamNode::LoadXMLString(out, ""); out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); @@ -478,4 +485,9 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode // Don't provide population bonuses yet (but still do take up population cost) if (out.GetChild("Entity").GetChild("Cost").IsOk()) CParamNode::LoadXMLString(out, "0"); + + // Foundations should be visible themselves in fog-of-war if their base template is, + // but shouldn't have any vision range + if (out.GetChild("Entity").GetChild("Vision").IsOk()) + CParamNode::LoadXMLString(out, "0"); } diff --git a/source/simulation2/components/CCmpTerrain.cpp b/source/simulation2/components/CCmpTerrain.cpp index 8bb322552a..a5f8bdc883 100644 --- a/source/simulation2/components/CCmpTerrain.cpp +++ b/source/simulation2/components/CCmpTerrain.cpp @@ -94,7 +94,8 @@ public: { cmpRangeManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(), entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), - entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE)); + entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), + m_Terrain->GetVerticesPerSide()); } } diff --git a/source/simulation2/components/CCmpVision.cpp b/source/simulation2/components/CCmpVision.cpp new file mode 100644 index 0000000000..0cd0022cc7 --- /dev/null +++ b/source/simulation2/components/CCmpVision.cpp @@ -0,0 +1,76 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "simulation2/system/Component.h" +#include "ICmpVision.h" + +class CCmpVision: public ICmpVision +{ +public: + static void ClassInit(CComponentManager& componentManager) + { + } + + DEFAULT_COMPONENT_ALLOCATOR(Vision) + + entity_pos_t m_Range; + bool m_RetainInFog; + + static std::string GetSchema() + { + return + "" + "" + "" + "" + "" + ""; + } + + virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode) + { + m_Range = paramNode.GetChild("Range").ToFixed(); + m_RetainInFog = paramNode.GetChild("RetainInFog").ToBool(); + } + + virtual void Deinit(const CSimContext& UNUSED(context)) + { + } + + virtual void Serialize(ISerializer& UNUSED(serialize)) + { + } + + virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) + { + Init(context, paramNode); + } + + virtual entity_pos_t GetRange() + { + return m_Range; + } + + virtual bool GetRetainInFog() + { + return m_RetainInFog; + } +}; + +REGISTER_COMPONENT_TYPE(Vision) diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index d5ccbdb7ed..4bbcd5499f 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -21,6 +21,8 @@ #include "ICmpVisual.h" #include "ICmpPosition.h" +#include "ICmpRangeManager.h" +#include "ICmpVision.h" #include "simulation2/MessageTypes.h" #include "graphics/Frustum.h" @@ -52,7 +54,7 @@ public: std::wstring m_ActorName; CUnit* m_Unit; - bool m_Hidden; // only valid between Interpolate and RenderSubmit + ICmpRangeManager::ELosVisibility m_Visibility; // only valid between Interpolate and RenderSubmit // Current animation state float m_AnimRunThreshold; // if non-zero this is the special walk/run mode @@ -358,11 +360,15 @@ void CCmpVisualActor::Interpolate(float frameTime, float frameOffset) // Disable rendering of the unit if it has no position if (!cmpPosition->IsInWorld()) { - m_Hidden = true; + m_Visibility = ICmpRangeManager::VIS_HIDDEN; return; } - m_Hidden = false; + CmpPtr cmpRangeManager(GetSimContext(), SYSTEM_ENTITY); + m_Visibility = cmpRangeManager->GetLosVisibility(GetEntityId(), GetSimContext().GetCurrentDisplayedPlayer()); + + // Even if HIDDEN due to LOS, we need to set up the transforms + // so that projectiles will be launched from the right place bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater; @@ -377,11 +383,9 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr if (m_Unit == NULL) return; - if (m_Hidden) + if (m_Visibility == ICmpRangeManager::VIS_HIDDEN) return; - // TODO: need to think about things like LOS here - CModel& model = m_Unit->GetModel(); model.ValidatePosition(); @@ -389,7 +393,10 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBounds())) return; - // TODO: model->SetShadingColor(CColor(1.0f, 1.0f, 1.0f, 1.0f) if visible, CColor(0.7f, 0.7f, 0.7f, 1.0f) if hidden in FOW) + if (m_Visibility == ICmpRangeManager::VIS_FOGGED) + model.SetShadingColor(CColor(0.7f, 0.7f, 0.7f, 1.0f)); + else + model.SetShadingColor(CColor(1.0f, 1.0f, 1.0f, 1.0f)); collector.SubmitRecursive(&model); } diff --git a/source/simulation2/components/ICmpMinimap.h b/source/simulation2/components/ICmpMinimap.h index 56a3cef765..e2ebf9b46b 100644 --- a/source/simulation2/components/ICmpMinimap.h +++ b/source/simulation2/components/ICmpMinimap.h @@ -20,6 +20,8 @@ #include "simulation2/system/Interface.h" +#include "simulation2/helpers/Position.h" + /** * Per-unit minimap data. */ @@ -31,7 +33,7 @@ public: * If it should not be drawn, returns false; otherwise the arguments are set * to the colour and world position. */ - virtual bool GetRenderData(u8& r, u8& g, u8& b, float& x, float& z) = 0; + virtual bool GetRenderData(u8& r, u8& g, u8& b, entity_pos_t& x, entity_pos_t& z) = 0; DECLARE_INTERFACE_TYPE(Minimap) }; diff --git a/source/simulation2/components/ICmpRangeManager.cpp b/source/simulation2/components/ICmpRangeManager.cpp index 309248f642..1c461b31a9 100644 --- a/source/simulation2/components/ICmpRangeManager.cpp +++ b/source/simulation2/components/ICmpRangeManager.cpp @@ -29,4 +29,5 @@ DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableAct DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t) DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t) DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool) +DEFINE_INTERFACE_METHOD_1("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, bool) END_INTERFACE_WRAPPER(RangeManager) diff --git a/source/simulation2/components/ICmpRangeManager.h b/source/simulation2/components/ICmpRangeManager.h index e65ee934de..f0a9261e19 100644 --- a/source/simulation2/components/ICmpRangeManager.h +++ b/source/simulation2/components/ICmpRangeManager.h @@ -22,8 +22,14 @@ #include "simulation2/helpers/Position.h" +#include "graphics/Terrain.h" // for CELL_SIZE + /** - * Provides efficient range-based queries of the game world. + * Provides efficient range-based queries of the game world, + * and also LOS-based effects (fog of war). + * + * (These are somewhat distinct concepts but they share a lot of the implementation, + * so for efficiency they're combined into this class.) * * Possible use cases: * - combat units need to detect targetable enemies entering LOS, so they can choose @@ -67,8 +73,9 @@ public: * Set the bounds of the world. * Entities should not be outside the bounds (else efficiency will suffer). * @param x0,z0,x1,z1 Coordinates of the corners of the world + * @param vertices Number of terrain vertices per side */ - virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0; + virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices) = 0; /** * Execute a passive query. @@ -123,6 +130,98 @@ public: */ virtual void SetDebugOverlay(bool enabled) = 0; + // LOS interface: + + enum ELosState + { + LOS_UNEXPLORED = 0, + LOS_EXPLORED = 1, + LOS_VISIBLE = 2, + LOS_MASK = 3 + }; + + enum ELosVisibility + { + VIS_HIDDEN, + VIS_FOGGED, + VIS_VISIBLE + }; + + /** + * Object providing efficient abstracted access to the LOS state. + * This depends on some implementation details of CCmpRangeManager. + * + * This *ignores* the GetLosRevealAll flag - callers should check that explicitly. + */ + class CLosQuerier + { + private: + friend class CCmpRangeManager; + + CLosQuerier(int player, const std::vector& data, ssize_t verticesPerSide) : + m_Data(data), m_VerticesPerSide(verticesPerSide) + { + if (player > 0 && player <= 16) + m_PlayerMask = LOS_MASK << (2*(player-1)); + else + m_PlayerMask = 0; + } + + public: + /** + * Returns whether the given vertex is visible (i.e. is within a unit's LOS). + * i and j must be in the range [0, verticesPerSide). + */ + inline bool IsVisible(ssize_t i, ssize_t j) + { +#ifndef NDEBUG + debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide); +#endif + // Check high bit of each bit-pair + return (m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0xAAAAAAAAu; + } + + /** + * Returns whether the given vertex is explored (i.e. was (or still is) within a unit's LOS). + * i and j must be in the range [0, verticesPerSide). + */ + inline bool IsExplored(ssize_t i, ssize_t j) + { +#ifndef NDEBUG + debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide); +#endif + // Check low bit of each bit-pair + return (m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0x55555555u; + } + + private: + u32 m_PlayerMask; + const std::vector& m_Data; + ssize_t m_VerticesPerSide; + }; + + /** + * Returns a CLosQuerier for checking whether vertex positions are visible to the given player. + */ + virtual CLosQuerier GetLosQuerier(int player) = 0; + + /** + * Returns the visibility status of the given entity, with respect to the given player. + * Returns VIS_HIDDEN if the entity doesn't exist or is not in the world. + * This respects the GetLosRevealAll flag. + */ + virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player) = 0; + + /** + * Set globally whether the whole map should be made visible. + */ + virtual void SetLosRevealAll(bool enabled) = 0; + + /** + * Returns whether the whole map has been made visible to the given player. + */ + virtual bool GetLosRevealAll(int player) = 0; + DECLARE_INTERFACE_TYPE(RangeManager) }; diff --git a/source/simulation2/components/ICmpVision.cpp b/source/simulation2/components/ICmpVision.cpp new file mode 100644 index 0000000000..dcd1604682 --- /dev/null +++ b/source/simulation2/components/ICmpVision.cpp @@ -0,0 +1,27 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ICmpVision.h" + +#include "simulation2/system/InterfaceScripted.h" + +BEGIN_INTERFACE_WRAPPER(Vision) +DEFINE_INTERFACE_METHOD_0("GetRange", entity_pos_t, ICmpVision, GetRange) +DEFINE_INTERFACE_METHOD_0("GetRetainInFog", bool, ICmpVision, GetRetainInFog) +END_INTERFACE_WRAPPER(Vision) diff --git a/source/simulation2/components/ICmpVision.h b/source/simulation2/components/ICmpVision.h new file mode 100644 index 0000000000..2f4174cbd6 --- /dev/null +++ b/source/simulation2/components/ICmpVision.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_ICMPVISION +#define INCLUDED_ICMPVISION + +#include "simulation2/system/Interface.h" + +#include "simulation2/helpers/Position.h" + +/** + * Vision (LOS etc) interface (typically implemented by a scripted component). + */ +class ICmpVision : public IComponent +{ +public: + virtual entity_pos_t GetRange() = 0; + + virtual bool GetRetainInFog() = 0; + + DECLARE_INTERFACE_TYPE(Vision) +}; + +#endif // INCLUDED_ICMPVISION diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index ef742eabc7..044a756bc9 100644 --- a/source/simulation2/helpers/Selection.cpp +++ b/source/simulation2/helpers/Selection.cpp @@ -22,14 +22,18 @@ #include "graphics/Camera.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpOwnership.h" +#include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpVisual.h" -std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY) +std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player) { CVector3D origin, dir; camera.BuildCameraRay(screenX, screenY, origin, dir); + CmpPtr cmpRangeManager(simulation, SYSTEM_ENTITY); + debug_assert(!cmpRangeManager.null()); + std::vector > hits; // (dist^2, entity) pairs const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable); @@ -37,7 +41,9 @@ std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simu { entity_id_t ent = it->first; - // TODO: ought to filter out units hidden by current player's LOS + // Ignore entities hidden by LOS + if (cmpRangeManager->GetLosVisibility(ent, player) == ICmpRangeManager::VIS_HIDDEN) + continue; CmpPtr cmpVisual(simulation.GetSimContext(), ent); if (cmpVisual.null()) @@ -81,6 +87,9 @@ std::vector EntitySelection::PickEntitiesInRect(CSimulation2& simul std::vector hitEnts; + // (We don't bother doing LOS checks for these units, since we assume 'owner' + // will be the current local player and all their own units will always be visible) + const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable); for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h index 3bf631faf1..c2021a67b2 100644 --- a/source/simulation2/helpers/Selection.h +++ b/source/simulation2/helpers/Selection.h @@ -36,8 +36,9 @@ namespace EntitySelection /** * Finds all selectable entities under the given screen coordinates. * Returns list ordered by closeness of picking, closest first. + * Restricted to entities in the LOS of @p player, but with any owner. */ -std::vector PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY); +std::vector PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player); /** * Finds all selectable entities within the given screen coordinate rectangle, diff --git a/source/simulation2/system/SimContext.cpp b/source/simulation2/system/SimContext.cpp index df89692d05..f2e32a45e5 100644 --- a/source/simulation2/system/SimContext.cpp +++ b/source/simulation2/system/SimContext.cpp @@ -21,6 +21,8 @@ #include "ComponentManager.h" +#include "ps/Game.h" + CSimContext::CSimContext() : m_ComponentManager(NULL), m_UnitManager(NULL), m_Terrain(NULL) { @@ -63,3 +65,10 @@ ScriptInterface& CSimContext::GetScriptInterface() const { return GetComponentManager().GetScriptInterface(); } + +int CSimContext::GetCurrentDisplayedPlayer() const +{ + if (!g_Game) + return -1; + return g_Game->GetPlayerID(); +} diff --git a/source/simulation2/system/SimContext.h b/source/simulation2/system/SimContext.h index aa6f210fd5..f9214620cd 100644 --- a/source/simulation2/system/SimContext.h +++ b/source/simulation2/system/SimContext.h @@ -43,6 +43,12 @@ public: ScriptInterface& GetScriptInterface() const; + /** + * Returns the player ID that the current display is being rendered for. + * Currently relies on g_Game being initialised (evil globals...) + */ + int GetCurrentDisplayedPlayer() const; + private: CComponentManager* m_ComponentManager; CUnitManager* m_UnitManager; diff --git a/source/simulation2/tests/test_CmpTemplateManager.h b/source/simulation2/tests/test_CmpTemplateManager.h index efb16922e4..8352f8ca20 100644 --- a/source/simulation2/tests/test_CmpTemplateManager.h +++ b/source/simulation2/tests/test_CmpTemplateManager.h @@ -81,15 +81,26 @@ public: const CParamNode* preview = tempMan->LoadTemplate(ent2, "preview|unit", -1); TS_ASSERT(preview != NULL); - TS_ASSERT_WSTR_EQUALS(preview->ToXML(), L"0uprightfalseexample"); + TS_ASSERT_WSTR_EQUALS(preview->ToXML(), + L"0uprightfalse" + L"0true" + L"example"); const CParamNode* previewobstruct = tempMan->LoadTemplate(ent2, "preview|unitobstruct", -1); TS_ASSERT(previewobstruct != NULL); - TS_ASSERT_WSTR_EQUALS(previewobstruct->ToXML(), L"1.00uprightfalseexample"); + TS_ASSERT_WSTR_EQUALS(previewobstruct->ToXML(), + L"1.0" + L"" + L"0uprightfalse" + L"0true" + L"example"); const CParamNode* previewactor = tempMan->LoadTemplate(ent2, "preview|actor|example2", -1); TS_ASSERT(previewactor != NULL); - TS_ASSERT_WSTR_EQUALS(previewactor->ToXML(), L"0uprightfalseexample2"); + TS_ASSERT_WSTR_EQUALS(previewactor->ToXML(), + L"0uprightfalse" + L"0true" + L"example2"); } void test_LoadTemplate_scriptcache() diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index ff8155526a..d2f236d275 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.cpp +++ b/source/tools/atlas/GameInterface/ActorViewer.cpp @@ -40,6 +40,8 @@ #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" +#include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpVisual.h" @@ -116,6 +118,15 @@ ActorViewer::ActorViewer() // Start the simulation m.Simulation2.LoadDefaultScripts(); m.Simulation2.ResetState(); + + // Tell the simulation we've already loaded the terrain + CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY); + if (!cmpTerrain.null()) + cmpTerrain->ReloadTerrain(); + + CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY); + if (!cmpRangeManager.null()) + cmpRangeManager->SetLosRevealAll(true); } ActorViewer::~ActorViewer() diff --git a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp index 2aa6643a8a..2b632e8075 100644 --- a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -37,6 +37,7 @@ #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" +#include "simulation2/components/ICmpRangeManager.h" namespace { @@ -66,6 +67,11 @@ namespace LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); + + // Disable fog-of-war + CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (!cmpRangeManager.null()) + cmpRangeManager->SetLosRevealAll(true); } }