1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name fow.cpp - The fog of war. */
12 //
13 //      (c) Copyright 1999-2021 by Lutz Sammer, Vladi Shabanski,
14 //		Russell Smith, Jimmy Salmon, Pali Rohár, Andrettin and Alyokhin
15 //
16 //      This program is free software; you can redistribute it and/or modify
17 //      it under the terms of the GNU General Public License as published by
18 //      the Free Software Foundation; only version 2 of the License.
19 //
20 //      This program is distributed in the hope that it will be useful,
21 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
22 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 //      GNU General Public License for more details.
24 //
25 //      You should have received a copy of the GNU General Public License
26 //      along with this program; if not, write to the Free Software
27 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 //      02111-1307, USA.
29 //
30 
31 //@{
32 
33 /*----------------------------------------------------------------------------
34 --  Includes
35 ----------------------------------------------------------------------------*/
36 
37 #include <string.h>
38 #include <algorithm>
39 #include <omp.h>
40 
41 #include "stratagus.h"
42 
43 #include "fow.h"
44 #include "map.h"
45 #include "player.h"
46 #include "tile.h"
47 #include "ui.h"
48 #include "viewport.h"
49 #include "../video/intern_video.h"
50 
51 /*----------------------------------------------------------------------------
52 --  Defines
53 ----------------------------------------------------------------------------*/
54 
55 /*----------------------------------------------------------------------------
56 --  Variables
57 ----------------------------------------------------------------------------*/
58 /// FIXME: Maybe move it into CMap
59 CFogOfWar FogOfWar; /// Fog of war itself
60 CGraphic *CFogOfWar::TiledFogSrc {nullptr}; // Graphic tiles set for tiled fog
61 
62 /*----------------------------------------------------------------------------
63 -- Functions
64 ----------------------------------------------------------------------------*/
SetTiledFogGraphic(const std::string & fogGraphicFile)65 void CFogOfWar::SetTiledFogGraphic(const std::string &fogGraphicFile)
66 {
67 	if (CFogOfWar::TiledFogSrc) {
68 		CGraphic::Free(CFogOfWar::TiledFogSrc);
69 	}
70 	CFogOfWar::TiledFogSrc = CGraphic::New(fogGraphicFile, PixelTileSize.x, PixelTileSize.y);
71 }
72 
73 /// Calculate values of upscale table for explored/unexplored tiles
GenerateUpscaleTables(uint32_t (* table)[4],const uint8_t alphaFrom,const uint8_t alphaTo)74 void CFogOfWar::GenerateUpscaleTables(uint32_t (*table)[4], const uint8_t alphaFrom, const uint8_t alphaTo)
75 {
76     for (auto i = 0; i < 16; i++) {
77         for (auto j = 0; j < 4; j++) {
78             table[i][j] = 0;
79             for (auto pos = 0; pos < 4; pos ++) {
80                 uint32_t initValue {0};
81                 switch ((UpscaleTable_4x4[i][j] >> (8 * pos)) & 0xFF) {
82                     case 0xFF: // full
83                         initValue = alphaTo - alphaFrom;
84                     break;
85                     case 0x7F: // half
86                         initValue = (alphaTo - alphaFrom) / 2;
87                     break;
88                     default:   // zero
89                         initValue = 0;
90                 }
91                 table[i][j] |= initValue << (pos * 8);
92             }
93         }
94     }
95 
96 }
Init()97 void CFogOfWar::Init()
98 {
99     /// +1 to the top & left and +1 to the bottom & right for 4x scale algorithm purposes,
100     /// Extra tiles will always be VisionType::cUnseen.
101              this->VisTableWidth  = Map.Info.MapWidth  + 2;
102     const uint16_t visTableHeight = Map.Info.MapHeight + 2;
103     const size_t   tableSize      = VisTableWidth * visTableHeight;
104     VisTable.clear();
105     VisTable.resize(tableSize);
106     std::fill(VisTable.begin(), VisTable.end(), VisionType::cUnseen);
107 
108     VisTable_Index0 = VisTableWidth + 1;
109 
110     switch (Settings.Type) {
111         case FogOfWarTypes::cTiled:
112         case FogOfWarTypes::cTiledLegacy:
113 
114             InitTiled();
115             break;
116 
117         case FogOfWarTypes::cEnhanced:
118 
119             InitEnhanced();
120             break;
121 
122         default:
123             break;
124     }
125 
126     /// TODO: Add fog initialization for replays and observer players
127     VisionFor.clear();
128     ShowVisionFor(*ThisPlayer);
129 
130     this->State = cFirstEntry;
131 }
132 
133 /**
134 **  Initialize the tiled fog of war.
135 **  Build tables, setup functions.
136 */
InitTiled()137 void CFogOfWar::InitTiled()
138 {
139 	if (TiledAlphaFog || TileOfFogOnly) {
140 		this->Clean();
141 	}
142     CFogOfWar::TiledFogSrc->Load();
143 
144     if (Settings.Type == FogOfWarTypes::cTiledLegacy) {
145         TileOfFogOnly = SDL_CreateRGBSurface(SDL_SWSURFACE, PixelTileSize.x, PixelTileSize.y,
146                                              32, RMASK, GMASK, BMASK, AMASK);
147         SDL_FillRect(TileOfFogOnly, NULL, Settings.FogColorSDL | uint32_t(Settings.ExploredOpacity) << ASHIFT);
148     }
149 
150     SDL_Surface * const newFogSurface = SDL_ConvertSurfaceFormat(CFogOfWar::TiledFogSrc->Surface,
151                                                                  SDL_MasksToPixelFormatEnum(32, RMASK, GMASK, BMASK, AMASK), 0);
152     TiledAlphaFog = CGraphic::New("");
153     TiledAlphaFog->Surface = newFogSurface;
154     TiledAlphaFog->Width = PixelTileSize.x;
155     TiledAlphaFog->Height = PixelTileSize.y;
156     TiledAlphaFog->GraphicWidth = newFogSurface->w;
157     TiledAlphaFog->GraphicHeight = newFogSurface->h;
158     TiledAlphaFog->NumFrames = 16;
159     TiledAlphaFog->GenFramesMap();
160     SDL_SetSurfaceBlendMode(TiledAlphaFog->Surface, SDL_BLENDMODE_BLEND);
161 }
162 
163 /**
164 **  Initialize the enhanced fog of war.
165 **  Build tables, setup functions.
166 */
InitEnhanced()167 void CFogOfWar::InitEnhanced()
168 {
169 
170     /// +1 to the top & left for 4x scale algorithm purposes,
171     const uint16_t fogTextureWidth  = (Map.Info.MapWidth  + 1) * 4;
172     const uint16_t fogTextureHeight = (Map.Info.MapHeight + 1) * 4;
173 
174     FogTexture.Init(fogTextureWidth, fogTextureHeight, Settings.NumOfEasingSteps);
175 
176     RenderedFog.clear();
177     RenderedFog.resize(Map.Info.MapWidth * Map.Info.MapHeight * 16);
178     std::fill(RenderedFog.begin(), RenderedFog.end(), 0xFF);
179 
180     Blurer.Init(fogTextureWidth, fogTextureHeight, Settings.BlurRadius[Settings.UpscaleType], Settings.BlurIterations);
181 
182     SetFogColor(Settings.FogColor);
183 }
184 
SetFogColor(const uint8_t r,const uint8_t g,const uint8_t b)185 void CFogOfWar::SetFogColor(const uint8_t r, const uint8_t g, const uint8_t b)
186 {
187     SetFogColor(CColor(r, g, b));
188 }
189 
SetFogColor(const CColor color)190 void CFogOfWar::SetFogColor(const CColor color)
191 {
192     Settings.FogColor = color;
193     Settings.FogColorSDL = (color.R << RSHIFT) | (color.G << GSHIFT) | (color.B << BSHIFT);
194 }
195 
Clean(const bool isHardClean)196 void CFogOfWar::Clean(const bool isHardClean /*= false*/)
197 {
198     if(isHardClean) {
199         VisionFor.clear();
200     }
201 
202     VisTable.clear();
203     VisTableWidth   = 0;
204     VisTable_Index0 = 0;
205 
206     switch (Settings.Type) {
207         case FogOfWarTypes::cTiled:
208         case FogOfWarTypes::cTiledLegacy:
209 
210             CleanTiled(isHardClean);
211             break;
212 
213         case FogOfWarTypes::cEnhanced:
214 
215             if (isHardClean) {
216                 CleanTiled(isHardClean);
217             }
218             FogTexture.Clean();
219             RenderedFog.clear();
220             Blurer.Clean();
221             break;
222 
223         default:
224             break;
225     }
226 }
227 
228 /**
229 ** Select which type of Fog of War to use
230 **
231 ** @param fowType	type to set
232 ** @return true if success, false for wrong fow_type
233 */
SetType(const FogOfWarTypes fowType)234 bool CFogOfWar::SetType(const FogOfWarTypes fowType)
235 {
236 	if (fowType != Settings.Type && fowType < FogOfWarTypes::cNumOfTypes) {
237         if (Map.isInitialized()) {
238             this->Clean();
239             Settings.Type = fowType;
240             this->Init();
241         } else {
242             Settings.Type = fowType;
243         }
244 		return true;
245 	} else {
246 		return false;
247 	}
248 }
249 
250 /**
251 ** Set fog of war opacity (alpha chanel values) for different levels of visibility
252 **
253 ** @param explored  alpha channel value for explored tiles
254 ** @param revealed  alpha channel value for revealed tiles (when the map revealed)
255 ** @param unseen    alpha channel value for unseen tiles
256 **
257 */
SetOpacityLevels(const uint8_t explored,const uint8_t revealed,const uint8_t unseen)258 void CFogOfWar::SetOpacityLevels(const uint8_t explored, const uint8_t revealed, const uint8_t unseen)
259 {
260     Settings.ExploredOpacity = explored;
261     Settings.RevealedOpacity = revealed;
262     Settings.UnseenOpacity   = unseen;
263     GenerateUpscaleTables(UpscaleTableVisible, 0, explored);
264     GenerateUpscaleTables(UpscaleTableExplored, explored, unseen);
265     GenerateUpscaleTables(UpscaleTableRevealed, explored, revealed);
266 }
267 
268 /**
269 ** Enable or disable bilinear upscale for the final fog texture rendering
270 **
271 ** @param enable  cmd to enable/disable
272 **
273 */
EnableBilinearUpscale(const bool enable)274 void CFogOfWar::EnableBilinearUpscale(const bool enable)
275 {
276     const uint8_t prev = Settings.UpscaleType;
277     Settings.UpscaleType = enable ? UpscaleTypes::cBilinear : UpscaleTypes::cSimple;
278     if (prev != Settings.UpscaleType) {
279         Blurer.PrecalcParameters(Settings.BlurRadius[Settings.UpscaleType], Settings.BlurIterations);
280     }
281 }
282 
InitBlurer(const float radius1,const float radius2,const uint16_t numOfIterations)283 void CFogOfWar::InitBlurer(const float radius1, const float radius2, const uint16_t numOfIterations)
284 {
285     Settings.BlurRadius[cSimple]   = radius1;
286     Settings.BlurRadius[cBilinear] = radius2;
287     Settings.BlurIterations        = numOfIterations;
288     Blurer.PrecalcParameters(Settings.BlurRadius[Settings.UpscaleType], numOfIterations);
289 }
290 
291 /**
292 ** Generate fog of war:
293 ** fill map-sized table with values of visiblty for current player/players
294 **
295 */
GenerateFog()296 void CFogOfWar::GenerateFog()
297 {
298     /// FIXME: Maybe to update this with every change of shared vision
299     std::set<uint8_t> playersToRenderView;
300     for (const uint8_t player : VisionFor) {
301         playersToRenderView.insert(player);
302         for (const uint8_t playersSharedVision : Players[player].GetSharedVision()) {
303             playersToRenderView.insert(playersSharedVision);
304         }
305     }
306     CurrUpscaleTableExplored = GameSettings.RevealMap ? UpscaleTableRevealed
307                                                       : UpscaleTableExplored;
308 
309     const uint8_t visibleThreshold = Map.NoFogOfWar ? 1 : 2;
310 
311     #pragma omp parallel
312     {
313         const uint16_t thisThread   = omp_get_thread_num();
314         const uint16_t numOfThreads = omp_get_num_threads();
315 
316         const uint16_t lBound = (thisThread    ) * Map.Info.MapHeight / numOfThreads;
317         const uint16_t uBound = (thisThread + 1) * Map.Info.MapHeight / numOfThreads;
318 
319         for (uint16_t row = lBound; row < uBound; row++) {
320 
321             const size_t visIndex = VisTable_Index0 + row * VisTableWidth;
322             const size_t mapIndex = size_t(row) * Map.Info.MapHeight;
323 
324             for (uint16_t col = 0; col < Map.Info.MapWidth; col++) {
325 
326                 uint8_t &visCell = VisTable[visIndex + col];
327                 visCell = 0; /// Clear it before check for players
328                 const CMapField *mapField = Map.Field(mapIndex + col);
329                 for (const uint8_t player : playersToRenderView) {
330                     visCell = std::max<uint8_t>(visCell, mapField->playerInfo.Visible[player]);
331                     if (visCell >= visibleThreshold) {
332                         visCell = 2;
333                         break;
334                     }
335                 }
336             }
337         }
338     }
339 }
340 
341 /**
342 **  Proceed fog of war state update
343 **
344 **  @param doAtOnce     command to calculate fog of war in single sycle
345 **
346 */
Update(bool doAtOnce)347 void CFogOfWar::Update(bool doAtOnce /*= false*/)
348 {
349     if (Settings.Type == FogOfWarTypes::cTiled || Settings.Type == FogOfWarTypes::cTiledLegacy) {
350         if (doAtOnce || this->State == States::cFirstEntry){
351             GenerateFog();
352             this->State = States::cGenerateFog;
353         } else {
354             switch (this->State) {
355                 case States::cReady:
356                     this->State = cGenerateFog;
357                     break;
358 
359                 case States::cGenerateFog:
360                     GenerateFog();
361                     /// pass through
362                 default:
363                     this->State++;
364                     break;
365             }
366         }
367         return;
368     }
369 
370     /// FogOfWarTypes::cEnhanced
371     FogTexture.Ease();
372 
373     if (Settings.NumOfEasingSteps < States::cReady) doAtOnce = true;
374 
375     if (doAtOnce || this->State == States::cFirstEntry) {
376         GenerateFog();
377         FogUpscale4x4();
378         Blurer.Blur(FogTexture.GetNext());
379         FogTexture.PushNext(doAtOnce);
380         this->State = States::cGenerateFog;
381     } else {
382         switch (this->State) {
383             case States::cGenerateFog:
384                 GenerateFog();
385                 this->State++;
386                 break;
387 
388             case States::cGenerateTexture:
389                 FogUpscale4x4();
390                 this->State++;
391                 break;
392 
393             case States::cBlurTexture:
394                 Blurer.Blur(FogTexture.GetNext());
395                 this->State++;
396                 break;
397 
398             case States::cReady:
399                 if (FogTexture.isFullyEased()) {
400                     FogTexture.PushNext();
401                     this->State = cGenerateFog;
402                 }
403                 break;
404             default:
405                 break;
406         }
407     }
408 }
409 
410 /**
411 **  Generate fog of war texture for certain viewport.
412 **
413 **  @param viewport     viewport to generate fog of war texture for
414 **  @param vpFogSurface surface where to put the generated texture
415 */
Draw(CViewport & viewport)416 void CFogOfWar::Draw(CViewport &viewport)
417 {
418     if (Settings.Type == FogOfWarTypes::cTiledLegacy) {
419         DrawTiledLegacy(viewport);
420     } else {
421         SDL_FillRect(viewport.GetFogSurface(), NULL, 0x00);
422 
423         if (Settings.Type == FogOfWarTypes::cTiled) {
424             DrawTiled(viewport);
425         } else if (Settings.Type == FogOfWarTypes::cEnhanced) {
426             DrawEnhanced(viewport);
427         }
428     }
429 }
430 
431 
432 /**
433 **  Draw enhanced fog of war texture into certain viewport's surface.
434 **
435 **  @param viewport     viewport to generate fog of war texture for
436 **  @param vpFogSurface surface where to put the generated texture
437 */
DrawEnhanced(CViewport & viewport)438 void CFogOfWar::DrawEnhanced(CViewport &viewport)
439 {
440     SDL_Rect srcRect;
441     srcRect.x = (viewport.MapPos.x + 1) * 4 - 2;  /// '+1' because of 1 tile frame around the texture
442     srcRect.y = (viewport.MapPos.y + 1) * 4 - 2;  /// '-2' is a half-tile compensation
443     srcRect.w = viewport.MapWidth  * 4;
444     srcRect.h = viewport.MapHeight * 4;
445 
446     const uint16_t x0 = viewport.MapPos.x * 4;
447     const uint16_t y0 = viewport.MapPos.y * 4;
448 
449     FogTexture.DrawRegion(RenderedFog.data(), Map.Info.MapWidth * 4, x0, y0, srcRect);
450 
451     /// TODO: This part might be replaced by GPU shaders.
452     /// In that case vpFogSurface shall be filled up with FogTexture.DrawRegion()
453 
454     srcRect.x = x0;
455     srcRect.y = y0;
456 
457     SDL_Rect trgRect;
458     trgRect.x = 0;
459     trgRect.y = 0;
460     trgRect.w = viewport.MapWidth  * PixelTileSize.x;
461     trgRect.h = viewport.MapHeight * PixelTileSize.y;
462 
463     switch (this->Settings.UpscaleType) {
464         case cBilinear:
465             UpscaleBilinear(RenderedFog.data(), srcRect, Map.Info.MapWidth * 4, viewport.GetFogSurface(), trgRect);
466             break;
467         case cSimple:
468         default:
469             UpscaleSimple(RenderedFog.data(), srcRect, Map.Info.MapWidth * 4, viewport.GetFogSurface(), trgRect);
470             break;
471     }
472 }
473 
474 /**
475 **  4x4 upscale generated fog of war texture
476 **
477 */
FogUpscale4x4()478 void CFogOfWar::FogUpscale4x4()
479 {
480     /*
481     **  For all fields from VisTable in the given rectangle to calculate two patterns - Visible and Exlored.
482     **
483     **  [1][2] checks neighbours (#2,#3,#4) for tile #1 to calculate upscale patterns.
484     **  [3][4]
485     **
486     **  There is only 16 patterns.
487     **  According to these patterns fill the 4x4 sized alpha texture
488     **  with sum of UpscaleTable values (one for Visible and another for Explored)
489     **
490     **  VisTable     FogTexture
491     **              [x][*][0][0]   where X - 2 or 1 - Visible or Exlored
492     **   [X][0] --\ [*][0][0][0]         x - 1/2 transparency
493     **   [0][0] --/ [0][0][0][0]         * - 1/4 transperency
494     **              [0][0][0][0]         0 - full opacity
495     */
496 
497     /// Because we work with 4x4 scaled map tiles here, the textureIndex is in 32bits chunks (byte * 4)
498     uint32_t *const fogTexture = (uint32_t*)FogTexture.GetNext();
499 
500     /// Fog texture width and height in 32bit chunks
501     const uint16_t textureWidth  = FogTexture.GetWidth()  / 4;
502     const uint16_t textureHeight = FogTexture.GetHeight() / 4;
503     const uint16_t nextRowOffset = textureWidth * 4;
504 
505     #pragma omp parallel
506     {
507 
508         const uint16_t thisThread   = omp_get_thread_num();
509         const uint16_t numOfThreads = omp_get_num_threads();
510 
511         const uint16_t lBound = (thisThread    ) * textureHeight / numOfThreads;
512         const uint16_t uBound = (thisThread + 1) * textureHeight / numOfThreads;
513 
514         /// in fact it's viewport.MapPos.y -1 & viewport.MapPos.x -1 because of VisTable starts from [-1:-1]
515         size_t visIndex      = lBound * VisTableWidth;
516         size_t textureIndex  = lBound * nextRowOffset;
517 
518         for (uint16_t row = lBound; row < uBound; row++) {
519             for (uint16_t col = 0; col < textureWidth; col++) {
520                 /// Fill the 4x4 scaled tile
521                 FillUpscaledRec(fogTexture, textureWidth, textureIndex + col,
522                                 DeterminePattern(visIndex + col, VisionType::cVisible),
523                                 DeterminePattern(visIndex + col, VisionType::cVisible | VisionType::cExplored));
524             }
525             visIndex     += VisTableWidth;
526             textureIndex += nextRowOffset;
527         }
528     } // pragma omp parallel
529 }
530 
531 /**
532 ** Bilinear zoom Fog Of War texture into SDL surface
533 **
534 **  @param src          Image src.
535 **  @param srcRect      Rectangle in the src image to render
536 **  @param srcWidth     Image width
537 **  @param trgSurface   Where to render
538 **  @param trgRect      Scale src rectangle to this rectangle
539 **
540 */
UpscaleBilinear(const uint8_t * const src,const SDL_Rect & srcRect,const int16_t srcWidth,SDL_Surface * const trgSurface,const SDL_Rect & trgRect) const541 void CFogOfWar::UpscaleBilinear(const uint8_t *const src, const SDL_Rect &srcRect, const int16_t srcWidth,
542                                 SDL_Surface *const trgSurface, const SDL_Rect &trgRect) const
543 {
544     constexpr int32_t fixedOne = 65536;
545 
546     uint32_t *const target = (uint32_t*)trgSurface->pixels;
547     const uint16_t AShift = trgSurface->format->Ashift;
548 
549     /// FIXME: '-1' shouldn't be here, but without it the resulting fog has a shift to the left and upward
550     const int32_t xRatio = (int32_t(srcRect.w - 1) << 16) / trgRect.w;
551     const int32_t yRatio = (int32_t(srcRect.h - 1) << 16) / trgRect.h;
552 
553     #pragma omp parallel
554     {
555         const uint16_t thisThread   = omp_get_thread_num();
556         const uint16_t numOfThreads = omp_get_num_threads();
557 
558         const uint16_t lBound = (thisThread    ) * trgRect.h / numOfThreads;
559         const uint16_t uBound = (thisThread + 1) * trgRect.h / numOfThreads;
560 
561 
562         size_t  trgIndex = size_t(trgRect.y + lBound) * trgSurface->w + trgRect.x;
563         int64_t y        = ((int32_t)srcRect.y << 16) + lBound * yRatio;
564 
565         for (uint16_t yTrg = lBound; yTrg < uBound; yTrg++) {
566 
567             const int32_t ySrc          = int32_t(y >> 16);
568             const int64_t yDiff         = y - (ySrc << 16);
569             const int64_t one_min_yDiff = fixedOne - yDiff;
570             const size_t  yIndex        = ySrc * srcWidth;
571                   int64_t x             = int32_t(srcRect.x) << 16;
572 
573             for (uint16_t xTrg = 0; xTrg < trgRect.w; xTrg++) {
574 
575                 const int32_t xSrc          = int32_t(x >> 16);
576                 const int64_t xDiff         = x - (xSrc << 16);
577                 const int64_t one_min_xDiff = fixedOne - xDiff;
578                 const size_t  srcIndex      = yIndex + xSrc;
579 
580                 const uint8_t A = src[srcIndex];
581                 const uint8_t B = src[srcIndex + 1];
582                 const uint8_t C = src[srcIndex + srcWidth];
583                 const uint8_t D = src[srcIndex + srcWidth + 1];
584 
585                 const uint32_t alpha = ((  A * one_min_xDiff * one_min_yDiff
586                                          + B * xDiff * one_min_yDiff
587                                          + C * yDiff * one_min_xDiff
588                                          + D * xDiff * yDiff ) >> 32 );
589 
590                 target[trgIndex + xTrg] = (alpha << AShift) | Settings.FogColorSDL;
591                 x += xRatio;
592             }
593             y += yRatio;
594             trgIndex += trgSurface->w;
595         }
596     } /// pragma omp parallel
597 }
598 
599 /**
600 ** Simple zoom Fog Of War texture into SDL surface
601 **
602 **  @param src          Image src.
603 **  @param srcRect      Rectangle in the src image to render
604 **  @param srcWidth     Image width
605 **  @param trgSurface   Where to render
606 **  @param trgRect      Scale src rectangle to this rectangle
607 **
608 */
UpscaleSimple(const uint8_t * src,const SDL_Rect & srcRect,const int16_t srcWidth,SDL_Surface * const trgSurface,const SDL_Rect & trgRect) const609 void CFogOfWar::UpscaleSimple(const uint8_t *src, const SDL_Rect &srcRect, const int16_t srcWidth,
610                               SDL_Surface *const trgSurface, const SDL_Rect &trgRect) const
611 {
612     const uint16_t surfaceAShift = trgSurface->format->Ashift;
613 
614     const uint8_t texelWidth  = PixelTileSize.x / 4;
615     const uint8_t texelHeight = PixelTileSize.y / 4;
616 
617     uint32_t *const target =(uint32_t*)trgSurface->pixels;
618 
619     #pragma omp parallel
620     {
621         const uint16_t thisThread   = omp_get_thread_num();
622         const uint16_t numOfThreads = omp_get_num_threads();
623 
624         const uint16_t lBound = (thisThread    ) * srcRect.h / numOfThreads;
625         const uint16_t uBound = (thisThread + 1) * srcRect.h / numOfThreads;
626 
627         size_t srcIndex = size_t(srcRect.y + lBound) * srcWidth + srcRect.x;
628         size_t trgIndex = size_t(trgRect.y + lBound * texelHeight) * trgSurface->w + trgRect.x;
629 
630         for (uint16_t ySrc = lBound; ySrc < uBound; ySrc++) {
631             for (uint16_t xSrc = 0; xSrc < srcRect.w; xSrc++) {
632 
633                 const uint32_t texelValue = (uint32_t(src[srcIndex + xSrc]) << surfaceAShift)
634                                             | Settings.FogColorSDL;
635                 std::fill_n(&target[trgIndex + xSrc * texelWidth], texelWidth, texelValue);
636             }
637             for (uint8_t texelRow = 1; texelRow < texelHeight; texelRow++) {
638                 std::copy_n(&target[trgIndex], trgRect.w, &target[trgIndex + texelRow * trgSurface->w]);
639             }
640             srcIndex += srcWidth;
641             trgIndex += trgSurface->w * texelHeight;
642         }
643     } /// pragma omp parallel
644 }
645 
646 /**
647 **  Draw only fog of war
648 **
649 **  @param x  X position into video memory
650 **  @param y  Y position into video memory
651 */
DrawFullShroudOfFog(int16_t x,int16_t y,const uint8_t alpha,SDL_Surface * const vpFogSurface)652 void CFogOfWar::DrawFullShroudOfFog(int16_t x, int16_t y, const uint8_t alpha, SDL_Surface *const vpFogSurface)
653 {
654 	int oldx;
655 	int oldy;
656 	SDL_Rect srect;
657 	SDL_Rect drect;
658 
659 	srect.x = 0;
660 	srect.y = 0;
661 	srect.w = PixelTileSize.x;
662 	srect.h = PixelTileSize.y;
663 
664 	oldx = x;
665 	oldy = y;
666 	CLIP_RECTANGLE(x, y, srect.w, srect.h);
667 	srect.x += x - oldx;
668 	srect.y += y - oldy;
669 
670 	drect.x = x;
671 	drect.y = y;
672 
673     if (vpFogSurface == TheScreen) { /// FogOfWarTypes::cTiledLegacy
674         SDL_BlitSurface(TileOfFogOnly, &srect, TheScreen, &drect);
675     } else {
676         const uint32_t fogColor = GetFogColorSDL() | (uint32_t(alpha) << ASHIFT);
677         size_t index = drect.y * vpFogSurface->w + drect.x;
678         uint32_t *const dst = reinterpret_cast<uint32_t*>(vpFogSurface->pixels);
679         for (uint16_t row = 0; row < srect.h; row++) {
680             std::fill_n(&dst[index], srect.w, fogColor);
681             index += vpFogSurface->w;
682         }
683     }
684 }
685 
GetFogTile(const size_t visIndex,const size_t mapIndex,const size_t mapIndexBase,int * fogTile,int * blackFogTile) const686 void CFogOfWar::GetFogTile(const size_t visIndex, const  size_t mapIndex, const size_t mapIndexBase,
687                            int *fogTile, int *blackFogTile) const
688 {
689 	int w = Map.Info.MapWidth;
690 	int fogTileIndex = 0;
691 	int blackFogTileIndex = 0;
692 	int x = mapIndex - mapIndexBase;
693 
694 	if (ReplayRevealMap) {
695 		*fogTile = 0;
696 		*blackFogTile = 0;
697 		return;
698 	}
699 
700 	//
701 	//  Which Tile to draw for fog
702 	//
703 	// Investigate tiles around current tile
704 	// 1 2 3
705 	// 4 * 5
706 	// 6 7 8
707 
708 	//    2  3 1
709 	//   10 ** 5
710 	//    8 12 4
711 
712     const size_t visIndexBase = (visIndex - x);
713 	if (mapIndexBase) {
714 		const size_t index = visIndexBase - VisTableWidth;
715 		if (mapIndex != mapIndexBase) {
716 			if (!IsMapFieldExplored(x - 1 + index)) {
717 				blackFogTileIndex |= 2;
718 				fogTileIndex |= 2;
719 			} else if (!IsMapFieldVisible(x - 1 + index)) {
720 				fogTileIndex |= 2;
721 			}
722 		}
723 		if (!IsMapFieldExplored(x + index)) {
724 			blackFogTileIndex |= 3;
725 			fogTileIndex |= 3;
726 		} else if (!IsMapFieldVisible(x + index)) {
727 			fogTileIndex |= 3;
728 		}
729 		if (mapIndex != mapIndexBase + w - 1) {
730 			if (!IsMapFieldExplored(x + 1 + index)) {
731 				blackFogTileIndex |= 1;
732 				fogTileIndex |= 1;
733 
734 			} else if (!IsMapFieldVisible(x + 1 + index)) {
735 				fogTileIndex |= 1;
736 			}
737 		}
738 	}
739 
740 	if (mapIndex != mapIndexBase) {
741 		const size_t index = visIndexBase;
742 		if (!IsMapFieldExplored(x - 1 + index)) {
743 			blackFogTileIndex |= 10;
744 			fogTileIndex |= 10;
745 		} else if (!IsMapFieldVisible(x - 1 + index)) {
746 			fogTileIndex |= 10;
747 		}
748 	}
749 	if (mapIndex != mapIndexBase + w - 1) {
750 		const size_t index = visIndexBase;
751 		if (!IsMapFieldExplored(x + 1 + index)) {
752 			blackFogTileIndex |= 5;
753 			fogTileIndex |= 5;
754 		} else if (!IsMapFieldVisible(x + 1 + index)) {
755 			fogTileIndex |= 5;
756 		}
757 	}
758 
759 	if (mapIndexBase + w < Map.Info.MapHeight * w) {
760 		const size_t index = visIndexBase + VisTableWidth;
761 		if (mapIndex != mapIndexBase) {
762 			if (!IsMapFieldExplored(x - 1 + index)) {
763 				blackFogTileIndex |= 8;
764 				fogTileIndex |= 8;
765 			} else if (!IsMapFieldVisible(x - 1 + index)) {
766 				fogTileIndex |= 8;
767 			}
768 		}
769 		if (!IsMapFieldExplored(x + index)) {
770 			blackFogTileIndex |= 12;
771 			fogTileIndex |= 12;
772 		} else if (!IsMapFieldVisible(x + index)) {
773 			fogTileIndex |= 12;
774 		}
775 		if (mapIndex != mapIndexBase + w - 1) {
776 			if (!IsMapFieldExplored(x + 1 + index)) {
777 				blackFogTileIndex |= 4;
778 				fogTileIndex |= 4;
779 			} else if (!IsMapFieldVisible(x + 1 + index)) {
780 				fogTileIndex |= 4;
781 			}
782 		}
783 	}
784 
785 	*fogTile = this->TiledFogTable[fogTileIndex];
786 	*blackFogTile = this->TiledFogTable[blackFogTileIndex];
787 }
788 
789 /**
790 **  Draw fog of war tile.
791 **
792 **  @param visIndex  Offset in fields to current tile in VisTable
793 **  @param mapIndex  Offset in fields to current tile on the map
794 **  @param mapIndexBase  Start of the current row on the map
795 **  @param dx  X position into fog surface
796 **  @param dy  Y position into fog surface
797 **  @param vpFogSurface surface to draw fog
798 */
DrawFogTile(const size_t visIndex,const size_t mapIndex,const size_t mapIndexBase,const int16_t dx,const int16_t dy,SDL_Surface * const vpFogSurface)799 void CFogOfWar::DrawFogTile(const size_t visIndex, const size_t mapIndex, const size_t mapIndexBase,
800                             const int16_t dx, const int16_t dy, SDL_Surface *const vpFogSurface)
801 {
802 	int fogTile = 0;
803 	int blackFogTile = 0;
804 
805 	GetFogTile(visIndex, mapIndex, mapIndexBase, &fogTile, &blackFogTile);
806 
807     if (vpFogSurface != TheScreen) {
808         if (IsMapFieldVisible(visIndex) || ReplayRevealMap) {
809             if (fogTile && fogTile != blackFogTile) {
810                 TiledAlphaFog->DrawFrameClipCustomMod(fogTile, dx, dy, PixelModifier::CopyWithSrcAlphaKey,
811                                                                        GetExploredOpacity(),
812                                                                        vpFogSurface);
813             }
814         } else {
815             DrawFullShroudOfFog(dx, dy, FogOfWar.GetExploredOpacity(), vpFogSurface);
816         }
817         if (blackFogTile) {
818             TiledAlphaFog->DrawFrameClipCustomMod(blackFogTile, dx, dy, PixelModifier::CopyWithSrcAlphaKey,
819                                                                         GameSettings.RevealMap ? GetRevealedOpacity()
820                                                                                                : GetUnseenOpacity(),
821                                                                         vpFogSurface);
822         }
823     } else { /// legacy draw tiled fog into TheScreen surface (for slow machines)
824         if (IsMapFieldVisible(visIndex) || ReplayRevealMap) {
825             if (fogTile && fogTile != blackFogTile) {
826                 TiledAlphaFog->DrawFrameClipTrans(fogTile, dx, dy, GetExploredOpacity());
827             }
828         } else {
829             DrawFullShroudOfFog(dx, dy, GetExploredOpacity(), TheScreen);
830         }
831         if (blackFogTile) {
832             TiledFogSrc->DrawFrameClip(blackFogTile, dx, dy);
833         }
834     }
835 }
836 /**
837 **  Draw tiled fog of war texture into certain viewport's surface.
838 **
839 **  @param viewport     viewport to generate fog of war texture for
840 */
DrawTiled(CViewport & viewport)841 void CFogOfWar::DrawTiled(CViewport &viewport)
842 {
843     /// Save current clipping
844     PushClipping();
845 
846     // Set clipping to FogSurface coordinates
847 	SDL_Rect fogSurfaceClipRect {viewport.Offset.x,
848 							 	 viewport.Offset.y,
849 							 	 viewport.BottomRightPos.x - viewport.TopLeftPos.x + 1,
850 							 	 viewport.BottomRightPos.y - viewport.TopLeftPos.y + 1};
851     SetClipping(fogSurfaceClipRect.x,
852 				fogSurfaceClipRect.y,
853 				fogSurfaceClipRect.x + fogSurfaceClipRect.w,
854 				fogSurfaceClipRect.y + fogSurfaceClipRect.h);
855 
856 	const int ex = fogSurfaceClipRect.x + fogSurfaceClipRect.w;
857 	const int ey = fogSurfaceClipRect.y + fogSurfaceClipRect.h;
858 
859     #pragma omp parallel
860     {
861         const uint16_t thisThread   = omp_get_thread_num();
862         const uint16_t numOfThreads = omp_get_num_threads();
863 
864         uint16_t lBound = thisThread * fogSurfaceClipRect.h / numOfThreads;
865         lBound -= lBound % PixelTileSize.y;
866         uint16_t uBound = ey;
867         if (thisThread != numOfThreads - 1) {
868             uBound = (thisThread + 1) * fogSurfaceClipRect.h / numOfThreads;
869             uBound -= uBound % PixelTileSize.y;
870         }
871 
872         size_t mapIndexBase = (viewport.MapPos.y + lBound / PixelTileSize.y) * Map.Info.MapWidth;
873         size_t visIndexBase = (viewport.MapPos.y + lBound / PixelTileSize.y) * VisTableWidth + VisTable_Index0;
874 
875         int dy = lBound;
876         while (dy < uBound) {
877             size_t mapIndex = viewport.MapPos.x + mapIndexBase;
878             size_t visIndex = viewport.MapPos.x + visIndexBase;
879 
880             int dx = 0;
881             while (dx < ex) {
882                 if (VisTable[visIndex]) {
883                     DrawFogTile(visIndex, mapIndex, mapIndexBase, dx, dy, viewport.GetFogSurface());
884                 } else {
885                     DrawFullShroudOfFog(dx, dy, GameSettings.RevealMap ? GetRevealedOpacity()
886                                                                        : GetUnseenOpacity(),
887                                                 viewport.GetFogSurface());
888                 }
889                 mapIndex++;
890                 visIndex++;
891                 dx += PixelTileSize.x;
892             }
893             mapIndexBase += Map.Info.MapWidth;
894             visIndexBase += VisTableWidth;
895             dy += PixelTileSize.y;
896         }
897     } /// pragma omp parallel
898 
899 	// Restore Clipping to Viewport coordinates
900 	PopClipping();
901 
902 }
903 /**
904 **  Legacy draw tiled fog of war texture into TheScreen surface.
905 **
906 **  @param viewport     viewport to generate fog of war texture for
907 */
DrawTiledLegacy(CViewport & viewport)908 void CFogOfWar::DrawTiledLegacy(CViewport &viewport)
909 {
910 	const int ex = viewport.BottomRightPos.x;
911 	const int ey = viewport.BottomRightPos.y;
912 
913     size_t mapIndexBase = viewport.MapPos.y * Map.Info.MapWidth;
914     size_t visIndexBase = viewport.MapPos.y * VisTableWidth + VisTable_Index0;
915     int dy = viewport.TopLeftPos.y - viewport.Offset.y;
916 
917     while (dy < ey) {
918         size_t mapIndex = viewport.MapPos.x + mapIndexBase;
919         size_t visIndex = viewport.MapPos.x + visIndexBase;
920 
921         int dx = viewport.TopLeftPos.x - viewport.Offset.x;
922 
923         while (dx < ex) {
924             if (VisTable[visIndex]) {
925                 DrawFogTile(visIndex, mapIndex, mapIndexBase, dx, dy, TheScreen);
926             } else {
927                 Video.FillRectangleClip(Settings.FogColorSDL, dx, dy, PixelTileSize.x, PixelTileSize.y);
928             }
929             mapIndex++;
930             visIndex++;
931             dx += PixelTileSize.x;
932         }
933         mapIndexBase += Map.Info.MapWidth;
934         visIndexBase += VisTableWidth;
935         dy += PixelTileSize.y;
936     }
937 }
938 /**
939 **  Cleanup the fog of war.
940 */
CleanTiled(const bool isHardClean)941 void CFogOfWar::CleanTiled(const bool isHardClean /*= false*/)
942 {
943 
944 	if (isHardClean && CFogOfWar::TiledFogSrc) {
945 		CGraphic::Free(CFogOfWar::TiledFogSrc);
946 		CFogOfWar::TiledFogSrc = nullptr;
947 	}
948     if (TileOfFogOnly) {
949 		SDL_FreeSurface(TileOfFogOnly);
950 		TileOfFogOnly = nullptr;
951 	}
952     if (TiledAlphaFog) {
953         CGraphic::Free(TiledAlphaFog);
954         TiledAlphaFog = nullptr;
955     }
956 }
957 //@}
958