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