1#include "render_layer.h"
2
3#include <base/dbg.h>
4#include <base/log.h>
5#include <base/mem.h>
6#include <base/str.h>
7#include <base/time.h>
8
9#include <engine/graphics.h>
10#include <engine/map.h>
11#include <engine/shared/config.h>
12#include <engine/storage.h>
13
14#include <game/localization.h>
15#include <game/mapitems.h>
16
17#include <array>
18#include <cmath>
19
20/************************
21 * Render Buffer Helper *
22 ************************/
23class CTexCoords
24{
25public:
26 std::array<uint8_t, 4> m_aTexX;
27 std::array<uint8_t, 4> m_aTexY;
28};
29
30constexpr static CTexCoords CalculateTexCoords(unsigned int Flags)
31{
32 CTexCoords TexCoord;
33 TexCoord.m_aTexX = {0, 1, 1, 0};
34 TexCoord.m_aTexY = {0, 0, 1, 1};
35
36 if(Flags & TILEFLAG_XFLIP)
37 std::rotate(first: std::begin(cont&: TexCoord.m_aTexX), middle: std::begin(cont&: TexCoord.m_aTexX) + 2, last: std::end(cont&: TexCoord.m_aTexX));
38
39 if(Flags & TILEFLAG_YFLIP)
40 std::rotate(first: std::begin(cont&: TexCoord.m_aTexY), middle: std::begin(cont&: TexCoord.m_aTexY) + 2, last: std::end(cont&: TexCoord.m_aTexY));
41
42 if(Flags & (TILEFLAG_ROTATE >> 1))
43 {
44 std::rotate(first: std::begin(cont&: TexCoord.m_aTexX), middle: std::begin(cont&: TexCoord.m_aTexX) + 3, last: std::end(cont&: TexCoord.m_aTexX));
45 std::rotate(first: std::begin(cont&: TexCoord.m_aTexY), middle: std::begin(cont&: TexCoord.m_aTexY) + 3, last: std::end(cont&: TexCoord.m_aTexY));
46 }
47 return TexCoord;
48}
49
50template<std::size_t N>
51constexpr static std::array<CTexCoords, N> MakeTexCoordsTable()
52{
53 std::array<CTexCoords, N> aTexCoords = {};
54 for(std::size_t i = 0; i < N; ++i)
55 aTexCoords[i] = CalculateTexCoords(Flags: i);
56 return aTexCoords;
57}
58
59constexpr std::array<CTexCoords, 8> TEX_COORDS_TABLE = MakeTexCoordsTable<8>();
60
61static void FillTmpTile(CGraphicTile *pTmpTile, CGraphicTileTextureCoords *pTmpTex, unsigned char Flags, unsigned char Index, int x, int y, const ivec2 &Offset, int Scale)
62{
63 if(pTmpTex)
64 {
65 uint8_t TableFlag = (Flags & (TILEFLAG_XFLIP | TILEFLAG_YFLIP)) + ((Flags & TILEFLAG_ROTATE) >> 1);
66 const auto &aTexX = TEX_COORDS_TABLE[TableFlag].m_aTexX;
67 const auto &aTexY = TEX_COORDS_TABLE[TableFlag].m_aTexY;
68
69 pTmpTex->m_TexCoordTopLeft.x = aTexX[0];
70 pTmpTex->m_TexCoordTopLeft.y = aTexY[0];
71 pTmpTex->m_TexCoordBottomLeft.x = aTexX[3];
72 pTmpTex->m_TexCoordBottomLeft.y = aTexY[3];
73 pTmpTex->m_TexCoordTopRight.x = aTexX[1];
74 pTmpTex->m_TexCoordTopRight.y = aTexY[1];
75 pTmpTex->m_TexCoordBottomRight.x = aTexX[2];
76 pTmpTex->m_TexCoordBottomRight.y = aTexY[2];
77
78 pTmpTex->m_TexCoordTopLeft.z = Index;
79 pTmpTex->m_TexCoordBottomLeft.z = Index;
80 pTmpTex->m_TexCoordTopRight.z = Index;
81 pTmpTex->m_TexCoordBottomRight.z = Index;
82
83 bool HasRotation = (Flags & TILEFLAG_ROTATE) != 0;
84 pTmpTex->m_TexCoordTopLeft.w = HasRotation;
85 pTmpTex->m_TexCoordBottomLeft.w = HasRotation;
86 pTmpTex->m_TexCoordTopRight.w = HasRotation;
87 pTmpTex->m_TexCoordBottomRight.w = HasRotation;
88 }
89
90 vec2 TopLeft(x * Scale + Offset.x, y * Scale + Offset.y);
91 vec2 BottomRight(x * Scale + Scale + Offset.x, y * Scale + Scale + Offset.y);
92 pTmpTile->m_TopLeft = TopLeft;
93 pTmpTile->m_BottomLeft.x = TopLeft.x;
94 pTmpTile->m_BottomLeft.y = BottomRight.y;
95 pTmpTile->m_TopRight.x = BottomRight.x;
96 pTmpTile->m_TopRight.y = TopLeft.y;
97 pTmpTile->m_BottomRight = BottomRight;
98}
99
100static void FillTmpTileSpeedup(CGraphicTile *pTmpTile, CGraphicTileTextureCoords *pTmpTex, unsigned char Flags, int x, int y, const ivec2 &Offset, int Scale, short AngleRotate)
101{
102 int Angle = AngleRotate % 360;
103 FillTmpTile(pTmpTile, pTmpTex, Flags: Angle >= 270 ? ROTATION_270 : (Angle >= 180 ? ROTATION_180 : (Angle >= 90 ? ROTATION_90 : 0)), Index: AngleRotate % 90, x, y, Offset, Scale);
104}
105
106static bool AddTile(std::vector<CGraphicTile> &vTmpTiles, std::vector<CGraphicTileTextureCoords> &vTmpTileTexCoords, unsigned char Index, unsigned char Flags, int x, int y, bool DoTextureCoords, bool FillSpeedup = false, int AngleRotate = -1, const ivec2 &Offset = ivec2{0, 0}, int Scale = 32)
107{
108 if(Index <= 0)
109 return false;
110
111 vTmpTiles.emplace_back();
112 CGraphicTile &Tile = vTmpTiles.back();
113 CGraphicTileTextureCoords *pTileTex = nullptr;
114 if(DoTextureCoords)
115 {
116 vTmpTileTexCoords.emplace_back();
117 CGraphicTileTextureCoords &TileTex = vTmpTileTexCoords.back();
118 pTileTex = &TileTex;
119 }
120 if(FillSpeedup)
121 FillTmpTileSpeedup(pTmpTile: &Tile, pTmpTex: pTileTex, Flags, x, y, Offset, Scale, AngleRotate);
122 else
123 FillTmpTile(pTmpTile: &Tile, pTmpTex: pTileTex, Flags, Index, x, y, Offset, Scale);
124
125 return true;
126}
127
128class CTmpQuadVertexTextured
129{
130public:
131 float m_X, m_Y, m_CenterX, m_CenterY;
132 unsigned char m_R, m_G, m_B, m_A;
133 float m_U, m_V;
134};
135
136class CTmpQuadVertex
137{
138public:
139 float m_X, m_Y, m_CenterX, m_CenterY;
140 unsigned char m_R, m_G, m_B, m_A;
141};
142
143class CTmpQuad
144{
145public:
146 CTmpQuadVertex m_aVertices[4];
147};
148
149class CTmpQuadTextured
150{
151public:
152 CTmpQuadVertexTextured m_aVertices[4];
153};
154
155bool CRenderLayerTile::CTileLayerVisuals::Init(unsigned int Width, unsigned int Height)
156{
157 m_Width = Width;
158 m_Height = Height;
159 if(Width == 0 || Height == 0)
160 return false;
161 if constexpr(sizeof(unsigned int) >= sizeof(ptrdiff_t))
162 if(Width >= std::numeric_limits<std::ptrdiff_t>::max() || Height >= std::numeric_limits<std::ptrdiff_t>::max())
163 return false;
164
165 m_vTilesOfLayer.resize(sz: (size_t)Height * (size_t)Width);
166
167 m_vBorderTop.resize(sz: Width);
168 m_vBorderBottom.resize(sz: Width);
169
170 m_vBorderLeft.resize(sz: Height);
171 m_vBorderRight.resize(sz: Height);
172 return true;
173}
174
175/**************
176 * Base Layer *
177 **************/
178
179CRenderLayer::CRenderLayer(int GroupId, int LayerId, int Flags) :
180 m_GroupId(GroupId), m_LayerId(LayerId), m_Flags(Flags) {}
181
182void CRenderLayer::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
183{
184 CRenderComponent::OnInit(pGraphics, pTextRender, pRenderMap);
185 m_pMap = pMap;
186 m_pMapImages = pMapImages;
187 m_RenderUploadCallback = FRenderUploadCallbackOptional;
188 m_pEnvelopeManager = pEnvelopeManager;
189}
190
191void CRenderLayer::UseTexture(IGraphics::CTextureHandle TextureHandle)
192{
193 if(TextureHandle.IsValid())
194 Graphics()->TextureSet(Texture: TextureHandle);
195 else
196 Graphics()->TextureClear();
197}
198
199void CRenderLayer::RenderLoading() const
200{
201 const char *pLoadingTitle = Localize(pStr: "Loading map");
202 const char *pLoadingMessage = Localize(pStr: "Uploading map data to GPU");
203 if(m_RenderUploadCallback.has_value())
204 (*m_RenderUploadCallback)(pLoadingTitle, pLoadingMessage, 0);
205}
206
207bool CRenderLayer::IsVisibleInClipRegion(const std::optional<CClipRegion> &ClipRegion) const
208{
209 // always show unclipped regions
210 if(!ClipRegion.has_value())
211 return true;
212
213 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
214 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
215 float Left = ClipRegion->m_X;
216 float Top = ClipRegion->m_Y;
217 float Right = ClipRegion->m_X + ClipRegion->m_Width;
218 float Bottom = ClipRegion->m_Y + ClipRegion->m_Height;
219
220 return Right >= ScreenX0 && Left <= ScreenX1 && Bottom >= ScreenY0 && Top <= ScreenY1;
221}
222
223/**************
224 * Group *
225 **************/
226
227CRenderLayerGroup::CRenderLayerGroup(int GroupId, CMapItemGroup *pGroup) :
228 CRenderLayer(GroupId, 0, 0), m_pGroup(pGroup) {}
229
230bool CRenderLayerGroup::DoRender(const CRenderLayerParams &Params)
231{
232 if(!g_Config.m_GfxNoclip || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN)
233 {
234 Graphics()->ClipDisable();
235 if(m_pGroup->m_Version >= 2 && m_pGroup->m_UseClipping)
236 {
237 // set clipping
238 Graphics()->MapScreenToInterface(CenterX: Params.m_Center.x, CenterY: Params.m_Center.y, Zoom: Params.m_Zoom);
239
240 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
241 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
242 float ScreenWidth = ScreenX1 - ScreenX0;
243 float ScreenHeight = ScreenY1 - ScreenY0;
244 float Left = m_pGroup->m_ClipX - ScreenX0;
245 float Top = m_pGroup->m_ClipY - ScreenY0;
246 float Right = m_pGroup->m_ClipX + m_pGroup->m_ClipW - ScreenX0;
247 float Bottom = m_pGroup->m_ClipY + m_pGroup->m_ClipH - ScreenY0;
248
249 if(Right < 0.0f || Left > ScreenWidth || Bottom < 0.0f || Top > ScreenHeight)
250 return false;
251
252 // Render debug before enabling the clip
253 if(Params.m_DebugRenderGroupClips)
254 {
255 char aDebugText[32];
256 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d", m_GroupId);
257 RenderMap()->RenderDebugClip(ClipX: m_pGroup->m_ClipX, ClipY: m_pGroup->m_ClipY, ClipW: m_pGroup->m_ClipW, ClipH: m_pGroup->m_ClipH, Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
258 }
259
260 int ClipX = (int)std::round(x: Left * Graphics()->ScreenWidth() / ScreenWidth);
261 int ClipY = (int)std::round(x: Top * Graphics()->ScreenHeight() / ScreenHeight);
262
263 Graphics()->ClipEnable(
264 x: ClipX,
265 y: ClipY,
266 w: (int)std::round(x: Right * Graphics()->ScreenWidth() / ScreenWidth) - ClipX,
267 h: (int)std::round(x: Bottom * Graphics()->ScreenHeight() / ScreenHeight) - ClipY);
268 }
269 }
270 return true;
271}
272
273void CRenderLayerGroup::Render(const CRenderLayerParams &Params)
274{
275 int ParallaxZoom = std::clamp(val: (maximum(a: m_pGroup->m_ParallaxX, b: m_pGroup->m_ParallaxY)), lo: 0, hi: 100);
276 float aPoints[4];
277 Graphics()->MapScreenToWorld(CenterX: Params.m_Center.x, CenterY: Params.m_Center.y, ParallaxX: m_pGroup->m_ParallaxX, ParallaxY: m_pGroup->m_ParallaxY, ParallaxZoom: (float)ParallaxZoom,
278 OffsetX: m_pGroup->m_OffsetX, OffsetY: m_pGroup->m_OffsetY, Aspect: Graphics()->ScreenAspect(), Zoom: Params.m_Zoom, pPoints: aPoints);
279 Graphics()->MapScreen(TopLeftX: aPoints[0], TopLeftY: aPoints[1], BottomRightX: aPoints[2], BottomRightY: aPoints[3]);
280}
281
282/**************
283 * Tile Layer *
284 **************/
285
286CRenderLayerTile::CRenderLayerTile(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
287 CRenderLayer(GroupId, LayerId, Flags)
288{
289 m_pLayerTilemap = pLayerTilemap;
290 m_Color = ColorRGBA(m_pLayerTilemap->m_Color.r / 255.0f, m_pLayerTilemap->m_Color.g / 255.0f, m_pLayerTilemap->m_Color.b / 255.0f, pLayerTilemap->m_Color.a / 255.0f);
291 m_pTiles = nullptr;
292}
293
294void CRenderLayerTile::RenderTileLayer(const ColorRGBA &Color, const CRenderLayerParams &Params, CTileLayerVisuals *pTileLayerVisuals)
295{
296 CTileLayerVisuals &Visuals = pTileLayerVisuals ? *pTileLayerVisuals : m_VisualTiles.value();
297 if(Visuals.m_BufferContainerIndex == -1)
298 return; // no visuals were created
299
300 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
301 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
302
303 int ScreenRectY0 = std::floor(x: ScreenY0 / 32);
304 int ScreenRectX0 = std::floor(x: ScreenX0 / 32);
305 int ScreenRectY1 = std::ceil(x: ScreenY1 / 32);
306 int ScreenRectX1 = std::ceil(x: ScreenX1 / 32);
307
308 if(IsVisibleInClipRegion(ClipRegion: m_LayerClip))
309 {
310 // create the indice buffers we want to draw -- reuse them
311 std::vector<char *> vpIndexOffsets;
312 std::vector<unsigned int> vDrawCounts;
313
314 int X0 = std::max(a: ScreenRectX0, b: 0);
315 int X1 = std::min(a: ScreenRectX1, b: (int)Visuals.m_Width);
316 int XR = X1 == std::numeric_limits<int>::min() ? X1 : X1 - 1;
317 if(X0 <= XR)
318 {
319 int Y0 = std::max(a: ScreenRectY0, b: 0);
320 int Y1 = std::min(a: ScreenRectY1, b: (int)Visuals.m_Height);
321
322 unsigned long long Reserve = absolute(a: Y1 - Y0) + 1;
323 vpIndexOffsets.reserve(n: Reserve);
324 vDrawCounts.reserve(n: Reserve);
325
326 for(int y = Y0; y < Y1; ++y)
327 {
328 dbg_assert(Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].IndexBufferByteOffset() >= Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset(), "Tile offsets are not monotone.");
329 unsigned int NumVertices = ((Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].IndexBufferByteOffset() - Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset()) / sizeof(unsigned int)) + (Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].DoDraw() ? 6lu : 0lu);
330
331 if(NumVertices)
332 {
333 vpIndexOffsets.push_back(x: (offset_ptr_size)Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset());
334 vDrawCounts.push_back(x: NumVertices);
335 }
336 }
337
338 int DrawCount = vpIndexOffsets.size();
339 if(DrawCount != 0)
340 {
341 Graphics()->RenderTileLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pOffsets: vpIndexOffsets.data(), pIndicedVertexDrawNum: vDrawCounts.data(), NumIndicesOffset: DrawCount);
342 }
343 }
344 }
345
346 if(Params.m_RenderTileBorder && (ScreenRectX1 > (int)Visuals.m_Width || ScreenRectY1 > (int)Visuals.m_Height || ScreenRectX0 < 0 || ScreenRectY0 < 0))
347 {
348 RenderTileBorder(Color, BorderX0: ScreenRectX0, BorderY0: ScreenRectY0, BorderX1: ScreenRectX1, BorderY1: ScreenRectY1, pTileLayerVisuals: &Visuals);
349 }
350}
351
352void CRenderLayerTile::RenderTileBorder(const ColorRGBA &Color, int BorderX0, int BorderY0, int BorderX1, int BorderY1, CTileLayerVisuals *pTileLayerVisuals)
353{
354 CTileLayerVisuals &Visuals = *pTileLayerVisuals;
355
356 int Y0 = std::max(a: 0, b: BorderY0);
357 int X0 = std::max(a: 0, b: BorderX0);
358 int Y1 = std::min(a: (int)Visuals.m_Height, b: BorderY1);
359 int X1 = std::min(a: (int)Visuals.m_Width, b: BorderX1);
360
361 // corners
362 auto DrawCorner = [&](vec2 Offset, vec2 Scale, CTileLayerVisuals::CTileVisual &Visual) {
363 Offset *= 32.0f;
364 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: (offset_ptr_size)Visual.IndexBufferByteOffset(), Offset, Scale, DrawNum: 1);
365 };
366
367 if(BorderX0 < 0)
368 {
369 // Draw corners on left side
370 if(BorderY0 < 0 && Visuals.m_BorderTopLeft.DoDraw())
371 {
372 DrawCorner(
373 vec2(0, 0),
374 vec2(std::abs(x: BorderX0), std::abs(x: BorderY0)),
375 Visuals.m_BorderTopLeft);
376 }
377 if(BorderY1 > (int)Visuals.m_Height && Visuals.m_BorderBottomLeft.DoDraw())
378 {
379 DrawCorner(
380 vec2(0, Visuals.m_Height),
381 vec2(std::abs(x: BorderX0), BorderY1 - Visuals.m_Height),
382 Visuals.m_BorderBottomLeft);
383 }
384 }
385 if(BorderX1 > (int)Visuals.m_Width)
386 {
387 // Draw corners on right side
388 if(BorderY0 < 0 && Visuals.m_BorderTopRight.DoDraw())
389 {
390 DrawCorner(
391 vec2(Visuals.m_Width, 0),
392 vec2(BorderX1 - Visuals.m_Width, std::abs(x: BorderY0)),
393 Visuals.m_BorderTopRight);
394 }
395 if(BorderY1 > (int)Visuals.m_Height && Visuals.m_BorderBottomRight.DoDraw())
396 {
397 DrawCorner(
398 vec2(Visuals.m_Width, Visuals.m_Height),
399 vec2(BorderX1 - Visuals.m_Width, BorderY1 - Visuals.m_Height),
400 Visuals.m_BorderBottomRight);
401 }
402 }
403
404 // borders
405 auto DrawBorder = [&](vec2 Offset, vec2 Scale, CTileLayerVisuals::CTileVisual &StartVisual, CTileLayerVisuals::CTileVisual &EndVisual) {
406 unsigned int DrawNum = ((EndVisual.IndexBufferByteOffset() - StartVisual.IndexBufferByteOffset()) / (sizeof(unsigned int) * 6)) + (EndVisual.DoDraw() ? 1lu : 0lu);
407 offset_ptr_size pOffset = (offset_ptr_size)StartVisual.IndexBufferByteOffset();
408 Offset *= 32.0f;
409 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: pOffset, Offset, Scale, DrawNum);
410 };
411
412 if(Y0 < (int)Visuals.m_Height && Y1 > 0)
413 {
414 if(BorderX1 > (int)Visuals.m_Width)
415 {
416 // Draw right border
417 DrawBorder(
418 vec2(Visuals.m_Width, 0),
419 vec2(BorderX1 - Visuals.m_Width, 1.f),
420 Visuals.m_vBorderRight[Y0], Visuals.m_vBorderRight[Y1 - 1]);
421 }
422 if(BorderX0 < 0)
423 {
424 // Draw left border
425 DrawBorder(
426 vec2(0, 0),
427 vec2(std::abs(x: BorderX0), 1),
428 Visuals.m_vBorderLeft[Y0], Visuals.m_vBorderLeft[Y1 - 1]);
429 }
430 }
431
432 if(X0 < (int)Visuals.m_Width && X1 > 0)
433 {
434 if(BorderY0 < 0)
435 {
436 // Draw top border
437 DrawBorder(
438 vec2(0, 0),
439 vec2(1, std::abs(x: BorderY0)),
440 Visuals.m_vBorderTop[X0], Visuals.m_vBorderTop[X1 - 1]);
441 }
442 if(BorderY1 > (int)Visuals.m_Height)
443 {
444 // Draw bottom border
445 DrawBorder(
446 vec2(0, Visuals.m_Height),
447 vec2(1, BorderY1 - Visuals.m_Height),
448 Visuals.m_vBorderBottom[X0], Visuals.m_vBorderBottom[X1 - 1]);
449 }
450 }
451}
452
453void CRenderLayerTile::RenderKillTileBorder(const ColorRGBA &Color)
454{
455 CTileLayerVisuals &Visuals = m_VisualTiles.value();
456 if(Visuals.m_BufferContainerIndex == -1)
457 return; // no visuals were created
458
459 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
460 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
461
462 int BorderY0 = std::floor(x: ScreenY0 / 32);
463 int BorderX0 = std::floor(x: ScreenX0 / 32);
464 int BorderY1 = std::ceil(x: ScreenY1 / 32);
465 int BorderX1 = std::ceil(x: ScreenX1 / 32);
466
467 if(BorderX0 >= -BorderRenderDistance && BorderY0 >= -BorderRenderDistance && BorderX1 <= (int)Visuals.m_Width + BorderRenderDistance && BorderY1 <= (int)Visuals.m_Height + BorderRenderDistance)
468 return;
469 if(!Visuals.m_BorderKillTile.DoDraw())
470 return;
471
472 BorderX0 = std::clamp(val: BorderX0, lo: -300, hi: (int)Visuals.m_Width + 299);
473 BorderY0 = std::clamp(val: BorderY0, lo: -300, hi: (int)Visuals.m_Height + 299);
474 BorderX1 = std::clamp(val: BorderX1, lo: -300, hi: (int)Visuals.m_Width + 299);
475 BorderY1 = std::clamp(val: BorderY1, lo: -300, hi: (int)Visuals.m_Height + 299);
476
477 auto DrawKillBorder = [&](vec2 Offset, vec2 Scale) {
478 offset_ptr_size pOffset = (offset_ptr_size)Visuals.m_BorderKillTile.IndexBufferByteOffset();
479 Offset *= 32.0f;
480 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: pOffset, Offset, Scale, DrawNum: 1);
481 };
482
483 // Draw left kill tile border
484 if(BorderX0 < -BorderRenderDistance)
485 {
486 DrawKillBorder(
487 vec2(BorderX0, BorderY0),
488 vec2(-BorderRenderDistance - BorderX0, BorderY1 - BorderY0));
489 }
490 // Draw top kill tile border
491 if(BorderY0 < -BorderRenderDistance)
492 {
493 DrawKillBorder(
494 vec2(std::max(a: BorderX0, b: -BorderRenderDistance), BorderY0),
495 vec2(std::min(a: BorderX1, b: (int)Visuals.m_Width + BorderRenderDistance) - std::max(a: BorderX0, b: -BorderRenderDistance), -BorderRenderDistance - BorderY0));
496 }
497 // Draw right kill tile border
498 if(BorderX1 > (int)Visuals.m_Width + BorderRenderDistance)
499 {
500 DrawKillBorder(
501 vec2(Visuals.m_Width + BorderRenderDistance, BorderY0),
502 vec2(BorderX1 - (Visuals.m_Width + BorderRenderDistance), BorderY1 - BorderY0));
503 }
504 // Draw bottom kill tile border
505 if(BorderY1 > (int)Visuals.m_Height + BorderRenderDistance)
506 {
507 DrawKillBorder(
508 vec2(std::max(a: BorderX0, b: -BorderRenderDistance), Visuals.m_Height + BorderRenderDistance),
509 vec2(std::min(a: BorderX1, b: (int)Visuals.m_Width + BorderRenderDistance) - std::max(a: BorderX0, b: -BorderRenderDistance), BorderY1 - (Visuals.m_Height + BorderRenderDistance)));
510 }
511}
512
513ColorRGBA CRenderLayerTile::GetRenderColor(const CRenderLayerParams &Params) const
514{
515 ColorRGBA Color = m_Color;
516 if(Params.m_EntityOverlayVal && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
517 Color.a *= (100 - Params.m_EntityOverlayVal) / 100.0f;
518
519 ColorRGBA ColorEnv = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
520 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: m_pLayerTilemap->m_ColorEnvOffset, EnvelopeIndex: m_pLayerTilemap->m_ColorEnv, Result&: ColorEnv, Channels: 4);
521 Color = Color.Multiply(Other: ColorEnv);
522 return Color;
523}
524
525void CRenderLayerTile::Render(const CRenderLayerParams &Params)
526{
527 UseTexture(TextureHandle: GetTexture());
528 ColorRGBA Color = GetRenderColor(Params);
529 if(Graphics()->IsTileBufferingEnabled() && Params.m_TileAndQuadBuffering)
530 {
531 RenderTileLayerWithTileBuffer(Color, Params);
532 }
533 else
534 {
535 RenderTileLayerNoTileBuffer(Color, Params);
536 }
537
538 if(Params.m_DebugRenderTileClips && m_LayerClip.has_value())
539 {
540 const CClipRegion &Clip = m_LayerClip.value();
541 char aDebugText[32];
542 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d LayerId %d", m_GroupId, m_LayerId);
543 RenderMap()->RenderDebugClip(ClipX: Clip.m_X, ClipY: Clip.m_Y, ClipW: Clip.m_Width, ClipH: Clip.m_Height, Color: ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
544 }
545}
546
547bool CRenderLayerTile::DoRender(const CRenderLayerParams &Params)
548{
549 // skip rendering if we render background force, but deactivated tile layer and want to render a tilelayer
550 if(!g_Config.m_ClBackgroundShowTilesLayers && Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE)
551 return false;
552
553 // skip rendering anything but entities if we only want to render entities
554 if(Params.m_EntityOverlayVal == 100 && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
555 return false;
556
557 // skip rendering if detail layers if not wanted
558 if(m_Flags & LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail && Params.m_RenderType != ERenderType::RENDERTYPE_FULL_DESIGN) // detail but no details
559 return false;
560 return true;
561}
562
563void CRenderLayerTile::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
564{
565 RenderTileLayer(Color, Params);
566}
567
568void CRenderLayerTile::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
569{
570 Graphics()->BlendNone();
571 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
572 Graphics()->BlendNormal();
573 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
574}
575
576void CRenderLayerTile::Init()
577{
578 if(m_pLayerTilemap->m_Image >= 0 && m_pLayerTilemap->m_Image < m_pMapImages->Num())
579 m_TextureHandle = m_pMapImages->Get(Index: m_pLayerTilemap->m_Image);
580 else
581 m_TextureHandle.Invalidate();
582 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
583}
584
585void CRenderLayerTile::UploadTileData(std::optional<CTileLayerVisuals> &VisualsOptional, int CurOverlay, bool AddAsSpeedup, bool IsGameLayer)
586{
587 if(!Graphics()->IsTileBufferingEnabled())
588 return;
589
590 // prepare all visuals for all tile layers
591 std::vector<CGraphicTile> vTmpTiles;
592 std::vector<CGraphicTileTextureCoords> vTmpTileTexCoords;
593 std::vector<CGraphicTile> vTmpBorderTopTiles;
594 std::vector<CGraphicTileTextureCoords> vTmpBorderTopTilesTexCoords;
595 std::vector<CGraphicTile> vTmpBorderLeftTiles;
596 std::vector<CGraphicTileTextureCoords> vTmpBorderLeftTilesTexCoords;
597 std::vector<CGraphicTile> vTmpBorderRightTiles;
598 std::vector<CGraphicTileTextureCoords> vTmpBorderRightTilesTexCoords;
599 std::vector<CGraphicTile> vTmpBorderBottomTiles;
600 std::vector<CGraphicTileTextureCoords> vTmpBorderBottomTilesTexCoords;
601 std::vector<CGraphicTile> vTmpBorderCorners;
602 std::vector<CGraphicTileTextureCoords> vTmpBorderCornersTexCoords;
603
604 const bool DoTextureCoords = GetTexture().IsValid();
605
606 // create the visual and set it in the optional, afterwards get it
607 CTileLayerVisuals v;
608 v.OnInit(pRenderComponent: this);
609 VisualsOptional = v;
610 CTileLayerVisuals &Visuals = VisualsOptional.value();
611
612 if(!Visuals.Init(Width: m_pLayerTilemap->m_Width, Height: m_pLayerTilemap->m_Height))
613 return;
614
615 Visuals.m_IsTextured = DoTextureCoords;
616
617 if(!DoTextureCoords)
618 {
619 vTmpTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height);
620 vTmpBorderTopTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width);
621 vTmpBorderBottomTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width);
622 vTmpBorderLeftTiles.reserve(n: (size_t)m_pLayerTilemap->m_Height);
623 vTmpBorderRightTiles.reserve(n: (size_t)m_pLayerTilemap->m_Height);
624 vTmpBorderCorners.reserve(n: (size_t)4);
625 }
626 else
627 {
628 vTmpTileTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height);
629 vTmpBorderTopTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width);
630 vTmpBorderBottomTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width);
631 vTmpBorderLeftTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Height);
632 vTmpBorderRightTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Height);
633 vTmpBorderCornersTexCoords.reserve(n: (size_t)4);
634 }
635
636 int DrawLeft = m_pLayerTilemap->m_Width;
637 int DrawRight = 0;
638 int DrawTop = m_pLayerTilemap->m_Height;
639 int DrawBottom = 0;
640
641 int x = 0;
642 int y = 0;
643 for(y = 0; y < m_pLayerTilemap->m_Height; ++y)
644 {
645 for(x = 0; x < m_pLayerTilemap->m_Width; ++x)
646 {
647 unsigned char Index = 0;
648 unsigned char Flags = 0;
649 int AngleRotate = -1;
650 GetTileData(pIndex: &Index, pFlags: &Flags, pAngleRotate: &AngleRotate, x, y, CurOverlay);
651
652 // the amount of tiles handled before this tile
653 int TilesHandledCount = vTmpTiles.size();
654 Visuals.m_vTilesOfLayer[y * m_pLayerTilemap->m_Width + x].SetIndexBufferByteOffset((offset_ptr32)(TilesHandledCount));
655
656 if(AddTile(vTmpTiles, vTmpTileTexCoords, Index, Flags, x, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate))
657 {
658 Visuals.m_vTilesOfLayer[y * m_pLayerTilemap->m_Width + x].Draw(SetDraw: true);
659
660 // calculate clip region boundaries based on draws
661 DrawLeft = std::min(a: DrawLeft, b: x);
662 DrawRight = std::max(a: DrawRight, b: x);
663 DrawTop = std::min(a: DrawTop, b: y);
664 DrawBottom = std::max(a: DrawBottom, b: y);
665 }
666
667 // do the border tiles
668 if(x == 0)
669 {
670 if(y == 0)
671 {
672 Visuals.m_BorderTopLeft.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
673 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, -32}))
674 Visuals.m_BorderTopLeft.Draw(SetDraw: true);
675 }
676 else if(y == m_pLayerTilemap->m_Height - 1)
677 {
678 Visuals.m_BorderBottomLeft.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
679 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, 0}))
680 Visuals.m_BorderBottomLeft.Draw(SetDraw: true);
681 }
682 Visuals.m_vBorderLeft[y].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderLeftTiles.size()));
683 if(AddTile(vTmpTiles&: vTmpBorderLeftTiles, vTmpTileTexCoords&: vTmpBorderLeftTilesTexCoords, Index, Flags, x: 0, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, 0}))
684 Visuals.m_vBorderLeft[y].Draw(SetDraw: true);
685 }
686 else if(x == m_pLayerTilemap->m_Width - 1)
687 {
688 if(y == 0)
689 {
690 Visuals.m_BorderTopRight.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
691 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, -32}))
692 Visuals.m_BorderTopRight.Draw(SetDraw: true);
693 }
694 else if(y == m_pLayerTilemap->m_Height - 1)
695 {
696 Visuals.m_BorderBottomRight.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
697 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
698 Visuals.m_BorderBottomRight.Draw(SetDraw: true);
699 }
700 Visuals.m_vBorderRight[y].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderRightTiles.size()));
701 if(AddTile(vTmpTiles&: vTmpBorderRightTiles, vTmpTileTexCoords&: vTmpBorderRightTilesTexCoords, Index, Flags, x: 0, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
702 Visuals.m_vBorderRight[y].Draw(SetDraw: true);
703 }
704 if(y == 0)
705 {
706 Visuals.m_vBorderTop[x].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderTopTiles.size()));
707 if(AddTile(vTmpTiles&: vTmpBorderTopTiles, vTmpTileTexCoords&: vTmpBorderTopTilesTexCoords, Index, Flags, x, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, -32}))
708 Visuals.m_vBorderTop[x].Draw(SetDraw: true);
709 }
710 else if(y == m_pLayerTilemap->m_Height - 1)
711 {
712 Visuals.m_vBorderBottom[x].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderBottomTiles.size()));
713 if(AddTile(vTmpTiles&: vTmpBorderBottomTiles, vTmpTileTexCoords&: vTmpBorderBottomTilesTexCoords, Index, Flags, x, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
714 Visuals.m_vBorderBottom[x].Draw(SetDraw: true);
715 }
716 }
717 }
718
719 // shrink clip region
720 // we only apply the clip once for the first overlay type (tile visuals). Physic layers can have multiple layers for text, e.g. speedup force
721 // the first overlay is always the largest and you will never find an overlay, where the text is written over AIR
722 if(CurOverlay == 0)
723 {
724 if(DrawLeft > DrawRight || DrawTop > DrawBottom)
725 {
726 // we are drawing nothing, layer is empty
727 m_LayerClip->m_Height = 0.0f;
728 m_LayerClip->m_Width = 0.0f;
729 }
730 else
731 {
732 m_LayerClip->m_X = DrawLeft * 32.0f;
733 m_LayerClip->m_Y = DrawTop * 32.0f;
734 m_LayerClip->m_Width = (DrawRight - DrawLeft + 1) * 32.0f;
735 m_LayerClip->m_Height = (DrawBottom - DrawTop + 1) * 32.0f;
736 }
737 }
738
739 // append one kill tile to the gamelayer
740 if(IsGameLayer)
741 {
742 Visuals.m_BorderKillTile.SetIndexBufferByteOffset((offset_ptr32)(vTmpTiles.size()));
743 if(AddTile(vTmpTiles, vTmpTileTexCoords, Index: TILE_DEATH, Flags: 0, x: 0, y: 0, DoTextureCoords))
744 Visuals.m_BorderKillTile.Draw(SetDraw: true);
745 }
746
747 // inserts and clears tiles and tile texture coords
748 auto InsertTiles = [&](std::vector<CGraphicTile> &vTiles, std::vector<CGraphicTileTextureCoords> &vTexCoords) {
749 vTmpTiles.insert(position: vTmpTiles.end(), first: vTiles.begin(), last: vTiles.end());
750 vTmpTileTexCoords.insert(position: vTmpTileTexCoords.end(), first: vTexCoords.begin(), last: vTexCoords.end());
751 vTiles.clear();
752 vTexCoords.clear();
753 };
754
755 // add the border corners, then the borders and fix their byte offsets
756 int TilesHandledCount = vTmpTiles.size();
757 Visuals.m_BorderTopLeft.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
758 Visuals.m_BorderTopRight.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
759 Visuals.m_BorderBottomLeft.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
760 Visuals.m_BorderBottomRight.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
761
762 // add the Corners to the tiles
763 InsertTiles(vTmpBorderCorners, vTmpBorderCornersTexCoords);
764
765 // now the borders
766 int TilesHandledCountTop = vTmpTiles.size();
767 int TilesHandledCountBottom = TilesHandledCountTop + vTmpBorderTopTiles.size();
768 int TilesHandledCountLeft = TilesHandledCountBottom + vTmpBorderBottomTiles.size();
769 int TilesHandledCountRight = TilesHandledCountLeft + vTmpBorderLeftTiles.size();
770
771 if(m_pLayerTilemap->m_Width > 0 && m_pLayerTilemap->m_Height > 0)
772 {
773 for(int i = 0; i < std::max(a: m_pLayerTilemap->m_Width, b: m_pLayerTilemap->m_Height); ++i)
774 {
775 if(i < m_pLayerTilemap->m_Width)
776 {
777 Visuals.m_vBorderTop[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountTop);
778 Visuals.m_vBorderBottom[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountBottom);
779 }
780 if(i < m_pLayerTilemap->m_Height)
781 {
782 Visuals.m_vBorderLeft[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountLeft);
783 Visuals.m_vBorderRight[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountRight);
784 }
785 }
786 }
787
788 InsertTiles(vTmpBorderTopTiles, vTmpBorderTopTilesTexCoords);
789 InsertTiles(vTmpBorderBottomTiles, vTmpBorderBottomTilesTexCoords);
790 InsertTiles(vTmpBorderLeftTiles, vTmpBorderLeftTilesTexCoords);
791 InsertTiles(vTmpBorderRightTiles, vTmpBorderRightTilesTexCoords);
792
793 Visuals.m_BufferContainerIndex = -1;
794
795 // upload data to gpu
796 size_t UploadDataSize = vTmpTileTexCoords.size() * sizeof(CGraphicTileTextureCoords) + vTmpTiles.size() * sizeof(CGraphicTile);
797 if(UploadDataSize == 0)
798 {
799 RenderLoading();
800 return;
801 }
802
803 void *pUploadData = malloc(size: UploadDataSize);
804
805 if(DoTextureCoords)
806 {
807 class CVertex
808 {
809 public:
810 vec2 m_Pos;
811 ubvec4 m_Tex;
812 };
813
814 static_assert(sizeof(CVertex) == sizeof(vec2) + sizeof(ubvec4)); // no padding
815
816 CVertex *pDst = static_cast<CVertex *>(pUploadData);
817 dbg_assert(UploadDataSize == vTmpTiles.size() * sizeof(*pDst) * 4, "invalid upload size");
818
819 for(size_t TileIndex = 0; TileIndex < vTmpTiles.size(); ++TileIndex)
820 {
821 const auto &GraphicTile = vTmpTiles[TileIndex];
822 const auto &GraphicCoords = vTmpTileTexCoords[TileIndex];
823
824 *pDst++ = {.m_Pos: GraphicTile.m_TopLeft, .m_Tex: GraphicCoords.m_TexCoordTopLeft};
825 *pDst++ = {.m_Pos: GraphicTile.m_TopRight, .m_Tex: GraphicCoords.m_TexCoordTopRight};
826 *pDst++ = {.m_Pos: GraphicTile.m_BottomRight, .m_Tex: GraphicCoords.m_TexCoordBottomRight};
827 *pDst++ = {.m_Pos: GraphicTile.m_BottomLeft, .m_Tex: GraphicCoords.m_TexCoordBottomLeft};
828 }
829 }
830 else
831 {
832 // we don't have texture coords, so we can optimize
833 dbg_assert(UploadDataSize == vTmpTiles.size() * sizeof(CGraphicTile), "invalid upload size");
834 mem_copy(dest: pUploadData, source: vTmpTiles.data(), size: vTmpTiles.size() * sizeof(CGraphicTile));
835 }
836
837 // first create the buffer object
838 int BufferObjectIndex = Graphics()->CreateBufferObject(UploadDataSize, pUploadData, CreateFlags: 0, IsMovedPointer: true);
839
840 // then create the buffer container
841 SBufferContainerInfo ContainerInfo;
842 ContainerInfo.m_Stride = (DoTextureCoords ? (sizeof(float) * 2 + sizeof(ubvec4)) : 0);
843 ContainerInfo.m_VertBufferBindingIndex = BufferObjectIndex;
844 ContainerInfo.m_vAttributes.emplace_back();
845 SBufferContainerInfo::SAttribute *pAttr = &ContainerInfo.m_vAttributes.back();
846 pAttr->m_DataTypeCount = 2;
847 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
848 pAttr->m_Normalized = false;
849 pAttr->m_pOffset = nullptr;
850 pAttr->m_FuncType = 0;
851 if(DoTextureCoords)
852 {
853 ContainerInfo.m_vAttributes.emplace_back();
854 pAttr = &ContainerInfo.m_vAttributes.back();
855 pAttr->m_DataTypeCount = 4;
856 pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE;
857 pAttr->m_Normalized = false;
858 pAttr->m_pOffset = (void *)(sizeof(vec2));
859 pAttr->m_FuncType = 1;
860 }
861
862 Visuals.m_BufferContainerIndex = Graphics()->CreateBufferContainer(pContainerInfo: &ContainerInfo);
863 // and finally inform the backend how many indices are required
864 Graphics()->IndicesNumRequiredNotify(RequiredIndicesCount: vTmpTiles.size() * 6);
865
866 RenderLoading();
867}
868
869void CRenderLayerTile::Unload()
870{
871 if(m_VisualTiles.has_value())
872 {
873 m_VisualTiles->Unload();
874 m_VisualTiles = std::nullopt;
875 }
876}
877
878void CRenderLayerTile::CTileLayerVisuals::Unload()
879{
880 Graphics()->DeleteBufferContainer(ContainerIndex&: m_BufferContainerIndex);
881}
882
883int CRenderLayerTile::GetDataIndex(unsigned int &TileSize) const
884{
885 TileSize = sizeof(CTile);
886 return m_pLayerTilemap->m_Data;
887}
888
889void *CRenderLayerTile::GetRawData() const
890{
891 unsigned int TileSize;
892 unsigned int DataIndex = GetDataIndex(TileSize);
893 void *pTiles = m_pMap->GetData(Index: DataIndex);
894 int Size = m_pMap->GetDataSize(Index: DataIndex);
895
896 if(!pTiles || Size < m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height * (int)TileSize)
897 return nullptr;
898
899 return pTiles;
900}
901
902void CRenderLayerTile::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
903{
904 CRenderLayer::OnInit(pGraphics, pTextRender, pRenderMap, pEnvelopeManager, pMap, pMapImages, FRenderUploadCallbackOptional);
905 InitTileData();
906
907 // set clip region
908 if(!Graphics()->IsTileBufferingEnabled())
909 {
910 // shrink clip region, this is done in `UploadTileData` for buffered backends
911 int MinX = m_pLayerTilemap->m_Width;
912 int MaxX = 0;
913 int MinY = m_pLayerTilemap->m_Height;
914 int MaxY = 0;
915 for(int TileY = 0; TileY < m_pLayerTilemap->m_Height; ++TileY)
916 {
917 for(int TileX = 0; TileX < m_pLayerTilemap->m_Width; ++TileX)
918 {
919 unsigned char Index = 0;
920 unsigned char Flags = 0;
921 int Angle = 0;
922 GetTileData(pIndex: &Index, pFlags: &Flags, pAngleRotate: &Angle, x: static_cast<unsigned int>(TileX), y: static_cast<unsigned int>(TileY), CurOverlay: 0);
923
924 if(Index > 0)
925 {
926 MinX = std::min(a: TileX, b: MinX);
927 MaxX = std::max(a: TileX, b: MaxX);
928 MinY = std::min(a: TileY, b: MinY);
929 MaxY = std::max(a: TileY, b: MaxY);
930 }
931 }
932 }
933
934 if(MinX > MaxX || MinY > MaxY)
935 {
936 // layer is empty
937 m_LayerClip = CClipRegion(0.0f, 0.0f, 0.0f, 0.0f);
938 }
939 else
940 {
941 m_LayerClip = CClipRegion(MinX * 32.0f, MinY * 32.0f, (MaxX - MinX + 1) * 32.0f, (MaxY - MinY + 1) * 32.0f);
942 }
943 }
944 else
945 {
946 m_LayerClip = CClipRegion(0.0f, 0.0f, m_pLayerTilemap->m_Width * 32.0f, m_pLayerTilemap->m_Height * 32.0f);
947 }
948}
949
950void CRenderLayerTile::InitTileData()
951{
952 m_pTiles = GetData<CTile>();
953}
954
955template<class T>
956T *CRenderLayerTile::GetData() const
957{
958 return (T *)GetRawData();
959}
960
961void CRenderLayerTile::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
962{
963 *pIndex = m_pTiles[y * m_pLayerTilemap->m_Width + x].m_Index;
964 *pFlags = m_pTiles[y * m_pLayerTilemap->m_Width + x].m_Flags;
965}
966
967/**************
968 * Quad Layer *
969 **************/
970
971CRenderLayerQuads::CRenderLayerQuads(int GroupId, int LayerId, int Flags, CMapItemLayerQuads *pLayerQuads) :
972 CRenderLayer(GroupId, LayerId, Flags)
973{
974 m_pLayerQuads = pLayerQuads;
975 m_pQuads = nullptr;
976}
977
978void CRenderLayerQuads::RenderQuadLayer(float Alpha, const CRenderLayerParams &Params)
979{
980 CQuadLayerVisuals &Visuals = m_VisualQuad.value();
981 if(Visuals.m_BufferContainerIndex == -1)
982 return; // no visuals were created
983
984 for(auto &QuadCluster : m_vQuadClusters)
985 {
986 if(!IsVisibleInClipRegion(ClipRegion: QuadCluster.m_ClipRegion))
987 continue;
988
989 if(!QuadCluster.m_Grouped)
990 {
991 bool AnyVisible = false;
992 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
993 {
994 CQuad *pQuad = &m_pQuads[QuadCluster.m_StartIndex + QuadClusterId];
995
996 ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
997 if(pQuad->m_ColorEnv >= 0)
998 {
999 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: pQuad->m_ColorEnvOffset, EnvelopeIndex: pQuad->m_ColorEnv, Result&: Color, Channels: 4);
1000 }
1001 Color.a *= Alpha;
1002
1003 SQuadRenderInfo &QInfo = QuadCluster.m_vQuadRenderInfo[QuadClusterId];
1004 if(Color.a < 0.0f)
1005 Color.a = 0.0f;
1006 QInfo.m_Color = Color;
1007 const bool IsVisible = Color.a >= 0.0f;
1008 AnyVisible |= IsVisible;
1009
1010 if(IsVisible)
1011 {
1012 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
1013 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: pQuad->m_PosEnvOffset, EnvelopeIndex: pQuad->m_PosEnv, Result&: Position, Channels: 3);
1014 QInfo.m_Offsets.x = Position.r;
1015 QInfo.m_Offsets.y = Position.g;
1016 QInfo.m_Rotation = Position.b / 180.0f * pi;
1017 }
1018 }
1019 if(AnyVisible)
1020 Graphics()->RenderQuadLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, pQuadInfo: QuadCluster.m_vQuadRenderInfo.data(), QuadNum: QuadCluster.m_NumQuads, QuadOffset: QuadCluster.m_StartIndex);
1021 }
1022 else
1023 {
1024 SQuadRenderInfo &QInfo = QuadCluster.m_vQuadRenderInfo[0];
1025
1026 ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
1027 if(QuadCluster.m_ColorEnv >= 0)
1028 {
1029 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: QuadCluster.m_ColorEnvOffset, EnvelopeIndex: QuadCluster.m_ColorEnv, Result&: Color, Channels: 4);
1030 }
1031
1032 Color.a *= Alpha;
1033 if(Color.a <= 0.0f)
1034 continue;
1035 QInfo.m_Color = Color;
1036
1037 if(QuadCluster.m_PosEnv >= 0)
1038 {
1039 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
1040 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: QuadCluster.m_PosEnvOffset, EnvelopeIndex: QuadCluster.m_PosEnv, Result&: Position, Channels: 3);
1041
1042 QInfo.m_Offsets.x = Position.r;
1043 QInfo.m_Offsets.y = Position.g;
1044 QInfo.m_Rotation = Position.b / 180.0f * pi;
1045 }
1046 Graphics()->RenderQuadLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, pQuadInfo: &QInfo, QuadNum: (size_t)QuadCluster.m_NumQuads, QuadOffset: QuadCluster.m_StartIndex, Grouped: true);
1047 }
1048 }
1049
1050 if(Params.m_DebugRenderClusterClips)
1051 {
1052 for(auto &QuadCluster : m_vQuadClusters)
1053 {
1054 if(!IsVisibleInClipRegion(ClipRegion: QuadCluster.m_ClipRegion) || !QuadCluster.m_ClipRegion.has_value())
1055 continue;
1056
1057 char aDebugText[64];
1058 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d, quad layer %d, quad start %d, grouped %d", m_GroupId, m_LayerId, QuadCluster.m_StartIndex, QuadCluster.m_Grouped);
1059 RenderMap()->RenderDebugClip(ClipX: QuadCluster.m_ClipRegion->m_X, ClipY: QuadCluster.m_ClipRegion->m_Y, ClipW: QuadCluster.m_ClipRegion->m_Width, ClipH: QuadCluster.m_ClipRegion->m_Height, Color: ColorRGBA(1.0f, 0.0f, 1.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
1060 }
1061 }
1062}
1063
1064void CRenderLayerQuads::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
1065{
1066 CRenderLayer::OnInit(pGraphics, pTextRender, pRenderMap, pEnvelopeManager, pMap, pMapImages, FRenderUploadCallbackOptional);
1067 int DataSize = m_pMap->GetDataSize(Index: m_pLayerQuads->m_Data);
1068 if(m_pLayerQuads->m_NumQuads > 0 && DataSize / (int)sizeof(CQuad) >= m_pLayerQuads->m_NumQuads)
1069 m_pQuads = (CQuad *)m_pMap->GetDataSwapped(Index: m_pLayerQuads->m_Data);
1070}
1071
1072void CRenderLayerQuads::Init()
1073{
1074 if(m_pLayerQuads->m_Image >= 0 && m_pLayerQuads->m_Image < m_pMapImages->Num())
1075 m_TextureHandle = m_pMapImages->Get(Index: m_pLayerQuads->m_Image);
1076 else
1077 m_TextureHandle.Invalidate();
1078
1079 if(!Graphics()->IsQuadBufferingEnabled())
1080 {
1081 // create clip region for unbuffered backends
1082 CQuadCluster QuadCluster;
1083 QuadCluster.m_Grouped = false;
1084 QuadCluster.m_StartIndex = 0;
1085 QuadCluster.m_NumQuads = m_pLayerQuads->m_NumQuads;
1086
1087 // unused, because cluster is not grouped
1088 QuadCluster.m_PosEnv = -1;
1089 QuadCluster.m_PosEnvOffset = 0;
1090 QuadCluster.m_ColorEnv = -1;
1091 QuadCluster.m_ColorEnvOffset = 0;
1092
1093 CalculateClipping(QuadCluster);
1094 return;
1095 }
1096
1097 std::vector<CTmpQuad> vTmpQuads;
1098 std::vector<CTmpQuadTextured> vTmpQuadsTextured;
1099 CQuadLayerVisuals v;
1100 v.OnInit(pRenderComponent: this);
1101 m_VisualQuad = v;
1102 CQuadLayerVisuals *pQLayerVisuals = &(m_VisualQuad.value());
1103
1104 const bool Textured = m_pLayerQuads->m_Image >= 0 && m_pLayerQuads->m_Image < m_pMapImages->Num();
1105
1106 if(Textured)
1107 vTmpQuadsTextured.resize(sz: m_pLayerQuads->m_NumQuads);
1108 else
1109 vTmpQuads.resize(sz: m_pLayerQuads->m_NumQuads);
1110
1111 auto SetQuadRenderInfo = [&](SQuadRenderInfo &QInfo, int QuadId, bool InitInfo) {
1112 CQuad *pQuad = &m_pQuads[QuadId];
1113
1114 // init for envelopeless quad layers
1115 if(InitInfo)
1116 {
1117 QInfo.m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
1118 QInfo.m_Offsets.x = 0;
1119 QInfo.m_Offsets.y = 0;
1120 QInfo.m_Rotation = 0;
1121 }
1122
1123 for(int j = 0; j < 4; ++j)
1124 {
1125 int QuadIdX = j;
1126 if(j == 2)
1127 QuadIdX = 3;
1128 else if(j == 3)
1129 QuadIdX = 2;
1130 if(!Textured)
1131 {
1132 // ignore the conversion for the position coordinates
1133 vTmpQuads[QuadId].m_aVertices[j].m_X = fx2f(v: pQuad->m_aPoints[QuadIdX].x);
1134 vTmpQuads[QuadId].m_aVertices[j].m_Y = fx2f(v: pQuad->m_aPoints[QuadIdX].y);
1135 vTmpQuads[QuadId].m_aVertices[j].m_CenterX = fx2f(v: pQuad->m_aPoints[4].x);
1136 vTmpQuads[QuadId].m_aVertices[j].m_CenterY = fx2f(v: pQuad->m_aPoints[4].y);
1137 vTmpQuads[QuadId].m_aVertices[j].m_R = (unsigned char)pQuad->m_aColors[QuadIdX].r;
1138 vTmpQuads[QuadId].m_aVertices[j].m_G = (unsigned char)pQuad->m_aColors[QuadIdX].g;
1139 vTmpQuads[QuadId].m_aVertices[j].m_B = (unsigned char)pQuad->m_aColors[QuadIdX].b;
1140 vTmpQuads[QuadId].m_aVertices[j].m_A = (unsigned char)pQuad->m_aColors[QuadIdX].a;
1141 }
1142 else
1143 {
1144 // ignore the conversion for the position coordinates
1145 vTmpQuadsTextured[QuadId].m_aVertices[j].m_X = fx2f(v: pQuad->m_aPoints[QuadIdX].x);
1146 vTmpQuadsTextured[QuadId].m_aVertices[j].m_Y = fx2f(v: pQuad->m_aPoints[QuadIdX].y);
1147 vTmpQuadsTextured[QuadId].m_aVertices[j].m_CenterX = fx2f(v: pQuad->m_aPoints[4].x);
1148 vTmpQuadsTextured[QuadId].m_aVertices[j].m_CenterY = fx2f(v: pQuad->m_aPoints[4].y);
1149 vTmpQuadsTextured[QuadId].m_aVertices[j].m_U = fx2f(v: pQuad->m_aTexcoords[QuadIdX].x);
1150 vTmpQuadsTextured[QuadId].m_aVertices[j].m_V = fx2f(v: pQuad->m_aTexcoords[QuadIdX].y);
1151 vTmpQuadsTextured[QuadId].m_aVertices[j].m_R = (unsigned char)pQuad->m_aColors[QuadIdX].r;
1152 vTmpQuadsTextured[QuadId].m_aVertices[j].m_G = (unsigned char)pQuad->m_aColors[QuadIdX].g;
1153 vTmpQuadsTextured[QuadId].m_aVertices[j].m_B = (unsigned char)pQuad->m_aColors[QuadIdX].b;
1154 vTmpQuadsTextured[QuadId].m_aVertices[j].m_A = (unsigned char)pQuad->m_aColors[QuadIdX].a;
1155 }
1156 }
1157 };
1158
1159 m_vQuadClusters.clear();
1160 CQuadCluster QuadCluster;
1161
1162 // create quad clusters
1163 int QuadStart = 0;
1164 while(QuadStart < m_pLayerQuads->m_NumQuads)
1165 {
1166 QuadCluster.m_StartIndex = QuadStart;
1167 QuadCluster.m_Grouped = true;
1168 QuadCluster.m_ColorEnv = m_pQuads[QuadStart].m_ColorEnv;
1169 QuadCluster.m_ColorEnvOffset = m_pQuads[QuadStart].m_ColorEnvOffset;
1170 QuadCluster.m_PosEnv = m_pQuads[QuadStart].m_PosEnv;
1171 QuadCluster.m_PosEnvOffset = m_pQuads[QuadStart].m_PosEnvOffset;
1172
1173 int QuadOffset = 0;
1174 for(int QuadClusterId = 0; QuadClusterId < m_pLayerQuads->m_NumQuads - QuadStart; ++QuadClusterId)
1175 {
1176 const CQuad *pQuad = &m_pQuads[QuadStart + QuadClusterId];
1177 bool IsGrouped = QuadCluster.m_Grouped && pQuad->m_ColorEnv == QuadCluster.m_ColorEnv && pQuad->m_ColorEnvOffset == QuadCluster.m_ColorEnvOffset && pQuad->m_PosEnv == QuadCluster.m_PosEnv && pQuad->m_PosEnvOffset == QuadCluster.m_PosEnvOffset;
1178
1179 // we are reaching gpu batch limit, here we break and close the QuadCluster if it's ungrouped
1180 if(QuadClusterId >= (int)GRAPHICS_MAX_QUADS_RENDER_COUNT)
1181 {
1182 // expand a cluster, if it's grouped
1183 if(!IsGrouped)
1184 break;
1185 }
1186 QuadOffset++;
1187 QuadCluster.m_Grouped = IsGrouped;
1188 }
1189 QuadCluster.m_NumQuads = QuadOffset;
1190
1191 // fill cluster info
1192 if(QuadCluster.m_Grouped)
1193 {
1194 // grouped quads only need one render info, because all their envs and env offsets are equal
1195 QuadCluster.m_vQuadRenderInfo.resize(sz: 1);
1196 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
1197 SetQuadRenderInfo(QuadCluster.m_vQuadRenderInfo[0], QuadCluster.m_StartIndex + QuadClusterId, QuadClusterId == 0);
1198 }
1199 else
1200 {
1201 QuadCluster.m_vQuadRenderInfo.resize(sz: QuadCluster.m_NumQuads);
1202 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
1203 SetQuadRenderInfo(QuadCluster.m_vQuadRenderInfo[QuadClusterId], QuadCluster.m_StartIndex + QuadClusterId, true);
1204 }
1205
1206 CalculateClipping(QuadCluster);
1207
1208 m_vQuadClusters.push_back(x: QuadCluster);
1209 QuadStart += QuadOffset;
1210 }
1211
1212 // gpu upload
1213 size_t UploadDataSize = 0;
1214 if(Textured)
1215 UploadDataSize = vTmpQuadsTextured.size() * sizeof(CTmpQuadTextured);
1216 else
1217 UploadDataSize = vTmpQuads.size() * sizeof(CTmpQuad);
1218
1219 if(UploadDataSize > 0)
1220 {
1221 void *pUploadData = nullptr;
1222 if(Textured)
1223 pUploadData = vTmpQuadsTextured.data();
1224 else
1225 pUploadData = vTmpQuads.data();
1226 // create the buffer object
1227 int BufferObjectIndex = Graphics()->CreateBufferObject(UploadDataSize, pUploadData, CreateFlags: 0);
1228 // then create the buffer container
1229 SBufferContainerInfo ContainerInfo;
1230 ContainerInfo.m_Stride = (Textured ? (sizeof(CTmpQuadTextured) / 4) : (sizeof(CTmpQuad) / 4));
1231 ContainerInfo.m_VertBufferBindingIndex = BufferObjectIndex;
1232 ContainerInfo.m_vAttributes.emplace_back();
1233 SBufferContainerInfo::SAttribute *pAttr = &ContainerInfo.m_vAttributes.back();
1234 pAttr->m_DataTypeCount = 4;
1235 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1236 pAttr->m_Normalized = false;
1237 pAttr->m_pOffset = nullptr;
1238 pAttr->m_FuncType = 0;
1239 ContainerInfo.m_vAttributes.emplace_back();
1240 pAttr = &ContainerInfo.m_vAttributes.back();
1241 pAttr->m_DataTypeCount = 4;
1242 pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE;
1243 pAttr->m_Normalized = true;
1244 pAttr->m_pOffset = (void *)(sizeof(float) * 4);
1245 pAttr->m_FuncType = 0;
1246 if(Textured)
1247 {
1248 ContainerInfo.m_vAttributes.emplace_back();
1249 pAttr = &ContainerInfo.m_vAttributes.back();
1250 pAttr->m_DataTypeCount = 2;
1251 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1252 pAttr->m_Normalized = false;
1253 pAttr->m_pOffset = (void *)(sizeof(float) * 4 + sizeof(unsigned char) * 4);
1254 pAttr->m_FuncType = 0;
1255 }
1256
1257 pQLayerVisuals->m_BufferContainerIndex = Graphics()->CreateBufferContainer(pContainerInfo: &ContainerInfo);
1258 // and finally inform the backend how many indices are required
1259 Graphics()->IndicesNumRequiredNotify(RequiredIndicesCount: m_pLayerQuads->m_NumQuads * 6);
1260 }
1261 RenderLoading();
1262}
1263
1264void CRenderLayerQuads::Unload()
1265{
1266 if(m_VisualQuad.has_value())
1267 {
1268 m_VisualQuad->Unload();
1269 m_VisualQuad = std::nullopt;
1270 }
1271}
1272
1273void CRenderLayerQuads::CQuadLayerVisuals::Unload()
1274{
1275 Graphics()->DeleteBufferContainer(ContainerIndex&: m_BufferContainerIndex);
1276}
1277
1278bool CRenderLayerQuads::CalculateQuadClipping(const CQuadCluster &QuadCluster, float aQuadOffsetMin[2], float aQuadOffsetMax[2]) const
1279{
1280 // check if the grouped clipping is available for early exit
1281 if(QuadCluster.m_Grouped)
1282 {
1283 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(EnvelopeIndex: QuadCluster.m_PosEnv);
1284 if(!Extrema.m_Available)
1285 return false;
1286 }
1287
1288 // calculate quad position offsets
1289 for(int Channel = 0; Channel < 2; ++Channel)
1290 {
1291 aQuadOffsetMin[Channel] = std::numeric_limits<float>::max(); // minimum of channel
1292 aQuadOffsetMax[Channel] = std::numeric_limits<float>::min(); // maximum of channel
1293 }
1294
1295 for(int QuadId = QuadCluster.m_StartIndex; QuadId < QuadCluster.m_StartIndex + QuadCluster.m_NumQuads; ++QuadId)
1296 {
1297 const CQuad *pQuad = &m_pQuads[QuadId];
1298
1299 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(EnvelopeIndex: pQuad->m_PosEnv);
1300 if(!Extrema.m_Available)
1301 return false;
1302
1303 // calculate clip region
1304 if(!Extrema.m_Rotating)
1305 {
1306 for(int QuadIdPoint = 0; QuadIdPoint < 4; ++QuadIdPoint)
1307 {
1308 for(int Channel = 0; Channel < 2; ++Channel)
1309 {
1310 float OffsetMinimum = fx2f(v: pQuad->m_aPoints[QuadIdPoint][Channel]);
1311 float OffsetMaximum = fx2f(v: pQuad->m_aPoints[QuadIdPoint][Channel]);
1312
1313 // calculate env offsets for every ungrouped quad
1314 if(!QuadCluster.m_Grouped && pQuad->m_PosEnv >= 0)
1315 {
1316 OffsetMinimum += fx2f(v: Extrema.m_Minima[Channel]);
1317 OffsetMaximum += fx2f(v: Extrema.m_Maxima[Channel]);
1318 }
1319 aQuadOffsetMin[Channel] = std::min(a: aQuadOffsetMin[Channel], b: OffsetMinimum);
1320 aQuadOffsetMax[Channel] = std::max(a: aQuadOffsetMax[Channel], b: OffsetMaximum);
1321 }
1322 }
1323 }
1324 else
1325 {
1326 const CPoint &CenterFX = pQuad->m_aPoints[4];
1327 vec2 Center(fx2f(v: CenterFX.x), fx2f(v: CenterFX.y));
1328 float MaxDistance = 0;
1329 for(int QuadIdPoint = 0; QuadIdPoint < 4; ++QuadIdPoint)
1330 {
1331 const CPoint &QuadPointFX = pQuad->m_aPoints[QuadIdPoint];
1332 vec2 QuadPoint(fx2f(v: QuadPointFX.x), fx2f(v: QuadPointFX.y));
1333 float Distance = length(a: Center - QuadPoint);
1334 MaxDistance = std::max(a: Distance, b: MaxDistance);
1335 }
1336
1337 for(int Channel = 0; Channel < 2; ++Channel)
1338 {
1339 float OffsetMinimum = Center[Channel] - MaxDistance;
1340 float OffsetMaximum = Center[Channel] + MaxDistance;
1341 if(!QuadCluster.m_Grouped && pQuad->m_PosEnv >= 0)
1342 {
1343 OffsetMinimum += fx2f(v: Extrema.m_Minima[Channel]);
1344 OffsetMaximum += fx2f(v: Extrema.m_Maxima[Channel]);
1345 }
1346 aQuadOffsetMin[Channel] = std::min(a: aQuadOffsetMin[Channel], b: OffsetMinimum);
1347 aQuadOffsetMax[Channel] = std::max(a: aQuadOffsetMax[Channel], b: OffsetMaximum);
1348 }
1349 }
1350 }
1351
1352 // add env offsets for the quad group
1353 if(QuadCluster.m_Grouped && QuadCluster.m_PosEnv >= 0)
1354 {
1355 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(EnvelopeIndex: QuadCluster.m_PosEnv);
1356
1357 for(int Channel = 0; Channel < 2; ++Channel)
1358 {
1359 aQuadOffsetMin[Channel] += fx2f(v: Extrema.m_Minima[Channel]);
1360 aQuadOffsetMax[Channel] += fx2f(v: Extrema.m_Maxima[Channel]);
1361 }
1362 }
1363 return true;
1364}
1365
1366void CRenderLayerQuads::CalculateClipping(CQuadCluster &QuadCluster)
1367{
1368 float aQuadOffsetMin[2];
1369 float aQuadOffsetMax[2];
1370
1371 bool CreateClip = CalculateQuadClipping(QuadCluster, aQuadOffsetMin, aQuadOffsetMax);
1372
1373 if(!CreateClip)
1374 return;
1375
1376 QuadCluster.m_ClipRegion = std::make_optional<CClipRegion>();
1377 std::optional<CClipRegion> &ClipRegion = QuadCluster.m_ClipRegion;
1378
1379 // X channel
1380 ClipRegion->m_X = aQuadOffsetMin[0];
1381 ClipRegion->m_Width = aQuadOffsetMax[0] - aQuadOffsetMin[0];
1382
1383 // Y channel
1384 ClipRegion->m_Y = aQuadOffsetMin[1];
1385 ClipRegion->m_Height = aQuadOffsetMax[1] - aQuadOffsetMin[1];
1386
1387 // update layer clip
1388 if(!m_LayerClip.has_value())
1389 {
1390 m_LayerClip = ClipRegion;
1391 }
1392 else
1393 {
1394 float ClipRight = std::max(a: ClipRegion->m_X + ClipRegion->m_Width, b: m_LayerClip->m_X + m_LayerClip->m_Width);
1395 float ClipBottom = std::max(a: ClipRegion->m_Y + ClipRegion->m_Height, b: m_LayerClip->m_Y + m_LayerClip->m_Height);
1396 m_LayerClip->m_X = std::min(a: ClipRegion->m_X, b: m_LayerClip->m_X);
1397 m_LayerClip->m_Y = std::min(a: ClipRegion->m_Y, b: m_LayerClip->m_Y);
1398 m_LayerClip->m_Width = ClipRight - m_LayerClip->m_X;
1399 m_LayerClip->m_Height = ClipBottom - m_LayerClip->m_Y;
1400 }
1401}
1402
1403void CRenderLayerQuads::Render(const CRenderLayerParams &Params)
1404{
1405 UseTexture(TextureHandle: GetTexture());
1406
1407 bool Force = Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN;
1408 float Alpha = Force ? 1.f : (100 - Params.m_EntityOverlayVal) / 100.0f;
1409 if(!Graphics()->IsQuadBufferingEnabled() || !Params.m_TileAndQuadBuffering)
1410 {
1411 RenderMap()->ForceRenderQuads(pQuads: m_pQuads, NumQuads: m_pLayerQuads->m_NumQuads, Flags: LAYERRENDERFLAG_TRANSPARENT, pEnvEval: m_pEnvelopeManager->EnvelopeEval(), Alpha);
1412 }
1413 else
1414 {
1415 RenderQuadLayer(Alpha, Params);
1416 }
1417
1418 if(Params.m_DebugRenderQuadClips && m_LayerClip.has_value())
1419 {
1420 char aDebugText[64];
1421 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d, quad layer %d", m_GroupId, m_LayerId);
1422 RenderMap()->RenderDebugClip(ClipX: m_LayerClip->m_X, ClipY: m_LayerClip->m_Y, ClipW: m_LayerClip->m_Width, ClipH: m_LayerClip->m_Height, Color: ColorRGBA(1.0f, 0.0f, 0.5f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
1423 }
1424}
1425
1426bool CRenderLayerQuads::DoRender(const CRenderLayerParams &Params)
1427{
1428 // skip rendering anything but entities if we only want to render entities
1429 if(Params.m_EntityOverlayVal == 100 && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
1430 return false;
1431
1432 // skip rendering if detail layers if not wanted
1433 if(m_Flags & LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail && Params.m_RenderType != ERenderType::RENDERTYPE_FULL_DESIGN) // detail but no details
1434 return false;
1435
1436 // this option only deactivates quads in the background
1437 if(Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND || Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE)
1438 {
1439 if(!g_Config.m_ClShowQuads)
1440 return false;
1441 }
1442
1443 return IsVisibleInClipRegion(ClipRegion: m_LayerClip);
1444}
1445
1446/****************
1447 * Entity Layer *
1448 ****************/
1449// BASE
1450CRenderLayerEntityBase::CRenderLayerEntityBase(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1451 CRenderLayerTile(GroupId, LayerId, Flags, pLayerTilemap) {}
1452
1453bool CRenderLayerEntityBase::DoRender(const CRenderLayerParams &Params)
1454{
1455 // skip rendering if we render background force or full design
1456 if(Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN)
1457 return false;
1458
1459 // skip rendering of entities if don't want them
1460 if(!Params.m_EntityOverlayVal)
1461 return false;
1462
1463 return true;
1464}
1465
1466IGraphics::CTextureHandle CRenderLayerEntityBase::GetTexture() const
1467{
1468 return m_pMapImages->GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH);
1469}
1470
1471// GAME
1472CRenderLayerEntityGame::CRenderLayerEntityGame(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1473 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1474
1475void CRenderLayerEntityGame::Init()
1476{
1477 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false, IsGameLayer: true);
1478}
1479
1480void CRenderLayerEntityGame::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1481{
1482 if(Params.m_RenderTileBorder)
1483 RenderKillTileBorder(Color: Color.Multiply(Other: GetDeathBorderColor()));
1484 RenderTileLayer(Color, Params);
1485}
1486
1487void CRenderLayerEntityGame::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1488{
1489 Graphics()->BlendNone();
1490 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1491 Graphics()->BlendNormal();
1492
1493 if(Params.m_RenderTileBorder)
1494 {
1495 RenderMap()->RenderTileRectangle(RectX: -BorderRenderDistance, RectY: -BorderRenderDistance, RectW: m_pLayerTilemap->m_Width + 2 * BorderRenderDistance, RectH: m_pLayerTilemap->m_Height + 2 * BorderRenderDistance,
1496 IndexIn: TILE_AIR, IndexOut: TILE_DEATH, // display air inside, death outside
1497 Scale: 32.0f, Color: Color.Multiply(Other: GetDeathBorderColor()), RenderFlags: TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT);
1498 }
1499
1500 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1501}
1502
1503ColorRGBA CRenderLayerEntityGame::GetDeathBorderColor() const
1504{
1505 // draw kill tiles outside the entity clipping rectangle
1506 // slow blinking to hint that it's not a part of the map
1507 float Seconds = time_get() / (float)time_freq();
1508 float Alpha = 0.3f + 0.35f * (1.f + std::sin(x: 2.f * pi * Seconds / 3.f));
1509 return ColorRGBA(1.f, 1.f, 1.f, Alpha);
1510}
1511
1512// FRONT
1513CRenderLayerEntityFront::CRenderLayerEntityFront(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1514 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1515
1516int CRenderLayerEntityFront::GetDataIndex(unsigned int &TileSize) const
1517{
1518 TileSize = sizeof(CTile);
1519 return m_pLayerTilemap->m_Front;
1520}
1521
1522// TELE
1523CRenderLayerEntityTele::CRenderLayerEntityTele(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1524 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1525
1526int CRenderLayerEntityTele::GetDataIndex(unsigned int &TileSize) const
1527{
1528 TileSize = sizeof(CTeleTile);
1529 return m_pLayerTilemap->m_Tele;
1530}
1531
1532void CRenderLayerEntityTele::Init()
1533{
1534 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
1535 UploadTileData(VisualsOptional&: m_VisualTeleNumbers, CurOverlay: 1, AddAsSpeedup: false);
1536}
1537
1538void CRenderLayerEntityTele::InitTileData()
1539{
1540 m_pTeleTiles = GetData<CTeleTile>();
1541}
1542
1543void CRenderLayerEntityTele::Unload()
1544{
1545 CRenderLayerTile::Unload();
1546 if(m_VisualTeleNumbers.has_value())
1547 {
1548 m_VisualTeleNumbers->Unload();
1549 m_VisualTeleNumbers = std::nullopt;
1550 }
1551}
1552
1553void CRenderLayerEntityTele::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1554{
1555 RenderTileLayer(Color, Params);
1556 if(Params.m_RenderText)
1557 {
1558 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayCenter());
1559 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualTeleNumbers.value());
1560 }
1561}
1562
1563void CRenderLayerEntityTele::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1564{
1565 Graphics()->BlendNone();
1566 RenderMap()->RenderTelemap(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1567 Graphics()->BlendNormal();
1568 RenderMap()->RenderTelemap(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1569 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1570 RenderMap()->RenderTeleOverlay(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1571}
1572
1573void CRenderLayerEntityTele::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1574{
1575 *pIndex = m_pTeleTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1576 *pFlags = 0;
1577 if(CurOverlay == 1)
1578 {
1579 if(IsTeleTileNumberUsedAny(Index: *pIndex))
1580 *pIndex = m_pTeleTiles[y * m_pLayerTilemap->m_Width + x].m_Number;
1581 else
1582 *pIndex = 0;
1583 }
1584}
1585
1586// SPEEDUP
1587CRenderLayerEntitySpeedup::CRenderLayerEntitySpeedup(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1588 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1589
1590IGraphics::CTextureHandle CRenderLayerEntitySpeedup::GetTexture() const
1591{
1592 return m_pMapImages->GetSpeedupArrow();
1593}
1594
1595int CRenderLayerEntitySpeedup::GetDataIndex(unsigned int &TileSize) const
1596{
1597 TileSize = sizeof(CSpeedupTile);
1598 return m_pLayerTilemap->m_Speedup;
1599}
1600
1601void CRenderLayerEntitySpeedup::Init()
1602{
1603 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: true);
1604 UploadTileData(VisualsOptional&: m_VisualForce, CurOverlay: 1, AddAsSpeedup: false);
1605 UploadTileData(VisualsOptional&: m_VisualMaxSpeed, CurOverlay: 2, AddAsSpeedup: false);
1606}
1607
1608void CRenderLayerEntitySpeedup::InitTileData()
1609{
1610 m_pSpeedupTiles = GetData<CSpeedupTile>();
1611}
1612
1613void CRenderLayerEntitySpeedup::Unload()
1614{
1615 CRenderLayerTile::Unload();
1616 if(m_VisualForce.has_value())
1617 {
1618 m_VisualForce->Unload();
1619 m_VisualForce = std::nullopt;
1620 }
1621 if(m_VisualMaxSpeed.has_value())
1622 {
1623 m_VisualMaxSpeed->Unload();
1624 m_VisualMaxSpeed = std::nullopt;
1625 }
1626}
1627
1628void CRenderLayerEntitySpeedup::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1629{
1630 *pIndex = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1631 unsigned char Force = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Force;
1632 unsigned char MaxSpeed = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_MaxSpeed;
1633 *pFlags = 0;
1634 *pAngleRotate = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Angle;
1635 if((Force == 0 && *pIndex == TILE_SPEED_BOOST_OLD) || (Force == 0 && MaxSpeed == 0 && *pIndex == TILE_SPEED_BOOST) || !IsValidSpeedupTile(Index: *pIndex))
1636 *pIndex = 0;
1637 else if(CurOverlay == 1)
1638 *pIndex = Force;
1639 else if(CurOverlay == 2)
1640 *pIndex = MaxSpeed;
1641}
1642
1643void CRenderLayerEntitySpeedup::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1644{
1645 // draw arrow -- clamp to the edge of the arrow image
1646 Graphics()->WrapClamp();
1647 UseTexture(TextureHandle: GetTexture());
1648 RenderTileLayer(Color, Params);
1649 Graphics()->WrapNormal();
1650
1651 if(Params.m_RenderText)
1652 {
1653 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayBottom());
1654 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualForce.value());
1655 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayTop());
1656 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualMaxSpeed.value());
1657 }
1658}
1659
1660void CRenderLayerEntitySpeedup::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1661{
1662 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1663 RenderMap()->RenderSpeedupOverlay(pSpeedup: m_pSpeedupTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1664}
1665
1666// SWITCH
1667CRenderLayerEntitySwitch::CRenderLayerEntitySwitch(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1668 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1669
1670IGraphics::CTextureHandle CRenderLayerEntitySwitch::GetTexture() const
1671{
1672 return m_pMapImages->GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_SWITCH);
1673}
1674
1675int CRenderLayerEntitySwitch::GetDataIndex(unsigned int &TileSize) const
1676{
1677 TileSize = sizeof(CSwitchTile);
1678 return m_pLayerTilemap->m_Switch;
1679}
1680
1681void CRenderLayerEntitySwitch::Init()
1682{
1683 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
1684 UploadTileData(VisualsOptional&: m_VisualSwitchNumberTop, CurOverlay: 1, AddAsSpeedup: false);
1685 UploadTileData(VisualsOptional&: m_VisualSwitchNumberBottom, CurOverlay: 2, AddAsSpeedup: false);
1686}
1687
1688void CRenderLayerEntitySwitch::InitTileData()
1689{
1690 m_pSwitchTiles = GetData<CSwitchTile>();
1691}
1692
1693void CRenderLayerEntitySwitch::Unload()
1694{
1695 CRenderLayerTile::Unload();
1696 if(m_VisualSwitchNumberTop.has_value())
1697 {
1698 m_VisualSwitchNumberTop->Unload();
1699 m_VisualSwitchNumberTop = std::nullopt;
1700 }
1701 if(m_VisualSwitchNumberBottom.has_value())
1702 {
1703 m_VisualSwitchNumberBottom->Unload();
1704 m_VisualSwitchNumberBottom = std::nullopt;
1705 }
1706}
1707
1708void CRenderLayerEntitySwitch::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1709{
1710 *pFlags = 0;
1711 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1712 if(CurOverlay == 0)
1713 {
1714 *pFlags = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Flags;
1715 if(*pIndex == TILE_SWITCHTIMEDOPEN)
1716 *pIndex = 8;
1717 }
1718 else if(CurOverlay == 1)
1719 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Number;
1720 else if(CurOverlay == 2)
1721 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Delay;
1722}
1723
1724void CRenderLayerEntitySwitch::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1725{
1726 RenderTileLayer(Color, Params);
1727 if(Params.m_RenderText)
1728 {
1729 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayTop());
1730 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualSwitchNumberTop.value());
1731 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayBottom());
1732 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualSwitchNumberBottom.value());
1733 }
1734}
1735
1736void CRenderLayerEntitySwitch::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1737{
1738 Graphics()->BlendNone();
1739 RenderMap()->RenderSwitchmap(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1740 Graphics()->BlendNormal();
1741 RenderMap()->RenderSwitchmap(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1742 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1743 RenderMap()->RenderSwitchOverlay(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1744}
1745
1746// TUNE
1747CRenderLayerEntityTune::CRenderLayerEntityTune(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1748 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1749
1750IGraphics::CTextureHandle CRenderLayerEntityTune::GetTexture() const
1751{
1752 return m_pMapImages->GetTuneColors();
1753}
1754
1755void CRenderLayerEntityTune::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1756{
1757 const unsigned char Number = m_pTuneTiles[y * m_pLayerTilemap->m_Width + x].m_Number;
1758 unsigned char Index = 0;
1759
1760 if(Number != 0)
1761 {
1762 // assign color index instead of tune number for higher color distance
1763 Index = m_TuneColorMapper.TuneNumberToColorIndex(TuneNumber: Number);
1764 }
1765
1766 *pIndex = Index;
1767 *pFlags = 0;
1768}
1769
1770void CRenderLayerEntityTune::Init()
1771{
1772 m_TuneColorMapper.Reset();
1773 CRenderLayerTile::Init();
1774}
1775
1776int CRenderLayerEntityTune::GetDataIndex(unsigned int &TileSize) const
1777{
1778 TileSize = sizeof(CTuneTile);
1779 return m_pLayerTilemap->m_Tune;
1780}
1781
1782void CRenderLayerEntityTune::InitTileData()
1783{
1784 m_pTuneTiles = GetData<CTuneTile>();
1785}
1786
1787void CRenderLayerEntityTune::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1788{
1789 Graphics()->BlendNone();
1790 RenderMap()->RenderTunemap(pTune: m_pTuneTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE, pTuneColorMapper: &m_TuneColorMapper);
1791 Graphics()->BlendNormal();
1792 RenderMap()->RenderTunemap(pTune: m_pTuneTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT, pTuneColorMapper: &m_TuneColorMapper);
1793}
1794