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