3303: Respect outline in skin metrics r=def- a=Jupeyy

beast.png has no body at all,
so for this skin we need to calculate the metrics with the outline.

Sadly comes with more overheat ofc

old:
![screenshot_2020-11-12_08-42-52](https://user-images.githubusercontent.com/6654924/98910148-15544900-24c3-11eb-8cde-3c5bfb847527.png)
new:
![screenshot_2020-11-12_08-41-30](https://user-images.githubusercontent.com/6654924/98910094-0077b580-24c3-11eb-804b-7849a7d18d14.png)

## Checklist

- [x] Tested the change ingame
- [x] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
bors[bot] 2020-11-13 09:08:52 +00:00 committed by GitHub
commit cb9e546440
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 107 deletions

View file

@ -55,6 +55,41 @@ int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
return pSelf->LoadSkin(aNameWithoutPng, aBuf, DirType);
}
static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, uint8_t *pImg, int ImgWidth, int ImgX, int ImgY, int CheckWidth, int CheckHeight)
{
int MaxY = -1;
int MinY = CheckHeight + 1;
int MaxX = -1;
int MinX = CheckWidth + 1;
for(int y = 0; y < CheckHeight; y++)
{
for(int x = 0; x < CheckWidth; x++)
{
int OffsetAlpha = (y + ImgY) * ImgWidth + (x + ImgX) * 4 + 3;
uint8_t AlphaValue = pImg[OffsetAlpha];
if(AlphaValue > 0)
{
if(MaxY < y)
MaxY = y;
if(MinY > y)
MinY = y;
if(MaxX < x)
MaxX = x;
if(MinX > x)
MinX = x;
}
}
}
Metrics.m_Width = clamp((MaxX - MinX) + 1, 1, CheckWidth);
Metrics.m_Height = clamp((MaxY - MinY) + 1, 1, CheckHeight);
Metrics.m_OffsetX = clamp(MinX, 0, CheckWidth - 1);
Metrics.m_OffsetY = clamp(MinY, 0, CheckHeight - 1);
Metrics.m_MaxWidth = CheckWidth;
Metrics.m_MaxHeight = CheckHeight;
}
int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
{
char aBuf[512];
@ -86,17 +121,27 @@ int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
int FeetOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_X * FeetGridPixelsWidth;
int FeetOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_Y * FeetGridPixelsHeight;
int FeetOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridx);
int FeetOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridy);
int FeetOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_W * FeetOutlineGridPixelsWidth;
int FeetOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_H * FeetOutlineGridPixelsHeight;
int FeetOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_X * FeetOutlineGridPixelsWidth;
int FeetOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_Y * FeetOutlineGridPixelsHeight;
int BodyOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridx);
int BodyOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridy);
int BodyOutlineSize = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_H * BodyOutlineGridPixelsHeight;
int BodyOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_X * BodyOutlineGridPixelsWidth;
int BodyOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_Y * BodyOutlineGridPixelsHeight;
int BodySize = g_pData->m_aSprites[SPRITE_TEE_BODY].m_H * (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy); // body size
if(BodySize > Info.m_Height)
return 0;
unsigned char *d = (unsigned char *)Info.m_pData;
int Pitch = Info.m_Width * 4;
int MaxBodyY = -1;
int MinBodyY = BodySize + 1;
int MaxBodyX = -1;
int MinBodyX = BodySize + 1;
// dig out blood color
{
int aColors[3] = {0};
@ -110,17 +155,6 @@ int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
aColors[1] += d[y * Pitch + x * 4 + 1];
aColors[2] += d[y * Pitch + x * 4 + 2];
}
if(AlphaValue > 0)
{
if(MaxBodyY < y)
MaxBodyY = y;
if(MinBodyY > y)
MinBodyY = y;
if(MaxBodyX < x)
MaxBodyX = x;
if(MinBodyX > x)
MinBodyX = x;
}
}
if(aColors[0] != 0 && aColors[1] != 0 && aColors[2] != 0)
Skin.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2])));
@ -128,48 +162,16 @@ int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
Skin.m_BloodColor = ColorRGBA(0, 0, 0, 1);
}
Skin.m_Metrics.m_BodyHeight = clamp((MaxBodyY - MinBodyY) + 1, 1, BodySize);
Skin.m_Metrics.m_BodyWidth = clamp((MaxBodyX - MinBodyX) + 1, 1, BodySize);
Skin.m_Metrics.m_BodyOffsetX = clamp(MinBodyX, 0, BodySize - 1);
Skin.m_Metrics.m_BodyOffsetY = clamp(MinBodyY, 0, BodySize - 1);
CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, 0, 0, BodySize, BodySize);
Skin.m_Metrics.m_BodyMaxWidth = BodySize;
Skin.m_Metrics.m_BodyMaxHeight = BodySize;
// body outline metrics
CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, BodyOutlineOffsetX, BodyOutlineOffsetY, BodyOutlineSize, BodyOutlineSize);
// get feet size
{
int MaxFeetY = -1;
int MinFeetY = FeetHeight + 1;
int MaxFeetX = -1;
int MinFeetX = FeetWidth + 1;
CheckMetrics(Skin.m_Metrics.m_Feet, d, Pitch, FeetOffsetX, FeetOffsetY, FeetWidth, FeetHeight);
for(int y = 0; y < FeetHeight; y++)
{
for(int x = 0; x < FeetWidth; x++)
{
int OffsetAlpha = (y + FeetOffsetY) * Pitch + (x + FeetOffsetX) * 4 + 3;
uint8_t AlphaValue = d[OffsetAlpha];
if(AlphaValue > 0)
{
if(MaxFeetY < y)
MaxFeetY = y;
if(MinFeetY > y)
MinFeetY = y;
if(MaxFeetX < x)
MaxFeetX = x;
if(MinFeetX > x)
MinFeetX = x;
}
}
}
Skin.m_Metrics.m_FeetWidth = clamp((MaxFeetX - MinFeetX) + 1, 1, FeetWidth);
Skin.m_Metrics.m_FeetHeight = clamp((MaxFeetY - MinFeetY) + 1, 1, FeetHeight);
Skin.m_Metrics.m_FeetOffsetX = clamp(MinFeetX, 0, FeetWidth - 1);
Skin.m_Metrics.m_FeetOffsetY = clamp(MinFeetY, 0, FeetHeight - 1);
Skin.m_Metrics.m_FeetMaxWidth = FeetWidth;
Skin.m_Metrics.m_FeetMaxHeight = FeetHeight;
}
// get feet outline size
CheckMetrics(Skin.m_Metrics.m_Feet, d, Pitch, FeetOutlineOffsetX, FeetOutlineOffsetY, FeetOutlineWidth, FeetOutlineHeight);
// create colorless version
int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3;

View file

@ -566,10 +566,10 @@ void CRenderTools::GetRenderTeeBodySize(CAnimState *pAnim, CTeeRenderInfo *pInfo
float BodyScale;
GetRenderTeeBodyScale(BaseSize, BodyScale);
Width = pInfo->m_SkinMetrics.BodyWidthNormalized() * 64.0f * BodyScale;
Height = pInfo->m_SkinMetrics.BodyHeightNormalized() * 64.0f * BodyScale;
BodyOffset.x = pInfo->m_SkinMetrics.BodyOffsetXNormalized() * 64.0f * BodyScale;
BodyOffset.y = pInfo->m_SkinMetrics.BodyOffsetYNormalized() * 64.0f * 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)
@ -580,10 +580,10 @@ void CRenderTools::GetRenderTeeFeetSize(CAnimState *pAnim, CTeeRenderInfo *pInfo
float FeetScaleWidth, FeetScaleHeight;
GetRenderTeeFeetScale(BaseSize, FeetScaleWidth, FeetScaleHeight);
Width = pInfo->m_SkinMetrics.FeetWidthNormalized() * 64.0f * FeetScaleWidth;
Height = pInfo->m_SkinMetrics.FeetHeightNormalized() * 32.0f * FeetScaleHeight;
FeetOffset.x = pInfo->m_SkinMetrics.FeetOffsetXNormalized() * 64.0f * FeetScaleWidth;
FeetOffset.y = pInfo->m_SkinMetrics.FeetOffsetYNormalized() * 32.0f * 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)

View file

@ -5,6 +5,7 @@
#include <base/tl/sorted_array.h>
#include <base/vmath.h>
#include <engine/graphics.h>
#include <limits>
// do this better and nicer
struct CSkin
@ -42,16 +43,80 @@ struct CSkin
char m_aName[24];
ColorRGBA m_BloodColor;
struct SSkinMetrics
template<bool IsSizeType>
struct SSkinMetricVariableInt
{
int m_BodyWidth;
int m_BodyHeight;
int m_BodyOffsetX;
int m_BodyOffsetY;
int m_Value;
operator int() { return m_Value; }
SSkinMetricVariableInt &operator=(int NewVal)
{
if(IsSizeType)
m_Value = maximum(m_Value, NewVal);
else
m_Value = minimum(m_Value, NewVal);
return *this;
}
SSkinMetricVariableInt()
{
Reset();
}
void Reset()
{
if(IsSizeType)
m_Value = std::numeric_limits<int>::lowest();
else
m_Value = std::numeric_limits<int>::max();
}
};
struct SSkinMetricVariable
{
SSkinMetricVariableInt<true> m_Width;
SSkinMetricVariableInt<true> m_Height;
SSkinMetricVariableInt<false> m_OffsetX;
SSkinMetricVariableInt<false> m_OffsetY;
// these can be used to normalize the metrics
int m_BodyMaxWidth;
int m_BodyMaxHeight;
SSkinMetricVariableInt<true> m_MaxWidth;
SSkinMetricVariableInt<true> m_MaxHeight;
float WidthNormalized()
{
return (float)m_Width / (float)m_MaxWidth;
}
float HeightNormalized()
{
return (float)m_Height / (float)m_MaxHeight;
}
float OffsetXNormalized()
{
return (float)m_OffsetX / (float)m_MaxWidth;
}
float OffsetYNormalized()
{
return (float)m_OffsetY / (float)m_MaxHeight;
}
void Reset()
{
m_Width.Reset();
m_Height.Reset();
m_OffsetX.Reset();
m_OffsetY.Reset();
m_MaxWidth.Reset();
m_MaxHeight.Reset();
}
};
struct SSkinMetrics
{
SSkinMetricVariable m_Body;
SSkinMetricVariable m_Feet;
int m_FeetWidth;
int m_FeetHeight;
@ -64,47 +129,13 @@ struct CSkin
void Reset()
{
m_BodyWidth = m_BodyHeight = m_BodyOffsetX = m_BodyOffsetY = m_FeetWidth = m_FeetHeight = m_FeetOffsetX = m_FeetOffsetY = -1;
m_Body.Reset();
m_Feet.Reset();
}
float BodyWidthNormalized()
SSkinMetrics()
{
return (float)m_BodyWidth / (float)m_BodyMaxWidth;
}
float BodyHeightNormalized()
{
return (float)m_BodyHeight / (float)m_BodyMaxHeight;
}
float BodyOffsetXNormalized()
{
return (float)m_BodyOffsetX / (float)m_BodyMaxWidth;
}
float BodyOffsetYNormalized()
{
return (float)m_BodyOffsetY / (float)m_BodyMaxHeight;
}
float FeetWidthNormalized()
{
return (float)m_FeetWidth / (float)m_FeetMaxWidth;
}
float FeetHeightNormalized()
{
return (float)m_FeetHeight / (float)m_FeetMaxHeight;
}
float FeetOffsetXNormalized()
{
return (float)m_FeetOffsetX / (float)m_FeetMaxWidth;
}
float FeetOffsetYNormalized()
{
return (float)m_FeetOffsetY / (float)m_FeetMaxHeight;
Reset();
}
};
SSkinMetrics m_Metrics;