#pragma once #include #define GLAD_GL_IMPLEMENTATION #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace glm; #define PREFERENCES_DIRECTORY "anm2ed" #define ROUND_NEAREST_MULTIPLE(value, multiple) (roundf((value) / (multiple)) * (multiple)) #define FLOAT_TO_UINT8(x) (static_cast((x) * 255.0f)) #define UINT8_TO_FLOAT(x) ((x) / 255.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f) #define UNIT_TO_PERCENT(x) (x * 100.0f) #define SECOND 1000.0f #define TICK_DELAY (SECOND / 30.0) #define UPDATE_DELAY (SECOND / 120.0) #define ID_NONE -1 #define INDEX_NONE -1 #define VALUE_NONE -1 #define TIME_NONE -1.0f #define GL_ID_NONE 0 #ifdef _WIN32 #define POPEN _popen #define PCLOSE _pclose #define PWRITE_MODE "wb" #define PREAD_MODE "r" #else #define POPEN popen #define PCLOSE pclose #define PWRITE_MODE "w" #define PREAD_MODE "r" #endif static const GLuint GL_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0}; static const vec4 COLOR_RED = {1.0f, 0.0f, 0.0f, 1.0f}; static const vec4 COLOR_GREEN = {0.0f, 1.0f, 0.0f, 1.0f}; static const vec4 COLOR_BLUE = {0.0f, 0.0f, 1.0f, 1.0f}; static const vec4 COLOR_PINK = {1.0f, 0.0f, 1.0f, 1.0f}; static const vec4 COLOR_OPAQUE = {1.0f, 1.0f, 1.0f, 1.0f}; static const vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 0.0f}; static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f}; static inline std::string preferences_path_get(void) { char* preferencesPath = SDL_GetPrefPath("", PREFERENCES_DIRECTORY); std::string preferencesPathString = preferencesPath; SDL_free(preferencesPath); return preferencesPathString; } static inline bool string_to_bool(const std::string& string) { if (string == "1") return true; std::string lower = string; std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); return lower == "true"; } static inline std::string string_quote(const std::string& string) { return "\"" + string + "\""; } static inline std::string string_to_lowercase(std::string string) { std::transform(string.begin(), string.end(), string.begin(), [](char character) { return std::tolower(character); }); return string; } static inline std::string string_backslash_replace(std::string string) { for (char& character : string) if (character == '\\') character = '/'; return string; } #define FLOAT_FORMAT_MAX_DECIMALS 5 #define FLOAT_FORMAT_EPSILON 1e-5f static constexpr float FLOAT_FORMAT_POW10[] = {1.f, 10.f, 100.f, 1000.f, 10000.f, 100000.f}; static inline int float_decimals_needed(float value) { for (int decimalCount = 0; decimalCount <= FLOAT_FORMAT_MAX_DECIMALS; ++decimalCount) { float scale = FLOAT_FORMAT_POW10[decimalCount]; float rounded = roundf(value * scale) / scale; if (fabsf(value - rounded) < FLOAT_FORMAT_EPSILON) return decimalCount; } return FLOAT_FORMAT_MAX_DECIMALS; } static inline const char* float_format_get(float value) { static std::string formatString; const int decimalCount = float_decimals_needed(value); formatString = (decimalCount == 0) ? "%.0f" : ("%." + std::to_string(decimalCount) + "f"); return formatString.c_str(); } static inline const char* vec2_format_get(const vec2& value) { static std::string formatString; const int decimalCountX = float_decimals_needed(value.x); const int decimalCountY = float_decimals_needed(value.y); const int decimalCount = (decimalCountX > decimalCountY) ? decimalCountX : decimalCountY; formatString = (decimalCount == 0) ? "%.0f" : ("%." + std::to_string(decimalCount) + "f"); return formatString.c_str(); } static inline std::string working_directory_from_file_set(const std::string& path) { std::filesystem::path filePath = path; std::filesystem::path parentPath = filePath.parent_path(); std::filesystem::current_path(parentPath); return parentPath.string(); }; static inline std::string path_extension_change(const std::string& path, const std::string& extension) { std::filesystem::path filePath(path); filePath.replace_extension(extension); return filePath.string(); } static inline bool path_is_extension(const std::string& path, const std::string& extension) { auto e = std::filesystem::path(path).extension().string(); std::transform(e.begin(), e.end(), e.begin(), ::tolower); return e == ("." + extension); } static inline bool path_exists(const std::filesystem::path& pathCheck) { std::error_code errorCode; return std::filesystem::exists(pathCheck, errorCode) && ((void)std::filesystem::status(pathCheck, errorCode), !errorCode); } static inline bool path_is_valid(const std::filesystem::path& pathCheck) { namespace fs = std::filesystem; std::error_code ec; if (fs::is_directory(pathCheck, ec)) return false; fs::path parentDir = pathCheck.has_parent_path() ? pathCheck.parent_path() : fs::path("."); if (!fs::is_directory(parentDir, ec)) return false; bool existedBefore = fs::exists(pathCheck, ec); std::ofstream testStream(pathCheck, std::ios::app | std::ios::binary); bool isValid = testStream.is_open(); testStream.close(); if (!existedBefore && isValid) fs::remove(pathCheck, ec); return isValid; } static inline int string_to_enum(const std::string& string, const char* const* array, int n) { for (int i = 0; i < n; i++) if (string == array[i]) return i; return -1; }; template T& dummy_value() { static T value{}; return value; } template static inline int map_next_id_get(const std::map& map) { int id = 0; for (const auto& [key, value] : map) { if (key != id) break; ++id; } return id; } template static inline auto map_find(Map& map, typename Map::key_type id) -> typename Map::mapped_type* { if (auto it = map.find(id); it != map.end()) return &it->second; return nullptr; } template static inline void map_swap(Map& map, const Key& key1, const Key& key2) { if (key1 == key2) return; auto it1 = map.find(key1); auto it2 = map.find(key2); if (it1 != map.end() && it2 != map.end()) { using std::swap; swap(it1->second, it2->second); } else if (it1 != map.end()) { map[key2] = std::move(it1->second); map.erase(it1); } else if (it2 != map.end()) { map[key1] = std::move(it2->second); map.erase(it2); } }; template static inline void map_insert_shift(std::map& map, int id, const T& value) { const int insertID = id + 1; std::vector> toShift; for (auto it = map.rbegin(); it != map.rend(); ++it) { if (it->first < insertID) break; toShift.emplace_back(it->first + 1, std::move(it->second)); } for (const auto& [newKey, _] : toShift) map.erase(newKey - 1); for (auto& [newKey, val] : toShift) map[newKey] = std::move(val); map[insertID] = value; } template auto map_keys_to_set(const Map& m) { using Key = typename Map::key_type; std::unordered_set s; s.reserve(m.size()); for (const auto& [key, _] : m) { s.insert(key); } return s; } template Set set_symmetric_difference(const Set& a, const Set& b) { Set result; result.reserve(a.size() + b.size()); for (const auto& x : a) { if (!b.contains(x)) { result.insert(x); } } for (const auto& x : b) { if (!a.contains(x)) { result.insert(x); } } return result; } template static inline T* vector_find(std::vector& v, const T& value) { auto it = std::find(v.begin(), v.end(), value); return (it != v.end()) ? &(*it) : nullptr; } template static inline void vector_value_erase(std::vector& v, const T& value) { v.erase(std::remove(v.begin(), v.end(), value), v.end()); } template static inline void vector_value_swap(std::vector& v, const T& a, const T& b) { for (auto& element : v) { if (element == a) element = b; else if (element == b) element = a; } } template static inline T* vector_get(std::vector& v, size_t index) { if (index < v.size()) return &v[index]; return nullptr; } template static inline void vector_move(std::vector& v, size_t from, size_t to) { if (from >= v.size() || to >= v.size() || from == to) return; if (from < to) { std::rotate(v.begin() + from, v.begin() + from + 1, v.begin() + to + 1); } else { std::rotate(v.begin() + to, v.begin() + from, v.begin() + from + 1); } } template void vector_erase_indices(std::vector& v, const std::set& indices) { size_t i = 0; v.erase(std::remove_if(v.begin(), v.end(), [&](const T&) { return indices.count(i++) > 0; }), v.end()); } template static inline void set_key_toggle(std::set& set, T key) { if (auto it = set.find(key); it != set.end()) set.erase(it); else set.insert(key); } template static inline void set_list(std::set& s, const T& key, bool isCtrl, bool isShift, T* lastSelected) { if (isShift && lastSelected) { s.clear(); T a = std::min(*lastSelected, key); T b = std::max(*lastSelected, key); for (T i = a; i <= b; i++) s.insert(i); } else if (isCtrl) { set_key_toggle(s, key); *lastSelected = key; } else { s = {key}; *lastSelected = key; } } static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) { vec2 scaleAbsolute = glm::abs(scale); vec2 scaleSign = glm::sign(scale); vec2 pivotScaled = pivot * scaleAbsolute; vec2 sizeScaled = size * scaleAbsolute; mat4 model(1.0f); model = glm::translate(model, vec3(position - pivotScaled, 0.0f)); model = glm::translate(model, vec3(pivotScaled, 0.0f)); model = glm::scale(model, vec3(scaleSign, 1.0f)); model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1)); model = glm::translate(model, vec3(-pivotScaled, 0.0f)); model = glm::scale(model, vec3(sizeScaled, 1.0f)); return model; } static inline mat4 quad_model_parent_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) { vec2 scaleSign = glm::sign(scale); vec2 scaleAbsolute = glm::abs(scale); float handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; mat4 local(1.0f); local = glm::translate(local, vec3(pivot, 0.0f)); local = glm::scale(local, vec3(scaleSign, 1.0f)); local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1)); local = glm::translate(local, vec3(-pivot, 0.0f)); local = glm::scale(local, vec3(scaleAbsolute, 1.0f)); return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; } #define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \ static inline enumType function(const std::string& string) { return static_cast(string_to_enum(string, stringArray, count)); }; #define DATATYPE_LIST \ X(TYPE_INT, int) \ X(TYPE_BOOL, bool) \ X(TYPE_FLOAT, float) \ X(TYPE_STRING, std::string) \ X(TYPE_IVEC2, ivec2) \ X(TYPE_IVEC2_WH, ivec2) \ X(TYPE_VEC2, vec2) \ X(TYPE_VEC2_WH, vec2) \ X(TYPE_VEC3, vec3) \ X(TYPE_VEC4, vec4) enum DataType { #define X(symbol, ctype) symbol, DATATYPE_LIST #undef X }; #define DATATYPE_TO_CTYPE(dt) DATATYPE_CTYPE_##dt #define X(symbol, ctype) typedef ctype DATATYPE_CTYPE_##symbol; DATATYPE_LIST #undef X enum OriginType { ORIGIN_TOP_LEFT, ORIGIN_CENTER }; struct WorkingDirectory { std::filesystem::path previous; WorkingDirectory(const std::string& file) { previous = std::filesystem::current_path(); working_directory_from_file_set(file); } ~WorkingDirectory() { std::filesystem::current_path(previous); } };