Added initial implementation of a dynamic spritesheet generator
This isn't used anywhere yet. Right now it only takes a directory and generates one sheet for all the images in that directory and its sub-directories. The sheets are fully-functional textures, and a path -> rect mapping exists, but I haven't added anything that uses it yet. This will definitely need some improvements and changes to properly integrate with @jyrkive 's OGL work.
This commit is contained in:
parent
769f102f2e
commit
f7d2924b27
4 changed files with 243 additions and 0 deletions
|
@ -1554,6 +1554,7 @@
|
|||
<ClCompile Include="..\..\src\help\manager.cpp">
|
||||
<Filter>Help</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\spritesheet_generator.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\src\addon\client.hpp">
|
||||
|
@ -3014,6 +3015,7 @@
|
|||
<ClInclude Include="..\..\src\help\manager.hpp">
|
||||
<Filter>Help</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\spritesheet_generator.hpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp">
|
||||
|
|
|
@ -335,6 +335,7 @@ scripting/plugins/manager.cpp
|
|||
sdl/point.cpp
|
||||
settings.cpp
|
||||
side_filter.cpp
|
||||
spritesheet_generator.hpp
|
||||
statistics.cpp
|
||||
storyscreen/controller.cpp
|
||||
storyscreen/parser.cpp
|
||||
|
|
218
src/spritesheet_generator.cpp
Normal file
218
src/spritesheet_generator.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
Copyright (C) 2018 by Charles Dang <exodia339@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "spritesheet_generator.hpp"
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "image.hpp"
|
||||
#include "sdl/point.hpp"
|
||||
#include "sdl/rect.hpp"
|
||||
#include "sdl/surface.hpp"
|
||||
#include "sdl/texture.hpp"
|
||||
#include "sdl/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
namespace image
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct sheet_elment_data
|
||||
{
|
||||
/**
|
||||
* The spritesheet.
|
||||
*
|
||||
* Do note The texture class is really a "texture reference" class; it only
|
||||
* owns a shared pointer to the actual texture in memory. Therefor, having
|
||||
* this object does not mean we're keeping multiple copies of each sheet.
|
||||
*/
|
||||
texture sheet;
|
||||
|
||||
/** The coordinates of this image on the spritesheet. */
|
||||
SDL_Rect rect;
|
||||
};
|
||||
|
||||
/** Helper sorting struct for build_sheet_from_images to use. */
|
||||
struct surf_area_sort
|
||||
{
|
||||
bool operator()(const surface& lhs, const surface& rhs) const
|
||||
{
|
||||
return lhs->w * lhs->h < rhs->w * rhs->h;
|
||||
}
|
||||
};
|
||||
|
||||
/** Map of path to rect. */
|
||||
std::map<std::string, sheet_elment_data> path_sheet_mapping;
|
||||
|
||||
void build_sheet_from_images(const std::vector<std::string>& file_paths)
|
||||
{
|
||||
// Surface -> [path, sheet element data] map, sorted by surface area (largest last).
|
||||
// TODO: simplify
|
||||
std::multimap<surface, std::pair<std::string, sheet_elment_data>, surf_area_sort> surf_path_map;
|
||||
|
||||
// Load all the images.
|
||||
for(const auto& f : file_paths) {
|
||||
surface temp = image::get_image(f);
|
||||
|
||||
if(!temp.null()) {
|
||||
std::pair<std::string, sheet_elment_data> another_temp(f, {});
|
||||
surf_path_map.emplace(std::move(temp), another_temp);
|
||||
}
|
||||
}
|
||||
|
||||
if(surf_path_map.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned total_area = std::accumulate(surf_path_map.begin(), surf_path_map.end(), 0,
|
||||
[](const int val, const auto& s) { return val + (s.first->w * s.first->h); });
|
||||
|
||||
const unsigned side_length = static_cast<unsigned>(std::sqrt(total_area) * 1.3);
|
||||
|
||||
std::vector<sheet_elment_data> packaged_data;
|
||||
packaged_data.reserve(surf_path_map.size());
|
||||
|
||||
std::vector<unsigned> max_row_heights;
|
||||
unsigned current_row_max_height = 0;
|
||||
|
||||
point origin(0, 0);
|
||||
|
||||
//
|
||||
// Calculate the destination rects for the images.
|
||||
// This uses the Shelf Next Fit algorithm as described here: http://clb.demon.fi/files/RectangleBinPack.pdf
|
||||
// Our method forgoes the orientation consideration and works top-down instead of bottom-up, however.
|
||||
//
|
||||
for(auto& iter : surf_path_map) {
|
||||
SDL_Rect r = get_non_transparent_portion(iter.first);
|
||||
|
||||
current_row_max_height = std::max<unsigned>(current_row_max_height, r.h);
|
||||
|
||||
// If we can't fit this element without getting cut off, move to the next line.
|
||||
if(static_cast<unsigned>(origin.x + r.w) > side_length) {
|
||||
// Reset the origin.
|
||||
origin.x = 0;
|
||||
origin.y += current_row_max_height;
|
||||
|
||||
// Save this row's max height.
|
||||
max_row_heights.push_back(current_row_max_height);
|
||||
current_row_max_height = 0;
|
||||
}
|
||||
|
||||
r.x = origin.x;
|
||||
r.y = origin.y;
|
||||
|
||||
// Save this element's rect.
|
||||
iter.second.second.rect = r;
|
||||
|
||||
// Shift the rect origin for the next element.
|
||||
origin.x += r.w;
|
||||
}
|
||||
|
||||
// If we never reached max width during rect placement, max_row_heights will be empty.
|
||||
// In that case, that forces res_h below to be 0. Add the current max height to compensate.
|
||||
// TODO: maybe we should just handle a 0 value in res_h's calculation?
|
||||
if(max_row_heights.empty()) {
|
||||
max_row_heights.push_back(current_row_max_height);
|
||||
}
|
||||
|
||||
const unsigned res_w = side_length;
|
||||
const unsigned res_h = std::min<unsigned>(side_length, std::accumulate(max_row_heights.begin(), max_row_heights.end(), 0,
|
||||
[](const int val, const unsigned h) { return val + h; }));
|
||||
|
||||
// Check that we won't exceed max texture size and that neither dimension is 0. TODO: handle?
|
||||
assert(res_w > 0 && res_w <= 8192 && res_h > 0 && res_h <= 8192);
|
||||
|
||||
// Assemble everything
|
||||
surface res = create_neutral_surface(res_w, res_h);
|
||||
assert(!res.null() && "Spritesheet surface is null!");
|
||||
|
||||
for(auto& iter : surf_path_map) {
|
||||
const surface& s = iter.first;
|
||||
sheet_elment_data& data = iter.second.second;
|
||||
|
||||
SDL_Rect src_rect = get_non_transparent_portion(s);
|
||||
sdl_blit(s, &src_rect, res, &data.rect);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SPRITESHEET_OUTPUT
|
||||
static unsigned test_i = 0;
|
||||
image::save_image(res, "spritesheets/sheet_test_" + std::to_string(test_i++) + ".png");
|
||||
#endif
|
||||
|
||||
// Convert the sheet to a texture.
|
||||
texture sheet_tex(res);
|
||||
|
||||
// Add path mappings.
|
||||
for(auto& iter : surf_path_map) {
|
||||
// Copy a texture reference;
|
||||
sheet_elment_data& data = iter.second.second;
|
||||
data.sheet = sheet_tex;
|
||||
|
||||
#ifdef HAVE_CXX17
|
||||
path_sheet_mapping.insert(std::exchange(iter->second, {}));
|
||||
#else
|
||||
path_sheet_mapping.emplace(iter.second.first, data);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void build_spritesheet_from_impl(const std::string& dir, const std::string& subdir)
|
||||
{
|
||||
const std::string checked_dir = dir + subdir;
|
||||
|
||||
if(!filesystem::is_directory(checked_dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> files_found;
|
||||
std::vector<std::string> dirs_found;
|
||||
|
||||
filesystem::get_files_in_dir(checked_dir, &files_found, &dirs_found,
|
||||
filesystem::FILE_NAME_ONLY,
|
||||
filesystem::NO_FILTER,
|
||||
filesystem::DONT_REORDER
|
||||
);
|
||||
|
||||
for(std::string& file : files_found) {
|
||||
file = subdir + file;
|
||||
}
|
||||
|
||||
if(!files_found.empty()) {
|
||||
build_sheet_from_images(files_found);
|
||||
}
|
||||
|
||||
for(const auto& d : dirs_found) {
|
||||
build_spritesheet_from_impl(dir, subdir + d + "/");
|
||||
}
|
||||
}
|
||||
|
||||
} // end anon namespace
|
||||
void build_spritesheet_from(const std::string& subdir)
|
||||
{
|
||||
#ifdef DEBUG_SPRITESHEET_OUTPUT
|
||||
const std::size_t start = SDL_GetTicks();
|
||||
#endif
|
||||
|
||||
for(const auto& p : filesystem::get_binary_paths("images")) {
|
||||
build_spritesheet_from_impl(p, subdir);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SPRITESHEET_OUTPUT
|
||||
std::cerr << "Spritesheet generation of '" << subdir << "' took: " << (SDL_GetTicks() - start) << "ms\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace image
|
22
src/spritesheet_generator.hpp
Normal file
22
src/spritesheet_generator.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Copyright (C) 2018 by Charles Dang <exodia339@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace image
|
||||
{
|
||||
void build_spritesheet_from(const std::string& subdir);
|
||||
} // namespace image
|
Loading…
Add table
Reference in a new issue