1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "mapimages.h"
4
5#include <base/dbg.h>
6#include <base/log.h>
7#include <base/math.h>
8#include <base/mem.h>
9
10#include <engine/gfx/image_manipulation.h>
11#include <engine/graphics.h>
12#include <engine/map.h>
13#include <engine/storage.h>
14#include <engine/textrender.h>
15
16#include <generated/client_data.h>
17
18#include <game/client/gameclient.h>
19#include <game/layers.h>
20#include <game/localization.h>
21#include <game/mapitems.h>
22
23CMapImages::CMapImages()
24{
25 m_Count = 0;
26 std::fill(first: std::begin(arr&: m_aEntitiesIsLoaded), last: std::end(arr&: m_aEntitiesIsLoaded), value: false);
27 m_SpeedupArrowIsLoaded = false;
28 m_TuneColorsIsLoaded = false;
29
30 str_copy(dst&: m_aEntitiesPath, src: "editor/entities_clear");
31
32 static_assert(std::size(gs_apModEntitiesNames) == MAP_IMAGE_MOD_TYPE_COUNT, "Mod name string count is not equal to mod type count");
33}
34
35void CMapImages::OnInit()
36{
37 m_TextureScale = g_Config.m_ClTextEntitiesSize;
38 InitOverlayTextures();
39
40 if(str_comp(a: g_Config.m_ClAssetsEntities, b: "default") == 0)
41 str_copy(dst&: m_aEntitiesPath, src: "editor/entities_clear");
42 else
43 {
44 str_format(buffer: m_aEntitiesPath, buffer_size: sizeof(m_aEntitiesPath), format: "assets/entities/%s", g_Config.m_ClAssetsEntities);
45 }
46
47 Console()->Chain(pName: "cl_text_entities_size", pfnChainFunc: ConchainClTextEntitiesSize, pUser: this);
48}
49
50void CMapImages::Unload()
51{
52 // unload all textures
53 for(int i = 0; i < m_Count; i++)
54 {
55 Graphics()->UnloadTexture(pIndex: &m_aTextures[i]);
56 }
57}
58
59void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
60{
61 Unload();
62
63 int Start;
64 pMap->GetType(Type: MAPITEMTYPE_IMAGE, pStart: &Start, pNum: &m_Count);
65 m_Count = std::clamp<int>(val: m_Count, lo: 0, hi: MAX_MAPIMAGES);
66
67 unsigned char aTextureUsedByTileOrQuadLayerFlag[MAX_MAPIMAGES] = {0}; // 0: nothing, 1(as flag): tile layer, 2(as flag): quad layer
68 for(int GroupIndex = 0; GroupIndex < pLayers->NumGroups(); GroupIndex++)
69 {
70 const CMapItemGroup *pGroup = pLayers->GetGroup(Index: GroupIndex);
71 if(!pGroup)
72 {
73 continue;
74 }
75
76 for(int LayerIndex = 0; LayerIndex < pGroup->m_NumLayers; LayerIndex++)
77 {
78 const CMapItemLayer *pLayer = pLayers->GetLayer(Index: pGroup->m_StartLayer + LayerIndex);
79 if(!pLayer)
80 {
81 continue;
82 }
83
84 if(pLayer->m_Type == LAYERTYPE_TILES)
85 {
86 const CMapItemLayerTilemap *pLayerTilemap = reinterpret_cast<const CMapItemLayerTilemap *>(pLayer);
87 if(pLayerTilemap->m_Image >= 0 && pLayerTilemap->m_Image < m_Count)
88 {
89 aTextureUsedByTileOrQuadLayerFlag[pLayerTilemap->m_Image] |= 1;
90 }
91 }
92 else if(pLayer->m_Type == LAYERTYPE_QUADS)
93 {
94 const CMapItemLayerQuads *pLayerQuads = reinterpret_cast<const CMapItemLayerQuads *>(pLayer);
95 if(pLayerQuads->m_Image >= 0 && pLayerQuads->m_Image < m_Count)
96 {
97 aTextureUsedByTileOrQuadLayerFlag[pLayerQuads->m_Image] |= 2;
98 }
99 }
100 }
101 }
102
103 // load new textures
104 bool ShowWarning = false;
105 for(int i = 0; i < m_Count; i++)
106 {
107 if(aTextureUsedByTileOrQuadLayerFlag[i] == 0)
108 {
109 // skip loading unused images
110 continue;
111 }
112
113 const int LoadFlag = (((aTextureUsedByTileOrQuadLayerFlag[i] & 1) != 0) ? Graphics()->TextureLoadFlags() : 0) | (((aTextureUsedByTileOrQuadLayerFlag[i] & 2) != 0) ? 0 : (Graphics()->HasTextureArraysSupport() ? IGraphics::TEXLOAD_NO_2D_TEXTURE : 0));
114 const CMapItemImage_v2 *pImg = static_cast<const CMapItemImage_v2 *>(pMap->GetItem(Index: Start + i));
115
116 const char *pName = pMap->GetDataString(Index: pImg->m_ImageName);
117 if(pName == nullptr || pName[0] == '\0')
118 {
119 if(pImg->m_External)
120 {
121 log_error("mapimages", "Failed to load map image %d: failed to load name.", i);
122 ShowWarning = true;
123 continue;
124 }
125 pName = "(error)";
126 }
127
128 if(pImg->m_Version > 1 && pImg->m_MustBe1 != 1)
129 {
130 log_error("mapimages", "Failed to load map image %d '%s': invalid map image type.", i, pName);
131 ShowWarning = true;
132 continue;
133 }
134
135 if(pImg->m_External)
136 {
137 char aPath[IO_MAX_PATH_LENGTH];
138 bool Translated = false;
139 if(Client()->IsSixup())
140 {
141 Translated =
142 !str_comp(a: pName, b: "grass_doodads") ||
143 !str_comp(a: pName, b: "grass_main") ||
144 !str_comp(a: pName, b: "winter_main") ||
145 !str_comp(a: pName, b: "generic_shadows") ||
146 !str_comp(a: pName, b: "generic_unhookable") ||
147 !str_comp(a: pName, b: "easter");
148 }
149 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "mapres/%s%s.png", pName, Translated ? "_0.7" : "");
150 m_aTextures[i] = Graphics()->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL, Flags: LoadFlag);
151 }
152 else
153 {
154 if(pImg->m_Width <= 0 || pImg->m_Height <= 0)
155 {
156 log_error("mapimages", "Failed to load map image %d '%s': invalid image dimensions.", i, pName);
157 ShowWarning = true;
158 continue;
159 }
160
161 CImageInfo ImageInfo;
162 ImageInfo.m_Width = pImg->m_Width;
163 ImageInfo.m_Height = pImg->m_Height;
164 ImageInfo.m_Format = CImageInfo::FORMAT_RGBA;
165 ImageInfo.m_pData = static_cast<uint8_t *>(pMap->GetData(Index: pImg->m_ImageData));
166 if(ImageInfo.m_pData && (size_t)pMap->GetDataSize(Index: pImg->m_ImageData) >= ImageInfo.DataSize())
167 {
168 char aTexName[IO_MAX_PATH_LENGTH];
169 str_format(buffer: aTexName, buffer_size: sizeof(aTexName), format: "embedded: %s", pName);
170 m_aTextures[i] = Graphics()->LoadTextureRaw(Image: ImageInfo, Flags: LoadFlag, pTexName: aTexName);
171 pMap->UnloadData(Index: pImg->m_ImageData);
172 }
173 else
174 {
175 pMap->UnloadData(Index: pImg->m_ImageData);
176 log_error("mapimages", "Failed to load map image %d: failed to load data.", i);
177 ShowWarning = true;
178 continue;
179 }
180 }
181 pMap->UnloadData(Index: pImg->m_ImageName);
182 ShowWarning = ShowWarning || m_aTextures[i].IsNullTexture();
183 }
184 if(ShowWarning)
185 {
186 Client()->AddWarning(Warning: SWarning(Localize(pStr: "Some map images could not be loaded. Check the local console for details.")));
187 }
188}
189
190void CMapImages::OnMapLoad()
191{
192 OnMapLoadImpl(pLayers: GameClient()->Layers(), pMap: GameClient()->Map());
193}
194
195void CMapImages::LoadBackground(class CLayers *pLayers, class IMap *pMap)
196{
197 OnMapLoadImpl(pLayers, pMap);
198}
199
200static EMapImageModType GetEntitiesModType(const CGameInfo &GameInfo)
201{
202 if(GameInfo.m_EntitiesFDDrace)
203 return MAP_IMAGE_MOD_TYPE_FDDRACE;
204 else if(GameInfo.m_EntitiesDDNet)
205 return MAP_IMAGE_MOD_TYPE_DDNET;
206 else if(GameInfo.m_EntitiesDDRace)
207 return MAP_IMAGE_MOD_TYPE_DDRACE;
208 else if(GameInfo.m_EntitiesRace)
209 return MAP_IMAGE_MOD_TYPE_RACE;
210 else if(GameInfo.m_EntitiesBW)
211 return MAP_IMAGE_MOD_TYPE_BLOCKWORLDS;
212 else if(GameInfo.m_EntitiesFNG)
213 return MAP_IMAGE_MOD_TYPE_FNG;
214 else if(GameInfo.m_EntitiesVanilla)
215 return MAP_IMAGE_MOD_TYPE_VANILLA;
216 else
217 return MAP_IMAGE_MOD_TYPE_DDNET;
218}
219
220static bool IsValidTile(int LayerType, bool EntitiesAreMasked, EMapImageModType EntitiesModType, int TileIndex)
221{
222 if(TileIndex == TILE_AIR)
223 return false;
224 if(!EntitiesAreMasked)
225 return true;
226
227 if(EntitiesModType == MAP_IMAGE_MOD_TYPE_DDNET || EntitiesModType == MAP_IMAGE_MOD_TYPE_DDRACE)
228 {
229 if(EntitiesModType == MAP_IMAGE_MOD_TYPE_DDNET || TileIndex != TILE_SPEED_BOOST_OLD)
230 {
231 if(LayerType == MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH &&
232 !IsValidGameTile(Index: TileIndex) &&
233 !IsValidFrontTile(Index: TileIndex) &&
234 !IsValidSpeedupTile(Index: TileIndex) &&
235 !IsValidTeleTile(Index: TileIndex) &&
236 !IsValidTuneTile(Index: TileIndex))
237 {
238 return false;
239 }
240 else if(LayerType == MAP_IMAGE_ENTITY_LAYER_TYPE_SWITCH &&
241 !IsValidSwitchTile(Index: TileIndex))
242 {
243 return false;
244 }
245 }
246 }
247 else if(EntitiesModType == MAP_IMAGE_MOD_TYPE_RACE && IsCreditsTile(TileIndex))
248 {
249 return false;
250 }
251 else if(EntitiesModType == MAP_IMAGE_MOD_TYPE_FNG && IsCreditsTile(TileIndex))
252 {
253 return false;
254 }
255 else if(EntitiesModType == MAP_IMAGE_MOD_TYPE_VANILLA && IsCreditsTile(TileIndex))
256 {
257 return false;
258 }
259 return true;
260}
261
262IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType EntityLayerType)
263{
264 const bool EntitiesAreMasked = !GameClient()->m_GameInfo.m_DontMaskEntities;
265 const EMapImageModType EntitiesModType = GetEntitiesModType(GameInfo: GameClient()->m_GameInfo);
266
267 if(!m_aEntitiesIsLoaded[(EntitiesModType * 2) + (int)EntitiesAreMasked])
268 {
269 m_aEntitiesIsLoaded[(EntitiesModType * 2) + (int)EntitiesAreMasked] = true;
270
271 int TextureLoadFlag = 0;
272 if(Graphics()->HasTextureArraysSupport())
273 TextureLoadFlag = Graphics()->TextureLoadFlags() | IGraphics::TEXLOAD_NO_2D_TEXTURE;
274
275 CImageInfo ImgInfo;
276 char aPath[IO_MAX_PATH_LENGTH];
277 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s/%s.png", m_aEntitiesPath, gs_apModEntitiesNames[EntitiesModType]);
278 Graphics()->LoadPng(Image&: ImgInfo, pFilename: aPath, StorageType: IStorage::TYPE_ALL);
279
280 // try as single ddnet replacement
281 if(ImgInfo.m_pData == nullptr && EntitiesModType == MAP_IMAGE_MOD_TYPE_DDNET)
282 {
283 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s.png", m_aEntitiesPath);
284 Graphics()->LoadPng(Image&: ImgInfo, pFilename: aPath, StorageType: IStorage::TYPE_ALL);
285 }
286
287 // try default
288 if(ImgInfo.m_pData == nullptr)
289 {
290 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "editor/entities_clear/%s.png", gs_apModEntitiesNames[EntitiesModType]);
291 Graphics()->LoadPng(Image&: ImgInfo, pFilename: aPath, StorageType: IStorage::TYPE_ALL);
292 }
293
294 if(ImgInfo.m_pData != nullptr)
295 {
296 CImageInfo BuildImageInfo;
297 BuildImageInfo.m_Width = ImgInfo.m_Width;
298 BuildImageInfo.m_Height = ImgInfo.m_Height;
299 BuildImageInfo.m_Format = ImgInfo.m_Format;
300 BuildImageInfo.Allocate(); // allocate already transparent image
301
302 // convert tune tile to gray
303 const size_t CopyWidth = ImgInfo.m_Width / 16;
304 const size_t CopyHeight = ImgInfo.m_Height / 16;
305 const size_t TuneTileX = static_cast<size_t>(TILE_TUNE % 16) * CopyWidth;
306 const size_t TuneTileY = static_cast<size_t>(TILE_TUNE / 16) * CopyHeight;
307
308 ConvertToGrayscaleRect(Image: ImgInfo, StartX: TuneTileX, StartY: TuneTileY, Width: CopyWidth, Height: CopyHeight);
309
310 // build game layer
311 for(int LayerType = 0; LayerType < MAP_IMAGE_ENTITY_LAYER_TYPE_COUNT; ++LayerType)
312 {
313 dbg_assert(!m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][LayerType].IsValid(), "entities texture already loaded when it should not be");
314
315 // set everything transparent
316 mem_zero(block: BuildImageInfo.m_pData, size: BuildImageInfo.DataSize());
317
318 for(int i = 0; i < 256; ++i)
319 {
320 int TileIndex = i;
321 if(IsValidTile(LayerType, EntitiesAreMasked, EntitiesModType, TileIndex))
322 {
323 if(LayerType == MAP_IMAGE_ENTITY_LAYER_TYPE_SWITCH && TileIndex == TILE_SWITCHTIMEDOPEN)
324 {
325 TileIndex = 8;
326 }
327
328 const size_t OffsetX = (size_t)(TileIndex % 16) * CopyWidth;
329 const size_t OffsetY = (size_t)(TileIndex / 16) * CopyHeight;
330 BuildImageInfo.CopyRectFrom(SrcImage: ImgInfo, SrcX: OffsetX, SrcY: OffsetY, Width: CopyWidth, Height: CopyHeight, DestX: OffsetX, DestY: OffsetY);
331 }
332 }
333
334 m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][LayerType] = Graphics()->LoadTextureRaw(Image: BuildImageInfo, Flags: TextureLoadFlag, pTexName: aPath);
335 }
336
337 BuildImageInfo.Free();
338
339 // build tune map from the tune tile
340 if(Graphics()->HasTextureArraysSupport())
341 {
342 CImageInfo TuneMapInfo;
343 TuneMapInfo.m_Width = ImgInfo.m_Width;
344 TuneMapInfo.m_Height = ImgInfo.m_Height;
345 TuneMapInfo.m_Format = ImgInfo.m_Format;
346 TuneMapInfo.AllocateFillZero();
347
348 for(int TileIndex = 1; TileIndex < 256; ++TileIndex)
349 {
350 size_t StartX = CopyWidth * (TileIndex % 16);
351 size_t StartY = CopyHeight * (TileIndex / 16);
352 TuneMapInfo.CopyRectFrom(SrcImage: ImgInfo, SrcX: TuneTileX, SrcY: TuneTileY, Width: CopyWidth, Height: CopyHeight, DestX: StartX, DestY: StartY);
353 float Hue = std::fmod(x: (TileIndex - 1) * normalized_golden_angle, y: 1.0f);
354 ColorizeWithHueRect(Image&: TuneMapInfo, Hue, Sat: 0.75f, StartX, StartY, Width: CopyWidth, Height: CopyHeight);
355 }
356 m_TuneColorMapTexture = Graphics()->LoadTextureRawMove(Image&: TuneMapInfo, Flags: TextureLoadFlag);
357 m_TuneColorsIsLoaded = true;
358 }
359
360 ImgInfo.Free();
361 }
362 }
363
364 return m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][EntityLayerType];
365}
366
367IGraphics::CTextureHandle CMapImages::GetSpeedupArrow()
368{
369 if(!m_SpeedupArrowIsLoaded)
370 {
371 int TextureLoadFlag = Graphics()->TextureLoadFlags() | IGraphics::TEXLOAD_NO_2D_TEXTURE;
372 m_SpeedupArrowTexture = Graphics()->LoadTexture(pFilename: "editor/speed_arrow_array.png", StorageType: IStorage::TYPE_ALL, Flags: TextureLoadFlag);
373 m_SpeedupArrowIsLoaded = true;
374 }
375 return m_SpeedupArrowTexture;
376}
377
378IGraphics::CTextureHandle CMapImages::GetTuneColors()
379{
380 if(Graphics()->HasTextureArraysSupport())
381 {
382 if(!m_TuneColorsIsLoaded)
383 {
384 // load entities, this also loads the tune map
385 GetEntities(EntityLayerType: EMapImageEntityLayerType::MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH);
386 dbg_assert(m_TuneColorsIsLoaded, "Entities did not load the tune color map");
387 }
388 return m_TuneColorMapTexture;
389 }
390 else
391 {
392 return GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH);
393 }
394}
395
396IGraphics::CTextureHandle CMapImages::GetOverlayBottom()
397{
398 return m_OverlayBottomTexture;
399}
400
401IGraphics::CTextureHandle CMapImages::GetOverlayTop()
402{
403 return m_OverlayTopTexture;
404}
405
406IGraphics::CTextureHandle CMapImages::GetOverlayCenter()
407{
408 return m_OverlayCenterTexture;
409}
410
411void CMapImages::ChangeEntitiesPath(const char *pPath)
412{
413 if(str_comp(a: pPath, b: "default") == 0)
414 str_copy(dst&: m_aEntitiesPath, src: "editor/entities_clear");
415 else
416 {
417 str_format(buffer: m_aEntitiesPath, buffer_size: sizeof(m_aEntitiesPath), format: "assets/entities/%s", pPath);
418 }
419
420 for(int ModType = 0; ModType < MAP_IMAGE_MOD_TYPE_COUNT * 2; ++ModType)
421 {
422 if(m_aEntitiesIsLoaded[ModType])
423 {
424 for(int LayerType = 0; LayerType < MAP_IMAGE_ENTITY_LAYER_TYPE_COUNT; ++LayerType)
425 {
426 Graphics()->UnloadTexture(pIndex: &m_aaEntitiesTextures[ModType][LayerType]);
427 }
428 m_aEntitiesIsLoaded[ModType] = false;
429 }
430 }
431}
432
433void CMapImages::ConchainClTextEntitiesSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
434{
435 pfnCallback(pResult, pCallbackUserData);
436 if(pResult->NumArguments())
437 {
438 CMapImages *pThis = static_cast<CMapImages *>(pUserData);
439 pThis->SetTextureScale(g_Config.m_ClTextEntitiesSize);
440 }
441}
442
443void CMapImages::SetTextureScale(int Scale)
444{
445 if(m_TextureScale == Scale)
446 return;
447
448 m_TextureScale = Scale;
449
450 if(Graphics() && m_OverlayCenterTexture.IsValid()) // check if component was initialized
451 {
452 // reinitialize component
453 Graphics()->UnloadTexture(pIndex: &m_OverlayBottomTexture);
454 Graphics()->UnloadTexture(pIndex: &m_OverlayTopTexture);
455 Graphics()->UnloadTexture(pIndex: &m_OverlayCenterTexture);
456
457 InitOverlayTextures();
458 }
459}
460
461int CMapImages::GetTextureScale() const
462{
463 return m_TextureScale;
464}
465
466IGraphics::CTextureHandle CMapImages::UploadEntityLayerText(int TextureSize, int MaxWidth, int YOffset)
467{
468 CImageInfo TextImage;
469 TextImage.m_Width = 1024;
470 TextImage.m_Height = 1024;
471 TextImage.m_Format = CImageInfo::FORMAT_RGBA;
472 TextImage.AllocateFillZero();
473
474 UpdateEntityLayerText(TextImage, TextureSize, MaxWidth, YOffset, NumbersPower: 0);
475 UpdateEntityLayerText(TextImage, TextureSize, MaxWidth, YOffset, NumbersPower: 1);
476 UpdateEntityLayerText(TextImage, TextureSize, MaxWidth, YOffset, NumbersPower: 2, MaxNumber: 255);
477
478 const int TextureLoadFlag = Graphics()->TextureLoadFlags() | IGraphics::TEXLOAD_NO_2D_TEXTURE;
479 return Graphics()->LoadTextureRawMove(Image&: TextImage, Flags: TextureLoadFlag);
480}
481
482void CMapImages::UpdateEntityLayerText(CImageInfo &TextImage, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber)
483{
484 char aBuf[4];
485 int DigitsCount = NumbersPower + 1;
486
487 int CurrentNumber = std::pow(x: 10, y: NumbersPower);
488
489 if(MaxNumber == -1)
490 MaxNumber = CurrentNumber * 10 - 1;
491
492 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", CurrentNumber);
493
494 int CurrentNumberSuitableFontSize = TextRender()->AdjustFontSize(pText: aBuf, TextLength: DigitsCount, MaxSize: TextureSize, MaxWidth);
495 int UniversalSuitableFontSize = CurrentNumberSuitableFontSize * 0.92f; // should be smoothed enough to fit any digits combination
496
497 YOffset += ((TextureSize - UniversalSuitableFontSize) / 2);
498
499 for(; CurrentNumber <= MaxNumber; ++CurrentNumber)
500 {
501 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", CurrentNumber);
502
503 float x = (CurrentNumber % 16) * 64;
504 float y = (CurrentNumber / 16) * 64;
505
506 int ApproximateTextWidth = TextRender()->CalculateTextWidth(pText: aBuf, TextLength: DigitsCount, FontWidth: 0, FontSize: UniversalSuitableFontSize);
507 int XOffSet = (MaxWidth - std::clamp(val: ApproximateTextWidth, lo: 0, hi: MaxWidth)) / 2;
508
509 TextRender()->UploadEntityLayerText(TextImage, TexSubWidth: (TextImage.m_Width / 16) - XOffSet, TexSubHeight: (TextImage.m_Height / 16) - YOffset, pText: aBuf, Length: DigitsCount, x: x + XOffSet, y: y + YOffset, FontSize: UniversalSuitableFontSize);
510 }
511}
512
513void CMapImages::InitOverlayTextures()
514{
515 int TextureSize = 64 * m_TextureScale / 100;
516 TextureSize = std::clamp(val: TextureSize, lo: 2, hi: 64);
517 int TextureToVerticalCenterOffset = (64 - TextureSize) / 2 + TextureSize * 0.1f; // should be used to move texture to the center of 64 pixels area
518
519 if(!m_OverlayBottomTexture.IsValid())
520 {
521 m_OverlayBottomTexture = UploadEntityLayerText(TextureSize: TextureSize / 2, MaxWidth: 64, YOffset: 32 + TextureToVerticalCenterOffset / 2);
522 }
523
524 if(!m_OverlayTopTexture.IsValid())
525 {
526 m_OverlayTopTexture = UploadEntityLayerText(TextureSize: TextureSize / 2, MaxWidth: 64, YOffset: TextureToVerticalCenterOffset / 2);
527 }
528
529 if(!m_OverlayCenterTexture.IsValid())
530 {
531 m_OverlayCenterTexture = UploadEntityLayerText(TextureSize, MaxWidth: 64, YOffset: TextureToVerticalCenterOffset);
532 }
533}
534