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