diff options
Diffstat (limited to 'extension/src/openvic-extension/utility')
-rw-r--r-- | extension/src/openvic-extension/utility/Utilities.cpp | 167 | ||||
-rw-r--r-- | extension/src/openvic-extension/utility/Utilities.hpp | 55 |
2 files changed, 222 insertions, 0 deletions
diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp new file mode 100644 index 0000000..5940373 --- /dev/null +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -0,0 +1,167 @@ +#include "Utilities.hpp" + +#include <numbers> + +#include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/resource_loader.hpp> +#include <godot_cpp/variant/utility_functions.hpp> + +#include <gli/convert.hpp> +#include <gli/load_dds.hpp> + +using namespace godot; +using namespace OpenVic; + +static Ref<Image> load_dds_image(String const& path) { + gli::texture2d texture { gli::load_dds(Utilities::godot_to_std_string(path)) }; + if (texture.empty()) { + UtilityFunctions::push_error("Failed to load DDS file: ", path); + return nullptr; + } + + static constexpr gli::format expected_format = gli::FORMAT_BGRA8_UNORM_PACK8; + const bool needs_bgr_to_rgb = texture.format() == expected_format; + if (!needs_bgr_to_rgb) { + texture = gli::convert(texture, expected_format); + if (texture.empty()) { + UtilityFunctions::push_error("Failed to convert DDS file: ", path); + return nullptr; + } + } + + const gli::texture2d::extent_type extent { texture.extent() }; + const int width = extent.x, height = extent.y, size = width * height * 4; + + /* Only fail if there aren't enough bytes, everything seems to work fine if there are extra bytes and we ignore them */ + if (size > texture.size()) { + UtilityFunctions::push_error( + "Texture size ", static_cast<int64_t>(texture.size()), " mismatched with dims-based size ", size, " for ", path + ); + return nullptr; + } + + PackedByteArray pixels; + pixels.resize(size); + /* Index offset used to control whether we are reading */ + const size_t rb_idx = 2 * needs_bgr_to_rgb; + uint8_t const* ptr = static_cast<uint8_t const*>(texture.data()); + for (size_t i = 0; i < size; i += 4) { + pixels[i + 0] = ptr[i + rb_idx]; + pixels[i + 1] = ptr[i + 1]; + pixels[i + 2] = ptr[i + 2 - rb_idx]; + pixels[i + 3] = ptr[i + 3]; + } + + return Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, pixels); +} + +Ref<Image> Utilities::load_godot_image(String const& path) { + if (path.ends_with(".dds")) { + return load_dds_image(path); + } + return Image::load_from_file(path); +} + +Ref<FontFile> Utilities::load_godot_font(String const& fnt_path, Ref<Image> const& image) { + Ref<FontFile> font; + font.instantiate(); + const Error err = font->load_bitmap_font(fnt_path); + font->set_texture_image(0, { font->get_fixed_size(), 0 }, 0, image); + if (err != OK) { + UtilityFunctions::push_error("Failed to load font (error ", err, "): ", fnt_path); + } + return font; +} + +// 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 Utilities::draw_pie_chart( + Ref<Image> 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( + Vector2 { static_cast<float>(x), static_cast<float>(y) } / 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/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp new file mode 100644 index 0000000..fc106db --- /dev/null +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include <godot_cpp/classes/font_file.hpp> +#include <godot_cpp/classes/image.hpp> + +#include <openvic-simulation/types/Colour.hpp> +#include <openvic-simulation/types/Vector.hpp> + +#define ERR(x) ((x) ? OK : FAILED) + +namespace OpenVic::Utilities { + + inline std::string godot_to_std_string(godot::String const& str) { + return str.ascii().get_data(); + } + + inline godot::String std_to_godot_string(std::string const& str) { + return str.c_str(); + } + + inline godot::String std_view_to_godot_string(std::string_view str) { + return std_to_godot_string(static_cast<std::string>(str)); + } + + inline godot::StringName std_to_godot_string_name(std::string const& str) { + return str.c_str(); + } + + inline godot::StringName std_view_to_godot_string_name(std::string_view str) { + return std_to_godot_string_name(static_cast<std::string>(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) + }; + } + + inline godot::Vector2i to_godot_ivec2(ivec2_t vec) { + return { vec.x, vec.y }; + } + + godot::Ref<godot::Image> load_godot_image(godot::String const& path); + + godot::Ref<godot::FontFile> load_godot_font(godot::String const& fnt_path, godot::Ref<godot::Image> const& image); + + void draw_pie_chart( + godot::Ref<godot::Image> 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 + ); +} |