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/math.h>
6#include <base/str.h>
7
8#include <engine/graphics.h>
9#include <engine/map.h>
10#include <engine/shared/config.h>
11#include <engine/shared/datafile.h>
12#include <engine/shared/map.h>
13#include <engine/textrender.h>
14
15#include <generated/client_data.h>
16
17#include <game/mapitems.h>
18#include <game/mapitems_ex.h>
19
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: maximum(a: m_NumPointsMax - StartPoint, b: 0));
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, 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 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
442 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
443
444 // calculate the final pixelsize for the tiles
445 float TilePixelSize = 1024 / 32.0f;
446 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
447 float FinalTilesetScale = FinalTileSize / TilePixelSize;
448
449 if(Graphics()->HasTextureArraysSupport())
450 Graphics()->QuadsTex3DBegin();
451 else
452 Graphics()->QuadsBegin();
453 Graphics()->SetColor(Color);
454
455 int StartY = (int)(ScreenY0 / Scale) - 1;
456 int StartX = (int)(ScreenX0 / Scale) - 1;
457 int EndY = (int)(ScreenY1 / Scale) + 1;
458 int EndX = (int)(ScreenX1 / Scale) + 1;
459
460 // adjust the texture shift according to mipmap level
461 float TexSize = 1024.0f;
462 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
463 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
464
465 for(int y = StartY; y < EndY; y++)
466 {
467 for(int x = StartX; x < EndX; x++)
468 {
469 unsigned char Index = (x >= RectX && x < RectX + RectW && y >= RectY && y < RectY + RectH) ? IndexIn : IndexOut;
470 if(Index)
471 {
472 bool Render = false;
473 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
474 Render = true;
475
476 if(Render)
477 {
478 int tx = Index % 16;
479 int ty = Index / 16;
480 int Px0 = tx * (1024 / 16);
481 int Py0 = ty * (1024 / 16);
482 int Px1 = Px0 + (1024 / 16) - 1;
483 int Py1 = Py0 + (1024 / 16) - 1;
484
485 float x0 = Nudge + Px0 / TexSize + Frac;
486 float y0 = Nudge + Py0 / TexSize + Frac;
487 float x1 = Nudge + Px1 / TexSize - Frac;
488 float y1 = Nudge + Py0 / TexSize + Frac;
489 float x2 = Nudge + Px1 / TexSize - Frac;
490 float y2 = Nudge + Py1 / TexSize - Frac;
491 float x3 = Nudge + Px0 / TexSize + Frac;
492 float y3 = Nudge + Py1 / TexSize - Frac;
493
494 if(Graphics()->HasTextureArraysSupport())
495 {
496 x0 = 0;
497 y0 = 0;
498 x1 = x0 + 1;
499 y1 = y0;
500 x2 = x0 + 1;
501 y2 = y0 + 1;
502 x3 = x0;
503 y3 = y0 + 1;
504 }
505
506 if(Graphics()->HasTextureArraysSupport())
507 {
508 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
509 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
510 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
511 }
512 else
513 {
514 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
515 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
516 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
517 }
518 }
519 }
520 }
521 }
522
523 if(Graphics()->HasTextureArraysSupport())
524 Graphics()->QuadsTex3DEnd();
525 else
526 Graphics()->QuadsEnd();
527 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
528}
529
530void CRenderMap::RenderTile(int x, int y, unsigned char Index, float Scale, ColorRGBA Color)
531{
532 if(Graphics()->HasTextureArraysSupport())
533 Graphics()->QuadsTex3DBegin();
534 else
535 Graphics()->QuadsBegin();
536
537 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
538 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
539
540 // calculate the final pixelsize for the tiles
541 float TilePixelSize = 1024 / Scale;
542 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
543 float FinalTilesetScale = FinalTileSize / TilePixelSize;
544
545 float TexSize = 1024.0f;
546 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
547 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
548
549 int tx = Index % 16;
550 int ty = Index / 16;
551 int Px0 = tx * (1024 / 16);
552 int Py0 = ty * (1024 / 16);
553 int Px1 = Px0 + (1024 / 16) - 1;
554 int Py1 = Py0 + (1024 / 16) - 1;
555
556 float x0 = Nudge + Px0 / TexSize + Frac;
557 float y0 = Nudge + Py0 / TexSize + Frac;
558 float x1 = Nudge + Px1 / TexSize - Frac;
559 float y1 = Nudge + Py0 / TexSize + Frac;
560 float x2 = Nudge + Px1 / TexSize - Frac;
561 float y2 = Nudge + Py1 / TexSize - Frac;
562 float x3 = Nudge + Px0 / TexSize + Frac;
563 float y3 = Nudge + Py1 / TexSize - Frac;
564
565 if(Graphics()->HasTextureArraysSupport())
566 {
567 x0 = 0;
568 y0 = 0;
569 x1 = x0 + 1;
570 y1 = y0;
571 x2 = x0 + 1;
572 y2 = y0 + 1;
573 x3 = x0;
574 y3 = y0 + 1;
575 }
576
577 if(Graphics()->HasTextureArraysSupport())
578 {
579 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
580 IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
581 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
582 }
583 else
584 {
585 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
586 IGraphics::CQuadItem QuadItem(x, y, Scale, Scale);
587 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
588 }
589
590 if(Graphics()->HasTextureArraysSupport())
591 Graphics()->QuadsTex3DEnd();
592 else
593 Graphics()->QuadsEnd();
594 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
595}
596
597void CRenderMap::RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
598{
599 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
600 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
601
602 // calculate the final pixelsize for the tiles
603 float TilePixelSize = 1024 / 32.0f;
604 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
605 float FinalTilesetScale = FinalTileSize / TilePixelSize;
606
607 if(Graphics()->HasTextureArraysSupport())
608 Graphics()->QuadsTex3DBegin();
609 else
610 Graphics()->QuadsBegin();
611 Graphics()->SetColor(Color);
612 const bool ColorOpaque = Color.a > 254.0f / 255.0f;
613
614 const bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
615
616 int StartY = (int)(ScreenY0 / Scale) - 1;
617 int StartX = (int)(ScreenX0 / Scale) - 1;
618 int EndY = (int)(ScreenY1 / Scale) + 1;
619 int EndX = (int)(ScreenX1 / Scale) + 1;
620 if(!ExtendTiles)
621 {
622 StartY = std::max(a: 0, b: StartY);
623 StartX = std::max(a: 0, b: StartX);
624 EndY = std::min(a: h, b: EndY);
625 EndX = std::min(a: w, b: EndX);
626 }
627
628 // adjust the texture shift according to mipmap level
629 float TexSize = 1024.0f;
630 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
631 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
632
633 for(int y = StartY; y < EndY; y++)
634 {
635 for(int x = StartX; x < EndX; x++)
636 {
637 int mx = x;
638 int my = y;
639
640 if(ExtendTiles)
641 {
642 if(mx < 0)
643 mx = 0;
644 if(mx >= w)
645 mx = w - 1;
646 if(my < 0)
647 my = 0;
648 if(my >= h)
649 my = h - 1;
650 }
651
652 int c = mx + my * w;
653
654 unsigned char Index = pTiles[c].m_Index;
655 if(Index)
656 {
657 unsigned char Flags = pTiles[c].m_Flags;
658
659 bool Render = false;
660 if(ColorOpaque && Flags & TILEFLAG_OPAQUE)
661 {
662 if(RenderFlags & LAYERRENDERFLAG_OPAQUE)
663 Render = true;
664 }
665 else
666 {
667 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
668 Render = true;
669 }
670
671 if(Render)
672 {
673 int tx = Index % 16;
674 int ty = Index / 16;
675 int Px0 = tx * (1024 / 16);
676 int Py0 = ty * (1024 / 16);
677 int Px1 = Px0 + (1024 / 16) - 1;
678 int Py1 = Py0 + (1024 / 16) - 1;
679
680 float x0 = Nudge + Px0 / TexSize + Frac;
681 float y0 = Nudge + Py0 / TexSize + Frac;
682 float x1 = Nudge + Px1 / TexSize - Frac;
683 float y1 = Nudge + Py0 / TexSize + Frac;
684 float x2 = Nudge + Px1 / TexSize - Frac;
685 float y2 = Nudge + Py1 / TexSize - Frac;
686 float x3 = Nudge + Px0 / TexSize + Frac;
687 float y3 = Nudge + Py1 / TexSize - Frac;
688
689 if(Graphics()->HasTextureArraysSupport())
690 {
691 x0 = 0;
692 y0 = 0;
693 x1 = x0 + 1;
694 y1 = y0;
695 x2 = x0 + 1;
696 y2 = y0 + 1;
697 x3 = x0;
698 y3 = y0 + 1;
699 }
700
701 if(Flags & TILEFLAG_XFLIP)
702 {
703 x0 = x2;
704 x1 = x3;
705 x2 = x3;
706 x3 = x0;
707 }
708
709 if(Flags & TILEFLAG_YFLIP)
710 {
711 y0 = y3;
712 y2 = y1;
713 y3 = y1;
714 y1 = y0;
715 }
716
717 if(Flags & TILEFLAG_ROTATE)
718 {
719 float Tmp = x0;
720 x0 = x3;
721 x3 = x2;
722 x2 = x1;
723 x1 = Tmp;
724 Tmp = y0;
725 y0 = y3;
726 y3 = y2;
727 y2 = y1;
728 y1 = Tmp;
729 }
730
731 if(Graphics()->HasTextureArraysSupport())
732 {
733 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
734 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
735 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
736 }
737 else
738 {
739 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
740 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
741 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
742 }
743 }
744 }
745 x += pTiles[c].m_Skip;
746 }
747 }
748
749 if(Graphics()->HasTextureArraysSupport())
750 Graphics()->QuadsTex3DEnd();
751 else
752 Graphics()->QuadsEnd();
753 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
754}
755
756void CRenderMap::RenderTeleOverlay(CTeleTile *pTele, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
757{
758 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
759 return;
760
761 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
762 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
763
764 int StartY = (int)(ScreenY0 / Scale) - 1;
765 int StartX = (int)(ScreenX0 / Scale) - 1;
766 int EndY = (int)(ScreenY1 / Scale) + 1;
767 int EndX = (int)(ScreenX1 / Scale) + 1;
768 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
769 return; // its useless to render text at this distance
770
771 StartY = std::max(a: 0, b: StartY);
772 StartX = std::max(a: 0, b: StartX);
773 EndY = std::min(a: h, b: EndY);
774 EndX = std::min(a: w, b: EndX);
775
776 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
777 char aBuf[16];
778
779 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
780 for(int y = StartY; y < EndY; y++)
781 {
782 for(int x = StartX; x < EndX; x++)
783 {
784 int c = x + y * w;
785
786 unsigned char Index = pTele[c].m_Number;
787 if(Index && IsTeleTileNumberUsedAny(Index: pTele[c].m_Type))
788 {
789 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
790 // Auto-resize text to fit inside the tile
791 float ScaledWidth = TextRender()->TextWidth(Size: Size * Scale, pText: aBuf, StrLength: -1);
792 float Factor = std::clamp(val: Scale / ScaledWidth, lo: 0.0f, hi: 1.0f);
793 float LocalSize = Size * Factor;
794 float ToCenterOffset = (1 - LocalSize) / 2.f;
795 TextRender()->Text(x: (x + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, y: (y + ToCenterOffset) * Scale, Size: LocalSize * Scale, pText: aBuf);
796 }
797 }
798 }
799 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
800 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
801}
802
803void CRenderMap::RenderSpeedupOverlay(CSpeedupTile *pSpeedup, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
804{
805 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
806 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
807
808 int StartY = (int)(ScreenY0 / Scale) - 1;
809 int StartX = (int)(ScreenX0 / Scale) - 1;
810 int EndY = (int)(ScreenY1 / Scale) + 1;
811 int EndX = (int)(ScreenX1 / Scale) + 1;
812 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
813 return; // its useless to render text at this distance
814
815 StartY = std::max(a: 0, b: StartY);
816 StartX = std::max(a: 0, b: StartX);
817 EndY = std::min(a: h, b: EndY);
818 EndX = std::min(a: w, b: EndX);
819
820 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
821 float ToCenterOffset = (1 - Size) / 2.f;
822 char aBuf[16];
823
824 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
825 for(int y = StartY; y < EndY; y++)
826 {
827 for(int x = StartX; x < EndX; x++)
828 {
829 int c = x + y * w;
830
831 int Force = (int)pSpeedup[c].m_Force;
832 int MaxSpeed = (int)pSpeedup[c].m_MaxSpeed;
833 int Type = (int)pSpeedup[c].m_Type;
834 int Angle = (int)pSpeedup[c].m_Angle;
835 if((Force && Type == TILE_SPEED_BOOST_OLD) || ((Force || MaxSpeed) && Type == TILE_SPEED_BOOST) || (OverlayRenderFlag & OVERLAYRENDERFLAG_EDITOR && (Type || Force || MaxSpeed || Angle)))
836 {
837 if(IsValidSpeedupTile(Index: Type))
838 {
839 // draw arrow
840 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_Id);
841 Graphics()->QuadsBegin();
842 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
843 Graphics()->SelectSprite(Id: SPRITE_SPEEDUP_ARROW);
844 Graphics()->QuadsSetRotation(Angle: pSpeedup[c].m_Angle * (pi / 180.0f));
845 Graphics()->DrawSprite(x: x * Scale + 16, y: y * Scale + 16, Size: 35.0f);
846 Graphics()->QuadsEnd();
847
848 // draw force and max speed
849 if(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT)
850 {
851 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Force);
852 TextRender()->Text(x: x * Scale, y: (y + 0.5f + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
853 if(MaxSpeed)
854 {
855 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", MaxSpeed);
856 TextRender()->Text(x: x * Scale, y: (y + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
857 }
858 }
859 }
860 else
861 {
862 // draw all three values
863 if(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT)
864 {
865 float LineSpacing = Size * Scale / 3.f;
866 float BaseY = (y + ToCenterOffset) * Scale;
867 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Force);
868 TextRender()->Text(x: x * Scale, y: BaseY, Size: LineSpacing, pText: aBuf);
869 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", MaxSpeed);
870 TextRender()->Text(x: x * Scale, y: BaseY + LineSpacing, Size: LineSpacing, pText: aBuf);
871 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Angle);
872 TextRender()->Text(x: x * Scale, y: BaseY + 2 * LineSpacing, Size: LineSpacing, pText: aBuf);
873 }
874 }
875 }
876 }
877 }
878 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
879 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
880}
881
882void CRenderMap::RenderSwitchOverlay(CSwitchTile *pSwitch, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
883{
884 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
885 return;
886
887 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
888 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
889
890 int StartY = (int)(ScreenY0 / Scale) - 1;
891 int StartX = (int)(ScreenX0 / Scale) - 1;
892 int EndY = (int)(ScreenY1 / Scale) + 1;
893 int EndX = (int)(ScreenX1 / Scale) + 1;
894 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
895 return; // its useless to render text at this distance
896
897 StartY = std::max(a: 0, b: StartY);
898 StartX = std::max(a: 0, b: StartX);
899 EndY = std::min(a: h, b: EndY);
900 EndX = std::min(a: w, b: EndX);
901
902 float Size = g_Config.m_ClTextEntitiesSize / 100.f;
903 float ToCenterOffset = (1 - Size) / 2.f;
904 char aBuf[16];
905
906 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
907 for(int y = StartY; y < EndY; y++)
908 {
909 for(int x = StartX; x < EndX; x++)
910 {
911 int c = x + y * w;
912
913 unsigned char Index = pSwitch[c].m_Number;
914 if(Index && IsSwitchTileNumberUsed(Index: pSwitch[c].m_Type))
915 {
916 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
917 TextRender()->Text(x: x * Scale, y: (y + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
918 }
919
920 unsigned char Delay = pSwitch[c].m_Delay;
921 if(Delay && IsSwitchTileDelayUsed(Index: pSwitch[c].m_Type))
922 {
923 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Delay);
924 TextRender()->Text(x: x * Scale, y: (y + 0.5f + ToCenterOffset / 2) * Scale, Size: Size * Scale / 2.f, pText: aBuf);
925 }
926 }
927 }
928 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
929 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
930}
931
932void CRenderMap::RenderTuneOverlay(CTuneTile *pTune, int w, int h, float Scale, int OverlayRenderFlag, float Alpha)
933{
934 if(!(OverlayRenderFlag & OVERLAYRENDERFLAG_TEXT))
935 return;
936
937 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
938 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
939
940 int StartY = (int)(ScreenY0 / Scale) - 1;
941 int StartX = (int)(ScreenX0 / Scale) - 1;
942 int EndY = (int)(ScreenY1 / Scale) + 1;
943 int EndX = (int)(ScreenX1 / Scale) + 1;
944 if(EndX - StartX > Graphics()->ScreenWidth() / g_Config.m_GfxTextOverlay || EndY - StartY > Graphics()->ScreenHeight() / g_Config.m_GfxTextOverlay)
945 return; // its useless to render text at this distance
946
947 StartY = std::max(a: 0, b: StartY);
948 StartX = std::max(a: 0, b: StartX);
949 EndY = std::min(a: h, b: EndY);
950 EndX = std::min(a: w, b: EndX);
951
952 float Size = g_Config.m_ClTextEntitiesSize / 200.f;
953 char aBuf[16];
954
955 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha);
956 for(int y = StartY; y < EndY; y++)
957 {
958 for(int x = StartX; x < EndX; x++)
959 {
960 int c = x + y * w;
961
962 unsigned char Index = pTune[c].m_Number;
963 if(Index)
964 {
965 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Index);
966 // Auto-resize text to fit inside the tile
967 float ScaledWidth = TextRender()->TextWidth(Size: Size * Scale, pText: aBuf, StrLength: -1);
968 float Factor = std::clamp(val: Scale / ScaledWidth, lo: 0.0f, hi: 1.0f);
969 float LocalSize = Size * Factor;
970 float ToCenterOffset = (1 - LocalSize) / 2.f;
971 TextRender()->Text(x: (x + 0.5f) * Scale - (ScaledWidth * Factor) / 2.0f, y: (y + ToCenterOffset) * Scale, Size: LocalSize * Scale, pText: aBuf);
972 }
973 }
974 }
975 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
976 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
977}
978
979void CRenderMap::RenderTelemap(CTeleTile *pTele, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
980{
981 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
982 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
983
984 // calculate the final pixelsize for the tiles
985 float TilePixelSize = 1024 / 32.0f;
986 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
987 float FinalTilesetScale = FinalTileSize / TilePixelSize;
988
989 if(Graphics()->HasTextureArraysSupport())
990 Graphics()->QuadsTex3DBegin();
991 else
992 Graphics()->QuadsBegin();
993 Graphics()->SetColor(Color);
994
995 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
996
997 int StartY = (int)(ScreenY0 / Scale) - 1;
998 int StartX = (int)(ScreenX0 / Scale) - 1;
999 int EndY = (int)(ScreenY1 / Scale) + 1;
1000 int EndX = (int)(ScreenX1 / Scale) + 1;
1001 if(!ExtendTiles)
1002 {
1003 StartY = std::max(a: 0, b: StartY);
1004 StartX = std::max(a: 0, b: StartX);
1005 EndY = std::min(a: h, b: EndY);
1006 EndX = std::min(a: w, b: EndX);
1007 }
1008
1009 // adjust the texture shift according to mipmap level
1010 float TexSize = 1024.0f;
1011 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1012 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1013
1014 for(int y = StartY; y < EndY; y++)
1015 for(int x = StartX; x < EndX; x++)
1016 {
1017 int mx = x;
1018 int my = y;
1019
1020 if(ExtendTiles)
1021 {
1022 if(mx < 0)
1023 mx = 0;
1024 if(mx >= w)
1025 mx = w - 1;
1026 if(my < 0)
1027 my = 0;
1028 if(my >= h)
1029 my = h - 1;
1030 }
1031
1032 int c = mx + my * w;
1033
1034 unsigned char Index = pTele[c].m_Type;
1035 if(Index)
1036 {
1037 bool Render = false;
1038 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1039 Render = true;
1040
1041 if(Render)
1042 {
1043 int tx = Index % 16;
1044 int ty = Index / 16;
1045 int Px0 = tx * (1024 / 16);
1046 int Py0 = ty * (1024 / 16);
1047 int Px1 = Px0 + (1024 / 16) - 1;
1048 int Py1 = Py0 + (1024 / 16) - 1;
1049
1050 float x0 = Nudge + Px0 / TexSize + Frac;
1051 float y0 = Nudge + Py0 / TexSize + Frac;
1052 float x1 = Nudge + Px1 / TexSize - Frac;
1053 float y1 = Nudge + Py0 / TexSize + Frac;
1054 float x2 = Nudge + Px1 / TexSize - Frac;
1055 float y2 = Nudge + Py1 / TexSize - Frac;
1056 float x3 = Nudge + Px0 / TexSize + Frac;
1057 float y3 = Nudge + Py1 / TexSize - Frac;
1058
1059 if(Graphics()->HasTextureArraysSupport())
1060 {
1061 x0 = 0;
1062 y0 = 0;
1063 x1 = x0 + 1;
1064 y1 = y0;
1065 x2 = x0 + 1;
1066 y2 = y0 + 1;
1067 x3 = x0;
1068 y3 = y0 + 1;
1069 }
1070
1071 if(Graphics()->HasTextureArraysSupport())
1072 {
1073 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1074 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1075 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1076 }
1077 else
1078 {
1079 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1080 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1081 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1082 }
1083 }
1084 }
1085 }
1086
1087 if(Graphics()->HasTextureArraysSupport())
1088 Graphics()->QuadsTex3DEnd();
1089 else
1090 Graphics()->QuadsEnd();
1091 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
1092}
1093
1094void CRenderMap::RenderSwitchmap(CSwitchTile *pSwitchTile, int w, int h, float Scale, ColorRGBA Color, int RenderFlags)
1095{
1096 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
1097 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
1098
1099 // calculate the final pixelsize for the tiles
1100 float TilePixelSize = 1024 / 32.0f;
1101 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
1102 float FinalTilesetScale = FinalTileSize / TilePixelSize;
1103
1104 if(Graphics()->HasTextureArraysSupport())
1105 Graphics()->QuadsTex3DBegin();
1106 else
1107 Graphics()->QuadsBegin();
1108 Graphics()->SetColor(Color);
1109
1110 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
1111
1112 int StartY = (int)(ScreenY0 / Scale) - 1;
1113 int StartX = (int)(ScreenX0 / Scale) - 1;
1114 int EndY = (int)(ScreenY1 / Scale) + 1;
1115 int EndX = (int)(ScreenX1 / Scale) + 1;
1116 if(!ExtendTiles)
1117 {
1118 StartY = std::max(a: 0, b: StartY);
1119 StartX = std::max(a: 0, b: StartX);
1120 EndY = std::min(a: h, b: EndY);
1121 EndX = std::min(a: w, b: EndX);
1122 }
1123
1124 // adjust the texture shift according to mipmap level
1125 float TexSize = 1024.0f;
1126 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1127 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1128
1129 for(int y = StartY; y < EndY; y++)
1130 for(int x = StartX; x < EndX; x++)
1131 {
1132 int mx = x;
1133 int my = y;
1134
1135 if(ExtendTiles)
1136 {
1137 if(mx < 0)
1138 mx = 0;
1139 if(mx >= w)
1140 mx = w - 1;
1141 if(my < 0)
1142 my = 0;
1143 if(my >= h)
1144 my = h - 1;
1145 }
1146
1147 int c = mx + my * w;
1148
1149 unsigned char Index = pSwitchTile[c].m_Type;
1150 if(Index)
1151 {
1152 if(Index == TILE_SWITCHTIMEDOPEN)
1153 Index = 8;
1154
1155 unsigned char Flags = pSwitchTile[c].m_Flags;
1156
1157 bool Render = false;
1158 if(Flags & TILEFLAG_OPAQUE)
1159 {
1160 if(RenderFlags & LAYERRENDERFLAG_OPAQUE)
1161 Render = true;
1162 }
1163 else
1164 {
1165 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1166 Render = true;
1167 }
1168
1169 if(Render)
1170 {
1171 int tx = Index % 16;
1172 int ty = Index / 16;
1173 int Px0 = tx * (1024 / 16);
1174 int Py0 = ty * (1024 / 16);
1175 int Px1 = Px0 + (1024 / 16) - 1;
1176 int Py1 = Py0 + (1024 / 16) - 1;
1177
1178 float x0 = Nudge + Px0 / TexSize + Frac;
1179 float y0 = Nudge + Py0 / TexSize + Frac;
1180 float x1 = Nudge + Px1 / TexSize - Frac;
1181 float y1 = Nudge + Py0 / TexSize + Frac;
1182 float x2 = Nudge + Px1 / TexSize - Frac;
1183 float y2 = Nudge + Py1 / TexSize - Frac;
1184 float x3 = Nudge + Px0 / TexSize + Frac;
1185 float y3 = Nudge + Py1 / TexSize - Frac;
1186
1187 if(Graphics()->HasTextureArraysSupport())
1188 {
1189 x0 = 0;
1190 y0 = 0;
1191 x1 = x0 + 1;
1192 y1 = y0;
1193 x2 = x0 + 1;
1194 y2 = y0 + 1;
1195 x3 = x0;
1196 y3 = y0 + 1;
1197 }
1198
1199 if(Flags & TILEFLAG_XFLIP)
1200 {
1201 x0 = x2;
1202 x1 = x3;
1203 x2 = x3;
1204 x3 = x0;
1205 }
1206
1207 if(Flags & TILEFLAG_YFLIP)
1208 {
1209 y0 = y3;
1210 y2 = y1;
1211 y3 = y1;
1212 y1 = y0;
1213 }
1214
1215 if(Flags & TILEFLAG_ROTATE)
1216 {
1217 float Tmp = x0;
1218 x0 = x3;
1219 x3 = x2;
1220 x2 = x1;
1221 x1 = Tmp;
1222 Tmp = y0;
1223 y0 = y3;
1224 y3 = y2;
1225 y2 = y1;
1226 y1 = Tmp;
1227 }
1228
1229 if(Graphics()->HasTextureArraysSupport())
1230 {
1231 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1232 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1233 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1234 }
1235 else
1236 {
1237 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1238 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1239 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1240 }
1241 }
1242 }
1243 }
1244
1245 if(Graphics()->HasTextureArraysSupport())
1246 Graphics()->QuadsTex3DEnd();
1247 else
1248 Graphics()->QuadsEnd();
1249 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
1250}
1251
1252void CRenderMap::RenderTunemap(CTuneTile *pTune, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, CTuneColorMapper *pTuneColorMapper)
1253{
1254 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
1255 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
1256
1257 // calculate the final pixelsize for the tiles
1258 float TilePixelSize = 1024 / 32.0f;
1259 float FinalTileSize = Scale / (ScreenX1 - ScreenX0) * Graphics()->ScreenWidth();
1260 float FinalTilesetScale = FinalTileSize / TilePixelSize;
1261
1262 if(Graphics()->HasTextureArraysSupport())
1263 Graphics()->QuadsTex3DBegin();
1264 else
1265 Graphics()->QuadsBegin();
1266 Graphics()->SetColor(Color);
1267
1268 bool ExtendTiles = (RenderFlags & TILERENDERFLAG_EXTEND) != 0;
1269
1270 int StartY = (int)(ScreenY0 / Scale) - 1;
1271 int StartX = (int)(ScreenX0 / Scale) - 1;
1272 int EndY = (int)(ScreenY1 / Scale) + 1;
1273 int EndX = (int)(ScreenX1 / Scale) + 1;
1274 if(!ExtendTiles)
1275 {
1276 StartY = std::max(a: 0, b: StartY);
1277 StartX = std::max(a: 0, b: StartX);
1278 EndY = std::min(a: h, b: EndY);
1279 EndX = std::min(a: w, b: EndX);
1280 }
1281
1282 // adjust the texture shift according to mipmap level
1283 float TexSize = 1024.0f;
1284 float Frac = (1.25f / TexSize) * (1 / FinalTilesetScale);
1285 float Nudge = (0.5f / TexSize) * (1 / FinalTilesetScale);
1286
1287 for(int y = StartY; y < EndY; y++)
1288 for(int x = StartX; x < EndX; x++)
1289 {
1290 int mx = x;
1291 int my = y;
1292
1293 if(ExtendTiles)
1294 {
1295 if(mx < 0)
1296 mx = 0;
1297 if(mx >= w)
1298 mx = w - 1;
1299 if(my < 0)
1300 my = 0;
1301 if(my >= h)
1302 my = h - 1;
1303 }
1304
1305 int c = mx + my * w;
1306
1307 const unsigned char Index = pTune[c].m_Type;
1308
1309 if(Index)
1310 {
1311 bool Render = false;
1312 if(RenderFlags & LAYERRENDERFLAG_TRANSPARENT)
1313 Render = true;
1314
1315 if(Render)
1316 {
1317 const unsigned char Number = pTune[c].m_Number;
1318
1319 if(Number == 0 || pTuneColorMapper == nullptr)
1320 Graphics()->SetColor(Color);
1321 else
1322 {
1323 uint8_t ColorIndex = pTuneColorMapper->TuneNumberToColorIndex(TuneNumber: Number);
1324 Graphics()->SetColor(pTuneColorMapper->TuneColorIndexToColor(TuneColorIndex: ColorIndex).Multiply(Other: Color));
1325 }
1326
1327 int tx = Index % 16;
1328 int ty = Index / 16;
1329 int Px0 = tx * (1024 / 16);
1330 int Py0 = ty * (1024 / 16);
1331 int Px1 = Px0 + (1024 / 16) - 1;
1332 int Py1 = Py0 + (1024 / 16) - 1;
1333
1334 float x0 = Nudge + Px0 / TexSize + Frac;
1335 float y0 = Nudge + Py0 / TexSize + Frac;
1336 float x1 = Nudge + Px1 / TexSize - Frac;
1337 float y1 = Nudge + Py0 / TexSize + Frac;
1338 float x2 = Nudge + Px1 / TexSize - Frac;
1339 float y2 = Nudge + Py1 / TexSize - Frac;
1340 float x3 = Nudge + Px0 / TexSize + Frac;
1341 float y3 = Nudge + Py1 / TexSize - Frac;
1342
1343 if(Graphics()->HasTextureArraysSupport())
1344 {
1345 x0 = 0;
1346 y0 = 0;
1347 x1 = x0 + 1;
1348 y1 = y0;
1349 x2 = x0 + 1;
1350 y2 = y0 + 1;
1351 x3 = x0;
1352 y3 = y0 + 1;
1353 }
1354
1355 if(Graphics()->HasTextureArraysSupport())
1356 {
1357 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3, Index);
1358 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1359 Graphics()->QuadsTex3DDrawTL(pArray: &QuadItem, Num: 1);
1360 }
1361 else
1362 {
1363 Graphics()->QuadsSetSubsetFree(x0, y0, x1, y1, x2, y2, x3, y3);
1364 IGraphics::CQuadItem QuadItem(x * Scale, y * Scale, Scale, Scale);
1365 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
1366 }
1367 }
1368 }
1369 }
1370
1371 if(Graphics()->HasTextureArraysSupport())
1372 Graphics()->QuadsTex3DEnd();
1373 else
1374 Graphics()->QuadsEnd();
1375 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
1376}
1377
1378void CRenderMap::RenderDebugClip(float ClipX, float ClipY, float ClipW, float ClipH, ColorRGBA Color, float Zoom, const char *pLabel)
1379{
1380 Graphics()->TextureClear();
1381 Graphics()->LinesBegin();
1382 Graphics()->SetColor(Color);
1383 IGraphics::CLineItem aLineItems[] = {
1384 IGraphics::CLineItem(ClipX, ClipY, ClipX, ClipY + ClipH),
1385 IGraphics::CLineItem(ClipX + ClipW, ClipY, ClipX + ClipW, ClipY + ClipH),
1386 IGraphics::CLineItem(ClipX, ClipY, ClipX + ClipW, ClipY),
1387 IGraphics::CLineItem(ClipX, ClipY + ClipH, ClipX + ClipW, ClipY + ClipH),
1388 };
1389 Graphics()->LinesDraw(pArray: aLineItems, Num: std::size(aLineItems));
1390 Graphics()->LinesEnd();
1391
1392 TextRender()->TextColor(Color);
1393
1394 // clamp zoom and set line width, because otherwise the text can be partially clipped out
1395 TextRender()->Text(x: ClipX, y: ClipY, Size: std::min(a: 12.0f * Zoom, b: 20.0f), pText: pLabel, LineWidth: ClipW);
1396 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
1397}
1398
1399CTuneColorMapper::CTuneColorMapper()
1400{
1401 Reset();
1402}
1403
1404uint8_t CTuneColorMapper::TuneNumberToColorIndex(uint8_t TuneNumber)
1405{
1406 if(TuneNumber == 0)
1407 return 0;
1408
1409 uint8_t &TuneColorIndex = m_aTuneNumberToColorIndex[TuneNumber - 1];
1410 if(TuneColorIndex == 0)
1411 {
1412 TuneColorIndex = m_NextTuneNumberIndex + 1;
1413 ++m_NextTuneNumberIndex;
1414 }
1415 return TuneColorIndex;
1416}
1417
1418ColorRGBA CTuneColorMapper::TuneColorIndexToColor(uint8_t TuneColorIndex) const
1419{
1420 if(TuneColorIndex == 0)
1421 return ColorRGBA(1.0f, 1.0f, 1.0f);
1422
1423 float Hue = std::fmod(x: (TuneColorIndex - 1) * normalized_golden_angle, y: 1.0f);
1424 return color_cast<ColorRGBA>(hsl: ColorHSLA(Hue, 0.75f, 0.5f, 1.0f));
1425}
1426
1427void CTuneColorMapper::Reset()
1428{
1429 m_aTuneNumberToColorIndex.fill(u: 0);
1430 m_NextTuneNumberIndex = 0;
1431}
1432