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