aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension/utility/Utilities.cpp
blob: 8293e70d38841fac0692bb20c932173fa5f34f27 (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
#include "Utilities.hpp"

#include <numbers>

#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_formatted_string(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);
}

/* Float to formatted to 4 decimal place string. */
String Utilities::float_to_formatted_string(float val) {
   static constexpr int64_t decimal_places = 4;
   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) {
   std::string const& month_name = 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(std_to_godot_string_name(month_name)) + day_and_year;
   } else {
      return std_to_godot_string(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)) };
   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;
}

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;
}