Skip to content

Commit

Permalink
Merge pull request #220 from OpenVicProject/flag-sheet
Browse files Browse the repository at this point in the history
Switch to using a single flag sheet image/texture
  • Loading branch information
Hop311 authored Apr 21, 2024
2 parents 61e1e14 + b9d4aee commit 747ccf4
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 38 deletions.
14 changes: 9 additions & 5 deletions extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Error GFXMaskedFlagTexture::_generate_combined_image() {
set_region({ {}, button_image->get_size() });
}

if (mask_image.is_valid() && flag_image.is_valid()) {
if (mask_image.is_valid() && flag_image.is_valid() && flag_image_rect.has_area()) {
const Vector2i centre_translation = (mask_image->get_size() - button_image->get_size()) / 2;
for (Vector2i combined_image_point { 0, 0 }; combined_image_point.y < button_image->get_height(); ++combined_image_point.y) {
for (combined_image_point.x = 0; combined_image_point.x < button_image->get_width(); ++combined_image_point.x) {
Expand All @@ -46,10 +46,14 @@ Error GFXMaskedFlagTexture::_generate_combined_image() {
0 <= mask_image_point.y && mask_image_point.y < mask_image->get_height()
) {
const Color mask_image_colour = mask_image->get_pixelv(mask_image_point);

// Rescale from mask_image to flag_image coordinates.
const Vector2i flag_image_point = mask_image_point * flag_image->get_size() / mask_image->get_size();
const Vector2i flag_image_point =
flag_image_rect.position + mask_image_point * flag_image_rect.size / mask_image->get_size();

Color flag_image_colour = flag_image->get_pixelv(flag_image_point);
flag_image_colour.a = mask_image_colour.a;

button_image->set_pixelv(combined_image_point, flag_image_colour.blend(overlay_image_colour));
} else {
button_image->set_pixelv(combined_image_point, overlay_image_colour);
Expand Down Expand Up @@ -157,12 +161,12 @@ Error GFXMaskedFlagTexture::set_flag_country_and_type(Country const* new_flag_co
GameSingleton* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, FAILED);

const Ref<Image> new_flag_image = game_singleton->get_flag_image(new_flag_country, new_flag_type);
ERR_FAIL_NULL_V(new_flag_image, FAILED);
flag_image_rect = game_singleton->get_flag_sheet_rect(new_flag_country->get_index(), new_flag_type);
ERR_FAIL_COND_V(!flag_image_rect.has_area(), FAILED);

flag_country = new_flag_country;
flag_type = new_flag_type;
flag_image = new_flag_image;
flag_image = game_singleton->get_flag_sheet_image();
} else {
// TODO - use REB flag as default/error flag
flag_country = nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace OpenVic {
godot::StringName PROPERTY(flag_type);

godot::Ref<godot::Image> overlay_image, mask_image, flag_image;
godot::Rect2i flag_image_rect;
godot::Ref<godot::ImageTexture> combined_texture;

godot::Error _generate_combined_image();
Expand Down
140 changes: 112 additions & 28 deletions extension/src/openvic-extension/singletons/GameSingleton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ void GameSingleton::_bind_methods() {
OV_BIND_METHOD(GameSingleton::get_map_dims);
OV_BIND_METHOD(GameSingleton::get_map_aspect_ratio);
OV_BIND_METHOD(GameSingleton::get_terrain_texture);
OV_BIND_METHOD(GameSingleton::get_flag_dims);
OV_BIND_METHOD(GameSingleton::get_flag_sheet_texture);
OV_BIND_METHOD(GameSingleton::get_province_shape_image_subdivisions);
OV_BIND_METHOD(GameSingleton::get_province_shape_texture);
OV_BIND_METHOD(GameSingleton::get_province_colour_texture);
Expand Down Expand Up @@ -158,19 +160,36 @@ Ref<Texture2DArray> GameSingleton::get_terrain_texture() const {
return terrain_texture;
}

Ref<Image> GameSingleton::get_flag_image(Country const* country, StringName const& flag_type) const {
ERR_FAIL_NULL_V(country, nullptr);
const typename decltype(flag_image_map)::const_iterator it = flag_image_map.find(country);
Ref<Image> GameSingleton::get_flag_sheet_image() const {
return flag_sheet_image;
}

Ref<ImageTexture> GameSingleton::get_flag_sheet_texture() const {
return flag_sheet_texture;
}

int32_t GameSingleton::get_flag_sheet_index(int32_t country_index, godot::StringName const& flag_type) const {
ERR_FAIL_COND_V_MSG(
it == flag_image_map.end(), nullptr,
vformat("Failed to find flags for country: %s", std_view_to_godot_string(country->get_identifier()))
country_index < 0 || country_index >= game_manager.get_country_manager().get_country_count(), -1,
vformat("Invalid country index: %d", country_index)
);
const typename decltype(it->second)::const_iterator it2 = it->second.find(flag_type);

const typename decltype(flag_type_index_map)::const_iterator it = flag_type_index_map.find(flag_type);
ERR_FAIL_COND_V_MSG(it == flag_type_index_map.end(), -1, vformat("Invalid flag type %s", flag_type));

return flag_type_index_map.size() * country_index + it->second;
}

Rect2i GameSingleton::get_flag_sheet_rect(int32_t flag_index) const {
ERR_FAIL_COND_V_MSG(
it2 == it->second.end(), nullptr,
vformat("Failed to find %s flag for country: %s", flag_type, std_view_to_godot_string(country->get_identifier()))
flag_index < 0 || flag_index >= flag_sheet_count, {}, vformat("Invalid flag sheet index: %d", flag_index)
);
return it2->second;

return { Vector2i { flag_index % flag_sheet_dims.x, flag_index / flag_sheet_dims.x } * flag_dims, flag_dims };
}

Rect2i GameSingleton::get_flag_sheet_rect(int32_t country_index, godot::StringName const& flag_type) const {
return get_flag_sheet_rect(get_flag_sheet_index(country_index, flag_type));
}

Vector2i GameSingleton::get_province_shape_image_subdivisions() const {
Expand Down Expand Up @@ -383,50 +402,115 @@ Error GameSingleton::_load_terrain_variants() {
return OK;
}

Error GameSingleton::_load_flag_images() {
ERR_FAIL_COND_V_MSG(!flag_image_map.empty(), FAILED, "Flag images have already been loaded!");
const Vector2i GameSingleton::flag_dims { 128, 64 };

Error GameSingleton::_load_flag_sheet() {
ERR_FAIL_COND_V_MSG(
flag_sheet_image.is_valid() || flag_sheet_texture.is_valid(), FAILED,
"Flag sheet image and/or texture has already been generated!"
);

GovernmentTypeManager const& government_type_manager = game_manager.get_politics_manager().get_government_type_manager();
ERR_FAIL_COND_V_MSG(
!government_type_manager.government_types_are_locked(), FAILED,
"Cannot load flag images before government types are locked!"
government_type_manager.get_flag_types().empty() || !government_type_manager.government_types_are_locked(), FAILED,
"Cannot load flag images if flag types are empty or government types are not locked!"
);
CountryManager const& country_manager = game_manager.get_country_manager();
ERR_FAIL_COND_V_MSG(
!country_manager.countries_are_locked(), FAILED, "Cannot load flag images before countries are locked!"
country_manager.countries_empty() || !country_manager.countries_are_locked(), FAILED,
"Cannot load flag images if countries are empty or not locked!"
);

AssetManager* asset_manager = AssetManager::get_singleton();
ERR_FAIL_NULL_V(asset_manager, FAILED);

static const String flag_directory = "gfx/flags/";
static const String flag_separator = "_";
static const String flag_extension = ".tga";

std::vector<StringName> flag_types;
/* Generate flag type - index lookup map */
flag_type_index_map.clear();
for (std::string const& type : government_type_manager.get_flag_types()) {
flag_types.emplace_back(std_to_godot_string_name(type));
flag_type_index_map.emplace(std_to_godot_string_name(type), static_cast<int32_t>(flag_type_index_map.size()));
}

flag_image_map.reserve(country_manager.get_countries().size());
flag_sheet_count = country_manager.get_countries().size() * flag_type_index_map.size();

std::vector<Ref<Image>> flag_images;
flag_images.reserve(flag_sheet_count);

static constexpr Image::Format flag_format = Image::FORMAT_RGB8;

Error ret = OK;
for (Country const& country : country_manager.get_countries()) {
ordered_map<StringName, Ref<Image>>& flag_images = flag_image_map[&country];
flag_images.reserve(flag_types.size());
const String country_name = std_view_to_godot_string(country.get_identifier());
for (StringName const& flag_type : flag_types) {

for (auto const& [flag_type, flag_type_index] : flag_type_index_map) {
static const String flag_directory = "gfx/flags/";
static const String flag_separator = "_";
static const String flag_extension = ".tga";

const StringName flag_path =
flag_directory + country_name + (flag_type.is_empty() ? "" : flag_separator + flag_type) + flag_extension;
const Ref<Image> flag_image = asset_manager->get_image(flag_path);

/* Do not cache flag image, they should be freed after the flag sheet has been generated. */
const Ref<Image> flag_image = asset_manager->get_image(flag_path, AssetManager::LOAD_FLAG_NONE);

if (flag_image.is_valid()) {
flag_images.emplace(flag_type, flag_image);

if (flag_image->get_format() != flag_format) {
flag_image->convert(flag_format);
}

if (flag_image->get_size() != flag_dims) {
if (flag_image->get_width() > flag_dims.x || flag_image->get_height() > flag_dims.y) {
UtilityFunctions::push_warning(
"Flag image ", flag_path, " (", flag_image->get_size(), ") is larger than the sheet flag size (",
flag_dims, ")"
);
}

flag_image->resize(flag_dims.x, flag_dims.y, Image::INTERPOLATE_NEAREST);
}
} else {
UtilityFunctions::push_error("Failed to load flag image: ", flag_path);
ret = FAILED;
}

/* Add flag_image to the vector even if it's null to ensure each flag has the right index. */
flag_images.push_back(flag_image);
}
}

ERR_FAIL_COND_V(flag_images.size() != flag_sheet_count, FAILED);

/* Calculate the width that will make the sheet as close to a square as possible (taking flag dimensions into account.) */
flag_sheet_dims.x = (fixed_point_t { static_cast<int32_t>(flag_images.size()) } * flag_dims.y / flag_dims.x).sqrt().ceil();

/* Calculated corresponding height (rounded up). */
flag_sheet_dims.y = (static_cast<int32_t>(flag_images.size()) + flag_sheet_dims.x - 1 ) / flag_sheet_dims.x;

const Vector2i sheet_dims = flag_sheet_dims * flag_dims;

flag_sheet_image = Image::create(sheet_dims.x, sheet_dims.y, false, flag_format);
ERR_FAIL_NULL_V_MSG(flag_sheet_image, FAILED, "Failed to create flag sheet image!");

static const Rect2i flag_rect { { 0, 0 }, flag_dims };

/* Fill the flag sheet with the flag images. */
for (int32_t index = 0; index < flag_images.size(); ++index) {
Ref<Image> const& flag_image = flag_images[index];

const Vector2i sheet_pos = Vector2i { index % flag_sheet_dims.x, index / flag_sheet_dims.x } * flag_dims;

if (flag_image.is_valid()) {
flag_sheet_image->blit_rect(flag_image, flag_rect, sheet_pos);
} else {
static const Color error_colour { 1.0f, 0.0f, 1.0f, 1.0f }; /* Magenta */

flag_sheet_image->fill_rect({ sheet_pos, flag_dims }, error_colour);
}
}

flag_sheet_texture = ImageTexture::create_from_image(flag_sheet_image);
ERR_FAIL_NULL_V_MSG(flag_sheet_texture, FAILED, "Failed to create flag sheet texture!");

return ret;
}

Expand All @@ -450,8 +534,8 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi
UtilityFunctions::push_error("Failed to load terrain variants!");
err = FAILED;
}
if (_load_flag_images() != OK) {
UtilityFunctions::push_error("Failed to load flag textures!");
if (_load_flag_sheet() != OK) {
UtilityFunctions::push_error("Failed to load flag sheet!");
err = FAILED;
}
if (_load_map_images() != OK) {
Expand Down
21 changes: 16 additions & 5 deletions extension/src/openvic-extension/singletons/GameSingleton.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,21 @@ namespace OpenVic {
godot::Ref<godot::ImageTexture> province_colour_texture;
Mapmode::index_t mapmode_index = 0;
godot::Ref<godot::Texture2DArray> terrain_texture;
ordered_map<Country const*, ordered_map<godot::StringName, godot::Ref<godot::Image>>> flag_image_map;

static const godot::Vector2i PROPERTY(flag_dims); /* The size in pixels of an individual flag. */
int32_t flag_sheet_count = 0; /* The number of flags in the flag sheet. */
godot::Vector2i flag_sheet_dims; /* The size of the flag sheet in flags, rather than pixels. */
godot::Ref<godot::Image> flag_sheet_image;
godot::Ref<godot::ImageTexture> flag_sheet_texture;
ordered_map<godot::StringName, int32_t> flag_type_index_map;

static godot::StringName const& _signal_gamestate_updated();
static godot::StringName const& _signal_province_selected();
static godot::StringName const& _signal_clock_state_changed();

godot::Error _load_map_images();
godot::Error _load_terrain_variants();
godot::Error _load_flag_images();
godot::Error _load_flag_sheet();

/* Generate the province_colour_texture from the current mapmode. */
godot::Error _update_colour_image();
Expand Down Expand Up @@ -67,9 +73,14 @@ namespace OpenVic {
/* The cosmetic terrain textures stored in a Texture2DArray. */
godot::Ref<godot::Texture2DArray> get_terrain_texture() const;

/* The flag image corresponding to the requested country / flag_type
* combination, or nullptr if no such flag can be found. */
godot::Ref<godot::Image> get_flag_image(Country const* country, godot::StringName const& flag_type) const;
godot::Ref<godot::Image> get_flag_sheet_image() const;
godot::Ref<godot::ImageTexture> get_flag_sheet_texture() const;

/* The index of the flag in the flag sheet corresponding to the requested country / flag_type
* combination, or -1 if no such flag can be found. */
int32_t get_flag_sheet_index(int32_t country_index, godot::StringName const& flag_type) const;
godot::Rect2i get_flag_sheet_rect(int32_t flag_index) const;
godot::Rect2i get_flag_sheet_rect(int32_t country_index, godot::StringName const& flag_type) const;

/* Number of (vertical, horizontal) subdivisions the province shape image
* was split into when making the province_shape_texture to ensure no
Expand Down

0 comments on commit 747ccf4

Please sign in to comment.