aboutsummaryrefslogtreecommitdiff
path: root/extension/src/Utilities.cpp
blob: 4ca685594a89509f433e9755b341af8a302d5d33 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include "Utilities.hpp"

#include <numbers>

#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;
using namespace OpenVic;

Ref<Image> OpenVic::load_godot_image(String const& path) {
   if (path.begins_with("res://")) {
      ResourceLoader* loader = ResourceLoader::get_singleton();
      return loader ? loader->load(path) : nullptr;
   } else {
      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> 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<float>(x) / static_cast<float>(size),
              static_cast<float>(y) / static_cast<float>(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));
      }
   }
}