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