1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "render_map.h"
4
5#include <base/str.h>
6
7#include <engine/graphics.h>
8#include <engine/map.h>
9#include <engine/shared/config.h>
10#include <engine/shared/datafile.h>
11#include <engine/shared/map.h>
12#include <engine/textrender.h>
13
14#include <generated/client_data.h>
15
16#include <game/mapitems.h>
17#include <game/mapitems_ex.h>
18
19#include <algorithm>
20#include <array>
21#include <chrono>
22#include <cmath>
23
24using namespace std::chrono_literals;
25
26int IEnvelopePointAccess::FindPointIndex(CFixedTime Time) const
27{
28 // binary search for the interval around Time
29 int Low = 0;
30 int High = NumPoints() - 2;
31 int FoundIndex = -1;
32
33 while(Low <= High)
34 {
35 int Mid = Low + (High - Low) / 2;
36 const CEnvPoint *pMid = GetPoint(Index: Mid);
37 const CEnvPoint *pNext = GetPoint(Index: Mid + 1);
38 if(Time >= pMid->m_Time && Time < pNext->m_Time)
39 {
40 FoundIndex = Mid;
41 break;
42 }
43 else if(Time < pMid->m_Time)
44 {
45 High = Mid - 1;
46 }
47 else
48 {
49 Low = Mid + 1;
50 }
51 }
52 return FoundIndex;
53}
54
55CMapBasedEnvelopePointAccess::CMapBasedEnvelopePointAccess(IMap *pMap)
56{
57 bool FoundBezierEnvelope = false;
58 int EnvelopeStart, EnvelopeNum;
59 pMap->GetType(Type: MAPITEMTYPE_ENVELOPE, pStart: &EnvelopeStart, pNum: &EnvelopeNum);
60 for(int EnvelopeIndex = 0; EnvelopeIndex < EnvelopeNum; EnvelopeIndex++)
61 {
62 CMapItemEnvelope *pEnvelope = static_cast<CMapItemEnvelope *>(pMap->GetItem(Index: EnvelopeStart + EnvelopeIndex));
63 if(pEnvelope->m_Version >= CMapItemEnvelope::VERSION_TEEWORLDS_BEZIER)
64 {
65 FoundBezierEnvelope = true;
66 break;
67 }
68 }
69
70 if(FoundBezierEnvelope)
71 {
72 m_pPoints = nullptr;
73 m_pPointsBezier = nullptr;
74
75 int EnvPointStart, FakeEnvPointNum;
76 pMap->GetType(Type: MAPITEMTYPE_ENVPOINTS, pStart: &EnvPointStart, pNum: &FakeEnvPointNum);
77 if(FakeEnvPointNum > 0)
78 m_pPointsBezierUpstream = static_cast<CEnvPointBezier_upstream *>(pMap->GetItem(Index: EnvPointStart));
79 else
80 m_pPointsBezierUpstream = nullptr;
81
82 m_NumPointsMax = pMap->GetItemSize(Index: EnvPointStart) / sizeof(CEnvPointBezier_upstream);
83 }
84 else
85 {
86 int EnvPointStart, FakeEnvPointNum;
87 pMap->GetType(Type: MAPITEMTYPE_ENVPOINTS, pStart: &EnvPointStart, pNum: &FakeEnvPointNum);
88 if(FakeEnvPointNum > 0)
89 m_pPoints = static_cast<CEnvPoint *>(pMap->GetItem(Index: EnvPointStart));
90 else
91 m_pPoints = nullptr;
92
93 m_NumPointsMax = pMap->GetItemSize(Index: EnvPointStart) / sizeof(CEnvPoint);
94
95 int EnvPointBezierStart, FakeEnvPointBezierNum;
96 pMap->GetType(Type: MAPITEMTYPE_ENVPOINTS_BEZIER, pStart: &EnvPointBezierStart, pNum: &FakeEnvPointBezierNum);
97 const int NumPointsBezier = pMap->GetItemSize(Index: EnvPointBezierStart) / sizeof(CEnvPointBezier);
98 if(FakeEnvPointBezierNum > 0 && m_NumPointsMax == NumPointsBezier)
99 m_pPointsBezier = static_cast<CEnvPointBezier *>(pMap->GetItem(Index: EnvPointBezierStart));
100 else
101 m_pPointsBezier = nullptr;
102
103 m_pPointsBezierUpstream = nullptr;
104 }
105
106 SetPointsRange(StartPoint: 0, NumPoints: m_NumPointsMax);
107}
108
109void CMapBasedEnvelopePointAccess::SetPointsRange(int StartPoint, int NumPoints)
110{
111 m_StartPoint = std::clamp(val: StartPoint, lo: 0, hi: m_NumPointsMax);
112 m_NumPoints = std::clamp(val: NumPoints, lo: 0, hi: m_NumPointsMax - m_StartPoint);
113}
114
115int CMapBasedEnvelopePointAccess::StartPoint() const
116{
117 return m_StartPoint;
118}
119
120int CMapBasedEnvelopePointAccess::NumPoints() const
121{
122 return m_NumPoints;
123}
124
125int CMapBasedEnvelopePointAccess::NumPointsMax() const
126{
127 return m_NumPointsMax;
128}
129
130const CEnvPoint *CMapBasedEnvelopePointAccess::GetPoint(int Index) const
131{
132 if(Index < 0 || Index >= m_NumPoints)
133 return nullptr;
134 if(m_pPoints != nullptr)
135 return &m_pPoints[Index + m_StartPoint];
136 if(m_pPointsBezierUpstream != nullptr)
137 return &m_pPointsBezierUpstream[Index + m_StartPoint];
138 return nullptr;
139}
140
141const CEnvPointBezier *CMapBasedEnvelopePointAccess::GetBezier(int Index) const
142{
143 if(Index < 0 || Index >= m_NumPoints)
144 return nullptr;
145 if(m_pPointsBezier != nullptr)
146 return &m_pPointsBezier[Index + m_StartPoint];
147 if(m_pPointsBezierUpstream != nullptr)
148 return &m_pPointsBezierUpstream[Index + m_StartPoint].m_Bezier;
149 return nullptr;
150}
151
152static float SolveBezier(float x, float p0, float p1, float p2, float p3)
153{
154 const double x3 = -p0 + 3.0 * p1 - 3.0 * p2 + p3;
155 const double x2 = 3.0 * p0 - 6.0 * p1 + 3.0 * p2;
156 const double x1 = -3.0 * p0 + 3.0 * p1;
157 const double x0 = p0 - x;
158
159 if(x3 == 0.0 && x2 == 0.0)
160 {
161 // linear
162 // a * t + b = 0
163 const double a = x1;
164 const double b = x0;
165
166 if(a == 0.0)
167 return 0.0f;
168 return -b / a;
169 }
170 else if(x3 == 0.0)
171 {
172 // quadratic
173 // t * t + b * t + c = 0
174 const double b = x1 / x2;
175 const double c = x0 / x2;
176
177 if(c == 0.0)
178 return 0.0f;
179
180 const double D = b * b - 4.0 * c;
181 const double SqrtD = std::sqrt(x: D);
182
183 const double t = (-b + SqrtD) / 2.0;
184
185 if(0.0 <= t && t <= 1.0001)
186 return t;
187 return (-b - SqrtD) / 2.0;
188 }
189 else
190 {
191 // cubic
192 // t * t * t + a * t * t + b * t * t + c = 0
193 const double a = x2 / x3;
194 const double b = x1 / x3;
195 const double c = x0 / x3;
196
197 // substitute t = y - a / 3
198 const double Substitute = a / 3.0;
199
200 // depressed form x^3 + px + q = 0
201 // cardano's method
202 const double p = b / 3.0 - a * a / 9.0;
203 const double q = (2.0 * a * a * a / 27.0 - a * b / 3.0 + c) / 2.0;
204
205 const double D = q * q + p * p * p;
206
207 if(D > 0.0)
208 {
209 // only one 'real' solution
210 const double s = std::sqrt(x: D);
211 return std::cbrt(x: s - q) - std::cbrt(x: s + q) - Substitute;
212 }
213 else if(D == 0.0)
214 {
215 // one single, one double solution or triple solution
216 const double s = std::cbrt(x: -q);
217 const double t = 2.0 * s - Substitute;
218
219 if(0.0 <= t && t <= 1.0001)
220 return t;
221 return (-s - Substitute);
222 }
223 else
224 {
225 // Casus irreducibilis ... ,_,
226 const double Phi = std::acos(x: -q / std::sqrt(x: -(p * p * p))) / 3.0;
227 const double s = 2.0 * std::sqrt(x: -p);
228
229 const double t1 = s * std::cos(x: Phi) - Substitute;
230
231 if(0.0 <= t1 && t1 <= 1.0001)
232 return t1;
233
234 const double t2 = -s * std::cos(x: Phi + pi / 3.0) - Substitute;
235
236 if(0.0 <= t2 && t2 <= 1.0001)
237 return t2;
238 return -s * std::cos(x: Phi - pi / 3.0) - Substitute;
239 }
240 }
241}
242
243void CRenderMap::Init(IGraphics *pGraphics, ITextRender *pTextRender)
244{
245 m_pGraphics = pGraphics;
246 m_pTextRender = pTextRender;
247}
248
249void CRenderMap::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result, size_t Channels)
250{
251 const int NumPoints = pPoints->NumPoints();
252 if(NumPoints == 0)
253 {
254 return;
255 }
256
257 if(NumPoints == 1)
258 {
259 const CEnvPoint *pFirstPoint = pPoints->GetPoint(Index: 0);
260 for(size_t c = 0; c < Channels; c++)
261 {
262 Result[c] = fx2f(v: pFirstPoint->m_aValues[c]);
263 }
264 return;
265 }
266
267 const CEnvPoint *pLastPoint = pPoints->GetPoint(Index: NumPoints - 1);
268 const int64_t MaxPointTime = (int64_t)pLastPoint->m_Time.GetInternal() * std::chrono::nanoseconds(1ms).count();
269 if(MaxPointTime > 0) // TODO: remove this check when implementing a IO check for maps(in this case broken envelopes)
270 TimeNanos = std::chrono::nanoseconds(TimeNanos.count() % MaxPointTime);
271 else
272 TimeNanos = decltype(TimeNanos)::zero();
273
274 const double TimeMillis = TimeNanos.count() / (double)std::chrono::nanoseconds(1ms).count();
275
276 int FoundIndex = pPoints->FindPointIndex(Time: CFixedTime(TimeMillis));
277 if(FoundIndex == -1)
278 {
279 for(size_t c = 0; c < Channels; c++)
280 {
281 Result[c] = fx2f(v: pLastPoint->m_aValues[c]);
282 }
283 return;
284 }
285
286 const CEnvPoint *pCurrentPoint = pPoints->GetPoint(Index: FoundIndex);
287 const CEnvPoint *pNextPoint = pPoints->GetPoint(Index: FoundIndex + 1);
288
289 const CFixedTime Delta = pNextPoint->m_Time - pCurrentPoint->m_Time;
290 if(Delta <= CFixedTime(0))
291 {
292 for(size_t c = 0; c < Channels; c++)
293 {
294 Result[c] = fx2f(v: pCurrentPoint->m_aValues[c]);
295 }
296 return;
297 }
298
299 float a = (float)(TimeMillis - pCurrentPoint->m_Time.GetInternal()) / Delta.GetInternal();
300
301 switch(pCurrentPoint->m_Curvetype)
302 {
303 case CURVETYPE_STEP:
304 a = 0.0f;
305 break;
306
307 case CURVETYPE_SLOW:
308 a = a * a * a;
309 break;
310
311 case CURVETYPE_FAST:
312 a = 1.0f - a;
313 a = 1.0f - a * a * a;
314 break;
315
316 case CURVETYPE_SMOOTH:
317 a = -2.0f * a * a * a + 3.0f * a * a; // second hermite basis
318 break;
319
320 case CURVETYPE_BEZIER:
321 {
322 const CEnvPointBezier *pCurrentPointBezier = pPoints->GetBezier(Index: FoundIndex);
323 const CEnvPointBezier *pNextPointBezier = pPoints->GetBezier(Index: FoundIndex + 1);
324 if(pCurrentPointBezier == nullptr || pNextPointBezier == nullptr)
325 break; // fallback to linear
326 for(size_t c = 0; c < Channels; c++)
327 {
328 // monotonic 2d cubic bezier curve
329 const vec2 p0 = vec2(pCurrentPoint->m_Time.GetInternal(), fx2f(v: pCurrentPoint->m_aValues[c]));
330 const vec2 p3 = vec2(pNextPoint->m_Time.GetInternal(), fx2f(v: pNextPoint->m_aValues[c]));
331
332 const vec2 OutTang = vec2(pCurrentPointBezier->m_aOutTangentDeltaX[c].GetInternal(), fx2f(v: pCurrentPointBezier->m_aOutTangentDeltaY[c]));
333 const vec2 InTang = vec2(pNextPointBezier->m_aInTangentDeltaX[c].GetInternal(), fx2f(v: pNextPointBezier->m_aInTangentDeltaY[c]));
334
335 vec2 p1 = p0 + OutTang;
336 vec2 p2 = p3 + InTang;
337
338 // validate bezier curve
339 p1.x = std::clamp(val: p1.x, lo: p0.x, hi: p3.x);
340 p2.x = std::clamp(val: p2.x, lo: p0.x, hi: p3.x);
341
342 // solve x(a) = time for a
343 a = std::clamp(val: SolveBezier(x: TimeMillis, p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x), lo: 0.0f, hi: 1.0f);
344
345 // value = y(t)
346 Result[c] = bezier(p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y, amount: a);
347 }
348 return;
349 }
350
351 case CURVETYPE_LINEAR: [[fallthrough]];
352 default:
353 break;
354 }
355
356 for(size_t c = 0; c < Channels; c++)
357 {
358 const float v0 = fx2f(v: pCurrentPoint->m_aValues[c]);
359 const float v1 = fx2f(v: pNextPoint->m_aValues[c]);
360 Result[c] = v0 + (v1 - v0) * a;
361 }
362}
363
364static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation)
365{
366 int x = pPoint->x - pCenter->x;
367 int y = pPoint->y - pCenter->y;
368 pPoint->x = (int)(x * std::cos(x: Rotation) - y * std::sin(x: Rotation) + pCenter->x);
369 pPoint->y = (int)(x * std::sin(x: Rotation) + y * std::cos(x: Rotation) + pCenter->y);
370}
371
372void CRenderMap::ForceRenderQuads(CQuad *pQuads, int NumQuads, int RenderFlags, const IEnvelopeEval *pEnvEval, float Alpha)
373{
374 Graphics()->TrianglesBegin();
375 float Conv = 1 / 255.0f;
376 for(int i = 0; i < NumQuads; i++)
377 {
378 CQuad *pQuad = &pQuads[i];
379
380 ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
381 pEnvEval->EnvelopeEval(TimeOffsetMillis: pQuad->m_ColorEnvOffset, EnvelopeIndex: pQuad->m_ColorEnv, Result&: Color, Channels: 4);
382
383 if(Color.a <= 0.0f)
384 continue;
385
386 bool Opaque = false;
387 /* TODO: Analyze quadtexture
388 if(a < 0.01f || (q->m_aColors[0].a < 0.01f && q->m_aColors[1].a < 0.01f && q->m_aColors[2].a < 0.01f && q->m_aColors[3].a < 0.01f))
389 Opaque = true;
390 */
391 if(Opaque && !(RenderFlags & LAYERRENDERFLAG_OPAQUE))
392 continue;
393 if(!Opaque && !(RenderFlags & LAYERRENDERFLAG_TRANSPARENT))
394 continue;
395
396 Graphics()->QuadsSetSubsetFree(
397 x0: fx2f(v: pQuad->m_aTexcoords[0].x), y0: fx2f(v: pQuad->m_aTexcoords[0].y),
398 x1: fx2f(v: pQuad->m_aTexcoords[1].x), y1: fx2f(v: pQuad->m_aTexcoords[1].y),
399 x2: fx2f(v: pQuad->m_aTexcoords[2].x), y2: fx2f(v: pQuad->m_aTexcoords[2].y),
400 x3: fx2f(v: pQuad->m_aTexcoords[3].x), y3: fx2f(v: pQuad->m_aTexcoords[3].y));
401
402 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
403 pEnvEval->EnvelopeEval(TimeOffsetMillis: pQuad->m_PosEnvOffset, EnvelopeIndex: pQuad->m_PosEnv, Result&: Position, Channels: 3);
404 const vec2 Offset = vec2(Position.r, Position.g);
405 const float Rotation = Position.b / 180.0f * pi;
406
407 IGraphics::CColorVertex Array[4] = {
408 IGraphics::CColorVertex(0, pQuad->m_aColors[0].r * Conv * Color.r, pQuad->m_aColors[0].g * Conv * Color.g, pQuad->m_aColors[0].b * Conv * Color.b, pQuad->m_aColors[0].a * Conv * Color.a * Alpha),
409 IGraphics::CColorVertex(1, pQuad->m_aColors[1].r * Conv * Color.r, pQuad->m_aColors[1].g * Conv * Color.g, pQuad->m_aColors[1].b * Conv * Color.b, pQuad->m_aColors[1].a * Conv * Color.a * Alpha),
410 IGraphics::CColorVertex(2, pQuad->m_aColors[2].r * Conv * Color.r, pQuad->m_aColors[2].g * Conv * Color.g, pQuad->m_aColors[2].b * Conv * Color.b, pQuad->m_aColors[2].a * Conv * Color.a * Alpha),
411 IGraphics::CColorVertex(3, pQuad->m_aColors[3].r * Conv * Color.r, pQuad->m_aColors[3].g * Conv * Color.g, pQuad->m_aColors[3].b * Conv * Color.b, pQuad->m_aColors[3].a * Conv * Color.a * Alpha)};
412 Graphics()->SetColorVertex(pArray: Array, Num: 4);
413
414 CPoint *pPoints = pQuad->m_aPoints;
415
416 CPoint aRotated[4];
417 if(Rotation != 0.0f)
418 {
419 for(size_t p = 0; p < std::size(aRotated); ++p)
420 {
421 aRotated[p] = pQuad->m_aPoints[p];
422 Rotate(pCenter: &pQuad->m_aPoints[4], pPoint: &aRotated[p], Rotation);
423 }
424 pPoints = aRotated;
425 }
426
427 IGraphics::CFreeformItem Freeform(
428 fx2f(v: pPoints[0].x) + Offset.x, fx2f(v: pPoints[0].y) + Offset.y,
429 fx2f(v: pPoints[1].x) + Offset.x, fx2f(v: pPoints[1].y) + Offset.y,
430 fx2f(v: pPoints[2].x) + Offset.x, fx2f(v: pPoints[2].y) + Offset.y,
431 fx2f(v: pPoints[3].x) + Offset.x, fx2f(v: pPoints[3].y) + Offset.y);
432 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
433 }
434 Graphics()->TrianglesEnd();
435}
436
437void CRenderMap::RenderTileRectangle(int RectX, int RectY, int RectW, int RectH,
438 unsigned char IndexIn, unsigned char IndexOut,
439 float Scale, ColorRGBA Color, int RenderFlags)
440{
441 CScreenRect ScreenRect = Graphics()->GetScreen();
442
443 // calculate the final pixelsize for the tiles
444 float TilePixelSize = 1024 / 32.0f;
445 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
446 float FinalTilesetScale = FinalTileSize / TilePixelSize;
447
448 if(Graphics()->HasTextureArraysSupport())
449 Graphics()->QuadsTex3DBegin();
450 else
451 Graphics()->QuadsBegin();
452 Graphics()->SetColor(Color);
453
454 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
455 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
456 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
457 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
458
459 // adjust the texture shift according to mipmap level
460 float TexSize = 1024.0f;
461 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
462 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
463
464 for(int y = StartY; y < EndY; y++)
465 {
466 for(int x = StartX; x < EndX; x++)
467 {
468 unsigned char Index = (x >= RectX && x < RectX + RectW && y >= RectY && y < RectY + RectH) ? IndexIn : IndexOut;
469 if(Index)
470 {
471 bool Render = false;
472 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
473 Render = true;
474
475 if(Render)
476 {
477 int tx = Index % 16;
478 int ty = Index / 16;
479 int Px0 = tx * (1024 / 16);
480 int Py0 = ty * (1024 / 16);
481 int Px1 = Px0 + (1024 / 16) - 1;
482 int Py1 = Py0 + (1024 / 16) - 1;
483
484 float x0 = Nudge + Px0 / TexSize + Frac;
485 float y0 = Nudge + Py0 / TexSize + Frac;
486 float x1 = Nudge + Px1 / TexSize - Frac;
487 float y1 = Nudge + Py0 / TexSize + Frac;
488 float x2 = Nudge + Px1 / TexSize - Frac;
489 float y2 = Nudge + Py1 / TexSize - Frac;
490 float x3 = Nudge + Px0 / TexSize + Frac;
491 float y3 = Nudge + Py1 / TexSize - Frac;
492
493 if(Graphics()->HasTextureArraysSupport())
494 {
495 x0 = 0;
496 y0 = 0;
497 x1 = x0 + 1;
498 y1 = y0;
499 x2 = x0 + 1;
500 y2 = y0 + 1;
501 x3 = x0;
502 y3 = y0 + 1;
503 }
504
505 if(Graphics()->HasTextureArraysSupport())
506 {
507 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
508 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
509 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
510 }
511 else
512 {
513 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
514 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
515 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
516 }
517 }
518 }
519 }
520 }
521
522 if(Graphics()->HasTextureArraysSupport())
523 Graphics()->QuadsTex3DEnd();
524 else
525 Graphics()->QuadsEnd();
526 Graphics()->MapScreen(ScreenRect);
527}
528
529void CRenderMap::RenderTile(int x, int y, unsigned char Index, float Scale, ColorRGBA Color)
530{
531 if(Graphics()->HasTextureArraysSupport())
532 Graphics()->QuadsTex3DBegin();
533 else
534 Graphics()->QuadsBegin();
535
536 CScreenRect ScreenRect = Graphics()->GetScreen();
537
538 // calculate the final pixelsize for the tiles
539 float TilePixelSize = 1024 / Scale;
540 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
541 float FinalTilesetScale = FinalTileSize / TilePixelSize;
542
543 float TexSize = 1024.0f;
544 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
545 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
546
547 int tx = Index % 16;
548 int ty = Index / 16;
549 int Px0 = tx * (1024 / 16);
550 int Py0 = ty * (1024 / 16);
551 int Px1 = Px0 + (1024 / 16) - 1;
552 int Py1 = Py0 + (1024 / 16) - 1;
553
554 float x0 = Nudge + Px0 / TexSize + Frac;
555 float y0 = Nudge + Py0 / TexSize + Frac;
556 float x1 = Nudge + Px1 / TexSize - Frac;
557 float y1 = Nudge + Py0 / TexSize + Frac;
558 float x2 = Nudge + Px1 / TexSize - Frac;
559 float y2 = Nudge + Py1 / TexSize - Frac;
560 float x3 = Nudge + Px0 / TexSize + Frac;
561 float y3 = Nudge + Py1 / TexSize - Frac;
562
563 if(Graphics()->HasTextureArraysSupport())
564 {
565 x0 = 0;
566 y0 = 0;
567 x1 = x0 + 1;
568 y1 = y0;
569 x2 = x0 + 1;
570 y2 = y0 + 1;
571 x3 = x0;
572 y3 = y0 + 1;
573 }
574
575 if(Graphics()->HasTextureArraysSupport())
576 {
577 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
578 IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
579 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
580 }
581 else
582 {
583 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
584 IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
585 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
586 }
587
588 if(Graphics()->HasTextureArraysSupport())
589 Graphics()->QuadsTex3DEnd();
590 else
591 Graphics()->QuadsEnd();
592 Graphics()->MapScreen(ScreenRect);
593}
594
595void CRenderMap::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
596{
597 CScreenRect ScreenRect = Graphics()->GetScreen();
598
599 // calculate the final pixelsize for the tiles
600 float TilePixelSize = 1024 / 32.0f;
601 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
602 float FinalTilesetScale = FinalTileSize / TilePixelSize;
603
604 if(Graphics()->HasTextureArraysSupport())
605 Graphics()->QuadsTex3DBegin();
606 else
607 Graphics()->QuadsBegin();
608 Graphics()->SetColor(Color);
609 const bool ColorOpaque = Color.a > 254.0f / 255.0f;
610
611 const bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
612
613 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
614 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
615 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
616 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
617 if(!ExtendTiles)
618 {
619 StartY = std::max(a: 0, b: StartY);
620 StartX = std::max(a: 0, b: StartX);
621 EndY = std::min(a: h, b: EndY);
622 EndX = std::min(a: w, b: EndX);
623 }
624
625 // adjust the texture shift according to mipmap level
626 float TexSize = 1024.0f;
627 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
628 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
629
630 for(int y = StartY; y < EndY; y++)
631 {
632 for(int x = StartX; x < EndX; x++)
633 {
634 int mx = x;
635 int my = y;
636
637 if(ExtendTiles)
638 {
639 if(mx < 0)
640 mx = 0;
641 if(mx >= w)
642 mx = w - 1;
643 if(my < 0)
644 my = 0;
645 if(my >= h)
646 my = h - 1;
647 }
648
649 int c = mx + my * w;
650
651 unsigned char Index = pTiles[c].m_Index;
652 if(Index)
653 {
654 unsigned char Flags = pTiles[c].m_Flags;
655
656 bool Render = false;
657 if(ColorOpaque && Flags & TILEFLAG_OPAQUE)
658 {
659 if(RenderFlags & LAYERRENDERFLAG_OPAQUE)
660 Render = true;
661 }
662 else
663 {
664 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
665 Render = true;
666 }
667
668 if(Render)
669 {
670 int tx = Index % 16;
671 int ty = Index / 16;
672 int Px0 = tx * (1024 / 16);
673 int Py0 = ty * (1024 / 16);
674 int Px1 = Px0 + (1024 / 16) - 1;
675 int Py1 = Py0 + (1024 / 16) - 1;
676
677 float x0 = Nudge + Px0 / TexSize + Frac;
678 float y0 = Nudge + Py0 / TexSize + Frac;
679 float x1 = Nudge + Px1 / TexSize - Frac;
680 float y1 = Nudge + Py0 / TexSize + Frac;
681 float x2 = Nudge + Px1 / TexSize - Frac;
682 float y2 = Nudge + Py1 / TexSize - Frac;
683 float x3 = Nudge + Px0 / TexSize + Frac;
684 float y3 = Nudge + Py1 / TexSize - Frac;
685
686 if(Graphics()->HasTextureArraysSupport())
687 {
688 x0 = 0;
689 y0 = 0;
690 x1 = x0 + 1;
691 y1 = y0;
692 x2 = x0 + 1;
693 y2 = y0 + 1;
694 x3 = x0;
695 y3 = y0 + 1;
696 }
697
698 if(Flags & TILEFLAG_XFLIP)
699 {
700 x0 = x2;
701 x1 = x3;
702 x2 = x3;
703 x3 = x0;
704 }
705
706 if(Flags & TILEFLAG_YFLIP)
707 {
708 y0 = y3;
709 y2 = y1;
710 y3 = y1;
711 y1 = y0;
712 }
713
714 if(Flags & TILEFLAG_ROTATE)
715 {
716 float Tmp = x0;
717 x0 = x3;
718 x3 = x2;
719 x2 = x1;
720 x1 = Tmp;
721 Tmp = y0;
722 y0 = y3;
723 y3 = y2;
724 y2 = y1;
725 y1 = Tmp;
726 }
727
728 if(Graphics()->HasTextureArraysSupport())
729 {
730 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
731 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
732 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
733 }
734 else
735 {
736 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
737 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
738 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
739 }
740 }
741 }
742 x += pTiles[c].m_Skip;
743 }
744 }
745
746 if(Graphics()->HasTextureArraysSupport())
747 Graphics()->QuadsTex3DEnd();
748 else
749 Graphics()->QuadsEnd();
750 Graphics()->MapScreen(ScreenRect);
751}
752
753void CRenderMap::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
754{
755 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
756 return;
757
758 CScreenRect ScreenRect = Graphics()->GetScreen();
759
760 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
761 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
762 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
763 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
764 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
765 return; // its useless to render text at this distance
766
767 StartY = std::max(a: 0, b: StartY);
768 StartX = std::max(a: 0, b: StartX);
769 EndY = std::min(a: h, b: EndY);
770 EndX = std::min(a: w, b: EndX);
771
772 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
773 char aBuf[16];
774
775 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
776 for(int y = StartY; y < EndY; y++)
777 {
778 for(int x = StartX; x < EndX; x++)
779 {
780 int c = x + y * w;
781
782 unsigned char Index = pTele[c].m_Number;
783 if(Index && IsTeleTileNumberUsedAny(Index: pTele[c].m_Type))
784 {
785 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
786 // Auto-resize text to fit inside the tile
787 float ScaledWidth = TextRender()->TextWidth(Size: Size * Scale, pText: aBuf, StrLength: -1);
788 float Factor = std::clamp(val: Scale / ScaledWidth, lo: 0.0f, hi: 1.0f);
789 float LocalSize = Size * Factor;
790 float ToCenterOffset = (1 - LocalSize) / 2.f;
791 TextRender()->Text(x: (x + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, y: (y + ToCenterOffset) * Scale, Size: LocalSize * Scale, pText: aBuf);
792 }
793 }
794 }
795 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
796 Graphics()->MapScreen(ScreenRect);
797}
798
799void CRenderMap::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
800{
801 CScreenRect ScreenRect = Graphics()->GetScreen();
802
803 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
804 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
805 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
806 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
807 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
808 return; // its useless to render text at this distance
809
810 StartY = std::max(a: 0, b: StartY);
811 StartX = std::max(a: 0, b: StartX);
812 EndY = std::min(a: h, b: EndY);
813 EndX = std::min(a: w, b: EndX);
814
815 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
816 float ToCenterOffset = (1 - Size) / 2.f;
817 char aBuf[16];
818
819 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
820 for(int y = StartY; y < EndY; y++)
821 {
822 for(int x = StartX; x < EndX; x++)
823 {
824 int c = x + y * w;
825
826 int Force = (int)pSpeedup[c].m_Force;
827 int MaxSpeed = (int)pSpeedup[c].m_MaxSpeed;
828 int Type = (int)pSpeedup[c].m_Type;
829 int Angle = (int)pSpeedup[c].m_Angle;
830 if((Force && Type == TILE_SPEED_BOOST_OLD) || ((Force || MaxSpeed) && Type == TILE_SPEED_BOOST) || (OverlayRenderFlag & OVERLAYRENDERFLAG_EDITOR && (Type || Force || MaxSpeed || Angle)))
831 {
832 if(IsValidSpeedupTile(Index: Type))
833 {
834 // draw arrow
835 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_Id);
836 Graphics()->QuadsBegin();
837 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
838 Graphics()->SelectSprite(Id: SPRITE_SPEEDUP_ARROW);
839 Graphics()->QuadsSetRotation(Angle: pSpeedup[c].m_Angle * (pi / 180.0f));
840 Graphics()->DrawSprite(x: x * Scale + 16, y: y * Scale + 16, Size: 35.0f);
841 Graphics()->QuadsEnd();
842
843 // draw force and max speed
844 if(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT)
845 {
846 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Force);
847 TextRender()->Text(x: x * Scale, y: (y + 0.5f + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
848 if(MaxSpeed)
849 {
850 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", MaxSpeed);
851 TextRender()->Text(x: x * Scale, y: (y + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
852 }
853 }
854 }
855 else
856 {
857 // draw all three values
858 if(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT)
859 {
860 float LineSpacing = Size * Scale / 3.f;
861 float BaseY = (y + ToCenterOffset) * Scale;
862 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Force);
863 TextRender()->Text(x: x * Scale, y: BaseY, Size: LineSpacing, pText: aBuf);
864 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", MaxSpeed);
865 TextRender()->Text(x: x * Scale, y: BaseY + LineSpacing, Size: LineSpacing, pText: aBuf);
866 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Angle);
867 TextRender()->Text(x: x * Scale, y: BaseY + 2 * LineSpacing, Size: LineSpacing, pText: aBuf);
868 }
869 }
870 }
871 }
872 }
873 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
874 Graphics()->MapScreen(ScreenRect);
875}
876
877void CRenderMap::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
878{
879 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
880 return;
881
882 CScreenRect ScreenRect = Graphics()->GetScreen();
883
884 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
885 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
886 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
887 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
888 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
889 return; // its useless to render text at this distance
890
891 StartY = std::max(a: 0, b: StartY);
892 StartX = std::max(a: 0, b: StartX);
893 EndY = std::min(a: h, b: EndY);
894 EndX = std::min(a: w, b: EndX);
895
896 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
897 float ToCenterOffset = (1 - Size) / 2.f;
898 char aBuf[16];
899
900 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
901 for(int y = StartY; y < EndY; y++)
902 {
903 for(int x = StartX; x < EndX; x++)
904 {
905 int c = x + y * w;
906
907 unsigned char Index = pSwitch[c].m_Number;
908 if(Index && IsSwitchTileNumberUsed(Index: pSwitch[c].m_Type))
909 {
910 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
911 TextRender()->Text(x: x * Scale, y: (y + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
912 }
913
914 unsigned char Delay = pSwitch[c].m_Delay;
915 if(Delay && IsSwitchTileDelayUsed(Index: pSwitch[c].m_Type))
916 {
917 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Delay);
918 TextRender()->Text(x: x * Scale, y: (y + 0.5f + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
919 }
920 }
921 }
922 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
923 Graphics()->MapScreen(ScreenRect);
924}
925
926void CRenderMap::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
927{
928 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
929 return;
930
931 CScreenRect ScreenRect = Graphics()->GetScreen();
932
933 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
934 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
935 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
936 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
937 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
938 return; // its useless to render text at this distance
939
940 StartY = std::max(a: 0, b: StartY);
941 StartX = std::max(a: 0, b: StartX);
942 EndY = std::min(a: h, b: EndY);
943 EndX = std::min(a: w, b: EndX);
944
945 float Size = g_Config.m_ClTextEntitiesSize / 200.f;
946 char aBuf[16];
947
948 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
949 for(int y = StartY; y < EndY; y++)
950 {
951 for(int x = StartX; x < EndX; x++)
952 {
953 int c = x + y * w;
954
955 unsigned char Index = pTune[c].m_Number;
956 if(Index)
957 {
958 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
959 // Auto-resize text to fit inside the tile
960 float ScaledWidth = TextRender()->TextWidth(Size: Size * Scale, pText: aBuf, StrLength: -1);
961 float Factor = std::clamp(val: Scale / ScaledWidth, lo: 0.0f, hi: 1.0f);
962 float LocalSize = Size * Factor;
963 float ToCenterOffset = (1 - LocalSize) / 2.f;
964 TextRender()->Text(x: (x + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, y: (y + ToCenterOffset) * Scale, Size: LocalSize * Scale, pText: aBuf);
965 }
966 }
967 }
968 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
969 Graphics()->MapScreen(ScreenRect);
970}
971
972void CRenderMap::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
973{
974 CScreenRect ScreenRect = Graphics()->GetScreen();
975
976 // calculate the final pixelsize for the tiles
977 float TilePixelSize = 1024 / 32.0f;
978 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
979 float FinalTilesetScale = FinalTileSize / TilePixelSize;
980
981 if(Graphics()->HasTextureArraysSupport())
982 Graphics()->QuadsTex3DBegin();
983 else
984 Graphics()->QuadsBegin();
985 Graphics()->SetColor(Color);
986
987 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
988
989 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
990 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
991 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
992 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
993 if(!ExtendTiles)
994 {
995 StartY = std::max(a: 0, b: StartY);
996 StartX = std::max(a: 0, b: StartX);
997 EndY = std::min(a: h, b: EndY);
998 EndX = std::min(a: w, b: EndX);
999 }
1000
1001 // adjust the texture shift according to mipmap level
1002 float TexSize = 1024.0f;
1003 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1004 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1005
1006 for(int y = StartY; y < EndY; y++)
1007 for(int x = StartX; x < EndX; x++)
1008 {
1009 int mx = x;
1010 int my = y;
1011
1012 if(ExtendTiles)
1013 {
1014 if(mx < 0)
1015 mx = 0;
1016 if(mx >= w)
1017 mx = w - 1;
1018 if(my < 0)
1019 my = 0;
1020 if(my >= h)
1021 my = h - 1;
1022 }
1023
1024 int c = mx + my * w;
1025
1026 unsigned char Index = pTele[c].m_Type;
1027 if(Index)
1028 {
1029 bool Render = false;
1030 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1031 Render = true;
1032
1033 if(Render)
1034 {
1035 int tx = Index % 16;
1036 int ty = Index / 16;
1037 int Px0 = tx * (1024 / 16);
1038 int Py0 = ty * (1024 / 16);
1039 int Px1 = Px0 + (1024 / 16) - 1;
1040 int Py1 = Py0 + (1024 / 16) - 1;
1041
1042 float x0 = Nudge + Px0 / TexSize + Frac;
1043 float y0 = Nudge + Py0 / TexSize + Frac;
1044 float x1 = Nudge + Px1 / TexSize - Frac;
1045 float y1 = Nudge + Py0 / TexSize + Frac;
1046 float x2 = Nudge + Px1 / TexSize - Frac;
1047 float y2 = Nudge + Py1 / TexSize - Frac;
1048 float x3 = Nudge + Px0 / TexSize + Frac;
1049 float y3 = Nudge + Py1 / TexSize - Frac;
1050
1051 if(Graphics()->HasTextureArraysSupport())
1052 {
1053 x0 = 0;
1054 y0 = 0;
1055 x1 = x0 + 1;
1056 y1 = y0;
1057 x2 = x0 + 1;
1058 y2 = y0 + 1;
1059 x3 = x0;
1060 y3 = y0 + 1;
1061 }
1062
1063 if(Graphics()->HasTextureArraysSupport())
1064 {
1065 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1066 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1067 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1068 }
1069 else
1070 {
1071 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1072 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1073 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1074 }
1075 }
1076 }
1077 }
1078
1079 if(Graphics()->HasTextureArraysSupport())
1080 Graphics()->QuadsTex3DEnd();
1081 else
1082 Graphics()->QuadsEnd();
1083 Graphics()->MapScreen(ScreenRect);
1084}
1085
1086void CRenderMap::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
1087{
1088 CScreenRect ScreenRect = Graphics()->GetScreen();
1089
1090 // calculate the final pixelsize for the tiles
1091 float TilePixelSize = 1024 / 32.0f;
1092 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
1093 float FinalTilesetScale = FinalTileSize / TilePixelSize;
1094
1095 if(Graphics()->HasTextureArraysSupport())
1096 Graphics()->QuadsTex3DBegin();
1097 else
1098 Graphics()->QuadsBegin();
1099 Graphics()->SetColor(Color);
1100
1101 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
1102
1103 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
1104 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
1105 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
1106 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
1107 if(!ExtendTiles)
1108 {
1109 StartY = std::max(a: 0, b: StartY);
1110 StartX = std::max(a: 0, b: StartX);
1111 EndY = std::min(a: h, b: EndY);
1112 EndX = std::min(a: w, b: EndX);
1113 }
1114
1115 // adjust the texture shift according to mipmap level
1116 float TexSize = 1024.0f;
1117 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1118 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1119
1120 for(int y = StartY; y < EndY; y++)
1121 for(int x = StartX; x < EndX; x++)
1122 {
1123 int mx = x;
1124 int my = y;
1125
1126 if(ExtendTiles)
1127 {
1128 if(mx < 0)
1129 mx = 0;
1130 if(mx >= w)
1131 mx = w - 1;
1132 if(my < 0)
1133 my = 0;
1134 if(my >= h)
1135 my = h - 1;
1136 }
1137
1138 int c = mx + my * w;
1139
1140 unsigned char Index = pSwitchTile[c].m_Type;
1141 if(Index)
1142 {
1143 if(Index == TILE_SWITCHTIMEDOPEN)
1144 Index = 8;
1145
1146 unsigned char Flags = pSwitchTile[c].m_Flags;
1147
1148 bool Render = false;
1149 if(Flags & TILEFLAG_OPAQUE)
1150 {
1151 if(RenderFlags & LAYERRENDERFLAG_OPAQUE)
1152 Render = true;
1153 }
1154 else
1155 {
1156 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1157 Render = true;
1158 }
1159
1160 if(Render)
1161 {
1162 int tx = Index % 16;
1163 int ty = Index / 16;
1164 int Px0 = tx * (1024 / 16);
1165 int Py0 = ty * (1024 / 16);
1166 int Px1 = Px0 + (1024 / 16) - 1;
1167 int Py1 = Py0 + (1024 / 16) - 1;
1168
1169 float x0 = Nudge + Px0 / TexSize + Frac;
1170 float y0 = Nudge + Py0 / TexSize + Frac;
1171 float x1 = Nudge + Px1 / TexSize - Frac;
1172 float y1 = Nudge + Py0 / TexSize + Frac;
1173 float x2 = Nudge + Px1 / TexSize - Frac;
1174 float y2 = Nudge + Py1 / TexSize - Frac;
1175 float x3 = Nudge + Px0 / TexSize + Frac;
1176 float y3 = Nudge + Py1 / TexSize - Frac;
1177
1178 if(Graphics()->HasTextureArraysSupport())
1179 {
1180 x0 = 0;
1181 y0 = 0;
1182 x1 = x0 + 1;
1183 y1 = y0;
1184 x2 = x0 + 1;
1185 y2 = y0 + 1;
1186 x3 = x0;
1187 y3 = y0 + 1;
1188 }
1189
1190 if(Flags & TILEFLAG_XFLIP)
1191 {
1192 x0 = x2;
1193 x1 = x3;
1194 x2 = x3;
1195 x3 = x0;
1196 }
1197
1198 if(Flags & TILEFLAG_YFLIP)
1199 {
1200 y0 = y3;
1201 y2 = y1;
1202 y3 = y1;
1203 y1 = y0;
1204 }
1205
1206 if(Flags & TILEFLAG_ROTATE)
1207 {
1208 float Tmp = x0;
1209 x0 = x3;
1210 x3 = x2;
1211 x2 = x1;
1212 x1 = Tmp;
1213 Tmp = y0;
1214 y0 = y3;
1215 y3 = y2;
1216 y2 = y1;
1217 y1 = Tmp;
1218 }
1219
1220 if(Graphics()->HasTextureArraysSupport())
1221 {
1222 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1223 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1224 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1225 }
1226 else
1227 {
1228 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1229 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1230 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1231 }
1232 }
1233 }
1234 }
1235
1236 if(Graphics()->HasTextureArraysSupport())
1237 Graphics()->QuadsTex3DEnd();
1238 else
1239 Graphics()->QuadsEnd();
1240 Graphics()->MapScreen(ScreenRect);
1241}
1242
1243void CRenderMap::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, CTuneColorMapper *pTuneColorMapper)
1244{
1245 CScreenRect ScreenRect = Graphics()->GetScreen();
1246
1247 // calculate the final pixelsize for the tiles
1248 float TilePixelSize = 1024 / 32.0f;
1249 float FinalTileSize = Scale / ScreenRect.Width() * Graphics()->ScreenWidth();
1250 float FinalTilesetScale = FinalTileSize / TilePixelSize;
1251
1252 if(Graphics()->HasTextureArraysSupport())
1253 Graphics()->QuadsTex3DBegin();
1254 else
1255 Graphics()->QuadsBegin();
1256 Graphics()->SetColor(Color);
1257
1258 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
1259
1260 int StartY = (int)(ScreenRect.m_TopLeft.y / Scale) - 1;
1261 int StartX = (int)(ScreenRect.m_TopLeft.x / Scale) - 1;
1262 int EndY = (int)(ScreenRect.m_BottomRight.y / Scale) + 1;
1263 int EndX = (int)(ScreenRect.m_BottomRight.x / Scale) + 1;
1264 if(!ExtendTiles)
1265 {
1266 StartY = std::max(a: 0, b: StartY);
1267 StartX = std::max(a: 0, b: StartX);
1268 EndY = std::min(a: h, b: EndY);
1269 EndX = std::min(a: w, b: EndX);
1270 }
1271
1272 // adjust the texture shift according to mipmap level
1273 float TexSize = 1024.0f;
1274 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1275 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1276
1277 for(int y = StartY; y < EndY; y++)
1278 for(int x = StartX; x < EndX; x++)
1279 {
1280 int mx = x;
1281 int my = y;
1282
1283 if(ExtendTiles)
1284 {
1285 if(mx < 0)
1286 mx = 0;
1287 if(mx >= w)
1288 mx = w - 1;
1289 if(my < 0)
1290 my = 0;
1291 if(my >= h)
1292 my = h - 1;
1293 }
1294
1295 int c = mx + my * w;
1296
1297 const unsigned char Index = pTune[c].m_Type;
1298
1299 if(Index)
1300 {
1301 bool Render = false;
1302 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1303 Render = true;
1304
1305 if(Render)
1306 {
1307 const unsigned char Number = pTune[c].m_Number;
1308
1309 if(Number == 0 || pTuneColorMapper == nullptr)
1310 Graphics()->SetColor(Color);
1311 else
1312 {
1313 uint8_t ColorIndex = pTuneColorMapper->TuneNumberToColorIndex(TuneNumber: Number);
1314 Graphics()->SetColor(pTuneColorMapper->TuneColorIndexToColor(TuneColorIndex: ColorIndex).Multiply(Other: Color));
1315 }
1316
1317 int tx = Index % 16;
1318 int ty = Index / 16;
1319 int Px0 = tx * (1024 / 16);
1320 int Py0 = ty * (1024 / 16);
1321 int Px1 = Px0 + (1024 / 16) - 1;
1322 int Py1 = Py0 + (1024 / 16) - 1;
1323
1324 float x0 = Nudge + Px0 / TexSize + Frac;
1325 float y0 = Nudge + Py0 / TexSize + Frac;
1326 float x1 = Nudge + Px1 / TexSize - Frac;
1327 float y1 = Nudge + Py0 / TexSize + Frac;
1328 float x2 = Nudge + Px1 / TexSize - Frac;
1329 float y2 = Nudge + Py1 / TexSize - Frac;
1330 float x3 = Nudge + Px0 / TexSize + Frac;
1331 float y3 = Nudge + Py1 / TexSize - Frac;
1332
1333 if(Graphics()->HasTextureArraysSupport())
1334 {
1335 x0 = 0;
1336 y0 = 0;
1337 x1 = x0 + 1;
1338 y1 = y0;
1339 x2 = x0 + 1;
1340 y2 = y0 + 1;
1341 x3 = x0;
1342 y3 = y0 + 1;
1343 }
1344
1345 if(Graphics()->HasTextureArraysSupport())
1346 {
1347 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1348 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1349 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1350 }
1351 else
1352 {
1353 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1354 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1355 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1356 }
1357 }
1358 }
1359 }
1360
1361 if(Graphics()->HasTextureArraysSupport())
1362 Graphics()->QuadsTex3DEnd();
1363 else
1364 Graphics()->QuadsEnd();
1365 Graphics()->MapScreen(ScreenRect);
1366}
1367
1368void CRenderMap::RenderDebugClip(float ClipX, float ClipY, float ClipW, float ClipH, ColorRGBA Color, float Zoom, const char *pLabel)
1369{
1370 Graphics()->TextureClear();
1371 Graphics()->LinesBegin();
1372 Graphics()->SetColor(Color);
1373 IGraphics::CLineItem aLineItems[] = {
1374 IGraphics::CLineItem(ClipX, ClipY, ClipX, ClipY + ClipH),
1375 IGraphics::CLineItem(ClipX + ClipW, ClipY, ClipX + ClipW, ClipY + ClipH),
1376 IGraphics::CLineItem(ClipX, ClipY, ClipX + ClipW, ClipY),
1377 IGraphics::CLineItem(ClipX, ClipY + ClipH, ClipX + ClipW, ClipY + ClipH),
1378 };
1379 Graphics()->LinesDraw(pArray: aLineItems, Num: std::size(aLineItems));
1380 Graphics()->LinesEnd();
1381
1382 TextRender()->TextColor(Color);
1383
1384 // clamp zoom and set line width, because otherwise the text can be partially clipped out
1385 TextRender()->Text(x: ClipX, y: ClipY, Size: std::min(a: 12.0f * Zoom, b: 20.0f), pText: pLabel, LineWidth: ClipW);
1386 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
1387}
1388
1389CTuneColorMapper::CTuneColorMapper()
1390{
1391 Reset();
1392}
1393
1394uint8_t CTuneColorMapper::TuneNumberToColorIndex(uint8_t TuneNumber)
1395{
1396 if(TuneNumber == 0)
1397 return 0;
1398
1399 uint8_t &TuneColorIndex = m_aTuneNumberToColorIndex[TuneNumber - 1];
1400 if(TuneColorIndex == 0)
1401 {
1402 TuneColorIndex = m_NextTuneNumberIndex + 1;
1403 ++m_NextTuneNumberIndex;
1404 }
1405 return TuneColorIndex;
1406}
1407
1408ColorRGBA CTuneColorMapper::TuneColorIndexToColor(uint8_t TuneColorIndex) const
1409{
1410 if(TuneColorIndex == 0)
1411 return ColorRGBA(1.0f, 1.0f, 1.0f);
1412
1413 float Hue = std::fmod(x: (TuneColorIndex - 1) * normalized_golden_angle, y: 1.0f);
1414 return color_cast<ColorRGBA>(hsl: ColorHSLA(Hue, 0.75f, 0.5f, 1.0f));
1415}
1416
1417void CTuneColorMapper::Reset()
1418{
1419 m_aTuneNumberToColorIndex.fill(u: 0);
1420 m_NextTuneNumberIndex = 0;
1421}
1422