aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension/utility/Utilities.cpp
blob: 4a774a76d7c6e139a962bf13f1f57b92c7c39661 (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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "Utilities.hpp"

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

#include <gli/convert.hpp>
#include <gli/load_dds.hpp>

using namespace godot;
using namespace OpenVic;

/* Int to 2 decimal place string in terms of the largest suffix less than or equal to it,
 * or normal integer string if less than the smallest suffix. */
String Utilities::int_to_string_suffixed(int64_t val) {
   static const std::vector<std::pair<int64_t, String>> suffixes {
      { 1'000'000'000'000, "T" },
      { 1'000'000'000, "B" },
      { 1'000'000, "M" },
      { 1'000, "k" }
   };
   static constexpr int64_t decimal_places_multiplier = 100;
   const bool negative = val < 0;
   if (negative) {
      val = -val;
   }
   for (auto const& [suffix_val, suffix_str] : suffixes) {
      if (val >= suffix_val) {
         const int64_t whole = val / suffix_val;
         const int64_t frac = (val * decimal_places_multiplier / suffix_val) % decimal_places_multiplier;
         return (negative ? "-" : "") + String::num_int64(whole) + "." +
            (frac < 10 ? "0" : "") + String::num_int64(frac) + suffix_str;
      }
   }
   return (negative ? "-" : "") + String::num_int64(val);
}

String Utilities::float_to_string_suffixed(float val) {
   const float abs_val = std::abs(val);

   if (abs_val < 10'000.0f) {
      return float_to_string_dp(val, 1);
   }

   if (abs_val < 1'000'000.0f) {
      return float_to_string_dp(val / 1'000.0f, 2) + "k";
   }

   if (abs_val < 1'000'000'000.0f) {
      return float_to_string_dp(val / 1'000'000.0f, 2) + "M";
   }

   if (abs_val < 1'000'000'000'000.0f) {
      return float_to_string_dp(val / 1'000'000'000.0f, 2) + "B";
   }

   return float_to_string_dp(val / 1'000'000'000'000.0f, 2) + "T";
}

/* Float to string formatted with the specified number of decimal places. */
String Utilities::float_to_string_dp(float val, int32_t decimal_places) {
   return String::num(val, decimal_places).pad_decimals(decimal_places);
}

/* Date formatted like this: "January 1, 1836" (with the month localised, if possible). */
String Utilities::date_to_formatted_string(Date date) {
   const String month_name = Utilities::std_to_godot_string(date.get_month_name());
   const String day_and_year = " " + String::num_int64(date.get_day()) + ", " + String::num_int64(date.get_year());
   TranslationServer const* server = TranslationServer::get_singleton();
   if (server != nullptr) {
      return server->translate(month_name) + day_and_year;
   } else {
      return month_name + day_and_year;
   }
}

Ref<Resource> Utilities::load_resource(String const& path, String const& type_hint) {
   ResourceLoader* loader = ResourceLoader::get_singleton();
   ERR_FAIL_NULL_V(loader, nullptr);
   return loader->load(path, type_hint);
}

static Ref<Image> load_dds_image(String const& path) {
   gli::texture2d texture { gli::load_dds(Utilities::godot_to_std_string(path)) };
   ERR_FAIL_COND_V_MSG(texture.empty(), nullptr, vformat("Failed to load DDS file: %s", path));

   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);
      ERR_FAIL_COND_V_MSG(texture.empty(), nullptr, vformat("Failed to convert DDS file: %s", path));
   }

   const gli::texture2d::extent_type extent { texture.extent() };
   const int32_t 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 */
   ERR_FAIL_COND_V_MSG(
      size > texture.size(), nullptr,
      vformat("Texture size %d mismatched with dims-based size %d for %s", static_cast<int64_t>(texture.size()), size, path)
   );

   PackedByteArray pixels;
   ERR_FAIL_COND_V(pixels.resize(size) != OK, nullptr);

   /* 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) {
   ERR_FAIL_NULL_V(image, nullptr);
   Ref<FontFile> font;
   font.instantiate();
   ERR_FAIL_COND_V_MSG(font->load_bitmap_font(fnt_path) != OK, nullptr, vformat("Failed to load font: %s", fnt_path));
   font->set_texture_image(0, { font->get_fixed_size(), 0 }, 0, image);
   return font;
}

Ref<Image> Utilities::make_solid_colour_image(Color const& colour, int32_t width, int32_t height, Image::Format format) {
   const Ref<Image> result = Image::create(width, height, false, format);
   ERR_FAIL_NULL_V(result, nullptr);
   result->fill(colour);
   return result;
}

Ref<ImageTexture> Utilities::make_solid_colour_texture(Color const& colour, int32_t width, int32_t height, Image::Format format) {
   const Ref<Image> image = make_solid_colour_image(colour, width, height, format);
   ERR_FAIL_NULL_V(image, nullptr);
   const Ref<ImageTexture> result = ImageTexture::create_from_image(image);
   ERR_FAIL_NULL_V(result, nullptr);
   return result;
}