From 7e2700514652212e50a006ad525e7c7ca8e7840c Mon Sep 17 00:00:00 2001 From: Hop311 Date: Sat, 29 Apr 2023 20:00:44 +0100 Subject: Terrain textures + province colour alpha channel --- extension/src/GameSingleton.cpp | 197 +++++++++++++++++++++++++++------------- 1 file changed, 136 insertions(+), 61 deletions(-) (limited to 'extension/src/GameSingleton.cpp') diff --git a/extension/src/GameSingleton.cpp b/extension/src/GameSingleton.cpp index 64f6fc1..3476ac5 100644 --- a/extension/src/GameSingleton.cpp +++ b/extension/src/GameSingleton.cpp @@ -5,7 +5,6 @@ #include #include "openvic2/Logger.hpp" -#include "openvic2/Types.hpp" using namespace godot; using namespace OpenVic2; @@ -18,7 +17,8 @@ void GameSingleton::_bind_methods() { ClassDB::bind_method(D_METHOD("load_province_identifier_file", "file_path"), &GameSingleton::load_province_identifier_file); ClassDB::bind_method(D_METHOD("load_water_province_file", "file_path"), &GameSingleton::load_water_province_file); ClassDB::bind_method(D_METHOD("load_region_file", "file_path"), &GameSingleton::load_region_file); - ClassDB::bind_method(D_METHOD("load_province_shape_file", "file_path"), &GameSingleton::load_province_shape_file); + ClassDB::bind_method(D_METHOD("load_terrain_file", "file_path"), &GameSingleton::load_terrain_file); + ClassDB::bind_method(D_METHOD("load_map_images", "province_image_path", "terrain_image_path"), &GameSingleton::load_map_images); ClassDB::bind_method(D_METHOD("setup"), &GameSingleton::setup); ClassDB::bind_method(D_METHOD("get_province_index_from_uv_coords", "coords"), &GameSingleton::get_province_index_from_uv_coords); @@ -26,6 +26,7 @@ void GameSingleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_width"), &GameSingleton::get_width); ClassDB::bind_method(D_METHOD("get_height"), &GameSingleton::get_height); ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &GameSingleton::get_aspect_ratio); + ClassDB::bind_method(D_METHOD("get_terrain_texture"), &GameSingleton::get_terrain_texture); ClassDB::bind_method(D_METHOD("get_province_shape_image_subdivisions"), &GameSingleton::get_province_shape_image_subdivisions); ClassDB::bind_method(D_METHOD("get_province_shape_texture"), &GameSingleton::get_province_shape_texture); ClassDB::bind_method(D_METHOD("get_province_colour_texture"), &GameSingleton::get_province_colour_texture); @@ -57,27 +58,31 @@ GameSingleton* GameSingleton::get_singleton() { /* REQUIREMENTS: * MAP-21, MAP-25 */ -GameSingleton::GameSingleton() : game_manager{ [this]() { emit_signal("state_updated"); } } { +GameSingleton::GameSingleton() : game_manager{ [this]() { emit_signal("state_updated"); } }, terrain_variants{ "terrain variants" } { ERR_FAIL_COND(singleton != nullptr); singleton = this; Logger::set_info_func([](std::string&& str) { UtilityFunctions::print(str.c_str()); }); Logger::set_error_func([](std::string&& str) { UtilityFunctions::push_error(str.c_str()); }); + static constexpr colour_t HIGH_ALPHA_VALUE = to_alpha_value(0.5f); + static constexpr colour_t LOW_ALPHA_VALUE = to_alpha_value(0.2f); using mapmode_t = std::pair; const std::vector mapmodes = { - { "mapmode_province", [](Map const&, Province const& province) -> colour_t { return province.get_colour(); } }, + { "mapmode_terrain", [](Map const&, Province const& province) -> colour_t { + return LOW_ALPHA_VALUE | (province.is_water() ? 0x4287F5 : 0x0D7017); + } }, + { "mapmode_province", [](Map const&, Province const& province) -> colour_t { + return HIGH_ALPHA_VALUE | province.get_colour(); + } }, { "mapmode_region", [](Map const&, Province const& province) -> colour_t { Region const* region = province.get_region(); - if (region != nullptr) return region->get_colour(); - return province.get_colour(); - } }, - { "mapmode_terrain", [](Map const&, Province const& province) -> colour_t { - return province.is_water() ? 0x4287F5 : 0x0D7017; + if (region != nullptr) return (0xCC << 24) | region->get_colour(); + return NULL_COLOUR; } }, { "mapmode_index", [](Map const& map, Province const& province) -> colour_t { const uint8_t f = static_cast(province.get_index()) / static_cast(map.get_province_count()) * 255.0f; - return (f << 16) | (f << 8) | f; + return HIGH_ALPHA_VALUE | (f << 16) | (f << 8) | f; } } }; for (mapmode_t const& mapmode : mapmodes) @@ -99,7 +104,7 @@ GameSingleton::~GameSingleton() { singleton = nullptr; } -static Error load_json_file(String const& file_description, String const& file_path, Variant& result) { +static Error _load_json_file(String const& file_description, String const& file_path, Variant& result) { result.clear(); UtilityFunctions::print("Loading ", file_description, " file: ", file_path); const Ref file = FileAccess::open(file_path, FileAccess::ModeFlags::READ); @@ -122,10 +127,10 @@ static Error load_json_file(String const& file_description, String const& file_p using parse_json_entry_func_t = std::function; -static Error parse_json_dictionary_file(String const& file_description, String const& file_path, +static Error _parse_json_dictionary_file(String const& file_description, String const& file_path, String const& identifier_prefix, parse_json_entry_func_t parse_entry) { Variant json_var; - Error err = load_json_file(file_description, file_path, json_var); + Error err = _load_json_file(file_description, file_path, json_var); if (err != OK) return err; const Variant::Type type = json_var.get_type(); if (type != Variant::DICTIONARY) { @@ -150,40 +155,37 @@ static Error parse_json_dictionary_file(String const& file_description, String c return err; } -Error GameSingleton::_parse_province_identifier_entry(String const& identifier, Variant const& entry) { - const Variant::Type type = entry.get_type(); - colour_t colour = NULL_COLOUR; +static colour_t _parse_colour(Variant const& var) { + const Variant::Type type = var.get_type(); if (type == Variant::ARRAY) { - Array const& colour_array = entry; + Array const& colour_array = var; if (colour_array.size() == 3) { + colour_t colour = NULL_COLOUR; for (int jdx = 0; jdx < 3; ++jdx) { Variant const& var = colour_array[jdx]; - if (var.get_type() != Variant::FLOAT) { - colour = NULL_COLOUR; - break; - } + if (var.get_type() != Variant::FLOAT) return NULL_COLOUR; const double colour_double = var; - if (std::trunc(colour_double) != colour_double) { - colour = NULL_COLOUR; - break; - } + if (std::trunc(colour_double) != colour_double) return NULL_COLOUR; const int64_t colour_int = static_cast(colour_double); - if (colour_int < 0 || colour_int > 255) { - colour = NULL_COLOUR; - break; - } + if (colour_int < 0 || colour_int > 255) return NULL_COLOUR; colour = (colour << 8) | colour_int; } + return colour; } } else if (type == Variant::STRING) { - String const& colour_string = entry; + String const& colour_string = var; if (colour_string.is_valid_hex_number()) { const int64_t colour_int = colour_string.hex_to_int(); - if (0 <= colour_int && colour_int <= 0xFFFFFF) - colour = colour_int; + if (colour_int != NULL_COLOUR && colour_int <= MAX_COLOUR_RGB) + return colour_int; } } + return NULL_COLOUR; +} + +Error GameSingleton::_parse_province_identifier_entry(String const& identifier, Variant const& entry) { + const colour_t colour = _parse_colour(entry); if (colour == NULL_COLOUR) { UtilityFunctions::push_error("Invalid colour for province identifier \"", identifier, "\": ", entry); return FAILED; @@ -192,7 +194,7 @@ Error GameSingleton::_parse_province_identifier_entry(String const& identifier, } Error GameSingleton::load_province_identifier_file(String const& file_path) { - const Error err = parse_json_dictionary_file("province identifier", file_path, "prov_", + const Error err = _parse_json_dictionary_file("province identifier", file_path, "prov_", [this](String const& identifier, Variant const& entry) -> Error { return _parse_province_identifier_entry(identifier, entry); }); @@ -227,7 +229,7 @@ Error GameSingleton::_parse_region_entry(String const& identifier, Variant const } Error GameSingleton::load_region_file(String const& file_path) { - const Error err = parse_json_dictionary_file("region", file_path, "region_", + const Error err = _parse_json_dictionary_file("region", file_path, "region_", [this](String const& identifier, Variant const& entry) -> Error { return _parse_region_entry(identifier, entry); }); @@ -235,38 +237,107 @@ Error GameSingleton::load_region_file(String const& file_path) { return err; } -Error GameSingleton::load_province_shape_file(String const& file_path) { +TerrainVariant::TerrainVariant(std::string const& new_identfier, colour_t new_colour, Ref const& new_image) + : HasIdentifier(new_identfier), HasColour(new_colour), image(new_image) {} + +Ref TerrainVariant::get_image() const { return image; } + +Error GameSingleton::_parse_terrain_entry(String const& identifier, Variant const& entry) { + const colour_t colour = _parse_colour(entry); + if (colour == NULL_COLOUR) { + UtilityFunctions::push_error("Invalid colour for terrain texture \"", identifier, "\": ", entry); + return FAILED; + } + static const String terrain_folder = "res://art/terrain/"; + const String terrain_path = terrain_folder + identifier; + Ref terrain_image; + terrain_image.instantiate(); + const Error err = terrain_image->load(terrain_path); + if (err != OK) { + UtilityFunctions::push_error("Failed to load terrain image: ", terrain_path); + return err; + } + return ERR(terrain_variants.add_item({ identifier.utf8().get_data(), colour, terrain_image })); +} + +Error GameSingleton::load_terrain_file(String const& file_path) { + Error parse_err = _parse_json_dictionary_file("terrain variants", file_path, "", + [this](String const& identifier, Variant const& entry) -> Error { + return _parse_terrain_entry(identifier, entry); + }); + terrain_variants.lock(); + if (terrain_variants.get_item_count() == 0) parse_err = FAILED; + + Array terrain_images; + for (TerrainVariant const& var : terrain_variants.get_items()) { + terrain_variant_map[var.get_colour()] = terrain_images.size(); + terrain_images.append(var.get_image()); + } + + terrain_texture.instantiate(); + const Error texturearray_err = terrain_texture->create_from_images(terrain_images); + if (texturearray_err != OK) { + UtilityFunctions::push_error("Failed to create terrain texture array!"); + return texturearray_err; + } + return parse_err; +} + +Error GameSingleton::load_map_images(String const& province_image_path, String const& terrain_image_path) { if (province_shape_texture.is_valid()) { - UtilityFunctions::push_error("Province shape file has already been loaded, cannot load: ", file_path); + UtilityFunctions::push_error("Map images have already been loaded, cannot load: ", province_image_path, " and ", terrain_image_path); return FAILED; } - Ref province_shape_image; - province_shape_image.instantiate(); - Error err = province_shape_image->load(file_path); + + // Load images + Ref province_image, terrain_image; + province_image.instantiate(); + terrain_image.instantiate(); + Error err = province_image->load(province_image_path); + if (err != OK) { + UtilityFunctions::push_error("Failed to load province image: ", province_image_path); + return err; + } + err = terrain_image->load(terrain_image_path); if (err != OK) { - UtilityFunctions::push_error("Failed to load province shape file: ", file_path); + UtilityFunctions::push_error("Failed to load terrain image: ", terrain_image_path); return err; } - const Vector2i image_dims = province_shape_image->get_size(); - if (image_dims.x < 1 || image_dims.y < 1) { - UtilityFunctions::push_error("Invalid dimensions (", image_dims.x, "x", image_dims.y, ") for province shape file: ", file_path); + + // Validate dimensions and format + const Vector2i province_dims = province_image->get_size(), terrain_dims = terrain_image->get_size(); + if (province_dims.x < 1 || province_dims.y < 1) { + UtilityFunctions::push_error("Invalid dimensions (", province_dims.x, "x", province_dims.y, ") for province image: ", province_image_path); + err = FAILED; + } + if (province_dims != terrain_dims) { + UtilityFunctions::push_error("Invalid dimensions (", terrain_dims.x, "x", terrain_dims.y, ") for terrain image: ", + terrain_image_path, " (must match province image: (", province_dims.x, "x", province_dims.x, "))"); err = FAILED; } - static constexpr int32_t GPU_DIM_LIMIT = 0x3FFF; - // For each dimension of the image, this finds the small number of equal subdivisions required get the individual texture dims under GPU_DIM_LIMIT - for (int i = 0; i < 2; ++i) for (image_subdivisions[i] = 1; - image_dims[i] / image_subdivisions[i] > GPU_DIM_LIMIT || image_dims[i] % image_subdivisions[i] != 0; ++image_subdivisions[i]); static constexpr Image::Format expected_format = Image::FORMAT_RGB8; - const Image::Format format = province_shape_image->get_format(); - if (format != expected_format) { - UtilityFunctions::push_error("Invalid format (", format, ", should be ", expected_format, ") for province shape file: ", file_path); + const Image::Format province_format = province_image->get_format(), terrain_format = terrain_image->get_format(); + if (province_format != expected_format) { + UtilityFunctions::push_error("Invalid format (", province_format, ", should be ", expected_format, ") for province image: ", province_image_path); + err = FAILED; + } + if (terrain_format != expected_format) { + UtilityFunctions::push_error("Invalid format (", terrain_format, ", should be ", expected_format, ") for terrain image: ", terrain_image_path); err = FAILED; } if (err != OK) return err; - err = ERR(game_manager.map.generate_province_shape_image(image_dims.x, image_dims.y, province_shape_image->get_data().ptr())); - std::vector const& province_shape_data = game_manager.map.get_province_shape_image(); - const Vector2i divided_dims = image_dims / image_subdivisions; + // Generate interleaved province and terrain ID image + if (game_manager.map.generate_province_shape_image(province_dims.x, province_dims.y, province_image->get_data().ptr(), + terrain_image->get_data().ptr(), terrain_variant_map) != SUCCESS) return FAILED; + + static constexpr int32_t GPU_DIM_LIMIT = 0x3FFF; + // For each dimension of the image, this finds the small number of equal subdivisions required get the individual texture dims under GPU_DIM_LIMIT + for (int i = 0; i < 2; ++i) for (image_subdivisions[i] = 1; + province_dims[i] / image_subdivisions[i] > GPU_DIM_LIMIT || province_dims[i] % image_subdivisions[i] != 0; ++image_subdivisions[i]); + + Map::shape_pixel_t const* province_shape_data = game_manager.map.get_province_shape_image().data(); + const Vector2i divided_dims = province_dims / image_subdivisions; Array province_shape_images; province_shape_images.resize(image_subdivisions.x * image_subdivisions.y); for (int32_t v = 0; v < image_subdivisions.y; ++v) { @@ -276,15 +347,15 @@ Error GameSingleton::load_province_shape_file(String const& file_path) { for (int32_t y = 0; y < divided_dims.y; ++y) memcpy(index_data_array.ptrw() + y * divided_dims.x * sizeof(Map::shape_pixel_t), - province_shape_data.data() + (v * divided_dims.y + y) * image_dims.x + u * divided_dims.x, + province_shape_data + (v * divided_dims.y + y) * province_dims.x + u * divided_dims.x, divided_dims.x * sizeof(Map::shape_pixel_t)); - const Ref province_index_subimage = Image::create_from_data(divided_dims.x, divided_dims.y, false, Image::FORMAT_RGB8, index_data_array); - if (province_index_subimage.is_null()) { - UtilityFunctions::push_error("Failed to create province index image (", u, ", ", v, ")"); + const Ref province_shape_subimage = Image::create_from_data(divided_dims.x, divided_dims.y, false, Image::FORMAT_RGB8, index_data_array); + if (province_shape_subimage.is_null()) { + UtilityFunctions::push_error("Failed to create province shape image (", u, ", ", v, ")"); err = FAILED; } - province_shape_images[u + v * image_subdivisions.x] = province_index_subimage; + province_shape_images[u + v * image_subdivisions.x] = province_shape_subimage; } } @@ -305,7 +376,7 @@ godot::Error GameSingleton::setup() { Error GameSingleton::load_water_province_file(String const& file_path) { Variant json_var; - Error err = load_json_file("water province", file_path, json_var); + Error err = _load_json_file("water province", file_path, json_var); if (err != OK) return err; Variant::Type type = json_var.get_type(); if (type != Variant::ARRAY) { @@ -358,7 +429,7 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { buildings_array.resize(buildings.size()); for (size_t idx = 0; idx < buildings.size(); ++idx) { KEY(building) KEY(level) KEY(expansion_state) KEY(start_date) KEY(end_date) KEY(expansion_progress) - + Dictionary building_dict; Building const& building = buildings[idx]; building_dict[building_key] = building.get_identifier().c_str(); @@ -388,6 +459,10 @@ float GameSingleton::get_aspect_ratio() const { return static_cast(get_width()) / static_cast(get_height()); } +Ref GameSingleton::get_terrain_texture() const { + return terrain_texture; +} + Vector2i GameSingleton::get_province_shape_image_subdivisions() const { return image_subdivisions; } @@ -413,7 +488,7 @@ Error GameSingleton::update_colour_image() { if (province_colour_image.is_null()) province_colour_image.instantiate(); province_colour_image->set_data(PROVINCE_INDEX_SQRT, PROVINCE_INDEX_SQRT, - false, Image::FORMAT_RGB8, colour_data_array); + false, Image::FORMAT_RGBA8, colour_data_array); if (province_colour_image.is_null()) { UtilityFunctions::push_error("Failed to update province colour image"); return FAILED; -- cgit v1.2.3-56-ga3b1