/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include #include "animstate.h" #include "render.h" #include #include #include #include #include #include #include static float gs_SpriteWScale; static float gs_SpriteHScale; void CRenderTools::Init(IGraphics *pGraphics, ITextRender *pTextRender) { m_pGraphics = pGraphics; m_pTextRender = pTextRender; m_TeeQuadContainerIndex = Graphics()->CreateQuadContainer(false); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f * 0.4f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f * 0.4f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f * 0.4f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f * 0.4f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, 64.f * 0.4f); // Feet Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, -32.f, -16.f, 64.f, 32.f); Graphics()->QuadsSetSubset(0, 0, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, -32.f, -16.f, 64.f, 32.f); // Mirrored Feet Graphics()->QuadsSetSubsetFree(1, 0, 0, 0, 0, 1, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, -32.f, -16.f, 64.f, 32.f); Graphics()->QuadsSetSubsetFree(1, 0, 0, 0, 0, 1, 1, 1); QuadContainerAddSprite(m_TeeQuadContainerIndex, -32.f, -16.f, 64.f, 32.f); Graphics()->QuadContainerUpload(m_TeeQuadContainerIndex); } void CRenderTools::SelectSprite(CDataSprite *pSpr, int Flags, int sx, int sy) { int x = pSpr->m_X + sx; int y = pSpr->m_Y + sy; int w = pSpr->m_W; int h = pSpr->m_H; int cx = pSpr->m_pSet->m_Gridx; int cy = pSpr->m_pSet->m_Gridy; GetSpriteScaleImpl(w, h, gs_SpriteWScale, gs_SpriteHScale); float x1 = x / (float)cx + 0.5f / (float)(cx * 32); float x2 = (x + w) / (float)cx - 0.5f / (float)(cx * 32); float y1 = y / (float)cy + 0.5f / (float)(cy * 32); float y2 = (y + h) / (float)cy - 0.5f / (float)(cy * 32); if(Flags & SPRITE_FLAG_FLIP_Y) std::swap(y1, y2); if(Flags & SPRITE_FLAG_FLIP_X) std::swap(x1, x2); Graphics()->QuadsSetSubset(x1, y1, x2, y2); } void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) { if(Id < 0 || Id >= g_pData->m_NumSprites) return; SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy); } void CRenderTools::GetSpriteScale(client_data7::CDataSprite *pSprite, float &ScaleX, float &ScaleY) { int w = pSprite->m_W; int h = pSprite->m_H; GetSpriteScaleImpl(w, h, ScaleX, ScaleY); } void CRenderTools::GetSpriteScale(struct CDataSprite *pSprite, float &ScaleX, float &ScaleY) { int w = pSprite->m_W; int h = pSprite->m_H; GetSpriteScaleImpl(w, h, ScaleX, ScaleY); } void CRenderTools::GetSpriteScale(int Id, float &ScaleX, float &ScaleY) { GetSpriteScale(&g_pData->m_aSprites[Id], ScaleX, ScaleY); } void CRenderTools::GetSpriteScaleImpl(int Width, int Height, float &ScaleX, float &ScaleY) { const float f = length(vec2(Width, Height)); ScaleX = Width / f; ScaleY = Height / f; } void CRenderTools::DrawSprite(float x, float y, float Size) { IGraphics::CQuadItem QuadItem(x, y, Size * gs_SpriteWScale, Size * gs_SpriteHScale); Graphics()->QuadsDraw(&QuadItem, 1); } void CRenderTools::DrawSprite(float x, float y, float ScaledWidth, float ScaledHeight) { IGraphics::CQuadItem QuadItem(x, y, ScaledWidth, ScaledHeight); Graphics()->QuadsDraw(&QuadItem, 1); } void CRenderTools::RenderCursor(vec2 Center, float Size) { Graphics()->WrapClamp(); Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); Graphics()->QuadsBegin(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); IGraphics::CQuadItem QuadItem(Center.x, Center.y, Size, Size); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); Graphics()->WrapNormal(); } void CRenderTools::RenderIcon(int ImageId, int SpriteId, const CUIRect *pRect, const ColorRGBA *pColor) { Graphics()->TextureSet(g_pData->m_aImages[ImageId].m_Id); Graphics()->QuadsBegin(); SelectSprite(SpriteId); if(pColor) Graphics()->SetColor(pColor->r * pColor->a, pColor->g * pColor->a, pColor->b * pColor->a, pColor->a); IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float x, float y, float Size) { IGraphics::CQuadItem QuadItem(x, y, Size, Size); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size) { IGraphics::CQuadItem QuadItem(-(Size) / 2.f, -(Size) / 2.f, (Size), (Size)); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Width, float Height) { IGraphics::CQuadItem QuadItem(-(Width) / 2.f, -(Height) / 2.f, (Width), (Height)); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } int CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height) { IGraphics::CQuadItem QuadItem(X, Y, Width, Height); return Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1); } void CRenderTools::GetRenderTeeAnimScaleAndBaseSize(CAnimState *pAnim, CTeeRenderInfo *pInfo, float &AnimScale, float &BaseSize) { AnimScale = pInfo->m_Size * 1.0f / 64.0f; BaseSize = pInfo->m_Size; } void CRenderTools::GetRenderTeeBodyScale(float BaseSize, float &BodyScale) { BodyScale = g_Config.m_ClFatSkins ? BaseSize * 1.3f : BaseSize; BodyScale /= 64.0f; } void CRenderTools::GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight) { FeetScaleWidth = BaseSize / 64.0f; FeetScaleHeight = (BaseSize / 2) / 32.0f; } void CRenderTools::GetRenderTeeBodySize(CAnimState *pAnim, CTeeRenderInfo *pInfo, vec2 &BodyOffset, float &Width, float &Height) { float AnimScale, BaseSize; GetRenderTeeAnimScaleAndBaseSize(pAnim, pInfo, AnimScale, BaseSize); float BodyScale; GetRenderTeeBodyScale(BaseSize, BodyScale); Width = pInfo->m_SkinMetrics.m_Body.WidthNormalized() * 64.0f * BodyScale; Height = pInfo->m_SkinMetrics.m_Body.HeightNormalized() * 64.0f * BodyScale; BodyOffset.x = pInfo->m_SkinMetrics.m_Body.OffsetXNormalized() * 64.0f * BodyScale; BodyOffset.y = pInfo->m_SkinMetrics.m_Body.OffsetYNormalized() * 64.0f * BodyScale; } void CRenderTools::GetRenderTeeFeetSize(CAnimState *pAnim, CTeeRenderInfo *pInfo, vec2 &FeetOffset, float &Width, float &Height) { float AnimScale, BaseSize; GetRenderTeeAnimScaleAndBaseSize(pAnim, pInfo, AnimScale, BaseSize); float FeetScaleWidth, FeetScaleHeight; GetRenderTeeFeetScale(BaseSize, FeetScaleWidth, FeetScaleHeight); Width = pInfo->m_SkinMetrics.m_Feet.WidthNormalized() * 64.0f * FeetScaleWidth; Height = pInfo->m_SkinMetrics.m_Feet.HeightNormalized() * 32.0f * FeetScaleHeight; FeetOffset.x = pInfo->m_SkinMetrics.m_Feet.OffsetXNormalized() * 64.0f * FeetScaleWidth; FeetOffset.y = pInfo->m_SkinMetrics.m_Feet.OffsetYNormalized() * 32.0f * FeetScaleHeight; } void CRenderTools::GetRenderTeeOffsetToRenderedTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, vec2 &TeeOffsetToMid) { float AnimScale, BaseSize; GetRenderTeeAnimScaleAndBaseSize(pAnim, pInfo, AnimScale, BaseSize); vec2 BodyPos = vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale; float AssumedScale = BaseSize / 64.0f; // just use the lowest feet vec2 FeetPos; CAnimKeyframe *pFoot = pAnim->GetFrontFoot(); FeetPos = vec2(pFoot->m_X * AnimScale, pFoot->m_Y * AnimScale); pFoot = pAnim->GetBackFoot(); FeetPos = vec2(FeetPos.x, maximum(FeetPos.y, pFoot->m_Y * AnimScale)); vec2 BodyOffset; float BodyWidth, BodyHeight; GetRenderTeeBodySize(pAnim, pInfo, BodyOffset, BodyWidth, BodyHeight); // -32 is the assumed min relative position for the quad float MinY = -32.0f * AssumedScale; // the body pos shifts the body away from center MinY += BodyPos.y; // the actual body is smaller though, because it doesn't use the full skin image in most cases MinY += BodyOffset.y; vec2 FeetOffset; float FeetWidth, FeetHeight; GetRenderTeeFeetSize(pAnim, pInfo, FeetOffset, FeetWidth, FeetHeight); // MaxY builds up from the MinY float MaxY = MinY + BodyHeight; // if the body is smaller than the total feet offset, use feet // since feet are smaller in height, respect the assumed relative position MaxY = maximum(MaxY, (-16.0f * AssumedScale + FeetPos.y) + FeetOffset.y + FeetHeight); // now we got the full rendered size float FullHeight = (MaxY - MinY); // next step is to calculate the offset that was created compared to the assumed relative position float MidOfRendered = MinY + FullHeight / 2.0f; // TODO: x coordinate is ignored for now, bcs it's not really used yet anyway TeeOffsetToMid.x = 0; // negative value, because the calculation that uses this offset should work with addition. TeeOffsetToMid.y = -MidOfRendered; } void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) { vec2 Direction = Dir; vec2 Position = Pos; const CSkin::SSkinTextures *pSkinTextures = pInfo->m_CustomColoredSkin ? &pInfo->m_ColorableRenderSkin : &pInfo->m_OriginalRenderSkin; // first pass we draw the outline // second pass we draw the filling for(int p = 0; p < 2; p++) { int OutLine = p == 0 ? 1 : 0; for(int f = 0; f < 2; f++) { float AnimScale, BaseSize; GetRenderTeeAnimScaleAndBaseSize(pAnim, pInfo, AnimScale, BaseSize); if(f == 1) { Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); // draw body Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, Alpha); vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale; float BodyScale; GetRenderTeeBodyScale(BaseSize, BodyScale); Graphics()->TextureSet(OutLine == 1 ? pSkinTextures->m_BodyOutline : pSkinTextures->m_Body); Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, OutLine, BodyPos.x, BodyPos.y, BodyScale, BodyScale); // draw eyes if(p == 1) { int QuadOffset = 2; int EyeQuadOffset = 0; int TeeEye = 0; switch(Emote) { case EMOTE_PAIN: EyeQuadOffset = 0; TeeEye = SPRITE_TEE_EYE_PAIN - SPRITE_TEE_EYE_NORMAL; break; case EMOTE_HAPPY: EyeQuadOffset = 1; TeeEye = SPRITE_TEE_EYE_HAPPY - SPRITE_TEE_EYE_NORMAL; break; case EMOTE_SURPRISE: EyeQuadOffset = 2; TeeEye = SPRITE_TEE_EYE_SURPRISE - SPRITE_TEE_EYE_NORMAL; break; case EMOTE_ANGRY: EyeQuadOffset = 3; TeeEye = SPRITE_TEE_EYE_ANGRY - SPRITE_TEE_EYE_NORMAL; break; default: EyeQuadOffset = 4; break; } float EyeScale = BaseSize * 0.40f; float h = Emote == EMOTE_BLINK ? BaseSize * 0.15f : EyeScale; float EyeSeparation = (0.075f - 0.010f * absolute(Direction.x)) * BaseSize; vec2 Offset = vec2(Direction.x * 0.125f, -0.05f + Direction.y * 0.10f) * BaseSize; Graphics()->TextureSet(pSkinTextures->m_aEyes[TeeEye]); Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, QuadOffset + EyeQuadOffset, BodyPos.x - EyeSeparation + Offset.x, BodyPos.y + Offset.y, EyeScale / (64.f * 0.4f), h / (64.f * 0.4f)); Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, QuadOffset + EyeQuadOffset, BodyPos.x + EyeSeparation + Offset.x, BodyPos.y + Offset.y, -EyeScale / (64.f * 0.4f), h / (64.f * 0.4f)); } } // draw feet CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); float w = BaseSize; float h = BaseSize / 2; int QuadOffset = 7; if(Dir.x < 0 && pInfo->m_FeetFlipped) { QuadOffset += 2; } Graphics()->QuadsSetRotation(pFoot->m_Angle * pi * 2); bool Indicate = !pInfo->m_GotAirJump && g_Config.m_ClAirjumpindicator; float ColorScale = 1.0f; if(!OutLine) { ++QuadOffset; if(Indicate) ColorScale = 0.5f; } Graphics()->SetColor(pInfo->m_ColorFeet.r * ColorScale, pInfo->m_ColorFeet.g * ColorScale, pInfo->m_ColorFeet.b * ColorScale, Alpha); Graphics()->TextureSet(OutLine == 1 ? pSkinTextures->m_FeetOutline : pSkinTextures->m_Feet); Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, QuadOffset, Position.x + pFoot->m_X * AnimScale, Position.y + pFoot->m_Y * AnimScale, w / 64.f, h / 32.f); } } Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); Graphics()->QuadsSetRotation(0); } void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight) { const float Amount = 1150 * 1000; const float WMax = 1500; const float HMax = 1050; const float f = std::sqrt(Amount) / std::sqrt(Aspect); *pWidth = f * Aspect; *pHeight = f; // limit the view if(*pWidth > WMax) { *pWidth = WMax; *pHeight = *pWidth / Aspect; } if(*pHeight > HMax) { *pHeight = HMax; *pWidth = *pHeight * Aspect; } *pWidth *= Zoom; *pHeight *= Zoom; } void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY, float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints) { float Width, Height; CalcScreenParams(Aspect, Zoom, &Width, &Height); float Scale = (ParallaxZoom * (Zoom - 1.0f) + 100.0f) / 100.0f / Zoom; Width *= Scale; Height *= Scale; CenterX *= ParallaxX / 100.0f; CenterY *= ParallaxY / 100.0f; pPoints[0] = OffsetX + CenterX - Width / 2; pPoints[1] = OffsetY + CenterY - Height / 2; pPoints[2] = pPoints[0] + Width; pPoints[3] = pPoints[1] + Height; } void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, CMapItemGroupEx *pGroupEx, float Zoom) { float ParallaxZoom = GetParallaxZoom(pGroup, pGroupEx); float aPoints[4]; MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY, ParallaxZoom, pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, aPoints); Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); } void CRenderTools::MapScreenToInterface(float CenterX, float CenterY) { float aPoints[4]; MapScreenToWorld(CenterX, CenterY, 100.0f, 100.0f, 100.0f, 0, 0, Graphics()->ScreenAspect(), 1.0f, aPoints); Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); }