aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Spartan322 <Megacake1234@gmail.com>2023-12-19 03:41:57 +0100
committer Spartan322 <Megacake1234@gmail.com>2023-12-24 22:57:56 +0100
commit3770de7a03879a8ff6b8cf22b402217c19fa2b53 (patch)
tree0d77d82ab8cea8955e2b86d883d1c2fd10813717
parent14e47d58b85f657ec1fed8abf88219f09bd3efbb (diff)
Change colour_t to be a strongly typed structure
Make RGB default of `colour_t` Distinguish RGB and ARGB colors by type and colour_traits Add `_colour` and `_argb` colour user-defined literals Add `OpenVic::utility::unreachable`
-rw-r--r--src/openvic-simulation/GameManager.cpp123
-rw-r--r--src/openvic-simulation/country/Country.cpp6
-rw-r--r--src/openvic-simulation/dataloader/NodeTools.cpp22
-rw-r--r--src/openvic-simulation/dataloader/NodeTools.hpp4
-rw-r--r--src/openvic-simulation/economy/Good.cpp8
-rw-r--r--src/openvic-simulation/interface/GFX.cpp4
-rw-r--r--src/openvic-simulation/interface/GFX.hpp4
-rw-r--r--src/openvic-simulation/interface/UI.cpp12
-rw-r--r--src/openvic-simulation/interface/UI.hpp2
-rw-r--r--src/openvic-simulation/map/Map.cpp61
-rw-r--r--src/openvic-simulation/map/Map.hpp11
-rw-r--r--src/openvic-simulation/map/Province.cpp4
-rw-r--r--src/openvic-simulation/map/Region.cpp6
-rw-r--r--src/openvic-simulation/map/TerrainType.cpp10
-rw-r--r--src/openvic-simulation/politics/Ideology.cpp11
-rw-r--r--src/openvic-simulation/pop/Culture.cpp9
-rw-r--r--src/openvic-simulation/pop/Pop.cpp9
-rw-r--r--src/openvic-simulation/pop/Religion.cpp11
-rw-r--r--src/openvic-simulation/types/Colour.hpp441
-rw-r--r--src/openvic-simulation/types/IdentifierRegistry.cpp29
-rw-r--r--src/openvic-simulation/types/IdentifierRegistry.hpp49
-rw-r--r--src/openvic-simulation/utility/BMP.cpp4
-rw-r--r--src/openvic-simulation/utility/Getters.hpp5
-rw-r--r--src/openvic-simulation/utility/Utility.hpp14
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 44b896b..78150bb 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 927b832..6f5ebf9 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 ff27613..5e750cc 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 ec4691d..8b307be 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
+ }
+}