mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Add tileart tool to editor
This commit is contained in:
parent
c9557b2ee9
commit
946be50807
|
@ -2325,6 +2325,7 @@ if(CLIENT)
|
|||
proof_mode.h
|
||||
smooth_value.cpp
|
||||
smooth_value.h
|
||||
tileart.cpp
|
||||
)
|
||||
set(GAME_GENERATED_CLIENT
|
||||
src/game/generated/checksum.cpp
|
||||
|
|
|
@ -7208,7 +7208,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
|
|||
if(DoButton_Menu(&s_ToolsButton, "Tools", 0, &ToolsButton, 0, nullptr))
|
||||
{
|
||||
static SPopupMenuId s_PopupMenuToolsId;
|
||||
UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 50.0f, this, PopupMenuTools, PopupProperties);
|
||||
UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuTools, PopupProperties);
|
||||
}
|
||||
|
||||
MenuBar.VSplitLeft(5.0f, nullptr, &MenuBar);
|
||||
|
|
|
@ -1046,7 +1046,10 @@ public:
|
|||
POPEVENT_PREVENTUNUSEDTILES,
|
||||
POPEVENT_IMAGEDIV16,
|
||||
POPEVENT_IMAGE_MAX,
|
||||
POPEVENT_PLACE_BORDER_TILES
|
||||
POPEVENT_PLACE_BORDER_TILES,
|
||||
POPEVENT_PIXELART_BIG_IMAGE,
|
||||
POPEVENT_PIXELART_MANY_COLORS,
|
||||
POPEVENT_PIXELART_TOO_MANY_COLORS
|
||||
};
|
||||
|
||||
int m_PopupEventType;
|
||||
|
@ -1265,6 +1268,11 @@ public:
|
|||
|
||||
CLineInputBuffered<256> m_SettingsCommandInput;
|
||||
|
||||
CImageInfo m_TileartImageInfo;
|
||||
char m_aTileartFilename[IO_MAX_PATH_LENGTH];
|
||||
void AddTileart();
|
||||
void TileartCheckColors();
|
||||
|
||||
void PlaceBorderTiles();
|
||||
|
||||
void UpdateTooltip(const void *pID, const CUIRect *pRect, const char *pToolTip);
|
||||
|
@ -1327,6 +1335,7 @@ public:
|
|||
static bool CallbackAppendMap(const char *pFileName, int StorageType, void *pUser);
|
||||
static bool CallbackSaveMap(const char *pFileName, int StorageType, void *pUser);
|
||||
static bool CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser);
|
||||
static bool CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser);
|
||||
|
||||
void PopupSelectImageInvoke(int Current, float x, float y);
|
||||
int PopupSelectImageResult();
|
||||
|
|
|
@ -198,6 +198,15 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuTools(void *pContext, CUIRect Vi
|
|||
pEditor->UI()->DoPopupMenu(&s_PopupGotoId, Slot.x, Slot.y + Slot.h, 120, 52, pEditor, PopupGoto);
|
||||
}
|
||||
|
||||
static int s_TileartButton = 0;
|
||||
View.HSplitTop(2.0f, nullptr, &View);
|
||||
View.HSplitTop(12.0f, &Slot, &View);
|
||||
if(pEditor->DoButton_MenuItem(&s_TileartButton, "Add tileart", 0, &Slot, 0, "Generate tileart from image"))
|
||||
{
|
||||
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add tileart", "Open", "mapres", false, CallbackAddTileart, pEditor);
|
||||
return CUI::POPUP_CLOSE_CURRENT;
|
||||
}
|
||||
|
||||
return CUI::POPUP_KEEP_OPEN;
|
||||
}
|
||||
|
||||
|
@ -1764,6 +1773,21 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View,
|
|||
pTitle = "Place border tiles";
|
||||
pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?";
|
||||
}
|
||||
else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
|
||||
{
|
||||
pTitle = "Big image";
|
||||
pMessage = "The selected image is big. Converting it to tileart may take some time.\n\nContinue anyway?";
|
||||
}
|
||||
else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
|
||||
{
|
||||
pTitle = "Many colors";
|
||||
pMessage = "The selected image contains many colors, which will lead to a big mapfile. You may want to consider reducing the number of colors.\n\nContinue anyway?";
|
||||
}
|
||||
else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_TOO_MANY_COLORS)
|
||||
{
|
||||
pTitle = "Too many colors";
|
||||
pMessage = "The client only supports 64 images but more would be needed to add the selected image as tileart.";
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_assert(false, "m_PopupEventType invalid");
|
||||
|
@ -1786,12 +1810,19 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View,
|
|||
|
||||
// button bar
|
||||
ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar);
|
||||
if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX)
|
||||
if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX && pEditor->m_PopupEventType != POPEVENT_PIXELART_TOO_MANY_COLORS)
|
||||
{
|
||||
static int s_CancelButton = 0;
|
||||
if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, 0, nullptr))
|
||||
{
|
||||
pEditor->m_PopupEventWasActivated = false;
|
||||
|
||||
if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE || pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
|
||||
{
|
||||
free(pEditor->m_TileartImageInfo.m_pData);
|
||||
pEditor->m_TileartImageInfo.m_pData = nullptr;
|
||||
}
|
||||
|
||||
return CUI::POPUP_CLOSE_CURRENT;
|
||||
}
|
||||
}
|
||||
|
@ -1831,6 +1862,14 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View,
|
|||
{
|
||||
pEditor->PlaceBorderTiles();
|
||||
}
|
||||
else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
|
||||
{
|
||||
pEditor->TileartCheckColors();
|
||||
}
|
||||
else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
|
||||
{
|
||||
pEditor->AddTileart();
|
||||
}
|
||||
pEditor->m_PopupEventWasActivated = false;
|
||||
return CUI::POPUP_CLOSE_CURRENT;
|
||||
}
|
||||
|
|
264
src/game/editor/tileart.cpp
Normal file
264
src/game/editor/tileart.cpp
Normal file
|
@ -0,0 +1,264 @@
|
|||
#include "editor.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
bool operator<(const ColorRGBA &Left, const ColorRGBA &Right)
|
||||
{
|
||||
if(Left.r != Right.r)
|
||||
return Left.r < Right.r;
|
||||
else if(Left.g != Right.g)
|
||||
return Left.g < Right.g;
|
||||
else if(Left.b != Right.b)
|
||||
return Left.b < Right.b;
|
||||
else
|
||||
return Left.a < Right.a;
|
||||
}
|
||||
|
||||
static int GetNumColorChannels(const CImageInfo &Image)
|
||||
{
|
||||
switch(Image.m_Format)
|
||||
{
|
||||
case CImageInfo::FORMAT_RGB:
|
||||
return 3;
|
||||
case CImageInfo::FORMAT_SINGLE_COMPONENT:
|
||||
return 1;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
static ColorRGBA GetPixelColor(const CImageInfo &Image, int x, int y)
|
||||
{
|
||||
uint8_t *pData = static_cast<uint8_t *>(Image.m_pData);
|
||||
int NumColorChannels = GetNumColorChannels(Image);
|
||||
int PixelStartIndex = x * NumColorChannels + (Image.m_Width * NumColorChannels * y);
|
||||
|
||||
ColorRGBA Color = {255, 255, 255, 255};
|
||||
if(NumColorChannels == 1)
|
||||
{
|
||||
Color.a = pData[PixelStartIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.r = pData[PixelStartIndex + 0];
|
||||
Color.g = pData[PixelStartIndex + 1];
|
||||
Color.b = pData[PixelStartIndex + 2];
|
||||
|
||||
if(NumColorChannels == 4)
|
||||
Color.a = pData[PixelStartIndex + 3];
|
||||
}
|
||||
|
||||
return Color;
|
||||
}
|
||||
|
||||
static void SetPixelColor(CImageInfo *pImage, int x, int y, ColorRGBA Color)
|
||||
{
|
||||
uint8_t *pData = static_cast<uint8_t *>(pImage->m_pData);
|
||||
int NumColorChannels = GetNumColorChannels(*pImage);
|
||||
int PixelStartIndex = x * NumColorChannels + (pImage->m_Width * NumColorChannels * y);
|
||||
|
||||
if(NumColorChannels == 1)
|
||||
{
|
||||
pData[PixelStartIndex] = Color.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
pData[PixelStartIndex + 0] = Color.r;
|
||||
pData[PixelStartIndex + 1] = Color.g;
|
||||
pData[PixelStartIndex + 2] = Color.b;
|
||||
|
||||
if(NumColorChannels == 4)
|
||||
pData[PixelStartIndex + 3] = Color.a;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
|
||||
{
|
||||
std::set<ColorRGBA> ColorSet;
|
||||
std::vector<ColorRGBA> vUniqueColors;
|
||||
for(int x = 0; x < Image.m_Width; x++)
|
||||
{
|
||||
for(int y = 0; y < Image.m_Height; y++)
|
||||
{
|
||||
ColorRGBA Color = GetPixelColor(Image, x, y);
|
||||
if(Color.a > 0 && ColorSet.insert(Color).second)
|
||||
vUniqueColors.push_back(Color);
|
||||
}
|
||||
}
|
||||
std::sort(vUniqueColors.begin(), vUniqueColors.end());
|
||||
|
||||
return vUniqueColors;
|
||||
}
|
||||
|
||||
constexpr int NumTilesRow = 16;
|
||||
constexpr int NumTilesColumn = 16;
|
||||
constexpr int NumTiles = NumTilesRow * NumTilesColumn;
|
||||
constexpr int TileSize = 64;
|
||||
|
||||
static int GetColorIndex(const std::array<ColorRGBA, NumTiles> &ColorGroup, ColorRGBA Color)
|
||||
{
|
||||
std::array<ColorRGBA, NumTiles>::const_iterator Iterator = std::find(ColorGroup.begin(), ColorGroup.end(), Color);
|
||||
if(Iterator == ColorGroup.end())
|
||||
return 0;
|
||||
return Iterator - ColorGroup.begin();
|
||||
}
|
||||
|
||||
static std::vector<std::array<ColorRGBA, NumTiles>> GroupColors(const std::vector<ColorRGBA> &vColors)
|
||||
{
|
||||
std::vector<std::array<ColorRGBA, NumTiles>> vaColorGroups;
|
||||
|
||||
for(size_t i = 0; i < vColors.size(); i += NumTiles - 1)
|
||||
{
|
||||
auto &Group = vaColorGroups.emplace_back();
|
||||
std::copy_n(vColors.begin() + i, std::min<size_t>(NumTiles - 1, vColors.size() - i), Group.begin() + 1);
|
||||
}
|
||||
|
||||
return vaColorGroups;
|
||||
}
|
||||
|
||||
static void SetColorTile(CImageInfo *pImage, int x, int y, ColorRGBA Color)
|
||||
{
|
||||
for(int i = 0; i < TileSize; i++)
|
||||
{
|
||||
for(int j = 0; j < TileSize; j++)
|
||||
SetPixelColor(pImage, x * TileSize + i, y * TileSize + j, Color);
|
||||
}
|
||||
}
|
||||
|
||||
static CImageInfo ColorGroupToImage(const std::array<ColorRGBA, NumTiles> &aColorGroup)
|
||||
{
|
||||
CImageInfo Image;
|
||||
Image.m_Width = NumTilesRow * TileSize;
|
||||
Image.m_Height = NumTilesColumn * TileSize;
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
|
||||
uint8_t *pData = static_cast<uint8_t *>(malloc(static_cast<size_t>(Image.m_Width) * Image.m_Height * 4 * sizeof(uint8_t)));
|
||||
Image.m_pData = pData;
|
||||
|
||||
for(int y = 0; y < NumTilesColumn; y++)
|
||||
{
|
||||
for(int x = 0; x < NumTilesRow; x++)
|
||||
{
|
||||
int ColorIndex = x + NumTilesRow * y;
|
||||
SetColorTile(&Image, x, y, aColorGroup[ColorIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
return Image;
|
||||
}
|
||||
|
||||
static std::vector<CImageInfo> ColorGroupsToImages(const std::vector<std::array<ColorRGBA, NumTiles>> &vaColorGroups)
|
||||
{
|
||||
std::vector<CImageInfo> vImages;
|
||||
vImages.reserve(vaColorGroups.size());
|
||||
for(const auto &ColorGroup : vaColorGroups)
|
||||
vImages.push_back(ColorGroupToImage(ColorGroup));
|
||||
|
||||
return vImages;
|
||||
}
|
||||
|
||||
static std::shared_ptr<CEditorImage> ImageInfoToEditorImage(CEditor *pEditor, const CImageInfo &Image, const char *pName)
|
||||
{
|
||||
std::shared_ptr<CEditorImage> pEditorImage = std::make_shared<CEditorImage>(pEditor);
|
||||
pEditorImage->m_Width = Image.m_Width;
|
||||
pEditorImage->m_Height = Image.m_Height;
|
||||
pEditorImage->m_Format = Image.m_Format;
|
||||
pEditorImage->m_pData = Image.m_pData;
|
||||
|
||||
int TextureLoadFlag = pEditor->Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
pEditorImage->m_Texture = pEditor->Graphics()->LoadTextureRaw(Image.m_Width, Image.m_Height, Image.m_Format, Image.m_pData, CImageInfo::FORMAT_AUTO, TextureLoadFlag, pName);
|
||||
pEditorImage->m_External = 0;
|
||||
str_copy(pEditorImage->m_aName, pName);
|
||||
|
||||
return pEditorImage;
|
||||
}
|
||||
|
||||
static std::shared_ptr<CLayerTiles> AddLayerWithImage(CEditor *pEditor, const std::shared_ptr<CLayerGroup> &pGroup, int Width, int Height, const CImageInfo &Image, const char *pName)
|
||||
{
|
||||
std::shared_ptr<CEditorImage> pEditorImage = ImageInfoToEditorImage(pEditor, Image, pName);
|
||||
pEditor->m_Map.m_vpImages.push_back(pEditorImage);
|
||||
|
||||
std::shared_ptr<CLayerTiles> pLayer = std::make_shared<CLayerTiles>(Width, Height);
|
||||
str_copy(pLayer->m_aName, pName);
|
||||
pLayer->m_pEditor = pEditor;
|
||||
pLayer->m_Image = pEditor->m_Map.m_vpImages.size() - 1;
|
||||
pGroup->AddLayer(pLayer);
|
||||
|
||||
return pLayer;
|
||||
}
|
||||
|
||||
static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, const std::array<ColorRGBA, NumTiles> &aColorGroup, const CImageInfo &Image)
|
||||
{
|
||||
for(int x = 0; x < pLayer->m_Width; x++)
|
||||
{
|
||||
for(int y = 0; y < pLayer->m_Height; y++)
|
||||
pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, GetPixelColor(Image, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
void CEditor::AddTileart()
|
||||
{
|
||||
std::shared_ptr<CLayerGroup> pGroup = m_Map.NewGroup();
|
||||
str_copy(pGroup->m_aName, m_aTileartFilename);
|
||||
|
||||
auto vUniqueColors = GetUniqueColors(m_TileartImageInfo);
|
||||
auto vaColorGroups = GroupColors(vUniqueColors);
|
||||
auto vColorImages = ColorGroupsToImages(vaColorGroups);
|
||||
char aImageName[IO_MAX_PATH_LENGTH];
|
||||
for(size_t i = 0; i < vColorImages.size(); i++)
|
||||
{
|
||||
str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, m_aTileartFilename, i + 1);
|
||||
std::shared_ptr<CLayerTiles> pLayer = AddLayerWithImage(this, pGroup, m_TileartImageInfo.m_Width, m_TileartImageInfo.m_Height, vColorImages[i], aImageName);
|
||||
SetTilelayerIndices(pLayer, vaColorGroups[i], m_TileartImageInfo);
|
||||
}
|
||||
SortImages();
|
||||
|
||||
free(m_TileartImageInfo.m_pData);
|
||||
m_TileartImageInfo.m_pData = nullptr;
|
||||
m_Map.OnModify();
|
||||
m_Dialog = DIALOG_NONE;
|
||||
}
|
||||
|
||||
void CEditor::TileartCheckColors()
|
||||
{
|
||||
auto vUniqueColors = GetUniqueColors(m_TileartImageInfo);
|
||||
int NumColorGroups = std::ceil(vUniqueColors.size() / 255.0f);
|
||||
if(m_Map.m_vpImages.size() + NumColorGroups >= 64)
|
||||
{
|
||||
m_PopupEventType = CEditor::POPEVENT_PIXELART_TOO_MANY_COLORS;
|
||||
m_PopupEventActivated = true;
|
||||
free(m_TileartImageInfo.m_pData);
|
||||
m_TileartImageInfo.m_pData = nullptr;
|
||||
}
|
||||
else if(NumColorGroups > 1)
|
||||
{
|
||||
m_PopupEventType = CEditor::POPEVENT_PIXELART_MANY_COLORS;
|
||||
m_PopupEventActivated = true;
|
||||
}
|
||||
else
|
||||
AddTileart();
|
||||
}
|
||||
|
||||
bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser)
|
||||
{
|
||||
CEditor *pEditor = (CEditor *)pUser;
|
||||
|
||||
if(!pEditor->Graphics()->LoadPNG(&pEditor->m_TileartImageInfo, pFilepath, StorageType))
|
||||
{
|
||||
pEditor->ShowFileDialogError("Failed to load image from file '%s'.", pFilepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
IStorage::StripPathAndExtension(pFilepath, pEditor->m_aTileartFilename, sizeof(pEditor->m_aTileartFilename));
|
||||
if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000)
|
||||
{
|
||||
pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE;
|
||||
pEditor->m_PopupEventActivated = true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEditor->TileartCheckColors();
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue