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