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 <cmath>
4
5#include <base/math.h>
6
7#include "animstate.h"
8#include "render.h"
9
10#include <engine/graphics.h>
11#include <engine/shared/config.h>
12
13#include <game/generated/client_data.h>
14#include <game/generated/client_data7.h>
15#include <game/generated/protocol.h>
16
17#include <game/mapitems.h>
18
19static float gs_SpriteWScale;
20static float gs_SpriteHScale;
21
22void CRenderTools::Init(IGraphics *pGraphics, ITextRender *pTextRender)
23{
24 m_pGraphics = pGraphics;
25 m_pTextRender = pTextRender;
26 m_TeeQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false);
27 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f);
28
29 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
30 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f);
31 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
32 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f);
33
34 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
35 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f * 0.4f);
36 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
37 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f * 0.4f);
38 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
39 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f * 0.4f);
40 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
41 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f * 0.4f);
42 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
43 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, Size: 64.f * 0.4f);
44
45 // Feet
46 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
47 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, X: -32.f, Y: -16.f, Width: 64.f, Height: 32.f);
48 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
49 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, X: -32.f, Y: -16.f, Width: 64.f, Height: 32.f);
50
51 // Mirrored Feet
52 Graphics()->QuadsSetSubsetFree(x0: 1, y0: 0, x1: 0, y1: 0, x2: 0, y2: 1, x3: 1, y3: 1);
53 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, X: -32.f, Y: -16.f, Width: 64.f, Height: 32.f);
54 Graphics()->QuadsSetSubsetFree(x0: 1, y0: 0, x1: 0, y1: 0, x2: 0, y2: 1, x3: 1, y3: 1);
55 QuadContainerAddSprite(QuadContainerIndex: m_TeeQuadContainerIndex, X: -32.f, Y: -16.f, Width: 64.f, Height: 32.f);
56
57 Graphics()->QuadContainerUpload(ContainerIndex: m_TeeQuadContainerIndex);
58}
59
60void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) const
61{
62 int x = pSpr->m_X + sx;
63 int y = pSpr->m_Y + sy;
64 int w = pSpr->m_W;
65 int h = pSpr->m_H;
66 int cx = pSpr->m_pSet->m_Gridx;
67 int cy = pSpr->m_pSet->m_Gridy;
68
69 GetSpriteScaleImpl(Width: w, Height: h, ScaleX&: gs_SpriteWScale, ScaleY&: gs_SpriteHScale);
70
71 float x1 = x / (float)cx + 0.5f / (float)(cx * 32);
72 float x2 = (x + w) / (float)cx - 0.5f / (float)(cx * 32);
73 float y1 = y / (float)cy + 0.5f / (float)(cy * 32);
74 float y2 = (y + h) / (float)cy - 0.5f / (float)(cy * 32);
75
76 if(Flags & SPRITE_FLAG_FLIP_Y)
77 std::swap(a&: y1, b&: y2);
78
79 if(Flags & SPRITE_FLAG_FLIP_X)
80 std::swap(a&: x1, b&: x2);
81
82 Graphics()->QuadsSetSubset(TopLeftU: x1, TopLeftV: y1, BottomRightU: x2, BottomRightV: y2);
83}
84
85void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) const
86{
87 if(Id < 0 || Id >= g_pData->m_NumSprites)
88 return;
89 SelectSprite(pSpr: &g_pData->m_aSprites[Id], Flags, sx, sy);
90}
91
92void CRenderTools::GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const
93{
94 int w = pSprite->m_W;
95 int h = pSprite->m_H;
96 GetSpriteScaleImpl(Width: w, Height: h, ScaleX, ScaleY);
97}
98
99void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const
100{
101 GetSpriteScale(pSprite: &g_pData->m_aSprites[Id], ScaleX, ScaleY);
102}
103
104void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) const
105{
106 const float f = length(a: vec2(Width, Height));
107 ScaleX = Width / f;
108 ScaleY = Height / f;
109}
110
111void CRenderTools::DrawSprite(float x, float y, float Size) const
112{
113 IGraphics::CQuadItem QuadItem(x, y, Size * gs_SpriteWScale, Size * gs_SpriteHScale);
114 Graphics()->QuadsDraw(pArray: &QuadItem, Num: 1);
115}
116
117void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) const
118{
119 IGraphics::CQuadItem QuadItem(x, y, ScaledWidth, ScaledHeight);
120 Graphics()->QuadsDraw(pArray: &QuadItem, Num: 1);
121}
122
123void CRenderTools::RenderCursor(vec2 Center, float Size) const
124{
125 Graphics()->WrapClamp();
126 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_CURSOR].m_Id);
127 Graphics()->QuadsBegin();
128 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
129 IGraphics::CQuadItem QuadItem(Center.x, Center.y, Size, Size);
130 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
131 Graphics()->QuadsEnd();
132 Graphics()->WrapNormal();
133}
134
135void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor) const
136{
137 Graphics()->TextureSet(Texture: g_pData->m_aImages[ImageId].m_Id);
138 Graphics()->QuadsBegin();
139 SelectSprite(Id: SpriteId);
140 if(pColor)
141 Graphics()->SetColor(r: pColor->r * pColor->a, g: pColor->g * pColor->a, b: pColor->b * pColor->a, a: pColor->a);
142 IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h);
143 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
144 Graphics()->QuadsEnd();
145}
146
147int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) const
148{
149 IGraphics::CQuadItem QuadItem(x, y, Size, Size);
150 return Graphics()->QuadContainerAddQuads(ContainerIndex: QuadContainerIndex, pArray: &QuadItem, Num: 1);
151}
152
153int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size) const
154{
155 IGraphics::CQuadItem QuadItem(-(Size) / 2.f, -(Size) / 2.f, (Size), (Size));
156 return Graphics()->QuadContainerAddQuads(ContainerIndex: QuadContainerIndex, pArray: &QuadItem, Num: 1);
157}
158
159int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) const
160{
161 IGraphics::CQuadItem QuadItem(-(Width) / 2.f, -(Height) / 2.f, (Width), (Height));
162 return Graphics()->QuadContainerAddQuads(ContainerIndex: QuadContainerIndex, pArray: &QuadItem, Num: 1);
163}
164
165int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) const
166{
167 IGraphics::CQuadItem QuadItem(X, Y, Width, Height);
168 return Graphics()->QuadContainerAddQuads(ContainerIndex: QuadContainerIndex, pArray: &QuadItem, Num: 1);
169}
170
171void CRenderTools::GetRenderTeeAnimScaleAndBaseSize(const CTeeRenderInfo *pInfo, float &AnimScale, float &BaseSize)
172{
173 AnimScale = pInfo->m_Size * 1.0f / 64.0f;
174 BaseSize = pInfo->m_Size;
175}
176
177void CRenderTools::GetRenderTeeBodyScale(float BaseSize, float &BodyScale)
178{
179 BodyScale = g_Config.m_ClFatSkins ? BaseSize * 1.3f : BaseSize;
180 BodyScale /= 64.0f;
181}
182
183void CRenderTools::GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight)
184{
185 FeetScaleWidth = BaseSize / 64.0f;
186 FeetScaleHeight = (BaseSize / 2) / 32.0f;
187}
188
189void CRenderTools::GetRenderTeeBodySize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height)
190{
191 float AnimScale, BaseSize;
192 GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize);
193
194 float BodyScale;
195 GetRenderTeeBodyScale(BaseSize, BodyScale);
196
197 Width = pInfo->m_SkinMetrics.m_Body.WidthNormalized() * 64.0f * BodyScale;
198 Height = pInfo->m_SkinMetrics.m_Body.HeightNormalized() * 64.0f * BodyScale;
199 BodyOffset.x = pInfo->m_SkinMetrics.m_Body.OffsetXNormalized() * 64.0f * BodyScale;
200 BodyOffset.y = pInfo->m_SkinMetrics.m_Body.OffsetYNormalized() * 64.0f * BodyScale;
201}
202
203void CRenderTools::GetRenderTeeFeetSize(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &FeetOffset, float &Width, float &Height)
204{
205 float AnimScale, BaseSize;
206 GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize);
207
208 float FeetScaleWidth, FeetScaleHeight;
209 GetRenderTeeFeetScale(BaseSize, FeetScaleWidth, FeetScaleHeight);
210
211 Width = pInfo->m_SkinMetrics.m_Feet.WidthNormalized() * 64.0f * FeetScaleWidth;
212 Height = pInfo->m_SkinMetrics.m_Feet.HeightNormalized() * 32.0f * FeetScaleHeight;
213 FeetOffset.x = pInfo->m_SkinMetrics.m_Feet.OffsetXNormalized() * 64.0f * FeetScaleWidth;
214 FeetOffset.y = pInfo->m_SkinMetrics.m_Feet.OffsetYNormalized() * 32.0f * FeetScaleHeight;
215}
216
217void CRenderTools::GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, vec2 &TeeOffsetToMid)
218{
219 float AnimScale, BaseSize;
220 GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize);
221 vec2 BodyPos = vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale;
222
223 float AssumedScale = BaseSize / 64.0f;
224
225 // just use the lowest feet
226 vec2 FeetPos;
227 const CAnimKeyframe *pFoot = pAnim->GetFrontFoot();
228 FeetPos = vec2(pFoot->m_X * AnimScale, pFoot->m_Y * AnimScale);
229 pFoot = pAnim->GetBackFoot();
230 FeetPos = vec2(FeetPos.x, maximum(a: FeetPos.y, b: pFoot->m_Y * AnimScale));
231
232 vec2 BodyOffset;
233 float BodyWidth, BodyHeight;
234 GetRenderTeeBodySize(pAnim, pInfo, BodyOffset, Width&: BodyWidth, Height&: BodyHeight);
235
236 // -32 is the assumed min relative position for the quad
237 float MinY = -32.0f * AssumedScale;
238 // the body pos shifts the body away from center
239 MinY += BodyPos.y;
240 // the actual body is smaller though, because it doesn't use the full skin image in most cases
241 MinY += BodyOffset.y;
242
243 vec2 FeetOffset;
244 float FeetWidth, FeetHeight;
245 GetRenderTeeFeetSize(pAnim, pInfo, FeetOffset, Width&: FeetWidth, Height&: FeetHeight);
246
247 // MaxY builds up from the MinY
248 float MaxY = MinY + BodyHeight;
249 // if the body is smaller than the total feet offset, use feet
250 // since feet are smaller in height, respect the assumed relative position
251 MaxY = maximum(a: MaxY, b: (-16.0f * AssumedScale + FeetPos.y) + FeetOffset.y + FeetHeight);
252
253 // now we got the full rendered size
254 float FullHeight = (MaxY - MinY);
255
256 // next step is to calculate the offset that was created compared to the assumed relative position
257 float MidOfRendered = MinY + FullHeight / 2.0f;
258
259 // TODO: x coordinate is ignored for now, bcs it's not really used yet anyway
260 TeeOffsetToMid.x = 0;
261 // negative value, because the calculation that uses this offset should work with addition.
262 TeeOffsetToMid.y = -MidOfRendered;
263}
264
265void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const
266{
267 vec2 Direction = Dir;
268 vec2 Position = Pos;
269
270 const CSkin::SSkinTextures *pSkinTextures = pInfo->m_CustomColoredSkin ? &pInfo->m_ColorableRenderSkin : &pInfo->m_OriginalRenderSkin;
271
272 // first pass we draw the outline
273 // second pass we draw the filling
274 for(int p = 0; p < 2; p++)
275 {
276 int OutLine = p == 0 ? 1 : 0;
277
278 for(int f = 0; f < 2; f++)
279 {
280 float AnimScale, BaseSize;
281 GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize);
282 if(f == 1)
283 {
284 Graphics()->QuadsSetRotation(Angle: pAnim->GetBody()->m_Angle * pi * 2);
285
286 // draw body
287 Graphics()->SetColor(r: pInfo->m_ColorBody.r, g: pInfo->m_ColorBody.g, b: pInfo->m_ColorBody.b, a: Alpha);
288 vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale;
289 float BodyScale;
290 GetRenderTeeBodyScale(BaseSize, BodyScale);
291 Graphics()->TextureSet(Texture: OutLine == 1 ? pSkinTextures->m_BodyOutline : pSkinTextures->m_Body);
292 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_TeeQuadContainerIndex, QuadOffset: OutLine, X: BodyPos.x, Y: BodyPos.y, ScaleX: BodyScale, ScaleY: BodyScale);
293
294 // draw eyes
295 if(p == 1)
296 {
297 int QuadOffset = 2;
298 int EyeQuadOffset = 0;
299 int TeeEye = 0;
300
301 switch(Emote)
302 {
303 case EMOTE_PAIN:
304 EyeQuadOffset = 0;
305 TeeEye = SPRITE_TEE_EYE_PAIN - SPRITE_TEE_EYE_NORMAL;
306 break;
307 case EMOTE_HAPPY:
308 EyeQuadOffset = 1;
309 TeeEye = SPRITE_TEE_EYE_HAPPY - SPRITE_TEE_EYE_NORMAL;
310 break;
311 case EMOTE_SURPRISE:
312 EyeQuadOffset = 2;
313 TeeEye = SPRITE_TEE_EYE_SURPRISE - SPRITE_TEE_EYE_NORMAL;
314 break;
315 case EMOTE_ANGRY:
316 EyeQuadOffset = 3;
317 TeeEye = SPRITE_TEE_EYE_ANGRY - SPRITE_TEE_EYE_NORMAL;
318 break;
319 default:
320 EyeQuadOffset = 4;
321 break;
322 }
323
324 float EyeScale = BaseSize * 0.40f;
325 float h = Emote == EMOTE_BLINK ? BaseSize * 0.15f : EyeScale;
326 float EyeSeparation = (0.075f - 0.010f * absolute(a: Direction.x)) * BaseSize;
327 vec2 Offset = vec2(Direction.x * 0.125f, -0.05f + Direction.y * 0.10f) * BaseSize;
328
329 Graphics()->TextureSet(Texture: pSkinTextures->m_aEyes[TeeEye]);
330 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_TeeQuadContainerIndex, QuadOffset: QuadOffset + EyeQuadOffset, X: BodyPos.x - EyeSeparation + Offset.x, Y: BodyPos.y + Offset.y, ScaleX: EyeScale / (64.f * 0.4f), ScaleY: h / (64.f * 0.4f));
331 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_TeeQuadContainerIndex, QuadOffset: QuadOffset + EyeQuadOffset, X: BodyPos.x + EyeSeparation + Offset.x, Y: BodyPos.y + Offset.y, ScaleX: -EyeScale / (64.f * 0.4f), ScaleY: h / (64.f * 0.4f));
332 }
333 }
334
335 // draw feet
336 const CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot();
337
338 float w = BaseSize;
339 float h = BaseSize / 2;
340
341 int QuadOffset = 7;
342 if(Dir.x < 0 && pInfo->m_FeetFlipped)
343 {
344 QuadOffset += 2;
345 }
346
347 Graphics()->QuadsSetRotation(Angle: pFoot->m_Angle * pi * 2);
348
349 bool Indicate = !pInfo->m_GotAirJump && g_Config.m_ClAirjumpindicator;
350 float ColorScale = 1.0f;
351
352 if(!OutLine)
353 {
354 ++QuadOffset;
355 if(Indicate)
356 ColorScale = 0.5f;
357 }
358
359 Graphics()->SetColor(r: pInfo->m_ColorFeet.r * ColorScale, g: pInfo->m_ColorFeet.g * ColorScale, b: pInfo->m_ColorFeet.b * ColorScale, a: Alpha);
360
361 Graphics()->TextureSet(Texture: OutLine == 1 ? pSkinTextures->m_FeetOutline : pSkinTextures->m_Feet);
362 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_TeeQuadContainerIndex, QuadOffset, X: Position.x + pFoot->m_X * AnimScale, Y: Position.y + pFoot->m_Y * AnimScale, ScaleX: w / 64.f, ScaleY: h / 32.f);
363 }
364 }
365
366 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f);
367 Graphics()->QuadsSetRotation(Angle: 0);
368}
369
370void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight)
371{
372 const float Amount = 1150 * 1000;
373 const float WMax = 1500;
374 const float HMax = 1050;
375
376 const float f = std::sqrt(x: Amount) / std::sqrt(x: Aspect);
377 *pWidth = f * Aspect;
378 *pHeight = f;
379
380 // limit the view
381 if(*pWidth > WMax)
382 {
383 *pWidth = WMax;
384 *pHeight = *pWidth / Aspect;
385 }
386
387 if(*pHeight > HMax)
388 {
389 *pHeight = HMax;
390 *pWidth = *pHeight * Aspect;
391 }
392
393 *pWidth *= Zoom;
394 *pHeight *= Zoom;
395}
396
397void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
398 float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints)
399{
400 float Width, Height;
401 CalcScreenParams(Aspect, Zoom, pWidth: &Width, pHeight: &Height);
402
403 float Scale = (ParallaxZoom * (Zoom - 1.0f) + 100.0f) / 100.0f / Zoom;
404 Width *= Scale;
405 Height *= Scale;
406
407 CenterX *= ParallaxX / 100.0f;
408 CenterY *= ParallaxY / 100.0f;
409 pPoints[0] = OffsetX + CenterX - Width / 2;
410 pPoints[1] = OffsetY + CenterY - Height / 2;
411 pPoints[2] = pPoints[0] + Width;
412 pPoints[3] = pPoints[1] + Height;
413}
414
415void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom)
416{
417 float ParallaxZoom = clamp(val: (double)(maximum(a: pGroup->m_ParallaxX, b: pGroup->m_ParallaxY)), lo: 0., hi: 100.);
418 float aPoints[4];
419 MapScreenToWorld(CenterX, CenterY, ParallaxX: pGroup->m_ParallaxX, ParallaxY: pGroup->m_ParallaxY, ParallaxZoom,
420 OffsetX: pGroup->m_OffsetX, OffsetY: pGroup->m_OffsetY, Aspect: Graphics()->ScreenAspect(), Zoom, pPoints: aPoints);
421 Graphics()->MapScreen(TopLeftX: aPoints[0], TopLeftY: aPoints[1], BottomRightX: aPoints[2], BottomRightY: aPoints[3]);
422}
423
424void CRenderTools::MapScreenToInterface(float CenterX, float CenterY)
425{
426 float aPoints[4];
427 MapScreenToWorld(CenterX, CenterY, ParallaxX: 100.0f, ParallaxY: 100.0f, ParallaxZoom: 100.0f,
428 OffsetX: 0, OffsetY: 0, Aspect: Graphics()->ScreenAspect(), Zoom: 1.0f, pPoints: aPoints);
429 Graphics()->MapScreen(TopLeftX: aPoints[0], TopLeftY: aPoints[1], BottomRightX: aPoints[2], BottomRightY: aPoints[3]);
430}
431