diff options
Diffstat (limited to 'src')
24 files changed, 618 insertions, 241 deletions
diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp index 6c600ae..4d62bc1 100644 --- a/src/openvic-simulation/GameManager.cpp +++ b/src/openvic-simulation/GameManager.cpp @@ -1,6 +1,9 @@ #include "GameManager.hpp" +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; +using namespace OpenVic::colour_literals; GameManager::GameManager(state_updated_func_t state_updated_callback) : clock { @@ -75,54 +78,35 @@ bool GameManager::expand_building(Province::index_t province_index, std::string_ return province->expand_building(building_type_identifier); } -static constexpr colour_t ALPHA_VALUE = float_to_alpha_value(0.7f); - -static constexpr Mapmode::base_stripe_t combine_base_stripe(colour_t base, colour_t stripe) { - return (static_cast<Mapmode::base_stripe_t>(stripe) << (sizeof(colour_t) * 8)) | base; -} - -static constexpr Mapmode::base_stripe_t make_solid_base_stripe(colour_t colour) { - return combine_base_stripe(colour, colour); -} - -static constexpr auto make_solid_base_stripe_func(auto func) { - return [func](Map const& map, Province const& province) -> Mapmode::base_stripe_t { - return make_solid_base_stripe(func(map, province)); - }; -} - -template<std::derived_from<HasColour> T> -static constexpr Mapmode::base_stripe_t get_colour_mapmode(T const* item) { - return item != nullptr ? make_solid_base_stripe(ALPHA_VALUE | item->get_colour()) : NULL_COLOUR; -} +static constexpr colour_argb_t::value_type ALPHA_VALUE = colour_argb_t::colour_traits::alpha_from_float(0.7f); -template<std::derived_from<HasColour> T> +template<IsColour ColourT = colour_t, std::derived_from<_HasColour<ColourT>> T> static constexpr auto get_colour_mapmode(T const*(Province::*get_item)() const) { return [get_item](Map const& map, Province const& province) -> Mapmode::base_stripe_t { T const* item = (province.*get_item)(); - return item != nullptr ? make_solid_base_stripe(ALPHA_VALUE | item->get_colour()) : NULL_COLOUR; + return item != nullptr ? colour_argb_t { item->get_colour(), ALPHA_VALUE } : colour_argb_t::null(); }; } -template<std::derived_from<HasColour> T> +template<IsColour ColourT = colour_t, std::derived_from<_HasColour<ColourT>> T> static constexpr Mapmode::base_stripe_t shaded_mapmode(fixed_point_map_t<T const*> const& map) { const std::pair<fixed_point_map_const_iterator_t<T const*>, fixed_point_map_const_iterator_t<T const*>> largest = get_largest_two_items(map); if (largest.first != map.end()) { - const colour_t base_colour = ALPHA_VALUE | largest.first->first->get_colour(); + const colour_argb_t base_colour = colour_argb_t { largest.first->first->get_colour(), ALPHA_VALUE }; if (largest.second != map.end()) { /* If second largest is at least a third... */ if (largest.second->second * 3 >= get_total(map)) { - const colour_t stripe_colour = ALPHA_VALUE | largest.second->first->get_colour(); - return combine_base_stripe(base_colour, stripe_colour); + const colour_argb_t stripe_colour = colour_argb_t { largest.second->first->get_colour(), ALPHA_VALUE }; + return { base_colour, stripe_colour }; } } - return make_solid_base_stripe(base_colour); + return base_colour; } - return NULL_COLOUR; + return colour_argb_t::null(); } -template<std::derived_from<HasColour> T> +template<IsColour ColourT = colour_t, std::derived_from<_HasColour<ColourT>> T> static constexpr auto shaded_mapmode(fixed_point_map_t<T const*> const&(Province::*get_map)() const) { return [get_map](Map const& map, Province const& province) -> Mapmode::base_stripe_t { return shaded_mapmode((province.*get_map)()); @@ -137,7 +121,7 @@ bool GameManager::load_hardcoded_defines() { { "mapmode_terrain", [](Map const&, Province const& province) -> Mapmode::base_stripe_t { - return NULL_COLOUR; + return colour_argb_t::null(); } }, { @@ -145,19 +129,20 @@ bool GameManager::load_hardcoded_defines() { }, { "mapmode_province", - make_solid_base_stripe_func([](Map const&, Province const& province) -> colour_t { - return ALPHA_VALUE | province.get_colour(); - }) + [](Map const&, Province const& province) -> Mapmode::base_stripe_t { + return colour_argb_t { province.get_colour(), ALPHA_VALUE }; + } }, { "mapmode_region", get_colour_mapmode(&Province::get_region) }, { "mapmode_index", - make_solid_base_stripe_func([](Map const& map, Province const& province) -> colour_t { - const colour_t f = fraction_to_colour_byte(province.get_index(), map.get_province_count() + 1); - return ALPHA_VALUE | (f << 16) | (f << 8) | f; - }) + [](Map const& map, Province const& province) -> Mapmode::base_stripe_t { + const colour_argb_t::value_type f = + colour_argb_t::colour_traits::component_from_fraction(province.get_index(), map.get_province_count() + 1); + return colour_argb_t::fill_as(f).with_alpha(ALPHA_VALUE); + } }, { "mapmode_terrain_type", get_colour_mapmode(&Province::get_terrain_type) @@ -167,36 +152,35 @@ bool GameManager::load_hardcoded_defines() { }, { "mapmode_infrastructure", - make_solid_base_stripe_func([](Map const& map, Province const& province) -> colour_t { + [](Map const& map, Province const& province) -> Mapmode::base_stripe_t { BuildingInstance const* railroad = province.get_building_by_identifier("railroad"); if (railroad != nullptr) { - colour_t val = fraction_to_colour_byte(railroad->get_level(), - railroad->get_building_type().get_max_level() + 1, 0.5f, 1.0f); + const colour_argb_t::value_type val = colour_argb_t::colour_traits::component_from_fraction( + railroad->get_level(), railroad->get_building_type().get_max_level() + 1, 0.5f, 1.0f + ); switch (railroad->get_expansion_state()) { case BuildingInstance::ExpansionState::CannotExpand: - val <<= 16; - break; + return colour_argb_t { val, 0, 0, ALPHA_VALUE }; case BuildingInstance::ExpansionState::CanExpand: - break; + return colour_argb_t { 0, 0, val, ALPHA_VALUE }; default: - val <<= 8; - break; + return colour_argb_t { 0, val, 0, ALPHA_VALUE }; } - return ALPHA_VALUE | val; } - return NULL_COLOUR; - }) + return colour_argb_t::null(); + } }, { "mapmode_population", - make_solid_base_stripe_func([](Map const& map, Province const& province) -> colour_t { + [](Map const& map, Province const& province) -> Mapmode::base_stripe_t { // TODO - explore non-linear scaling to have more variation among non-massive provinces // TODO - when selecting a province, only show the population of provinces controlled (or owned?) // by the same country, relative to the most populous province in that set of provinces - return ALPHA_VALUE | (fraction_to_colour_byte( + const colour_argb_t::value_type val = colour_argb_t::colour_traits::component_from_fraction( province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f - ) << 8); - }) + ); + return colour_argb_t { 0, val, 0, ALPHA_VALUE }; + } }, { "mapmode_culture", shaded_mapmode(&Province::get_culture_distribution) @@ -209,37 +193,38 @@ bool GameManager::load_hardcoded_defines() { Province const* selected_province = map.get_selected_province(); if (selected_province != nullptr) { if (selected_province == &province) { - return make_solid_base_stripe(ALPHA_VALUE | 0xFFFFFF); + return (0xFFFFFF_argb).with_alpha(ALPHA_VALUE); } - colour_t base = NULL_COLOUR, stripe = NULL_COLOUR; + colour_argb_t base = colour_argb_t::null(), stripe = colour_argb_t::null(); Province::adjacency_t const* adj = selected_province->get_adjacency_to(&province); if (adj != nullptr) { - using enum Province::adjacency_t::type_t; + colour_argb_t::integer_type base_int; switch (adj->get_type()) { - case LAND: base = 0x00FF00; break; - case WATER: base = 0x0000FF; break; - case COASTAL: base = 0xF9D199; break; - case IMPASSABLE: base = 0x8B4513; break; - case STRAIT: base = 0x00FFFF; break; - case CANAL: base = 0x888888; break; - default: base = 0xFF0000; break; + using enum Province::adjacency_t::type_t; + case LAND: base_int = 0x00FF00; break; + case WATER: base_int = 0x0000FF; break; + case COASTAL: base_int = 0xF9D199; break; + case IMPASSABLE: base_int = 0x8B4513; break; + case STRAIT: base_int = 0x00FFFF; break; + case CANAL: base_int = 0x888888; break; + default: base_int = 0xFF0000; break; } - base |= ALPHA_VALUE; + base = colour_argb_t::from_integer(base_int).with_alpha(ALPHA_VALUE); stripe = base; } if (selected_province->has_adjacency_going_through(&province)) { - stripe = ALPHA_VALUE | 0xFFFF00; + stripe = (0xFFFF00_argb).with_alpha(ALPHA_VALUE); } - return combine_base_stripe(base, stripe); + return { base, stripe }; } - return NULL_COLOUR; + return colour_argb_t::null(); } }, { - "mapmode_port", make_solid_base_stripe_func([](Map const& map, Province const& province) -> colour_t { - return province.has_port() ? ALPHA_VALUE | 0xFFFFFF : NULL_COLOUR; - }) + "mapmode_port", [](Map const& map, Province const& province) -> Mapmode::base_stripe_t { + return province.has_port() ? (0xFFFFFF_argb).with_alpha(ALPHA_VALUE) : colour_argb_t::null(); + } } }; diff --git a/src/openvic-simulation/country/Country.cpp b/src/openvic-simulation/country/Country.cpp index cb4de8a..299407c 100644 --- a/src/openvic-simulation/country/Country.cpp +++ b/src/openvic-simulation/country/Country.cpp @@ -31,7 +31,7 @@ Country::Country( std::string_view new_identifier, colour_t new_colour, GraphicalCultureType const& new_graphical_culture, IdentifierRegistry<CountryParty>&& new_parties, unit_names_map_t&& new_unit_names, bool new_dynamic_tag, government_colour_map_t&& new_alternative_colours -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, graphical_culture { new_graphical_culture }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, graphical_culture { new_graphical_culture }, parties { std::move(new_parties) }, unit_names { std::move(new_unit_names) }, dynamic_tag { new_dynamic_tag }, alternative_colours { std::move(new_alternative_colours) } {} @@ -50,10 +50,6 @@ bool CountryManager::add_country( ); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid country colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } if (graphical_culture == nullptr) { Logger::error("Null graphical culture for country ", identifier); return false; diff --git a/src/openvic-simulation/dataloader/NodeTools.cpp b/src/openvic-simulation/dataloader/NodeTools.cpp index c4addb7..e68a185 100644 --- a/src/openvic-simulation/dataloader/NodeTools.cpp +++ b/src/openvic-simulation/dataloader/NodeTools.cpp @@ -1,5 +1,7 @@ #include "NodeTools.hpp" +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -118,31 +120,33 @@ node_callback_t NodeTools::expect_fixed_point(callback_t<fixed_point_t> callback node_callback_t NodeTools::expect_colour(callback_t<colour_t> callback) { return [callback](ast::NodeCPtr node) -> bool { - colour_t col = NULL_COLOUR; - uint32_t components = 0; + colour_t col = colour_t::null(); + int32_t components = 0; bool ret = expect_list_of_length(3, expect_fixed_point( [&col, &components](fixed_point_t val) -> bool { - components++; - col <<= 8; if (val < 0 || val > 255) { - Logger::error("Invalid colour component: ", val); + Logger::error("Invalid colour component #", components++, ": ", val); return false; } else { if (val <= 1) { val *= 255; + } else if (!val.is_integer()) { + Logger::warning("Fractional part of colour component #", components, " will be truncated: ", val); } - col |= val.to_int32_t(); + col[components++] = val.to_int64_t(); return true; } } ))(node); - ret &= callback(col << 8 * (3 - components)); + ret &= callback(col); return ret; }; } -node_callback_t NodeTools::expect_colour_hex(callback_t<colour_t> callback) { - return expect_uint(callback, 16); +node_callback_t NodeTools::expect_colour_hex(callback_t<colour_argb_t> callback) { + return expect_uint<colour_argb_t::integer_type>([callback](colour_argb_t::integer_type val) -> bool { + return callback(colour_argb_t::from_integer(val)); + }, 16); } callback_t<std::string_view> NodeTools::expect_date_str(callback_t<Date> callback) { diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp index 02ff239..c3eaf65 100644 --- a/src/openvic-simulation/dataloader/NodeTools.hpp +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -106,8 +106,10 @@ namespace OpenVic { callback_t<std::string_view> expect_fixed_point_str(callback_t<fixed_point_t> callback); node_callback_t expect_fixed_point(callback_t<fixed_point_t> callback); + /* Expect a list of 3 base 10 values, each either in the range [0, 1] or (1, 255], representing RGB components. */ node_callback_t expect_colour(callback_t<colour_t> callback); - node_callback_t expect_colour_hex(callback_t<colour_t> callback); + /* Expect a hexadecimal value representing a colour in ARGB format. */ + node_callback_t expect_colour_hex(callback_t<colour_argb_t> callback); callback_t<std::string_view> expect_date_str(callback_t<Date> callback); node_callback_t expect_date(callback_t<Date> callback); diff --git a/src/openvic-simulation/economy/Good.cpp b/src/openvic-simulation/economy/Good.cpp index 5940df2..3b420f1 100644 --- a/src/openvic-simulation/economy/Good.cpp +++ b/src/openvic-simulation/economy/Good.cpp @@ -10,7 +10,7 @@ GoodCategory::GoodCategory(std::string_view new_identifier) : HasIdentifier { ne Good::Good( std::string_view new_identifier, colour_t new_colour, index_t new_index, GoodCategory const& new_category, price_t new_base_price, bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, index { new_index }, category { new_category }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, index { new_index }, category { new_category }, base_price { new_base_price }, available_from_start { new_available_from_start }, tradeable { new_tradeable }, money { new_money }, overseas_penalty { new_overseas_penalty } { assert(base_price > NULL_PRICE); @@ -37,10 +37,6 @@ bool GoodManager::add_good( Logger::error("Invalid good identifier - empty!"); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid good colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } if (base_price <= Good::NULL_PRICE) { Logger::error("Invalid base price for ", identifier, ": ", base_price); return false; @@ -71,7 +67,7 @@ bool GoodManager::load_goods_file(ast::NodeCPtr root) { goods.reserve(goods.size() + total_expected_goods); ret &= expect_good_category_dictionary([this](GoodCategory const& good_category, ast::NodeCPtr good_category_value) -> bool { return expect_dictionary([this, &good_category](std::string_view key, ast::NodeCPtr value) -> bool { - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); Good::price_t base_price; bool available_from_start = true, tradeable = true; bool money = false, overseas_penalty = false; diff --git a/src/openvic-simulation/interface/GFX.cpp b/src/openvic-simulation/interface/GFX.cpp index f9ae56b..3624655 100644 --- a/src/openvic-simulation/interface/GFX.cpp +++ b/src/openvic-simulation/interface/GFX.cpp @@ -4,8 +4,8 @@ using namespace OpenVic; using namespace OpenVic::GFX; using namespace OpenVic::NodeTools; -Font::Font(std::string_view new_identifier, colour_t new_colour, std::string_view new_fontname) - : HasIdentifierAndColour { new_identifier, new_colour, false, true }, fontname { new_fontname } {} +Font::Font(std::string_view new_identifier, colour_argb_t new_colour, std::string_view new_fontname) + : HasIdentifierAndAlphaColour { new_identifier, new_colour, false }, fontname { new_fontname } {} node_callback_t Sprite::expect_sprite(callback_t<std::unique_ptr<Sprite>&&> callback) { return expect_dictionary_keys( diff --git a/src/openvic-simulation/interface/GFX.hpp b/src/openvic-simulation/interface/GFX.hpp index c7523b3..cdc49c2 100644 --- a/src/openvic-simulation/interface/GFX.hpp +++ b/src/openvic-simulation/interface/GFX.hpp @@ -8,7 +8,7 @@ namespace OpenVic { namespace OpenVic::GFX { - struct Font : HasIdentifierAndColour { + struct Font : HasIdentifierAndAlphaColour { friend class OpenVic::UIManager; private: @@ -16,7 +16,7 @@ namespace OpenVic::GFX { // TODO - colorcodes, effect - Font(std::string_view new_identifier, colour_t new_colour, std::string_view new_fontname); + Font(std::string_view new_identifier, colour_argb_t new_colour, std::string_view new_fontname); public: Font(Font&&) = default; diff --git a/src/openvic-simulation/interface/UI.cpp b/src/openvic-simulation/interface/UI.cpp index 3fc8295..344fc15 100644 --- a/src/openvic-simulation/interface/UI.cpp +++ b/src/openvic-simulation/interface/UI.cpp @@ -1,17 +1,23 @@ #include "UI.hpp" +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; using namespace OpenVic::GFX; using namespace OpenVic::GUI; -bool UIManager::add_font(std::string_view identifier, colour_t colour, std::string_view fontname) { +bool UIManager::add_font(std::string_view identifier, colour_argb_t colour, std::string_view fontname) { if (identifier.empty()) { Logger::error("Invalid font identifier - empty!"); return false; } + if (colour.alpha == colour_argb_t::colour_traits::null) { + Logger::error("Invalid colour for font ", identifier, " - completely transparent! (", colour, ")"); + return false; + } if (fontname.empty()) { - Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); + Logger::error("Invalid fontname for font ", identifier, " - empty!"); return false; } return fonts.add_item({ identifier, colour, fontname }, duplicate_warning_callback); @@ -19,7 +25,7 @@ bool UIManager::add_font(std::string_view identifier, colour_t colour, std::stri bool UIManager::_load_font(ast::NodeCPtr node) { std::string_view identifier, fontname; - colour_t colour = NULL_COLOUR; + colour_argb_t colour = colour_argb_t::null(); bool ret = expect_dictionary_keys( "name", ONE_EXACTLY, expect_string(assign_variable_callback(identifier)), "fontname", ONE_EXACTLY, expect_string(assign_variable_callback(fontname)), diff --git a/src/openvic-simulation/interface/UI.hpp b/src/openvic-simulation/interface/UI.hpp index ce9336c..286e4f7 100644 --- a/src/openvic-simulation/interface/UI.hpp +++ b/src/openvic-simulation/interface/UI.hpp @@ -12,7 +12,7 @@ namespace OpenVic { bool _load_font(ast::NodeCPtr node); public: - bool add_font(std::string_view identifier, colour_t colour, std::string_view fontname); + bool add_font(std::string_view identifier, colour_argb_t colour, std::string_view fontname); bool load_gfx_file(ast::NodeCPtr root); bool load_gui_file(std::string_view scene_name, ast::NodeCPtr root); diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 99108e4..aa8ed5e 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -5,11 +5,13 @@ #include "openvic-simulation/economy/Good.hpp" #include "openvic-simulation/history/ProvinceHistory.hpp" +#include "openvic-simulation/types/Colour.hpp" #include "openvic-simulation/utility/BMP.hpp" #include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; using namespace OpenVic::NodeTools; +using namespace OpenVic::colour_literals; Mapmode::Mapmode( std::string_view new_identifier, index_t new_index, colour_func_t new_colour_func @@ -18,11 +20,13 @@ Mapmode::Mapmode( } const Mapmode Mapmode::ERROR_MAPMODE { - "mapmode_error", 0, [](Map const& map, Province const& province) -> colour_t { return 0xFFFF0000; } + "mapmode_error", 0, [](Map const& map, Province const& province) -> base_stripe_t { + return { 0xFFFF0000_argb, colour_argb_t::null() }; + } }; Mapmode::base_stripe_t Mapmode::get_base_stripe_colours(Map const& map, Province const& province) const { - return colour_func ? colour_func(map, province) : NULL_COLOUR; + return colour_func ? colour_func(map, province) : colour_argb_t::null(); } Map::Map() @@ -47,8 +51,8 @@ bool Map::add_province(std::string_view identifier, colour_t colour) { ); return false; } - if (colour == NULL_COLOUR || colour > MAX_COLOUR_RGB) { - Logger::error("Invalid province colour for ", identifier, ": ", colour_to_hex_string(colour)); + if (colour.is_null()) { + Logger::error("Invalid province colour for ", identifier, " - null! (", colour, ")"); return false; } Province new_province { identifier, colour, static_cast<Province::index_t>(provinces.size() + 1) }; @@ -226,18 +230,18 @@ bool Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) cons } for (Province const& province : provinces.get_items()) { const Mapmode::base_stripe_t base_stripe = mapmode->get_base_stripe_colours(*this, province); - const colour_t base_colour = static_cast<colour_t>(base_stripe); - const colour_t stripe_colour = static_cast<colour_t>(base_stripe >> (sizeof(colour_t) * 8)); + colour_argb_t const& base_colour = base_stripe.base_colour; + colour_argb_t const& stripe_colour = base_stripe.stripe_colour; - *target++ = (base_colour >> 16) & COLOUR_COMPONENT; // red - *target++ = (base_colour >> 8) & COLOUR_COMPONENT; // green - *target++ = (base_colour >> 0) & COLOUR_COMPONENT; // blue - *target++ = (base_colour >> 24) & COLOUR_COMPONENT; // alpha + *target++ = base_colour.red; + *target++ = base_colour.green; + *target++ = base_colour.blue; + *target++ = base_colour.alpha; - *target++ = (stripe_colour >> 16) & COLOUR_COMPONENT; // red - *target++ = (stripe_colour >> 8) & COLOUR_COMPONENT; // green - *target++ = (stripe_colour >> 0) & COLOUR_COMPONENT; // blue - *target++ = (stripe_colour >> 24) & COLOUR_COMPONENT; // alpha + *target++ = stripe_colour.red; + *target++ = stripe_colour.green; + *target++ = stripe_colour.blue; + *target++ = stripe_colour.alpha; } return ret; } @@ -302,7 +306,7 @@ void Map::tick(Date today) { using namespace ovdl::csv; -static bool validate_province_definitions_header(LineObject const& header) { +static bool _validate_province_definitions_header(LineObject const& header) { static const std::vector<std::string> standard_header { "province", "red", "green", "blue" }; for (size_t i = 0; i < standard_header.size(); ++i) { const std::string_view val = header.get_value_for(i); @@ -316,18 +320,17 @@ static bool validate_province_definitions_header(LineObject const& header) { return true; } -static bool parse_province_colour(colour_t& colour, std::array<std::string_view, 3> components) { +static bool _parse_province_colour(colour_t& colour, std::array<std::string_view, 3> components) { bool ret = true; - colour = NULL_COLOUR; - for (std::string_view& c : components) { - colour <<= 8; - if (c.ends_with('.')) { - c.remove_suffix(1); + for (size_t i = 0; i < 3; ++i) { + std::string_view& component = components[i]; + if (component.ends_with('.')) { + component.remove_suffix(1); } bool successful = false; - uint64_t val = StringUtils::string_to_uint64(c, &successful, 10); - if (successful && val <= 255) { - colour |= val; + const uint64_t val = StringUtils::string_to_uint64(component, &successful, 10); + if (successful && val <= colour_t::max_value) { + colour[i] = val; } else { ret = false; } @@ -342,7 +345,7 @@ bool Map::load_province_definitions(std::vector<LineObject> const& lines) { } { LineObject const& header = lines.front(); - if (!validate_province_definitions_header(header)) { + if (!_validate_province_definitions_header(header)) { Logger::error( "Non-standard province definition file header - make sure this is not a province definition: ", header ); @@ -357,8 +360,8 @@ bool Map::load_province_definitions(std::vector<LineObject> const& lines) { std::for_each(lines.begin() + 1, lines.end(), [this, &ret](LineObject const& line) -> void { const std::string_view identifier = line.get_value_for(0); if (!identifier.empty()) { - colour_t colour = NULL_COLOUR; - if (!parse_province_colour(colour, { line.get_value_for(1), line.get_value_for(2), line.get_value_for(3) })) { + colour_t colour = colour_t::null(); + if (!_parse_province_colour(colour, { line.get_value_for(1), line.get_value_for(2), line.get_value_for(3) })) { Logger::error("Error reading colour in province definition: ", line); ret = false; } @@ -417,7 +420,7 @@ static constexpr colour_t colour_at(uint8_t const* colour_data, int32_t idx) { * triplet, then combine the bytes in reverse order. */ idx *= 3; - return (colour_data[idx + 2] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx]; + return { colour_data[idx + 2], colour_data[idx + 1], colour_data[idx] }; } bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain_path, bool detailed_errors) { @@ -508,7 +511,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain unrecognised_province_colours.insert(province_colour); if (detailed_errors) { Logger::warning( - "Unrecognised province colour ", colour_to_hex_string(province_colour), " at (", x, ", ", y, ")" + "Unrecognised province colour ", province_colour, " at (", x, ", ", y, ")" ); } } diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp index 185e99e..9cee86b 100644 --- a/src/openvic-simulation/map/Map.hpp +++ b/src/openvic-simulation/map/Map.hpp @@ -6,8 +6,9 @@ #include <openvic-dataloader/csv/LineObject.hpp> #include "openvic-simulation/map/Region.hpp" -#include "openvic-simulation/map/TerrainType.hpp" #include "openvic-simulation/map/State.hpp" +#include "openvic-simulation/map/TerrainType.hpp" +#include "openvic-simulation/types/Colour.hpp" namespace OpenVic { namespace fs = std::filesystem; @@ -17,7 +18,13 @@ namespace OpenVic { /* Bottom 32 bits are the base colour, top 32 are the stripe colour, both in ARGB format with the alpha channels * controlling interpolation with the terrain colour (0 = all terrain, 255 = all corresponding RGB) */ - using base_stripe_t = uint64_t; + struct base_stripe_t { + colour_argb_t base_colour; + colour_argb_t stripe_colour; + constexpr base_stripe_t(colour_argb_t base, colour_argb_t stripe) + : base_colour { base }, stripe_colour { stripe } {} + constexpr base_stripe_t(colour_argb_t both) : base_stripe_t { both, both } {} + }; using colour_func_t = std::function<base_stripe_t(Map const&, Province const&)>; using index_t = size_t; diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp index 6f1a0f6..79a6202 100644 --- a/src/openvic-simulation/map/Province.cpp +++ b/src/openvic-simulation/map/Province.cpp @@ -7,7 +7,7 @@ using namespace OpenVic::NodeTools; Province::Province( std::string_view new_identifier, colour_t new_colour, index_t new_index -) : HasIdentifierAndColour { new_identifier, new_colour, true, false }, index { new_index }, region { nullptr }, +) : HasIdentifierAndColour { new_identifier, new_colour, true }, index { new_index }, region { nullptr }, on_map { false }, has_region { false }, water { false }, coastal { false }, port { false }, default_terrain_type { nullptr }, positions {}, terrain_type { nullptr }, life_rating { 0 }, colony_status { colony_status_t::STATE }, state { nullptr }, owner { nullptr }, controller { nullptr }, slave { false }, @@ -21,7 +21,7 @@ bool Province::operator==(Province const& other) const { std::string Province::to_string() const { std::stringstream stream; - stream << "(#" << std::to_string(index) << ", " << get_identifier() << ", 0x" << colour_to_hex_string() << ")"; + stream << "(#" << std::to_string(index) << ", " << get_identifier() << ", 0x" << get_colour() << ")"; return stream.str(); } diff --git a/src/openvic-simulation/map/Region.cpp b/src/openvic-simulation/map/Region.cpp index e33d9c9..18a47a9 100644 --- a/src/openvic-simulation/map/Region.cpp +++ b/src/openvic-simulation/map/Region.cpp @@ -1,5 +1,7 @@ #include "Region.hpp" +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; ProvinceSet::ProvinceSet(provinces_t&& new_provinces) : provinces { std::move(new_provinces) } {} @@ -65,11 +67,11 @@ ProvinceSet::provinces_t const& ProvinceSet::get_provinces() const { return provinces; } -static constexpr colour_t ERROR_REGION_COLOUR = COLOUR_COMPONENT << 16; +static constexpr colour_t ERROR_REGION_COLOUR { colour_t::max_value, 0, 0 }; Region::Region(std::string_view new_identifier, provinces_t&& new_provinces, bool new_meta) : HasIdentifierAndColour { - new_identifier, new_provinces.size() > 0 ? new_provinces.front()->get_colour() : ERROR_REGION_COLOUR, false, false + new_identifier, new_provinces.size() > 0 ? new_provinces.front()->get_colour() : ERROR_REGION_COLOUR, false }, ProvinceSet { std::move(new_provinces) }, meta { new_meta } { lock(); } diff --git a/src/openvic-simulation/map/TerrainType.cpp b/src/openvic-simulation/map/TerrainType.cpp index bab2a3c..a4529bf 100644 --- a/src/openvic-simulation/map/TerrainType.cpp +++ b/src/openvic-simulation/map/TerrainType.cpp @@ -2,12 +2,14 @@ #include <limits> +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; TerrainType::TerrainType( std::string_view new_identifier, colour_t new_colour, ModifierValue&& new_modifier, bool new_is_water -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, modifier { std::move(new_modifier) }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, modifier { std::move(new_modifier) }, is_water { new_is_water } {} TerrainTypeMapping::TerrainTypeMapping( @@ -23,10 +25,6 @@ bool TerrainTypeManager::add_terrain_type( Logger::error("Invalid terrain type identifier - empty!"); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid terrain type colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } return terrain_types.add_item({ identifier, colour, std::move(values), is_water }); } @@ -68,7 +66,7 @@ node_callback_t TerrainTypeManager::_load_terrain_type_categories(ModifierManage const bool ret = expect_dictionary_reserve_length(terrain_types, [this, &modifier_manager](std::string_view type_key, ast::NodeCPtr type_node) -> bool { ModifierValue values; - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); bool is_water = false; bool ret = modifier_manager.expect_modifier_value_and_keys(move_variable_callback(values), "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), diff --git a/src/openvic-simulation/politics/Ideology.cpp b/src/openvic-simulation/politics/Ideology.cpp index fd5005d..577ab79 100644 --- a/src/openvic-simulation/politics/Ideology.cpp +++ b/src/openvic-simulation/politics/Ideology.cpp @@ -1,5 +1,7 @@ #include "Ideology.hpp" +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -8,7 +10,7 @@ IdeologyGroup::IdeologyGroup(std::string_view new_identifier) : HasIdentifier { Ideology::Ideology( std::string_view new_identifier, colour_t new_colour, IdeologyGroup const& new_group, bool new_uncivilised, bool new_can_reduce_militancy, Date new_spawn_date -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, group { new_group }, uncivilised { new_uncivilised }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, group { new_group }, uncivilised { new_uncivilised }, can_reduce_militancy { new_can_reduce_militancy }, spawn_date { new_spawn_date } {} bool IdeologyManager::add_ideology_group(std::string_view identifier) { @@ -29,11 +31,6 @@ bool IdeologyManager::add_ideology( return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid ideology colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - if (group == nullptr) { Logger::error("Null ideology group for ", identifier); return false; @@ -61,7 +58,7 @@ bool IdeologyManager::load_ideology_file(ast::NodeCPtr root) { IdeologyGroup const* ideology_group = get_ideology_group_by_identifier(ideology_group_key); return expect_dictionary([this, ideology_group](std::string_view key, ast::NodeCPtr value) -> bool { - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); bool uncivilised = true, can_reduce_militancy = false; Date spawn_date; diff --git a/src/openvic-simulation/pop/Culture.cpp b/src/openvic-simulation/pop/Culture.cpp index ee8ffd6..9b9d6c2 100644 --- a/src/openvic-simulation/pop/Culture.cpp +++ b/src/openvic-simulation/pop/Culture.cpp @@ -1,6 +1,7 @@ #include "Culture.hpp" #include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/types/Colour.hpp" using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -16,7 +17,7 @@ CultureGroup::CultureGroup( Culture::Culture( std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, std::vector<std::string>&& new_first_names, std::vector<std::string>&& new_last_names -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, group { new_group }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, group { new_group }, first_names { std::move(new_first_names) }, last_names { std::move(new_last_names) } {} bool CultureManager::add_graphical_culture_type(std::string_view identifier) { @@ -61,10 +62,6 @@ bool CultureManager::add_culture( Logger::error("Invalid culture identifier - empty!"); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } return cultures.add_item({ identifier, colour, group, std::move(first_names), std::move(last_names) }); } @@ -101,7 +98,7 @@ bool CultureManager::_load_culture( CultureGroup const& culture_group, std::string_view culture_key, ast::NodeCPtr culture_node ) { - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); std::vector<std::string> first_names, last_names; bool ret = expect_dictionary_keys( diff --git a/src/openvic-simulation/pop/Pop.cpp b/src/openvic-simulation/pop/Pop.cpp index 39deeaa..2b031c2 100644 --- a/src/openvic-simulation/pop/Pop.cpp +++ b/src/openvic-simulation/pop/Pop.cpp @@ -5,6 +5,7 @@ #include "openvic-simulation/dataloader/NodeTools.hpp" #include "openvic-simulation/map/Province.hpp" #include "openvic-simulation/politics/Rebel.hpp" +#include "openvic-simulation/types/Colour.hpp" #include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -32,7 +33,7 @@ PopType::PopType( bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_allowed_to_vote, bool new_is_slave, bool new_can_be_recruited, bool new_can_reduce_consciousness, bool new_administrative_efficiency, bool new_can_build, bool new_factory, bool new_can_work_factory, bool new_unemployment -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, strata { new_strata }, sprite { new_sprite }, +) : HasIdentifierAndColour { new_identifier, new_colour, false }, strata { new_strata }, sprite { new_sprite }, life_needs { std::move(new_life_needs) }, everyday_needs { std::move(new_everyday_needs) }, luxury_needs { std::move(new_luxury_needs) }, rebel_units { std::move(new_rebel_units) }, max_size { new_max_size }, merge_max_size { new_merge_max_size }, state_capital_only { new_state_capital_only }, @@ -68,10 +69,6 @@ bool PopManager::add_pop_type( Logger::error("Invalid pop type identifier - empty!"); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid pop type colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } if (strata == nullptr) { Logger::error("Invalid pop type strata for ", identifier, " - null!"); return false; @@ -116,7 +113,7 @@ void PopManager::reserve_pop_types(size_t count) { bool PopManager::load_pop_type_file( std::string_view filestem, UnitManager const& unit_manager, GoodManager const& good_manager, ast::NodeCPtr root ) { - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); Strata const* strata = nullptr; PopType::sprite_t sprite = 0; Good::good_map_t life_needs, everyday_needs, luxury_needs; diff --git a/src/openvic-simulation/pop/Religion.cpp b/src/openvic-simulation/pop/Religion.cpp index a5e9faf..0527da7 100644 --- a/src/openvic-simulation/pop/Religion.cpp +++ b/src/openvic-simulation/pop/Religion.cpp @@ -2,6 +2,8 @@ #include <cassert> +#include "openvic-simulation/types/Colour.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -9,8 +11,7 @@ ReligionGroup::ReligionGroup(std::string_view new_identifier) : HasIdentifier { Religion::Religion( std::string_view new_identifier, colour_t new_colour, ReligionGroup const& new_group, icon_t new_icon, bool new_pagan -) : HasIdentifierAndColour { new_identifier, new_colour, false, false }, group { new_group }, icon { new_icon }, - pagan { new_pagan } { +) : HasIdentifierAndColour { new_identifier, new_colour, false }, group { new_group }, icon { new_icon }, pagan { new_pagan } { assert(icon > 0); } @@ -33,10 +34,6 @@ bool ReligionManager::add_religion( Logger::error("Invalid religion identifier - empty!"); return false; } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid religion colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } if (icon <= 0) { Logger::error("Invalid religion icon for ", identifier, ": ", icon); return false; @@ -61,7 +58,7 @@ bool ReligionManager::load_religion_file(ast::NodeCPtr root) { religions.reserve(religions.size() + total_expected_religions); ret &= expect_religion_group_dictionary([this](ReligionGroup const& religion_group, ast::NodeCPtr religion_group_value) -> bool { return expect_dictionary([this, &religion_group](std::string_view key, ast::NodeCPtr value) -> bool { - colour_t colour = NULL_COLOUR; + colour_t colour = colour_t::null(); Religion::icon_t icon = 0; bool pagan = false; diff --git a/src/openvic-simulation/types/Colour.hpp b/src/openvic-simulation/types/Colour.hpp index 7c97b12..dc9c4c6 100644 --- a/src/openvic-simulation/types/Colour.hpp +++ b/src/openvic-simulation/types/Colour.hpp @@ -1,42 +1,431 @@ #pragma once #include <algorithm> +#include <array> +#include <cassert> +#include <charconv> +#include <climits> +#include <cmath> +#include <compare> #include <cstdint> #include <iomanip> +#include <limits> +#include <span> #include <sstream> #include <string> +#include <string_view> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "openvic-simulation/utility/Getters.hpp" +#include "openvic-simulation/utility/Utility.hpp" namespace OpenVic { + template<typename ValueT, typename IntT> + struct colour_traits { + using value_type = ValueT; + using integer_type = IntT; + static_assert(sizeof(value_type) * 4 <= sizeof(integer_type), "value_type must be 4x smaller then colour_integer_type"); + + /* When colour_t is used as an identifier, NULL_COLOUR is disallowed + * and should be reserved as an error value. + * When colour_t is used in a purely graphical context, NULL_COLOUR + * should be allowed. + */ + static constexpr integer_type null = 0; + static constexpr integer_type component = std::numeric_limits<ValueT>::max(); + static constexpr integer_type component_bit_size = sizeof(ValueT) * CHAR_BIT; + static constexpr integer_type blue_shift = 0; + static constexpr integer_type green_shift = component_bit_size; + static constexpr integer_type red_shift = component_bit_size * 2; + static constexpr integer_type alpha_shift = component_bit_size * 3; + static constexpr bool has_alpha = true; + + static constexpr integer_type make_rgb_integer(value_type red, value_type green, value_type blue) { + return (red << red_shift) | (green << green_shift) | (blue << blue_shift); + } + static constexpr integer_type make_argb_integer(value_type red, value_type green, value_type blue, value_type alpha) { + return make_rgb_integer(red, green, blue) | (alpha << alpha_shift); + } + + static constexpr value_type red_from_integer(integer_type colour) { + return (colour >> red_shift) & component; + } + static constexpr value_type blue_from_integer(integer_type colour) { + return (colour >> blue_shift) & component; + } + static constexpr value_type green_from_integer(integer_type colour) { + return (colour >> green_shift) & component; + } + static constexpr value_type alpha_from_integer(integer_type colour) { + return (colour >> alpha_shift) & component; + } + + static constexpr float component_to_float(value_type value) { + return static_cast<float>(value) / static_cast<float>(component); + } + + static constexpr value_type component_from_float(float f, float min = 0.0f, float max = 1.0f) { + constexpr auto floor = [](float f) { + const std::int64_t i = static_cast<std::int64_t>(f); + return f < i ? i - 1 : i; + }; + + return floor(std::clamp(min + f * (max - min), min, max) * static_cast<float>(component)); + } + + static constexpr value_type component_from_fraction(int n, int d, float min = 0.0f, float max = 1.0f) { + return component_from_float(static_cast<float>(n) / static_cast<float>(d), min, max); + } + + static constexpr float red_to_float(value_type red) { + return component_to_float(red); + } + static constexpr float green_to_float(value_type green) { + return component_to_float(green); + } + static constexpr float blue_to_float(value_type blue) { + return component_to_float(blue); + } + static constexpr float alpha_to_float(value_type alpha) { + return component_to_float(alpha); + } + + static constexpr value_type red_from_float(float red) { + return component_from_float(red); + } + static constexpr value_type green_from_float(float green) { + return component_from_float(green); + } + static constexpr value_type blue_from_float(float blue) { + return component_from_float(blue); + } + static constexpr value_type alpha_from_float(float alpha) { + return component_from_float(alpha); + } + + static constexpr integer_type max_rgb = make_rgb_integer(component, component, component); + static constexpr integer_type max_argb = make_argb_integer(component, component, component, component); + }; + /* Colour represented by an unsigned integer, either 24-bit RGB or 32-bit ARGB. */ - using colour_t = uint32_t; - - /* When colour_t is used as an identifier, NULL_COLOUR is disallowed - * and should be reserved as an error value. - * When colour_t is used in a purely graphical context, NULL_COLOUR - * should be allowed. - */ - static constexpr colour_t NULL_COLOUR = 0, COLOUR_COMPONENT = 0xFF; - static constexpr colour_t MAX_COLOUR_RGB = 0xFFFFFF, MAX_COLOUR_ARGB = 0xFFFFFFFF; - - constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) { - return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f); - } + template<typename ValueT, typename ColourIntT, typename ColourTraits = colour_traits<ValueT, ColourIntT>> + struct basic_colour_t : ReturnByValueProperty { + using colour_traits = ColourTraits; + using value_type = typename colour_traits::value_type; + using integer_type = typename colour_traits::integer_type; - constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) { - return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max); - } + static_assert(std::same_as<ValueT, value_type> && std::same_as<ColourIntT, integer_type>); - constexpr colour_t float_to_alpha_value(float a) { - return float_to_colour_byte(a) << 24; - } + static constexpr auto max_value = colour_traits::component; - constexpr float colour_byte_to_float(colour_t colour) { - return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f); - } + struct empty_value { + constexpr empty_value() {} + constexpr empty_value(value_type) {} + constexpr operator value_type() const { + return max_value; + } + }; + + private: + using _alpha_t = std::conditional_t<colour_traits::has_alpha, value_type, empty_value>; + + public: + value_type red; + value_type green; + value_type blue; + [[no_unique_address]] _alpha_t alpha; + + static constexpr std::integral_constant<std::size_t, std::is_same_v<decltype(alpha), value_type> ? 4 : 3> size = {}; + + static constexpr basic_colour_t fill_as(value_type value) { + if constexpr (colour_traits::has_alpha) { + return { value, value, value, value }; + } else { + return { value, value, value }; + } + } + + static constexpr basic_colour_t null() { + return fill_as(colour_traits::null); + } + + static constexpr basic_colour_t from_integer(integer_type integer) { + if constexpr (colour_traits::has_alpha) { + return { colour_traits::red_from_integer(integer), colour_traits::green_from_integer(integer), + colour_traits::blue_from_integer(integer), colour_traits::alpha_from_integer(integer) }; + } else { + assert( + colour_traits::alpha_from_integer(integer) == colour_traits::null || + colour_traits::alpha_from_integer(integer) == max_value + ); + return { colour_traits::red_from_integer(integer), colour_traits::green_from_integer(integer), + colour_traits::blue_from_integer(integer) }; + } + } + + static constexpr basic_colour_t + from_floats(float r, float g, float b, float a = colour_traits::alpha_to_float(max_value)) + requires(colour_traits::has_alpha) + { + return { colour_traits::red_from_float(r), colour_traits::green_from_float(g), colour_traits::blue_from_float(b), + colour_traits::alpha_from_float(a) }; + } + + static constexpr basic_colour_t from_floats(float r, float g, float b) + requires(!colour_traits::has_alpha) + { + return { colour_traits::red_from_float(r), colour_traits::green_from_float(g), colour_traits::blue_from_float(b) }; + } + + constexpr integer_type as_rgb() const { + return colour_traits::make_rgb_integer(red, green, blue); + } + + constexpr integer_type as_argb() const { + return colour_traits::make_argb_integer(red, green, blue, alpha); + } + + constexpr basic_colour_t() : basic_colour_t { null() } {} + + constexpr basic_colour_t(value_type r, value_type g, value_type b, value_type a = max_value) + requires(colour_traits::has_alpha) + : red(r), green(g), blue(b), alpha(a) {} + + constexpr basic_colour_t(value_type r, value_type g, value_type b) + requires(!colour_traits::has_alpha) + : red(r), green(g), blue(b) {} + + template<typename _ColourTraits> + requires(_ColourTraits::has_alpha && std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>) + explicit constexpr basic_colour_t(basic_colour_t<value_type, integer_type, _ColourTraits> const& colour) + requires(colour_traits::has_alpha) + : basic_colour_t { colour.red, colour.green, colour.blue, colour.alpha } {} + + template<typename _ColourTraits> + requires(!_ColourTraits::has_alpha && std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>) + explicit constexpr basic_colour_t( + basic_colour_t<value_type, integer_type, _ColourTraits> const& colour, value_type a = max_value + ) + requires(colour_traits::has_alpha) + : basic_colour_t { colour.red, colour.green, colour.blue, a } {} + + template<typename _ColourTraits> + requires(std::same_as<typename _ColourTraits::value_type, value_type> && std::same_as<typename _ColourTraits::integer_type, integer_type>) + explicit constexpr basic_colour_t(basic_colour_t<value_type, integer_type, _ColourTraits> const& colour) + requires(!colour_traits::has_alpha) + : basic_colour_t { colour.red, colour.green, colour.blue } {} + + constexpr explicit operator integer_type() const { + if constexpr (colour_traits::has_alpha) { + return as_argb(); + } else { + return as_rgb(); + } + } - inline std::string colour_to_hex_string(colour_t colour, bool alpha = false) { - std::ostringstream stream; - stream << std::uppercase << std::hex << std::setfill('0') << std::setw(!alpha ? 6 : 8) << colour; - return stream.str(); + constexpr basic_colour_t with_red(value_type r) const { + if constexpr (colour_traits::has_alpha) { + return { r, green, blue, alpha }; + } else { + return { r, green, blue }; + } + } + + constexpr basic_colour_t with_green(value_type g) const { + if constexpr (colour_traits::has_alpha) { + return { red, g, blue, alpha }; + } else { + return { red, g, blue }; + } + } + + constexpr basic_colour_t with_blue(value_type b) const { + if constexpr (colour_traits::has_alpha) { + return { red, green, b, alpha }; + } else { + return { red, green, b }; + } + } + + constexpr basic_colour_t with_alpha(value_type a) const + requires(colour_traits::has_alpha) + { + return { red, green, blue, a }; + } + + constexpr float redf() const { + return colour_traits::red_to_float(red); + } + + constexpr float greenf() const { + return colour_traits::green_to_float(green); + } + + constexpr float bluef() const { + return colour_traits::blue_to_float(blue); + } + + constexpr float alphaf() const { + return colour_traits::alpha_to_float(alpha); + } + + inline std::string to_hex_string(bool alpha = false) const { + using namespace std::string_view_literals; + static constexpr std::string_view digits = "0123456789ABCDEF"sv; + static constexpr std::size_t bits_per_digit = 4; + static constexpr std::size_t digit_mask = (1 << bits_per_digit) - 1; + static constexpr std::size_t argb_length = colour_traits::component_bit_size / bits_per_digit * 4; + static constexpr std::size_t rgb_length = colour_traits::component_bit_size / bits_per_digit * 3; + + const std::size_t length = alpha ? argb_length : rgb_length; + std::array<char, argb_length> address; + const integer_type value = alpha ? as_argb() : as_rgb(); + + for (std::size_t index = 0, j = (length - 1) * bits_per_digit; index < length; ++index, j -= bits_per_digit) { + address[index] = digits[(value >> j) & digit_mask]; + } + return { address.data(), length }; + } + + explicit operator std::string() const { + return to_hex_string(colour_traits::has_alpha); + } + + friend std::ostream& operator<<(std::ostream& stream, basic_colour_t const& colour) { + return stream << colour.operator std::string(); + } + + constexpr bool is_null() const { + return *this == null(); + } + + constexpr auto operator<=>(basic_colour_t const& rhs) const = default; + constexpr auto operator<=>(integer_type rhs) const { + return operator integer_type() <=> rhs; + } + + constexpr value_type& operator[](std::size_t index) { + return _array_access_helper<value_type>(*this, index); + } + + constexpr const value_type& operator[](std::size_t index) const { + return _array_access_helper<const value_type>(*this, index); + } + + private: + template<typename V, typename T> + static constexpr V& _array_access_helper(T& t, std::size_t index) { + switch (index) { + case 0: return t.red; + case 1: return t.green; + case 2: return t.blue; + } + if constexpr (size() == 4) { + if (index == 3) { + return t.alpha; + } + assert(index < 4); + } else { + assert(index < 3); + } + /* Segfault if index is out of bounds and NDEBUG is defined! */ + OpenVic::utility::unreachable(); + } + + public: + template<std::size_t Index> + auto&& get() & { + return get_helper<Index>(*this); + } + + template<std::size_t Index> + auto&& get() && { + return get_helper<Index>(*this); + } + + template<std::size_t Index> + auto&& get() const& { + return get_helper<Index>(*this); + } + + template<std::size_t Index> + auto&& get() const&& { + return get_helper<Index>(*this); + } + + private: + template<std::size_t Index, typename T> + static auto&& get_helper(T&& t) { + static_assert(Index < size(), "Index out of bounds for OpenVic::basic_colour_t"); + if constexpr (Index == 0) { + return std::forward<T>(t).red; + } + if constexpr (Index == 1) { + return std::forward<T>(t).green; + } + if constexpr (Index == 2) { + return std::forward<T>(t).blue; + } + if constexpr (Index == 3) { + return std::forward<T>(t).alpha; + } + } + }; + + template<typename T> + concept IsColour = requires(T t) { + { basic_colour_t { t } } -> std::same_as<T>; + }; + + template<typename ValueT, typename IntT> + struct rgb_colour_traits : colour_traits<ValueT, IntT> { + static constexpr bool has_alpha = false; + }; + + using colour_argb_t = basic_colour_t<std::uint8_t, std::uint32_t>; + using colour_rgb_t = basic_colour_t<std::uint8_t, std::uint32_t, rgb_colour_traits<std::uint8_t, std::uint32_t>>; + + using colour_t = colour_rgb_t; + + namespace colour_literals { + constexpr colour_t operator""_colour(unsigned long long value) { + return colour_t::from_integer(value); + } + + constexpr colour_argb_t operator""_argb(unsigned long long value) { + return colour_argb_t::from_integer(value); + } } } + +namespace std { + template<typename ValueT, typename ColourIntT, typename ColourTraits> + struct tuple_size<::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> + : integral_constant<size_t, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::size()> {}; + + template<size_t Index, typename ValueT, typename ColourIntT, typename ColourTraits> + struct tuple_element<Index, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> { + using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::red); + }; + template<typename ValueT, typename ColourIntT, typename ColourTraits> + struct tuple_element<1, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> { + using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::green); + }; + template<typename ValueT, typename ColourIntT, typename ColourTraits> + struct tuple_element<2, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> { + using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::blue); + }; + template<typename ValueT, typename ColourIntT, typename ColourTraits> + struct tuple_element<3, ::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> { + using type = decltype(::OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>::alpha); + }; + + template<typename ValueT, typename ColourIntT, typename ColourTraits> + struct hash<OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits>> { + size_t operator()(OpenVic::basic_colour_t<ValueT, ColourIntT, ColourTraits> const& s) const noexcept { + return hash<decltype(s.as_argb())> {}(s.as_argb()); + } + }; +} diff --git a/src/openvic-simulation/types/IdentifierRegistry.cpp b/src/openvic-simulation/types/IdentifierRegistry.cpp deleted file mode 100644 index 3964b12..0000000 --- a/src/openvic-simulation/types/IdentifierRegistry.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "IdentifierRegistry.hpp" - -#include <cassert> - -using namespace OpenVic; - -HasIdentifier::HasIdentifier(std::string_view new_identifier) : identifier { new_identifier } { - assert(!identifier.empty()); -} - -std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const& obj) { - return stream << obj.get_identifier(); -} - -std::ostream& OpenVic::operator<<(std::ostream& stream, HasIdentifier const* obj) { - return obj != nullptr ? stream << *obj : stream << "<NULL>"; -} - -HasColour::HasColour(colour_t new_colour, bool cannot_be_null, bool can_have_alpha) : colour(new_colour) { - assert((!cannot_be_null || colour != NULL_COLOUR) && colour <= (!can_have_alpha ? MAX_COLOUR_RGB : MAX_COLOUR_ARGB)); -} - -std::string HasColour::colour_to_hex_string() const { - return OpenVic::colour_to_hex_string(colour); -} - -HasIdentifierAndColour::HasIdentifierAndColour( - std::string_view new_identifier, colour_t new_colour, bool cannot_be_null, bool can_have_alpha -) : HasIdentifier { new_identifier }, HasColour { new_colour, cannot_be_null, can_have_alpha } {} diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index 3bf52e1..efdf361 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -1,5 +1,6 @@ #pragma once +#include <cassert> #include <concepts> #include <map> #include <type_traits> @@ -34,7 +35,9 @@ namespace OpenVic { const std::string PROPERTY(identifier); protected: - HasIdentifier(std::string_view new_identifier); + HasIdentifier(std::string_view new_identifier): identifier { new_identifier } { + assert(!identifier.empty()); + } public: HasIdentifier(HasIdentifier const&) = delete; @@ -43,41 +46,51 @@ namespace OpenVic { HasIdentifier& operator=(HasIdentifier&&) = delete; }; - std::ostream& operator<<(std::ostream& stream, HasIdentifier const& obj); - std::ostream& operator<<(std::ostream& stream, HasIdentifier const* obj); + inline std::ostream& operator<<(std::ostream& stream, HasIdentifier const& obj) { + return stream << obj.get_identifier(); + } + inline std::ostream& operator<<(std::ostream& stream, HasIdentifier const* obj) { + return obj != nullptr ? stream << *obj : stream << "<NULL>"; + } /* * Base class for objects with associated colour information. */ - class HasColour { - const colour_t PROPERTY(colour); + template<IsColour ColourT> + class _HasColour { + const ColourT PROPERTY(colour); protected: - HasColour(colour_t new_colour, bool cannot_be_null, bool can_have_alpha); + _HasColour(ColourT new_colour, bool cannot_be_null): colour { new_colour } { + assert(!cannot_be_null || !colour.is_null()); + } public: - HasColour(HasColour const&) = delete; - HasColour(HasColour&&) = default; - HasColour& operator=(HasColour const&) = delete; - HasColour& operator=(HasColour&&) = delete; - - std::string colour_to_hex_string() const; + _HasColour(_HasColour const&) = delete; + _HasColour(_HasColour&&) = default; + _HasColour& operator=(_HasColour const&) = delete; + _HasColour& operator=(_HasColour&&) = delete; }; /* * Base class for objects with a unique string identifier and associated colour information. */ - class HasIdentifierAndColour : public HasIdentifier, public HasColour { + template<IsColour ColourT> + class _HasIdentifierAndColour : public HasIdentifier, public _HasColour<ColourT> { protected: - HasIdentifierAndColour(std::string_view new_identifier, colour_t new_colour, bool cannot_be_null, bool can_have_alpha); + _HasIdentifierAndColour(std::string_view new_identifier, ColourT new_colour, bool cannot_be_null) + : HasIdentifier { new_identifier }, _HasColour<ColourT> { new_colour, cannot_be_null } {} public: - HasIdentifierAndColour(HasIdentifierAndColour const&) = delete; - HasIdentifierAndColour(HasIdentifierAndColour&&) = default; - HasIdentifierAndColour& operator=(HasIdentifierAndColour const&) = delete; - HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete; + _HasIdentifierAndColour(_HasIdentifierAndColour const&) = delete; + _HasIdentifierAndColour(_HasIdentifierAndColour&&) = default; + _HasIdentifierAndColour& operator=(_HasIdentifierAndColour const&) = delete; + _HasIdentifierAndColour& operator=(_HasIdentifierAndColour&&) = delete; }; + using HasIdentifierAndColour = _HasIdentifierAndColour<colour_t>; + using HasIdentifierAndAlphaColour = _HasIdentifierAndColour<colour_argb_t>; + /* Callbacks for trying to add duplicate keys via UniqueKeyRegistry::add_item */ static bool duplicate_fail_callback(std::string_view registry_name, std::string_view duplicate_identifier) { Logger::error( diff --git a/src/openvic-simulation/utility/BMP.cpp b/src/openvic-simulation/utility/BMP.cpp index d4e7cf7..83d26c4 100644 --- a/src/openvic-simulation/utility/BMP.cpp +++ b/src/openvic-simulation/utility/BMP.cpp @@ -102,7 +102,7 @@ bool BMP::read_header() { if (header.num_colours != 0 && header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT) { Logger::error( "Invalid BMP palette size: ", header.num_colours, " (should be 0 as bits per pixel is ", header.bits_per_pixel, - " > 8)" + " > ", PALETTE_BITS_PER_PIXEL_LIMIT, ")" ); header_validated = false; } @@ -207,7 +207,7 @@ bool BMP::read_pixel_data() { Logger::error("Failed to move to the pixel data in the BMP file!"); return false; } - const size_t pixel_data_size = get_width() * get_height() * header.bits_per_pixel / 8; + const size_t pixel_data_size = get_width() * get_height() * header.bits_per_pixel / CHAR_BIT; pixel_data.resize(pixel_data_size); file.read(reinterpret_cast<char*>(pixel_data.data()), pixel_data_size); if (file.fail()) { diff --git a/src/openvic-simulation/utility/Getters.hpp b/src/openvic-simulation/utility/Getters.hpp index a722071..1fb82b1 100644 --- a/src/openvic-simulation/utility/Getters.hpp +++ b/src/openvic-simulation/utility/Getters.hpp @@ -49,7 +49,10 @@ public: \ ACCESS: namespace OpenVic { - struct ReturnByValueProperty {}; + struct ReturnByValueProperty { + constexpr bool operator==(ReturnByValueProperty const&) const = default; + constexpr std::strong_ordering operator<=>(ReturnByValueProperty const&) const = default; + }; /* * Template function used to choose the return type and provide the implementation diff --git a/src/openvic-simulation/utility/Utility.hpp b/src/openvic-simulation/utility/Utility.hpp new file mode 100644 index 0000000..e8d7205 --- /dev/null +++ b/src/openvic-simulation/utility/Utility.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace OpenVic::utility { + [[noreturn]] inline void unreachable() { + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif + } +} |