Added CFogOfWar - class implementing enhanced FoW

This commit is contained in:
alyokhin 2020-06-11 19:26:34 +03:00
parent 53433785ff
commit 5efdb72dc7
3 changed files with 462 additions and 0 deletions

View file

@ -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

134
src/include/fow.h Normal file
View file

@ -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 <cstdint>
#include <vector>
#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<uint8_t> 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<uint8_t> 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__

326
src/map/fow.cpp Normal file
View file

@ -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 <string.h>
#include <algorithm>
#include "stratagus.h"
#include "fow.h"
#include "map.h"
#include "player.h"
#include "tile.h"
#include "viewport.h"
/*----------------------------------------------------------------------------
-- Defines
----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
-- Variables
----------------------------------------------------------------------------*/
std::vector<uint8_t> 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<uint32_t*>(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<int>(viewport.MapPos.x + viewport.MapWidth + 2, Map.Info.MapWidth);
int bottomEdge = std::min<int>(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<uint32_t*>(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;
}
}
//@}