From 4c43951e70aaa2e7265d3b3f3c4964c048b9328d Mon Sep 17 00:00:00 2001 From: Hop311 Date: Thu, 10 Aug 2023 12:00:54 +0100 Subject: PieChart data and image now come from c++ layer --- extension/src/GameSingleton.cpp | 81 ++++++++++++++++++++++++++++++++++--- extension/src/GameSingleton.hpp | 16 +++++++- extension/src/Utilities.cpp | 90 +++++++++++++++++++++++++++++++++++++++++ extension/src/Utilities.hpp | 10 +++++ 4 files changed, 191 insertions(+), 6 deletions(-) (limited to 'extension/src') diff --git a/extension/src/GameSingleton.cpp b/extension/src/GameSingleton.cpp index efacb8e..baf4d44 100644 --- a/extension/src/GameSingleton.cpp +++ b/extension/src/GameSingleton.cpp @@ -1,7 +1,5 @@ #include "GameSingleton.hpp" -#include - #include #include "openvic/utility/Logger.hpp" @@ -13,8 +11,7 @@ using namespace OpenVic; TerrainVariant::TerrainVariant(std::string const& new_identfier, colour_t new_colour, Ref const& new_image) - : HasIdentifier { new_identfier }, - HasColour { new_colour, true }, + : HasIdentifierAndColour { new_identfier, new_colour, true }, image { new_image } {} Ref TerrainVariant::get_image() const { @@ -75,6 +72,9 @@ void GameSingleton::_bind_methods() { ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_region_key"), &GameSingleton::get_province_info_region_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_life_rating_key"), &GameSingleton::get_province_info_life_rating_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_total_population_key"), &GameSingleton::get_province_info_total_population_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_types_key"), &GameSingleton::get_province_info_pop_types_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_ideologies_key"), &GameSingleton::get_province_info_pop_ideologies_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_cultures_key"), &GameSingleton::get_province_info_pop_cultures_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_rgo_key"), &GameSingleton::get_province_info_rgo_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_buildings_key"), &GameSingleton::get_province_info_buildings_key); @@ -84,6 +84,25 @@ void GameSingleton::_bind_methods() { ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_start_date_key"), &GameSingleton::get_building_info_start_date_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_end_date_key"), &GameSingleton::get_building_info_end_date_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_expansion_progress_key"), &GameSingleton::get_building_info_expansion_progress_key); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_culture_info_size_key"), &GameSingleton::get_piechart_info_size_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_culture_info_colour_key"), &GameSingleton::get_piechart_info_colour_key); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("draw_pie_chart", "image", "stopAngles", "colours", "radius", + "shadow_displacement", "shadow_tightness", "shadow_radius", "shadow_thickness", + "trim_colour", "trim_size", "gradient_falloff", "gradient_base", + "donut", "donut_inner_trim", "donut_inner_radius"), &GameSingleton::draw_pie_chart); +} + +void GameSingleton::draw_pie_chart(Ref image, + Array const& stopAngles, Array const& colours, float radius, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + OpenVic::draw_pie_chart(image, stopAngles, colours, radius, shadow_displacement, shadow_tightness, shadow_radius, shadow_thickness, + trim_colour, trim_size, gradient_falloff, gradient_base, + donut, donut_inner_trim, donut_inner_radius); } GameSingleton* GameSingleton::get_singleton() { @@ -153,11 +172,25 @@ Error GameSingleton::_load_hardcoded_defines() { } return HIGH_ALPHA_VALUE | val; } - return HIGH_ALPHA_VALUE; + return NULL_COLOUR; } }, { "mapmode_population", [](Map const& map, Province const& province) -> colour_t { return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8); + } }, + { "mapmode_culture", + [](Map const& map, Province const& province) -> colour_t { + distribution_t const& cultures = province.get_culture_distribution(); + if (!cultures.empty()) { + // This breaks if replaced with distribution_t::value_type, something + // about operator=(volatile const&) being deleted. + std::pair culture = *cultures.begin(); + for (distribution_t::value_type const p : cultures) { + if (p.second > culture.second) culture = p; + } + return HIGH_ALPHA_VALUE | culture.first->get_colour(); + } + return NULL_COLOUR; } } }; for (mapmode_t const& mapmode : mapmodes) @@ -208,6 +241,18 @@ StringName const& GameSingleton::get_province_info_total_population_key() { static const StringName key = "total_population"; return key; } +StringName const& GameSingleton::get_province_info_pop_types_key() { + static const StringName key = "pop_types"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_ideologies_key() { + static const StringName key = "pop_ideologies"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_cultures_key() { + static const StringName key = "pop_cultures"; + return key; +} StringName const& GameSingleton::get_province_info_rgo_key() { static const StringName key = "rgo"; return key; @@ -242,6 +287,26 @@ StringName const& GameSingleton::get_building_info_expansion_progress_key() { return key; } +StringName const& GameSingleton::get_piechart_info_size_key() { + static const StringName key = "size"; + return key; +} +StringName const& GameSingleton::get_piechart_info_colour_key() { + static const StringName key = "colour"; + return key; +} + +Dictionary GameSingleton::_distribution_to_dictionary(distribution_t const& dist) const { + Dictionary dict; + for (distribution_t::value_type const& p : dist) { + Dictionary sub_dict; + sub_dict[get_piechart_info_size_key()] = p.second; + sub_dict[get_piechart_info_colour_key()] = to_godot_color(p.first->get_colour()); + dict[std_to_godot_string(p.first->get_identifier())] = sub_dict; + } + return dict; +} + Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { Province const* province = game_manager.map.get_province_by_index(index); if (province == nullptr) return {}; @@ -257,6 +322,12 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[get_province_info_life_rating_key()] = province->get_life_rating(); ret[get_province_info_total_population_key()] = province->get_total_population(); + distribution_t const& pop_types = province->get_pop_type_distribution(); + if (!pop_types.empty()) ret[get_province_info_pop_types_key()] = _distribution_to_dictionary(pop_types); + //distribution_t const& ideologies = province->get_ideology_distribution(); + //if (!ideologies.empty()) ret[get_province_info_pop_ideologies_key()] = _distribution_to_dictionary(ideologies); + distribution_t const& cultures = province->get_culture_distribution(); + if (!cultures.empty()) ret[get_province_info_pop_cultures_key()] = _distribution_to_dictionary(cultures); std::vector const& buildings = province->get_buildings(); if (!buildings.empty()) { diff --git a/extension/src/GameSingleton.hpp b/extension/src/GameSingleton.hpp index 23eb334..c4e90e0 100644 --- a/extension/src/GameSingleton.hpp +++ b/extension/src/GameSingleton.hpp @@ -6,7 +6,7 @@ #include "openvic/GameManager.hpp" namespace OpenVic { - struct TerrainVariant : HasIdentifier, HasColour { + struct TerrainVariant : HasIdentifierAndColour { friend class GameSingleton; private: @@ -64,10 +64,18 @@ namespace OpenVic { godot::Error _update_colour_image(); void _on_state_updated(); + godot::Dictionary _distribution_to_dictionary(distribution_t const& dist) const; + protected: static void _bind_methods(); public: + static void draw_pie_chart(godot::Ref image, + godot::Array const& stopAngles, godot::Array const& colours, float radius, + godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + godot::Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius); + static GameSingleton* get_singleton(); GameSingleton(); @@ -106,6 +114,9 @@ namespace OpenVic { static godot::StringName const& get_province_info_region_key(); static godot::StringName const& get_province_info_life_rating_key(); static godot::StringName const& get_province_info_total_population_key(); + static godot::StringName const& get_province_info_pop_types_key(); + static godot::StringName const& get_province_info_pop_ideologies_key(); + static godot::StringName const& get_province_info_pop_cultures_key(); static godot::StringName const& get_province_info_rgo_key(); static godot::StringName const& get_province_info_buildings_key(); @@ -116,6 +127,9 @@ namespace OpenVic { static godot::StringName const& get_building_info_end_date_key(); static godot::StringName const& get_building_info_expansion_progress_key(); + static godot::StringName const& get_piechart_info_size_key(); + static godot::StringName const& get_piechart_info_colour_key(); + /* Get info to display in Province Overview Panel, packaged in * a Dictionary using the StringNames above as keys. */ diff --git a/extension/src/Utilities.cpp b/extension/src/Utilities.cpp index a912490..f09eae0 100644 --- a/extension/src/Utilities.cpp +++ b/extension/src/Utilities.cpp @@ -1,5 +1,7 @@ #include "Utilities.hpp" +#include + #include #include @@ -14,3 +16,91 @@ Ref OpenVic::load_godot_image(String const& path) { return Image::load_from_file(path); } } + +// Get the polar coordinates of a pixel relative to the center +static Vector2 getPolar(Vector2 UVin, Vector2 center) { + Vector2 relcoord = (UVin-center); + float dist = relcoord.length(); + float theta = std::numbers::pi / 2 + atan2(relcoord.y, relcoord.x); + if (theta < 0.0f) theta += std::numbers::pi * 2; + return { dist, theta }; +} + +// From thebookofshaders, returns a gradient falloff +static inline float parabola(float base, float x, float k){ + return powf(base * x * (1.0 - x), k); +} + +static inline float parabola_shadow(float base, float x){ + return base * x * x; +} + +static Color pie_chart_fragment(Vector2 UV, float radius, Array const& stopAngles, Array const& colours, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + Vector2 coords = getPolar(UV, { 0.5, 0.5 }); + float dist = coords.x; + float theta = coords.y; + + Vector2 shadow_polar = getPolar(UV, shadow_displacement); + float shadow_peak = radius + (radius - donut_inner_radius) / 2.0; + float shadow_gradient = shadow_thickness + parabola_shadow(shadow_tightness * -10.0, shadow_polar.x + shadow_peak - shadow_radius); + + // Inner hole of the donut => make it transparent + if (donut && dist <= donut_inner_radius) { + return { 0.1, 0.1, 0.1, shadow_gradient }; + } + // Inner trim + else if (donut && donut_inner_trim && dist <= donut_inner_radius + trim_size) { + return { trim_colour, 1.0 }; + } + // Interior + else if (dist <= radius-trim_size) { + Color col { 1.0f, 0.0f, 0.0f }; + for (int i = 0; i < stopAngles.size(); i++){ + if (theta <= float(stopAngles[i])) { + col = colours[i]; + break; + } + } + float gradient = parabola(gradient_base, dist, gradient_falloff); + return { col * (1.0 - gradient), 1.0 }; + } + // Outer trim + else if (dist <= radius) { + return { trim_colour, 1.0 }; + } + // Outside the circle + else{ + return { 0.1, 0.1, 0.1, shadow_gradient }; + } +} + +void OpenVic::draw_pie_chart(Ref image, + Array const& stopAngles, Array const& colours, float radius, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + ERR_FAIL_NULL_EDMSG(image, "Cannot draw pie chart to null image."); + const int32_t width = image->get_width(); + const int32_t height = image->get_height(); + ERR_FAIL_COND_EDMSG(width <= 0 || height <= 0, "Cannot draw pie chart to empty image."); + if (width != height) { + UtilityFunctions::push_warning("Drawing pie chart to non-square image: ", width, "x", height); + } + const int32_t size = std::min(width, height); + for (int32_t y = 0; y < size; ++y) { + for (int32_t x = 0; x < size; ++x) { + image->set_pixel(x, y, pie_chart_fragment( + { static_cast(x) / static_cast(size), + static_cast(y) / static_cast(size) }, + radius, stopAngles, colours, + shadow_displacement, shadow_tightness, shadow_radius, shadow_thickness, + trim_colour, trim_size, gradient_falloff, gradient_base, + donut, donut_inner_trim, donut_inner_radius)); + } + } +} diff --git a/extension/src/Utilities.hpp b/extension/src/Utilities.hpp index 681b893..e8796e9 100644 --- a/extension/src/Utilities.hpp +++ b/extension/src/Utilities.hpp @@ -16,5 +16,15 @@ namespace OpenVic { return str.c_str(); } + inline godot::Color to_godot_color(colour_t colour) { + return { colour_byte_to_float((colour >> 16) & 0xFF), colour_byte_to_float((colour >> 8) & 0xFF), colour_byte_to_float(colour & 0xFF) }; + } + godot::Ref load_godot_image(godot::String const& path); + + void draw_pie_chart(godot::Ref image, + godot::Array const& stopAngles, godot::Array const& colours, float radius, + godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + godot::Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius); } -- cgit v1.2.3-56-ga3b1