From 5efdb72dc7cf003601d4307dcddc31625447dafb Mon Sep 17 00:00:00 2001 From: alyokhin Date: Thu, 11 Jun 2020 19:26:34 +0300 Subject: [PATCH] Added CFogOfWar - class implementing enhanced FoW --- CMakeLists.txt | 2 + src/include/fow.h | 134 +++++++++++++++++++ src/map/fow.cpp | 326 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 462 insertions(+) create mode 100644 src/include/fow.h create mode 100644 src/map/fow.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b1e19f8ee..3e8df5e72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,7 @@ source_group(guichan FILES ${guichan_SRCS}) set(map_SRCS src/map/fov.cpp + src/map/fow.cpp src/map/map.cpp src/map/map_draw.cpp src/map/map_fog.cpp @@ -530,6 +531,7 @@ set(stratagus_generic_HDRS src/include/online_service.h src/include/font.h src/include/fov.h + src/include/fow.h src/include/game.h src/include/icons.h src/include/interface.h diff --git a/src/include/fow.h b/src/include/fow.h new file mode 100644 index 000000000..f5c9117da --- /dev/null +++ b/src/include/fow.h @@ -0,0 +1,134 @@ +// _________ __ __ +// / _____// |_____________ _/ |______ ____ __ __ ______ +// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/ +// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ | +// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ > +// \/ \/ \//_____/ \/ +// ______________________ ______________________ +// T H E W A R B E G I N S +// Stratagus - A free fantasy real time strategy game engine +// +/**@name fow.h - The fog of war headerfile. */ +// +// (c) Copyright 2020 Alyokhin +// +// This program 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; only version 2 of the License. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#ifndef __FOW_H__ +#define __FOW_H__ + +#include +#include +#include "player.h" +#include "video.h" +//#include "viewport.h" + +//@{ + + + + +class CViewport; + +/*---------------------------------------------------------------------------- +-- Declarations +----------------------------------------------------------------------------*/ + +class CFogOfWar +{ +public: + enum MapEdges { cNone = 0, cBottom = 0b001, cRight = 0b010, cBottomRight = 0b011, cUpper = 0b0100, cLeft = 0b1000}; + enum VisionType { cUnseen = 0, cExplored = 0b001, cVisible = 0b010, cCached = 0b100}; + + ~CFogOfWar() + { + Clean(); + } + + void Refresh(const CViewport &viewport, const CPlayer &thisPlayer); + + static void InitCache(); + static void CleanCache(); + static void ResetCache(); + +private: + void Clean(); + void AdjustToViewport(const CViewport &viewport); + void Render(const uint8_t *alphaTexture, const CViewport &viewport); + void GenerateFog(const CViewport &viewport, const CPlayer &thisPlayer); + void UpscaleFog(uint8_t *alphaTexture, const CViewport &viewport); + uint8_t DeterminePattern(intptr_t index, const uint8_t visFlag); + void FillUpscaledRec(uint32_t *texture, const int textureWidth, intptr_t index, const uint8_t patternVisible, + const uint8_t patternExplored); + +public: + +private: + /// cached vision table. Tiles filled only once even if it present in the several viewports + static std::vector VisTableCache; + static intptr_t VisCache_Index0; /// index in the cached vision table for [0:0] tile + static size_t VisCacheWidth; /// width of the cached vision table + + std::vector FogTexture; // Upscaled fog texture + SDL_Surface *WorkSurface {nullptr}; + SDL_Surface *FogSurface {nullptr}; + + uint16_t RenderWidth {0}; // In pixels + uint16_t RenderHeight {0}; // In pixels + uint16_t FogTextureWidth {0}; + uint16_t FogTextureHeight {0}; + + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + + static constexpr uint32_t UpscaleTable[16][4] { {0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F}, // 0 00:00 + {0x7F7F7F7F, 0x7F7F7F7F, 0x3F7F7F7F, 0x003F7F7F}, // 1 00:01 + {0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F3F, 0x7F7F3F00}, // 2 00:10 + {0x7F7F7F7F, 0x7F7F7F7F, 0x3F3F3F3F, 0x00000000}, // 3 00:11 + {0x003F7F7F, 0x3F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F}, // 4 01:00 + {0x003F7F7F, 0x003F7F7F, 0x003F7F7F, 0x003F7F7F}, // 5 01:01 + {0x003F7F7F, 0x3F7F7F7F, 0x7F7F7F3F, 0x7F7F3F00}, // 6 01:10 + {0x00003F7F, 0x0000003F, 0x00000000, 0x00000000}, // 7 01:11 + {0x7F7F3F00, 0x7F7F7F3F, 0x7F7F7F7F, 0x7F7F7F7F}, // 8 10:00 + {0x7F7F3F00, 0x7F7F7F3F, 0x3F7F7F7F, 0x003F7F7F}, // 9 10:01 + {0x7F7F3F00, 0x7F7F3F00, 0x7F7F3F00, 0x7F7F3F00}, // A 10:10 + {0x7F3F0000, 0x3F000000, 0x00000000, 0x00000000}, // B 10:11 + {0x00000000, 0x3F3F3F3F, 0x7F7F7F7F, 0x7F7F7F7F}, // C 11:00 + {0x00000000, 0x00000000, 0x0000003F, 0x00003F7F}, // D 11:01 + {0x00000000, 0x00000000, 0x3F000000, 0x7F3F0000}, // E 11:10 + {0x00000000, 0x00000000, 0x00000000, 0x00000000} }; // F 11:11 + +#else // big endian + static constexpr uint32_t UpscaleTable[16][4] { {0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F}, // 0 00:00 + {0x7F7F7F7F, 0x7F7F7F7F, 0x7F7F7F3F, 0x7F7F3F00}, // 1 00:01 + {0x7F7F7F7F, 0x7F7F7F7F, 0x3F7F7F7F, 0x003F7F7F}, // 2 00:10 + {0x7F7F7F7F, 0x7F7F7F7F, 0x00000000, 0x00000000}, // 3 00:11 + {0x7F7F3F00, 0x7F7F7F3F, 0x7F7F7F7F, 0x7F7F7F7F}, // 4 01:00 + {0x7F7F0000, 0x7F7F0000, 0x7F7F0000, 0x7F7F0000}, // 5 01:01 + {0x7F7F3F00, 0x7F7F7F3F, 0x3F7F7F7F, 0x003F7F7F}, // 6 01:10 + {0x7F3F0000, 0x3F000000, 0x00000000, 0x00000000}, // 7 01:11 + {0x003F7F7F, 0x3F7F7F7F, 0x7F7F7F7F, 0x7F7F7F7F}, // 8 10:00 + {0x003F7F7F, 0x3F7F7F7F, 0x7F7F7F3F, 0x7F7F3F00}, // 9 10:01 + {0x00007F7F, 0x00007F7F, 0x00007F7F, 0x00007F7F}, // A 10:10 + {0x00003F7F, 0x0000003F, 0x00000000, 0x00000000}, // B 10:11 + {0x00000000, 0x00000000, 0x7F7F7F7F, 0x7F7F7F7F}, // C 11:00 + {0x00000000, 0x00000000, 0x3F000000, 0x7F3F0000}, // D 11:01 + {0x00000000, 0x00000000, 0x0000003F, 0x00003F7F}, // E 11:10 + {0x00000000, 0x00000000, 0x00000000, 0x00000000} }; // F 11:11 +#endif +}; + +#endif // !__FOW_H__ diff --git a/src/map/fow.cpp b/src/map/fow.cpp new file mode 100644 index 000000000..b3297cd38 --- /dev/null +++ b/src/map/fow.cpp @@ -0,0 +1,326 @@ +// _________ __ __ +// / _____// |_____________ _/ |______ ____ __ __ ______ +// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/ +// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ | +// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ > +// \/ \/ \//_____/ \/ +// ______________________ ______________________ +// T H E W A R B E G I N S +// Stratagus - A free fantasy real time strategy game engine +// +/**@name fow.cpp - The fog of war. */ +// +// (c) Copyright 2020 by Alyokhin +// +// This program 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; only version 2 of the License. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +//@{ + +/*---------------------------------------------------------------------------- +-- Includes +----------------------------------------------------------------------------*/ + +#include +#include + +#include "stratagus.h" + +#include "fow.h" +#include "map.h" +#include "player.h" +#include "tile.h" +#include "viewport.h" + +/*---------------------------------------------------------------------------- +-- Defines +----------------------------------------------------------------------------*/ + + +/*---------------------------------------------------------------------------- +-- Variables +----------------------------------------------------------------------------*/ + +std::vector CFogOfWar::VisTableCache; + +intptr_t CFogOfWar::VisCache_Index0 = 0; +size_t CFogOfWar::VisCacheWidth = 0; + +/*---------------------------------------------------------------------------- +-- Functions +----------------------------------------------------------------------------*/ +void CFogOfWar::InitCache() +{ + /// +1 to the top & left and +1 to the bottom & right for 4x scale algorithm purposes, + /// +1 to the bottom & right because of UI.MapArea.ScrollPadding + /// Extra tiles will always be VisionType::cNone. + CFogOfWar::VisCacheWidth = Map.Info.MapHeight + 3; + CFogOfWar::VisCache_Index0 = CFogOfWar::VisCacheWidth + 1; + + size_t tableSize = CFogOfWar::VisCacheWidth * (Map.Info.MapHeight + 3); + VisTableCache.clear(); + VisTableCache.resize(tableSize); + ResetCache(); +} + +void CFogOfWar::CleanCache() +{ + VisTableCache.clear(); + CFogOfWar::VisCacheWidth = 0; + CFogOfWar::VisCache_Index0 = 0; + +} + +void CFogOfWar::ResetCache() +{ + std::fill(VisTableCache.begin(), VisTableCache.end(), 0); +} + +/** +** Adjust viewport +** +** @param viewport viewport to adjust fog of war for +** +*/ +void CFogOfWar::AdjustToViewport(const CViewport &viewport) +{ + /// TODO: Add support for maps which smaller than viewport + Assert(viewport.MapWidth <= Map.Info.MapWidth && viewport.MapHeight <= Map.Info.MapHeight); + + Clean(); + + /// +1 to the top & left and +1 to the bottom & right for 4x scale algorithm purposes, + /// +1 to the bottom & right because of UI.MapArea.ScrollPadding + uint16_t mapAreaWidth = viewport.MapWidth + 3; + uint16_t mapAreaHeight = viewport.MapHeight + 3; + + FogTextureWidth = mapAreaWidth * 4; + FogTextureHeight = mapAreaHeight * 4; + + FogTexture.resize(FogTextureWidth * FogTextureHeight); + std::fill(FogTexture.begin(), FogTexture.end(), 0xFF); + + RenderWidth = viewport.BottomRightPos.x - viewport.TopLeftPos.x + 1; + RenderHeight = viewport.BottomRightPos.y - viewport.TopLeftPos.y + 1; + + WorkSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, FogTextureWidth, FogTextureHeight, + 32, RMASK, GMASK, BMASK, AMASK); + SDL_SetSurfaceBlendMode(WorkSurface, SDL_BLENDMODE_NONE); + + FogSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, mapAreaWidth * PixelTileSize.x, + mapAreaHeight * PixelTileSize.y, + 32, RMASK, GMASK, BMASK, AMASK); + SDL_SetSurfaceBlendMode(FogSurface, SDL_BLENDMODE_BLEND); + SDL_FillRect(FogSurface, NULL, SDL_MapRGBA(FogSurface->format, 0, 0, 0, 0)); +} + +void CFogOfWar::Clean() +{ + FogTexture.clear(); + SDL_FreeSurface(WorkSurface); /// It is safe to pass NULL to this function. + WorkSurface = nullptr; + SDL_FreeSurface(FogSurface); /// It is safe to pass NULL to this function. + FogSurface = nullptr; +} + +/** +** Generate and render fog of war for certain viewport. +** +** @param viewport viewport to refresh fog of war for +** @param thisPlayer player to refresh fog of war for +*/ +void CFogOfWar::Refresh(const CViewport &viewport, const CPlayer &thisPlayer) +{ + // flags must redraw or not + if (ReplayRevealMap) { + return; + } + if ((viewport.BottomRightPos.x - viewport.TopLeftPos.x + 1) != RenderWidth + || (viewport.BottomRightPos.y - viewport.TopLeftPos.y + 1) != RenderHeight ) { + + AdjustToViewport(viewport); + } + + GenerateFog(viewport, thisPlayer); + Render(FogTexture.data(), viewport); +} + +/** +** Render fog of war texture in certain viewport. +** +** @param alphaTexture texture with alpha mask to render +** @param viewport viewport to render fog of war texture in +*/ + void CFogOfWar::Render(const uint8_t *alphaTexture, const CViewport &viewport) +{ + + /// Clear fog surface + SDL_FillRect(FogSurface, NULL, SDL_MapRGBA(FogSurface->format, 0, 0, 0, 0)); + + /// convert fog texture into surface to be able to scale it with SDL_BlitScaled + intptr_t renderIndex = 0; + for (uint16_t y = 0; y < WorkSurface->h; y++) { + for (uint16_t x = 0; x < WorkSurface->w; x++) { + reinterpret_cast(WorkSurface->pixels)[renderIndex] = SDL_MapRGBA(WorkSurface->format, 0, 0, 0, (alphaTexture[renderIndex])); + renderIndex++; + } + } + SDL_BlitScaled(WorkSurface, NULL, FogSurface, NULL); + + SDL_Rect screenRect; + screenRect.x = viewport.TopLeftPos.x; + screenRect.y = viewport.TopLeftPos.y; + screenRect.w = RenderWidth; + screenRect.h = RenderHeight; + + SDL_Rect fogRect; + fogRect.x = viewport.Offset.x + PixelTileSize.x / 2; + fogRect.y = viewport.Offset.y + PixelTileSize.y / 2; + fogRect.w = screenRect.w; + fogRect.h = screenRect.h; + SDL_BlitSurface(FogSurface, &fogRect, TheScreen, &screenRect); +} + +/** +** Generate fog of war texture for certain viewport. +** +** @param viewport viewport to generate fog of war for +** @param thisPlayer player to generate fog of war for +*/ +void CFogOfWar::GenerateFog(const CViewport &viewport, const CPlayer &thisPlayer) +{ + // Update for visibility all tile in viewport + /// +1 to the top & left and +1 to the bottom & right for 4x scale algorithm purposes, + /// +1 to the bottom & right because of UI.MapArea.ScrollPadding + int beginCol = std::max(viewport.MapPos.x - 1, 0); + int beginRow = std::max(viewport.MapPos.y - 1, 0); + int rightEdge = std::min(viewport.MapPos.x + viewport.MapWidth + 2, Map.Info.MapWidth); + int bottomEdge = std::min(viewport.MapPos.y + viewport.MapHeight + 2, Map.Info.MapHeight); + + intptr_t cacheIndex = CFogOfWar::VisCache_Index0 + beginRow * CFogOfWar::VisCacheWidth; + intptr_t mapIndex = beginRow * Map.Info.MapWidth; + for (int row = beginRow ; row < bottomEdge; row++) { + for (int col = beginCol; col < rightEdge; col++) { + /// FIXME: to speedup this part, maybe we have to use Map.Field(mapIndex + col)->playerInfo.Visible[thisPlayer.index] instead + /// this must be much faster + if (!(VisTableCache[cacheIndex + col] & VisionType::cCached)) { + VisTableCache[cacheIndex + col] = Map.Field(mapIndex + col)->playerInfo.TeamVisibilityState(thisPlayer); + VisTableCache[cacheIndex + col] |= VisionType::cCached; + } + } + cacheIndex += CFogOfWar::VisCacheWidth; + mapIndex += Map.Info.MapWidth; + } + + /// Set the fog texture fully opaque + std::fill(FogTexture.begin(), FogTexture.end(), 0xFF); + + UpscaleFog(FogTexture.data(), viewport); + /// TODO: Blur fog texture +} + + +/** +** 4x4 upscale for generated fog of war texture +** +** @param +** @param +*/ +void CFogOfWar::UpscaleFog(uint8_t *alphaTexture, const CViewport &viewport) +{ + /* + ** For all fields from VisTable in the given rectangle to calculate two patterns - Visible and Exlored. + ** + ** [1][2] checks neighbours (#2,#3,#4) for tile #1 to calculate upscale patterns. + ** [3][4] + ** + ** There is only 16 patterns. + ** According to these patterns fill the 4x4 sized alpha texture + ** with sum of UpscaleTable values (one for Visible and another for Explored) + ** + ** VisTable FogTexture + ** [x][*][0][0] where X - 2 or 1 - Visible or Exlored + ** [X][0] --\ [*][0][0][0] x - 1/2 transparency + ** [0][0] --/ [0][0][0][0] * - 1/4 transperency + ** [0][0][0][0] 0 - full opacity + */ + + /// Because we work with 4x4 scaled map tiles here, the textureIndex is in 32bits chunks (byte * 4) + uint32_t *fogTexture = reinterpret_cast(alphaTexture); + const size_t textureHeight = FogTextureHeight / 4; + const size_t textureWidth = FogTextureWidth / 4; + intptr_t textureIndex = 0; + + /// in fact it's viewport.MapPos.y -1 & viewport.MapPos.x -1 because of VisTableCache starts from [-1:-1] + intptr_t visIndex = viewport.MapPos.y * VisCacheWidth + viewport.MapPos.x; + + for (int row = 0; row < textureHeight; row++) { + for (int col = 0; col < textureWidth; col++) { + /// Fill the 4x4 scaled tile + FillUpscaledRec(fogTexture, textureWidth, textureIndex + col, + DeterminePattern(visIndex + col, VisionType::cVisible), + DeterminePattern(visIndex + col, VisionType::cVisible | VisionType::cExplored)); + } + visIndex += CFogOfWar::VisCacheWidth; + textureIndex += textureWidth * 4; + } +} + +/** +** Determine upscale patterns (index in the upscale table) for Visible and Explored layers +** +** @param index tile in the vision table +** @param visFlag layer to determine pattern for +** +*/ +uint8_t CFogOfWar::DeterminePattern(intptr_t index, uint8_t visFlag) +{ + Assert(visFlag == VisionType::cVisible || visFlag == (VisionType::cExplored | VisionType::cVisible)); + + uint8_t n1, n2, n3, n4; + n1 = (visFlag & VisTableCache[index]); + n2 = (visFlag & VisTableCache[index + 1]); + index += CFogOfWar::VisCacheWidth; + n3 = (visFlag & VisTableCache[index]); + n4 = (visFlag & VisTableCache[index + 1]); + + n1 >>= n1 - VisionType::cExplored; + n2 >>= n2 - VisionType::cExplored; + n3 >>= n3 - VisionType::cExplored; + n4 >>= n4 - VisionType::cExplored; + + return ( (n1 << 3) | (n2 << 2) | (n3 << 1) | n4 ); +} + + +/** +** Fill 4x4 sized tile in the fog texture according to the patterns +** +** @param texture pointer to the texture to fill +** @param textureWidth width of the texture +** @param index index of the tile to fill +** @param patternVisible index int the upscale table for Visible layer +** @param patternExplored index int the upscale table for Explored layer +** +*/ +void CFogOfWar::FillUpscaledRec(uint32_t *texture, const int textureWidth, intptr_t index, + const uint8_t patternVisible, const uint8_t patternExplored) +{ + for (int scan_line = 0; scan_line < 4; scan_line++) { + texture[index] = UpscaleTable[patternVisible][scan_line] + UpscaleTable[patternExplored][scan_line]; + index += textureWidth; + } +} +//@}