From b60c4bc295c1c9c7411cacf0ccd48705080e6928 Mon Sep 17 00:00:00 2001 From: shweet Date: Tue, 14 Apr 2026 14:28:14 -0400 Subject: [PATCH] I N T E R P O L A T I O N --- CMakeLists.txt | 1 - src/anm2/anm2.cpp | 29 ++++- src/anm2/anm2.hpp | 4 +- src/anm2/anm2_type.hpp | 7 +- src/anm2/frame.cpp | 52 ++++++++- src/anm2/frame.hpp | 13 ++- src/anm2/item.cpp | 35 +++++- src/document.cpp | 12 +- src/document.hpp | 6 +- src/imgui/documents.cpp | 8 +- src/imgui/imgui_.cpp | 110 +++++++++++++++++- src/imgui/imgui_.hpp | 24 ++++ src/imgui/taskbar.cpp | 16 ++- src/imgui/window/frame_properties.cpp | 28 +++-- src/imgui/window/regions.cpp | 18 ++- src/imgui/window/regions.hpp | 1 + src/imgui/window/timeline.cpp | 90 ++++++++++++-- .../wizard/change_all_frame_properties.cpp | 31 +++-- src/imgui/wizard/configure.cpp | 4 + src/loader.cpp | 95 --------------- src/manager.cpp | 18 ++- src/manager.hpp | 12 +- src/resource/icon.hpp | 17 ++- src/resource/strings.hpp | 19 ++- src/settings.hpp | 5 +- 25 files changed, 483 insertions(+), 172 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74eda3d..a299249 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,7 +180,6 @@ if(WIN32) set(PLATFORM_LIBS ws2_32 bcrypt - dbghelp imm32 version winmm diff --git a/src/anm2/anm2.cpp b/src/anm2/anm2.cpp index 54276bf..7b69379 100644 --- a/src/anm2/anm2.cpp +++ b/src/anm2/anm2.cpp @@ -124,6 +124,28 @@ namespace anm2ed::anm2 return {minX, minY, maxX - minX, maxY - minY}; } + void Anm2::bake_special_interpolated_frames(int interval, bool isRoundScale, bool isRoundRotation) + { + auto bake_item = [&](Item& item) + { + for (int i = (int)item.frames.size() - 1; i >= 0; --i) + { + auto interpolation = item.frames[i].interpolation; + if (interpolation == Frame::Interpolation::NONE || interpolation == Frame::Interpolation::LINEAR) continue; + item.frames_bake(i, interval, isRoundScale, isRoundRotation); + } + }; + + for (auto& animation : animations.items) + { + bake_item(animation.rootAnimation); + for (auto& item : animation.layerAnimations | std::views::values) + bake_item(item); + for (auto& item : animation.nullAnimations | std::views::values) + bake_item(item); + } + } + Anm2::Anm2(const std::filesystem::path& path, std::string* errorString) { XMLDocument document; @@ -168,10 +190,13 @@ namespace anm2ed::anm2 return element; } - bool Anm2::serialize(const std::filesystem::path& path, std::string* errorString, Flags flags) + bool Anm2::serialize(const std::filesystem::path& path, std::string* errorString, Flags flags, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { XMLDocument document; - document.InsertFirstChild(to_element(document, flags)); + auto serialized = *this; + if (isBakeSpecialInterpolatedFramesOnSave) serialized.bake_special_interpolated_frames(1, isRoundScale, isRoundRotation); + document.InsertFirstChild(serialized.to_element(document, flags)); File file(path, "wb"); if (!file) diff --git a/src/anm2/anm2.hpp b/src/anm2/anm2.hpp index 16c40ef..e74ea4d 100644 --- a/src/anm2/anm2.hpp +++ b/src/anm2/anm2.hpp @@ -32,7 +32,8 @@ namespace anm2ed::anm2 Anm2(); tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Flags = 0); - bool serialize(const std::filesystem::path&, std::string* = nullptr, Flags = 0); + bool serialize(const std::filesystem::path&, std::string* = nullptr, Flags = 0, bool = false, bool = true, + bool = true); std::string to_string(Flags = 0); Anm2(const std::filesystem::path&, std::string* = nullptr); uint64_t hash(); @@ -81,6 +82,7 @@ namespace anm2ed::anm2 bool animations_deserialize(const std::string&, int, std::set&, std::string* = nullptr); Frame frame_effective(int, const Frame&) const; glm::vec4 animation_rect(Animation&, bool) const; + void bake_special_interpolated_frames(int, bool, bool); Item* item_get(int, Type, int = -1); Reference layer_animation_add(Reference = {}, int = -1, std::string = {}, int = 0, diff --git a/src/anm2/anm2_type.hpp b/src/anm2/anm2_type.hpp index d80ced3..104caa1 100644 --- a/src/anm2/anm2_type.hpp +++ b/src/anm2/anm2_type.hpp @@ -106,7 +106,8 @@ namespace anm2ed::anm2 { NO_SOUNDS = 1 << 0, NO_REGIONS = 1 << 1, - FRAME_NO_REGION_VALUES = 1 << 2 + FRAME_NO_REGION_VALUES = 1 << 2, + INTERPOLATION_BOOL_ONLY = 1 << 3 }; typedef int Flags; @@ -114,5 +115,7 @@ namespace anm2ed::anm2 inline bool has_flag(Flags flags, Flag flag) { return (flags & flag) != 0; } inline const std::unordered_map COMPATIBILITY_FLAGS = { - {ISAAC, NO_SOUNDS | NO_REGIONS | FRAME_NO_REGION_VALUES}, {ANM2ED, 0}, {ANM2ED_LIMITED, FRAME_NO_REGION_VALUES}}; + {ISAAC, NO_SOUNDS | NO_REGIONS | FRAME_NO_REGION_VALUES | INTERPOLATION_BOOL_ONLY}, + {ANM2ED, 0}, + {ANM2ED_LIMITED, FRAME_NO_REGION_VALUES}}; } diff --git a/src/anm2/frame.cpp b/src/anm2/frame.cpp index 4415869..e9f0cc8 100644 --- a/src/anm2/frame.cpp +++ b/src/anm2/frame.cpp @@ -1,5 +1,7 @@ #include "frame.hpp" +#include + #include "math_.hpp" #include "xml_.hpp" @@ -8,8 +10,40 @@ using namespace tinyxml2; namespace anm2ed::anm2 { + namespace + { + Frame::Interpolation interpolation_from_xml(const char* value, bool fallback) + { + if (value) + { + if (std::strcmp(value, "EaseIn") == 0) return Frame::Interpolation::EASE_IN; + if (std::strcmp(value, "EaseOut") == 0) return Frame::Interpolation::EASE_OUT; + if (std::strcmp(value, "EaseInOut") == 0) return Frame::Interpolation::EASE_IN_OUT; + } + return fallback ? Frame::Interpolation::LINEAR : Frame::Interpolation::NONE; + } + + const char* interpolation_to_xml(Frame::Interpolation interpolation) + { + switch (interpolation) + { + case Frame::Interpolation::EASE_IN: + return "EaseIn"; + case Frame::Interpolation::EASE_OUT: + return "EaseOut"; + case Frame::Interpolation::EASE_IN_OUT: + return "EaseInOut"; + default: + return nullptr; + } + } + } + Frame::Frame(XMLElement* element, Type type) { + bool isInterpolatedBool{}; + const char* interpolationValue = element->Attribute("Interpolated"); + switch (type) { case ROOT: @@ -28,7 +62,8 @@ namespace anm2ed::anm2 xml::query_color_attribute(element, "GreenOffset", colorOffset.g); xml::query_color_attribute(element, "BlueOffset", colorOffset.b); element->QueryFloatAttribute("Rotation", &rotation); - element->QueryBoolAttribute("Interpolated", &isInterpolated); + element->QueryBoolAttribute("Interpolated", &isInterpolatedBool); + if (interpolationValue) interpolation = interpolation_from_xml(interpolationValue, isInterpolatedBool); break; case LAYER: element->QueryIntAttribute("RegionId", ®ionID); @@ -52,7 +87,8 @@ namespace anm2ed::anm2 xml::query_color_attribute(element, "GreenOffset", colorOffset.g); xml::query_color_attribute(element, "BlueOffset", colorOffset.b); element->QueryFloatAttribute("Rotation", &rotation); - element->QueryBoolAttribute("Interpolated", &isInterpolated); + element->QueryBoolAttribute("Interpolated", &isInterpolatedBool); + if (interpolationValue) interpolation = interpolation_from_xml(interpolationValue, isInterpolatedBool); break; case TRIGGER: { @@ -98,7 +134,11 @@ namespace anm2ed::anm2 element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g)); element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b)); element->SetAttribute("Rotation", rotation); - element->SetAttribute("Interpolated", isInterpolated); + if (has_flag(flags, INTERPOLATION_BOOL_ONLY) || interpolation == Interpolation::NONE || + interpolation == Interpolation::LINEAR) + element->SetAttribute("Interpolated", interpolation == Interpolation::LINEAR); + else if (const char* interpolationValue = interpolation_to_xml(interpolation)) + element->SetAttribute("Interpolated", interpolationValue); break; case LAYER: { @@ -131,7 +171,11 @@ namespace anm2ed::anm2 element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g)); element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b)); element->SetAttribute("Rotation", rotation); - element->SetAttribute("Interpolated", isInterpolated); + if (has_flag(flags, INTERPOLATION_BOOL_ONLY) || interpolation == Interpolation::NONE || + interpolation == Interpolation::LINEAR) + element->SetAttribute("Interpolated", interpolation == Interpolation::LINEAR); + else if (const char* interpolationValue = interpolation_to_xml(interpolation)) + element->SetAttribute("Interpolated", interpolationValue); break; } case TRIGGER: diff --git a/src/anm2/frame.hpp b/src/anm2/frame.hpp index bb33a1f..7c41859 100644 --- a/src/anm2/frame.hpp +++ b/src/anm2/frame.hpp @@ -15,8 +15,17 @@ namespace anm2ed::anm2 class Frame { public: + enum Interpolation + { + NONE, + LINEAR, + EASE_IN, + EASE_OUT, + EASE_IN_OUT + }; + bool isVisible{true}; - bool isInterpolated{false}; + Interpolation interpolation{NONE}; float rotation{}; int duration{FRAME_DURATION_MIN}; int atFrame{-1}; @@ -43,7 +52,7 @@ namespace anm2ed::anm2 struct FrameChange { std::optional isVisible{}; - std::optional isInterpolated{}; + std::optional interpolation{}; std::optional rotation{}; std::optional duration{}; std::optional regionID{}; diff --git a/src/anm2/item.cpp b/src/anm2/item.cpp index 08ba283..de9dc82 100644 --- a/src/anm2/item.cpp +++ b/src/anm2/item.cpp @@ -1,5 +1,6 @@ #include "item.hpp" #include +#include #include #include "vector_.hpp" @@ -11,6 +12,29 @@ using namespace glm; namespace anm2ed::anm2 { + namespace + { + float interpolation_factor(Frame::Interpolation interpolation, float value) + { + value = glm::clamp(value, 0.0f, 1.0f); + + switch (interpolation) + { + case Frame::Interpolation::LINEAR: + return value; + case Frame::Interpolation::EASE_IN: + return value * value; + case Frame::Interpolation::EASE_OUT: + return 1.0f - ((1.0f - value) * (1.0f - value)); + case Frame::Interpolation::EASE_IN_OUT: + return value < 0.5f ? (2.0f * value * value) : (1.0f - std::pow(-2.0f * value + 2.0f, 2.0f) * 0.5f); + case Frame::Interpolation::NONE: + default: + return 0.0f; + } + } + } + Item::Item(XMLElement* element, Type type, int* id) { if (type == LAYER && id) element->QueryIntAttribute("LayerId", id); @@ -112,9 +136,10 @@ namespace anm2ed::anm2 } } - if (type != TRIGGER && frame.isInterpolated && frameNext && frame.duration > 1) + if (type != TRIGGER && frame.interpolation != Frame::Interpolation::NONE && frameNext && frame.duration > 1) { - auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent); + auto interpolation = + interpolation_factor(frame.interpolation, (time - durationCurrent) / (durationNext - durationCurrent)); frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation); frame.position = glm::mix(frame.position, frameNext->position, interpolation); @@ -169,7 +194,7 @@ namespace anm2ed::anm2 Frame& frame = frames[i]; if (change.isVisible) frame.isVisible = *change.isVisible; - if (change.isInterpolated) frame.isInterpolated = *change.isInterpolated; + if (change.interpolation) frame.interpolation = *change.interpolation; if (change.isFlipX) frame.scale.x = -frame.scale.x; if (change.isFlipY) frame.scale.y = -frame.scale.y; @@ -280,9 +305,9 @@ namespace anm2ed::anm2 while (duration < original.duration) { Frame baked = original; - float interpolation = (float)duration / original.duration; + float interpolation = interpolation_factor(original.interpolation, (float)duration / original.duration); baked.duration = std::min(interval, original.duration - duration); - baked.isInterpolated = (i == index) ? original.isInterpolated : false; + baked.interpolation = (i == index) ? original.interpolation : Frame::Interpolation::NONE; baked.rotation = glm::mix(original.rotation, nextFrame.rotation, interpolation); baked.position = glm::mix(original.position, nextFrame.position, interpolation); baked.scale = glm::mix(original.scale, nextFrame.scale, interpolation); diff --git a/src/document.cpp b/src/document.cpp index ed7413e..339c46e 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -109,13 +109,15 @@ namespace anm2ed return *this; } - bool Document::save(const std::filesystem::path& path, std::string* errorString, anm2::Compatibility compatibility) + bool Document::save(const std::filesystem::path& path, std::string* errorString, anm2::Compatibility compatibility, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { this->path = !path.empty() ? path : this->path; auto absolutePath = this->path; auto absolutePathUtf8 = path::to_utf8(absolutePath); - if (anm2.serialize(absolutePath, errorString, serialize_flags_get(compatibility))) + if (anm2.serialize(absolutePath, errorString, serialize_flags_get(compatibility), + isBakeSpecialInterpolatedFramesOnSave, isRoundScale, isRoundRotation)) { toasts.push(std::vformat(localize.get(TOAST_SAVE_DOCUMENT), std::make_format_args(absolutePathUtf8))); logger.info( @@ -152,11 +154,13 @@ namespace anm2ed return restorePath; } - bool Document::autosave(std::string* errorString, anm2::Compatibility compatibility) + bool Document::autosave(std::string* errorString, anm2::Compatibility compatibility, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { auto autosavePath = autosave_path_get(); auto autosavePathUtf8 = path::to_utf8(autosavePath); - if (anm2.serialize(autosavePath, errorString, serialize_flags_get(compatibility))) + if (anm2.serialize(autosavePath, errorString, serialize_flags_get(compatibility), + isBakeSpecialInterpolatedFramesOnSave, isRoundScale, isRoundRotation)) { autosaveHash = hash; lastAutosaveTime = 0.0f; diff --git a/src/document.hpp b/src/document.hpp index 5e16874..81526ef 100644 --- a/src/document.hpp +++ b/src/document.hpp @@ -76,7 +76,8 @@ namespace anm2ed Document& operator=(const Document&) = delete; Document(Document&&) noexcept; Document& operator=(Document&&) noexcept; - bool save(const std::filesystem::path& = {}, std::string* = nullptr, anm2::Compatibility = anm2::ANM2ED); + bool save(const std::filesystem::path& = {}, std::string* = nullptr, anm2::Compatibility = anm2::ANM2ED, + bool = false, bool = true, bool = true); void hash_set(); void clean(); void change(ChangeType); @@ -100,7 +101,8 @@ namespace anm2ed void spritesheet_add(const std::filesystem::path&); void sound_add(const std::filesystem::path&); - bool autosave(std::string* = nullptr, anm2::Compatibility = anm2::ANM2ED); + bool autosave(std::string* = nullptr, anm2::Compatibility = anm2::ANM2ED, bool = false, bool = true, + bool = true); std::filesystem::path autosave_path_get(); std::filesystem::path path_from_autosave_get(std::filesystem::path&); diff --git a/src/imgui/documents.cpp b/src/imgui/documents.cpp index 05b5d4d..593256f 100644 --- a/src/imgui/documents.cpp +++ b/src/imgui/documents.cpp @@ -38,7 +38,9 @@ namespace anm2ed::imgui { document.lastAutosaveTime += ImGui::GetIO().DeltaTime; if (document.lastAutosaveTime > time::SECOND_M) - manager.autosave(document, (anm2::Compatibility)settings.fileCompatibility); + manager.autosave(document, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); } } @@ -154,7 +156,9 @@ namespace anm2ed::imgui if (ImGui::Button(localize.get(BASIC_YES), widgetSize)) { if (isDocumentDirty) - manager.save(closeDocumentIndex, {}, (anm2::Compatibility)settings.fileCompatibility); + manager.save(closeDocumentIndex, {}, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); if (isSpritesheetDirty) { diff --git a/src/imgui/imgui_.cpp b/src/imgui/imgui_.cpp index fc3f1d9..66334af 100644 --- a/src/imgui/imgui_.cpp +++ b/src/imgui/imgui_.cpp @@ -18,6 +18,110 @@ namespace anm2ed::imgui { static auto isRenaming = false; + namespace + { + const std::vector> CANONICAL_KEY_NAMES = { + {ImGuiKey_A, "A"}, + {ImGuiKey_B, "B"}, + {ImGuiKey_C, "C"}, + {ImGuiKey_D, "D"}, + {ImGuiKey_E, "E"}, + {ImGuiKey_F, "F"}, + {ImGuiKey_G, "G"}, + {ImGuiKey_H, "H"}, + {ImGuiKey_I, "I"}, + {ImGuiKey_J, "J"}, + {ImGuiKey_K, "K"}, + {ImGuiKey_L, "L"}, + {ImGuiKey_M, "M"}, + {ImGuiKey_N, "N"}, + {ImGuiKey_O, "O"}, + {ImGuiKey_P, "P"}, + {ImGuiKey_Q, "Q"}, + {ImGuiKey_R, "R"}, + {ImGuiKey_S, "S"}, + {ImGuiKey_T, "T"}, + {ImGuiKey_U, "U"}, + {ImGuiKey_V, "V"}, + {ImGuiKey_W, "W"}, + {ImGuiKey_X, "X"}, + {ImGuiKey_Y, "Y"}, + {ImGuiKey_Z, "Z"}, + {ImGuiKey_0, "0"}, + {ImGuiKey_1, "1"}, + {ImGuiKey_2, "2"}, + {ImGuiKey_3, "3"}, + {ImGuiKey_4, "4"}, + {ImGuiKey_5, "5"}, + {ImGuiKey_6, "6"}, + {ImGuiKey_7, "7"}, + {ImGuiKey_8, "8"}, + {ImGuiKey_9, "9"}, + {ImGuiKey_Keypad0, "Num0"}, + {ImGuiKey_Keypad1, "Num1"}, + {ImGuiKey_Keypad2, "Num2"}, + {ImGuiKey_Keypad3, "Num3"}, + {ImGuiKey_Keypad4, "Num4"}, + {ImGuiKey_Keypad5, "Num5"}, + {ImGuiKey_Keypad6, "Num6"}, + {ImGuiKey_Keypad7, "Num7"}, + {ImGuiKey_Keypad8, "Num8"}, + {ImGuiKey_Keypad9, "Num9"}, + {ImGuiKey_KeypadAdd, "NumAdd"}, + {ImGuiKey_KeypadSubtract, "NumSubtract"}, + {ImGuiKey_KeypadMultiply, "NumMultiply"}, + {ImGuiKey_KeypadDivide, "NumDivide"}, + {ImGuiKey_KeypadEnter, "NumEnter"}, + {ImGuiKey_KeypadDecimal, "NumDecimal"}, + {ImGuiKey_KeypadEqual, "NumEqual"}, + {ImGuiKey_F1, "F1"}, + {ImGuiKey_F2, "F2"}, + {ImGuiKey_F3, "F3"}, + {ImGuiKey_F4, "F4"}, + {ImGuiKey_F5, "F5"}, + {ImGuiKey_F6, "F6"}, + {ImGuiKey_F7, "F7"}, + {ImGuiKey_F8, "F8"}, + {ImGuiKey_F9, "F9"}, + {ImGuiKey_F10, "F10"}, + {ImGuiKey_F11, "F11"}, + {ImGuiKey_F12, "F12"}, + {ImGuiKey_UpArrow, "Up"}, + {ImGuiKey_DownArrow, "Down"}, + {ImGuiKey_LeftArrow, "Left"}, + {ImGuiKey_RightArrow, "Right"}, + {ImGuiKey_Space, "Space"}, + {ImGuiKey_Enter, "Enter"}, + {ImGuiKey_Escape, "Escape"}, + {ImGuiKey_Tab, "Tab"}, + {ImGuiKey_Backspace, "Backspace"}, + {ImGuiKey_Delete, "Delete"}, + {ImGuiKey_Insert, "Insert"}, + {ImGuiKey_Home, "Home"}, + {ImGuiKey_End, "End"}, + {ImGuiKey_PageUp, "PageUp"}, + {ImGuiKey_PageDown, "PageDown"}, + {ImGuiKey_Minus, "Minus"}, + {ImGuiKey_Equal, "Equal"}, + {ImGuiKey_LeftBracket, "LeftBracket"}, + {ImGuiKey_RightBracket, "RightBracket"}, + {ImGuiKey_Semicolon, "Semicolon"}, + {ImGuiKey_Apostrophe, "Apostrophe"}, + {ImGuiKey_Comma, "Comma"}, + {ImGuiKey_Period, "Period"}, + {ImGuiKey_Slash, "Slash"}, + {ImGuiKey_Backslash, "Backslash"}, + {ImGuiKey_GraveAccent, "GraveAccent"}, + }; + + const char* canonical_key_name(ImGuiKey key) + { + for (const auto& [mappedKey, name] : CANONICAL_KEY_NAMES) + if (mappedKey == key) return name; + return nullptr; + } + } + constexpr ImVec4 COLOR_LIGHT_BUTTON{0.98f, 0.98f, 0.98f, 1.0f}; constexpr ImVec4 COLOR_LIGHT_TITLE_BG{0.78f, 0.78f, 0.78f, 1.0f}; constexpr ImVec4 COLOR_LIGHT_TITLE_BG_ACTIVE{0.64f, 0.64f, 0.64f, 1.0f}; @@ -427,10 +531,10 @@ namespace anm2ed::imgui if (auto key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); key != ImGuiKey_None) { - if (const char* name = ImGui::GetKeyName(key); name && *name) + if (const char* name = canonical_key_name(key); name && *name) result += name; else - result += "Unknown"; + result += ImGui::GetKeyName(key); } if (!result.empty() && result.back() == '+') result.pop_back(); @@ -499,6 +603,8 @@ namespace anm2ed::imgui bool shortcut(ImGuiKeyChord chord, shortcut::Type type) { + if (chord == ImGuiKey_None) return false; + if (ImGui::GetTopMostPopupModal() != nullptr && (type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET)) return false; diff --git a/src/imgui/imgui_.hpp b/src/imgui/imgui_.hpp index cff5a5c..c53d097 100644 --- a/src/imgui/imgui_.hpp +++ b/src/imgui/imgui_.hpp @@ -115,6 +115,7 @@ namespace anm2ed::imgui {"NumDivide", ImGuiKey_KeypadDivide}, {"NumEnter", ImGuiKey_KeypadEnter}, {"NumDecimal", ImGuiKey_KeypadDecimal}, + {"NumEqual", ImGuiKey_KeypadEqual}, {"F1", ImGuiKey_F1}, {"F2", ImGuiKey_F2}, @@ -157,6 +158,29 @@ namespace anm2ed::imgui {"Slash", ImGuiKey_Slash}, {"Backslash", ImGuiKey_Backslash}, {"GraveAccent", ImGuiKey_GraveAccent}, + + // Legacy aliases for older saved shortcut strings. + {"Keypad0", ImGuiKey_Keypad0}, + {"Keypad1", ImGuiKey_Keypad1}, + {"Keypad2", ImGuiKey_Keypad2}, + {"Keypad3", ImGuiKey_Keypad3}, + {"Keypad4", ImGuiKey_Keypad4}, + {"Keypad5", ImGuiKey_Keypad5}, + {"Keypad6", ImGuiKey_Keypad6}, + {"Keypad7", ImGuiKey_Keypad7}, + {"Keypad8", ImGuiKey_Keypad8}, + {"Keypad9", ImGuiKey_Keypad9}, + {"KeypadAdd", ImGuiKey_KeypadAdd}, + {"KeypadSubtract", ImGuiKey_KeypadSubtract}, + {"KeypadMultiply", ImGuiKey_KeypadMultiply}, + {"KeypadDivide", ImGuiKey_KeypadDivide}, + {"KeypadEnter", ImGuiKey_KeypadEnter}, + {"KeypadDecimal", ImGuiKey_KeypadDecimal}, + {"KeypadEqual", ImGuiKey_KeypadEqual}, + {"UpArrow", ImGuiKey_UpArrow}, + {"DownArrow", ImGuiKey_DownArrow}, + {"LeftArrow", ImGuiKey_LeftArrow}, + {"RightArrow", ImGuiKey_RightArrow}, }; const std::unordered_map MOD_MAP = { diff --git a/src/imgui/taskbar.cpp b/src/imgui/taskbar.cpp index 28ca282..ddcc974 100644 --- a/src/imgui/taskbar.cpp +++ b/src/imgui/taskbar.cpp @@ -74,7 +74,9 @@ namespace anm2ed::imgui if (settings.fileIsWarnOverwrite) overwritePopup.open(); else - manager.save(document->path, (anm2::Compatibility)settings.fileCompatibility); + manager.save(document->path, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); } if (ImGui::MenuItem(localize.get(LABEL_SAVE_AS), settings.shortcutSaveAs.c_str(), false, document)) @@ -100,7 +102,9 @@ namespace anm2ed::imgui if (dialog.is_selected(Dialog::ANM2_SAVE)) { - manager.save(dialog.path, (anm2::Compatibility)settings.fileCompatibility); + manager.save(dialog.path, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); dialog.reset(); } @@ -240,7 +244,9 @@ namespace anm2ed::imgui if (ImGui::Button(localize.get(BASIC_YES), widgetSize)) { - manager.save({}, (anm2::Compatibility)settings.fileCompatibility); + manager.save({}, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); overwritePopup.close(); } @@ -260,7 +266,9 @@ namespace anm2ed::imgui if (settings.fileIsWarnOverwrite) overwritePopup.open(); else - manager.save({}, (anm2::Compatibility)settings.fileCompatibility); + manager.save({}, (anm2::Compatibility)settings.fileCompatibility, + settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale, + settings.bakeIsRoundRotation); } if (shortcut(manager.chords[SHORTCUT_SAVE_AS], shortcut::GLOBAL)) dialog.file_save(Dialog::ANM2_SAVE); if (shortcut(manager.chords[SHORTCUT_EXIT], shortcut::GLOBAL)) isQuitting = true; diff --git a/src/imgui/window/frame_properties.cpp b/src/imgui/window/frame_properties.cpp index 5100465..c0e1798 100644 --- a/src/imgui/window/frame_properties.cpp +++ b/src/imgui/window/frame_properties.cpp @@ -25,6 +25,17 @@ namespace anm2ed::imgui auto regionLabelsString = std::vector{localize.get(BASIC_NONE)}; auto regionLabels = std::vector{regionLabelsString[0].c_str()}; auto regionIds = std::vector{-1}; + auto interpolationLabelsString = + std::vector{localize.get(BASIC_NONE), localize.get(BASIC_LINEAR), localize.get(BASIC_EASE_IN), + localize.get(BASIC_EASE_OUT), localize.get(BASIC_EASE_IN_OUT)}; + auto interpolationLabels = + std::vector{interpolationLabelsString[0].c_str(), interpolationLabelsString[1].c_str(), + interpolationLabelsString[2].c_str(), interpolationLabelsString[3].c_str(), + interpolationLabelsString[4].c_str()}; + auto interpolationValues = + std::vector{anm2::Frame::Interpolation::NONE, anm2::Frame::Interpolation::LINEAR, + anm2::Frame::Interpolation::EASE_IN, anm2::Frame::Interpolation::EASE_OUT, + anm2::Frame::Interpolation::EASE_IN_OUT}; if (type == anm2::LAYER && document.reference.itemID != -1) { @@ -219,21 +230,20 @@ namespace anm2ed::imgui ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REGION)); ImGui::EndDisabled(); + auto interpolationValue = frame ? static_cast(useFrame.interpolation) : dummy_value(); + if (combo_id_mapped(localize.get(BASIC_INTERPOLATED), &interpolationValue, interpolationValues, + interpolationLabels) && + frame) + DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_INTERPOLATION), Document::FRAMES, + frame->interpolation = static_cast(interpolationValue)); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_INTERPOLATION)); + if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value()) && frame) DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_VISIBILITY), Document::FRAMES, frame->isVisible = useFrame.isVisible); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_VISIBILITY)); - ImGui::SameLine(); - - if (ImGui::Checkbox(localize.get(BASIC_INTERPOLATED), - frame ? &useFrame.isInterpolated : &dummy_value()) && - frame) - DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_INTERPOLATION), Document::FRAMES, - frame->isInterpolated = useFrame.isInterpolated); - ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_INTERPOLATION)); - auto widgetSize = widget_size_with_row_get(2); if (ImGui::Button(localize.get(LABEL_FLIP_X), widgetSize) && frame) diff --git a/src/imgui/window/regions.cpp b/src/imgui/window/regions.cpp index 261c3fb..e135684 100644 --- a/src/imgui/window/regions.cpp +++ b/src/imgui/window/regions.cpp @@ -34,6 +34,17 @@ namespace anm2ed::imgui auto spritesheet = map::find(anm2.content.spritesheets, spritesheetReference); + if (manager.isMakeRegionRequested) + { + spritesheetReference = manager.makeRegionSpritesheetId; + reference = -1; + editRegion = manager.makeRegion; + isPreserveEditRegionOnOpen = true; + propertiesPopup.open(); + manager.isMakeRegionRequested = false; + spritesheet = map::find(anm2.content.spritesheets, spritesheetReference); + } + auto remove_unused = [&]() { if (!spritesheet) return; @@ -474,7 +485,12 @@ namespace anm2ed::imgui auto childSize = child_size_get(5); auto& region = reference == -1 ? editRegion : spritesheet->regions.at(reference); - if (propertiesPopup.isJustOpened) editRegion = anm2::Spritesheet::Region{}; + if (propertiesPopup.isJustOpened) + { + if (!isPreserveEditRegionOnOpen) + editRegion = anm2::Spritesheet::Region{}; + isPreserveEditRegionOnOpen = false; + } if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders)) { diff --git a/src/imgui/window/regions.hpp b/src/imgui/window/regions.hpp index 54e3f28..49d75df 100644 --- a/src/imgui/window/regions.hpp +++ b/src/imgui/window/regions.hpp @@ -12,6 +12,7 @@ namespace anm2ed::imgui public: anm2::Spritesheet::Region editRegion{}; int newRegionId{-1}; + bool isPreserveEditRegionOnOpen{}; imgui::PopupHelper propertiesPopup{imgui::PopupHelper(LABEL_REGION_PROPERTIES, imgui::POPUP_SMALL_NO_HEIGHT)}; void update(Manager&, Settings&, Resources&, Clipboard&); diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index af9dfe1..36b124d 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -292,9 +292,27 @@ namespace anm2ed::imgui auto nextFrame = vector::in_bounds(item->frames, reference.frameIndex + 1) ? &item->frames[reference.frameIndex + 1] : nullptr; - if (frame->isInterpolated && nextFrame) + if (frame->interpolation != anm2::Frame::Interpolation::NONE && nextFrame) { float interpolation = (float)firstDuration / (float)originalDuration; + switch (frame->interpolation) + { + case anm2::Frame::Interpolation::EASE_IN: + interpolation *= interpolation; + break; + case anm2::Frame::Interpolation::EASE_OUT: + interpolation = 1.0f - ((1.0f - interpolation) * (1.0f - interpolation)); + break; + case anm2::Frame::Interpolation::EASE_IN_OUT: + interpolation = interpolation < 0.5f ? (2.0f * interpolation * interpolation) + : (1.0f - std::pow(-2.0f * interpolation + 2.0f, 2.0f) * 0.5f); + break; + case anm2::Frame::Interpolation::LINEAR: + case anm2::Frame::Interpolation::NONE: + default: + break; + } + splitFrame.rotation = glm::mix(frame->rotation, nextFrame->rotation, interpolation); splitFrame.position = glm::mix(frame->position, nextFrame->position, interpolation); splitFrame.scale = glm::mix(frame->scale, nextFrame->scale, interpolation); @@ -450,9 +468,37 @@ namespace anm2ed::imgui if (shortcut(manager.chords[SHORTCUT_BAKE], shortcut::FOCUSED)) frames_bake(); if (shortcut(manager.chords[SHORTCUT_FIT], shortcut::FOCUSED)) fit_animation_length(); + auto make_region = [&]() + { + auto frame = document.frame_get(); + if (!frame || reference.itemType != anm2::LAYER || reference.itemID == -1) return; + if (frame->regionID != -1) return; + if (!anm2.content.layers.contains(reference.itemID)) return; + + auto spritesheetID = anm2.content.layers.at(reference.itemID).spritesheetID; + if (!anm2.content.spritesheets.contains(spritesheetID)) return; + + anm2::Spritesheet::Region region{}; + region.crop = frame->crop; + region.size = frame->size; + region.pivot = frame->pivot; + region.origin = anm2::Spritesheet::Region::CUSTOM; + + document.spritesheet.reference = spritesheetID; + settings.windowIsRegions = true; + manager.makeRegionSpritesheetId = spritesheetID; + manager.makeRegion = region; + manager.isMakeRegionRequested = true; + }; + if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) { auto item = animation ? animation->item_get(reference.itemType, reference.itemID) : nullptr; + auto frame = document.frame_get(); + bool isMakeRegion = + frame && reference.itemType == anm2::LAYER && reference.itemID != -1 && frame->regionID == -1 && + anm2.content.layers.contains(reference.itemID) && + anm2.content.spritesheets.contains(anm2.content.layers.at(reference.itemID).spritesheetID); if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false, document.is_able_to_undo())) @@ -484,6 +530,8 @@ namespace anm2ed::imgui frames.selection.size() == 1)) frame_split(); + if (ImGui::MenuItem(localize.get(LABEL_MAKE_REGION), nullptr, false, isMakeRegion)) make_region(); + ImGui::Separator(); if (ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, !frames.selection.empty())) @@ -1130,10 +1178,7 @@ namespace anm2ed::imgui auto buttonPos = ImVec2(cursorPos.x + (frameTime * frameSize.x), cursorPos.y); if (frameFocusRequested && frameFocusIndex == (int)i && reference == frameReference) - { - ImGui::SetKeyboardFocusHere(); frameFocusRequested = false; - } ImGui::SetCursorPos(buttonPos); @@ -1161,7 +1206,9 @@ namespace anm2ed::imgui { if (ImGui::IsKeyDown(ImGuiMod_Alt)) DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_INTERPOLATION), Document::FRAMES, - frame.isInterpolated = !frame.isInterpolated); + frame.interpolation = frame.interpolation == anm2::Frame::Interpolation::NONE + ? anm2::Frame::Interpolation::LINEAR + : anm2::Frame::Interpolation::NONE); document.frameTime = frameTime; } @@ -1327,9 +1374,33 @@ namespace anm2ed::imgui auto borderThickness = isReferenced ? FRAME_BORDER_THICKNESS_REFERENCED : FRAME_BORDER_THICKNESS; drawList->AddRect(rectMin, rectMax, ImGui::GetColorU32(borderColor), FRAME_ROUNDING, 0, borderThickness); - auto icon = type == anm2::TRIGGER ? icon::TRIGGER - : frame.isInterpolated ? icon::INTERPOLATED - : icon::UNINTERPOLATED; + auto icon = icon::UNINTERPOLATED; + if (type == anm2::TRIGGER) + icon = icon::TRIGGER; + else + { + switch (frame.interpolation) + { + case anm2::Frame::Interpolation::NONE: + icon = icon::UNINTERPOLATED; + break; + case anm2::Frame::Interpolation::LINEAR: + icon = icon::INTERPOLATED; + break; + case anm2::Frame::Interpolation::EASE_IN: + icon = icon::EASE_IN; + break; + case anm2::Frame::Interpolation::EASE_OUT: + icon = icon::EASE_OUT; + break; + case anm2::Frame::Interpolation::EASE_IN_OUT: + icon = icon::EASE_IN_OUT; + break; + default: + icon = icon::UNINTERPOLATED; + break; + } + } auto iconPos = ImVec2(cursorPos.x + (frameTime * frameSize.x), cursorPos.y + (frameSize.y / 2) - (icon_size_get().y / 2)); ImGui::SetCursorPos(iconPos); @@ -1426,8 +1497,7 @@ namespace anm2ed::imgui auto childWidth = anm2.animations.length() * ImGui::GetTextLineHeight(); if (animation && animation->frameNum > anm2.animations.length()) childWidth = animation->frameNum * ImGui::GetTextLineHeight(); - else if (ImGui::GetContentRegionAvail().x > childWidth) - childWidth = ImGui::GetContentRegionAvail().x; + childWidth = std::max(childWidth, ImGui::GetContentRegionAvail().x); childWidth *= WIDTH_MULTIPLIER; diff --git a/src/imgui/wizard/change_all_frame_properties.cpp b/src/imgui/wizard/change_all_frame_properties.cpp index f346a20..602e460 100644 --- a/src/imgui/wizard/change_all_frame_properties.cpp +++ b/src/imgui/wizard/change_all_frame_properties.cpp @@ -35,7 +35,7 @@ namespace anm2ed::imgui::wizard auto& isColorOffsetG = settings.changeIsColorOffsetG; auto& isColorOffsetB = settings.changeIsColorOffsetB; auto& isVisibleSet = settings.changeIsVisibleSet; - auto& isInterpolatedSet = settings.changeIsInterpolatedSet; + auto& isInterpolationSet = settings.changeIsInterpolationSet; auto& isFlipXSet = settings.changeIsFlipXSet; auto& isFlipYSet = settings.changeIsFlipYSet; auto& isRegion = settings.changeIsRegion; @@ -50,7 +50,7 @@ namespace anm2ed::imgui::wizard auto& colorOffset = settings.changeColorOffset; auto& regionId = settings.changeRegionId; auto& isVisible = settings.changeIsVisible; - auto& isInterpolated = settings.changeIsInterpolated; + auto& interpolation = settings.changeInterpolation; auto& isFlipX = settings.changeIsFlipX; auto& isFlipY = settings.changeIsFlipY; auto& itemType = document.reference.itemType; @@ -65,6 +65,10 @@ namespace anm2ed::imgui::wizard auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value) { PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value), checkboxLabel, isEnabled) }; + auto enum_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value, + const std::vector& ids, std::vector& labels) + { PROPERTIES_WIDGET(combo_id_mapped(valueLabel, &value, ids, labels), checkboxLabel, isEnabled) }; + auto color3_value = [&](const char* checkboxRLabel, const char* checkboxGLabel, const char* checkboxBLabel, const char* valueRLabel, const char* valueGLabel, const char* valueBLabel, const char* label, bool& isREnabled, bool& isGEnabled, bool& isBEnabled, vec3& value) @@ -214,6 +218,17 @@ namespace anm2ed::imgui::wizard std::vector fallbackIds{-1}; std::vector fallbackLabelsString{localize.get(BASIC_NONE)}; std::vector fallbackLabels{fallbackLabelsString[0].c_str()}; + std::vector interpolationIds{anm2::Frame::Interpolation::NONE, anm2::Frame::Interpolation::LINEAR, + anm2::Frame::Interpolation::EASE_IN, anm2::Frame::Interpolation::EASE_OUT, + anm2::Frame::Interpolation::EASE_IN_OUT}; + std::vector interpolationLabelsString{ + localize.get(BASIC_NONE), localize.get(BASIC_LINEAR), localize.get(BASIC_EASE_IN), + localize.get(BASIC_EASE_OUT), localize.get(BASIC_EASE_IN_OUT)}; + std::vector interpolationLabels{interpolationLabelsString[0].c_str(), + interpolationLabelsString[1].c_str(), + interpolationLabelsString[2].c_str(), + interpolationLabelsString[3].c_str(), + interpolationLabelsString[4].c_str()}; const Storage* regionStorage = nullptr; if (itemType == anm2::LAYER && document.reference.itemID != -1) @@ -228,12 +243,11 @@ namespace anm2ed::imgui::wizard isRegion); ImGui::EndDisabled(); + enum_value("##Is Interpolation", localize.get(BASIC_INTERPOLATED), isInterpolationSet, interpolation, + interpolationIds, interpolationLabels); + bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible); - ImGui::SameLine(); - - bool_value("##Is Interpolated", localize.get(BASIC_INTERPOLATED), isInterpolatedSet, isInterpolated); - bool_value("##Is Flip X", localize.get(LABEL_FLIP_X), isFlipXSet, isFlipX); ImGui::SameLine(); @@ -268,7 +282,8 @@ namespace anm2ed::imgui::wizard if (isColorOffsetG) frameChange.colorOffsetG = colorOffset.g; if (isColorOffsetB) frameChange.colorOffsetB = colorOffset.b; if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible); - if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated); + if (isInterpolationSet) + frameChange.interpolation = std::make_optional(static_cast(interpolation)); if (isFlipXSet) frameChange.isFlipX = std::make_optional(isFlipX); if (isFlipYSet) frameChange.isFlipY = std::make_optional(isFlipY); @@ -283,7 +298,7 @@ namespace anm2ed::imgui::wizard bool isAnyProperty = isCropX || isCropY || isSizeX || isSizeY || isPositionX || isPositionY || isPivotX || isPivotY || isScaleX || isScaleY || isRotation || isDuration || isTintR || isTintG || isTintB || isTintA || isColorOffsetR || isColorOffsetG || isColorOffsetB || isRegion || - isVisibleSet || isInterpolatedSet || isFlipXSet || isFlipYSet; + isVisibleSet || isInterpolationSet || isFlipXSet || isFlipYSet; auto rowWidgetSize = widget_size_with_row_get(5); diff --git a/src/imgui/wizard/configure.cpp b/src/imgui/wizard/configure.cpp index eab9e0b..aee0e33 100644 --- a/src/imgui/wizard/configure.cpp +++ b/src/imgui/wizard/configure.cpp @@ -72,6 +72,10 @@ namespace anm2ed::imgui::wizard ImGui::RadioButton(localize.get(LABEL_ANM2ED_LIMITED), &temporary.fileCompatibility, anm2::ANM2ED_LIMITED); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COMPATIBILITY_ANM2ED_LIMITED)); + ImGui::Checkbox(localize.get(LABEL_BAKE_SPECIAL_INTERPOLATED_FRAMES_ON_SAVE), + &temporary.fileBakeSpecialInterpolatedFramesOnSave); + ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BAKE_SPECIAL_INTERPOLATED_FRAMES_ON_SAVE)); + ImGui::SeparatorText(localize.get(LABEL_OPTIONS)); ImGui::Checkbox(localize.get(LABEL_OVERWRITE_WARNING), &temporary.fileIsWarnOverwrite); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OVERWRITE_WARNING)); diff --git a/src/loader.cpp b/src/loader.cpp index 7fdfd6b..6ac8632 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -21,8 +21,6 @@ #include "util/math_.hpp" #ifdef _WIN32 - #include "util/path_.hpp" - #include #include #endif @@ -46,95 +44,6 @@ namespace anm2ed constexpr int SOCKET_ERROR_ADDRESS_IN_USE = EADDRINUSE; #endif -#ifdef _WIN32 - std::filesystem::path g_minidump_dir{}; - PVOID g_vectored_handler{}; - LONG g_dump_in_progress{}; - - void windows_minidump_write(EXCEPTION_POINTERS* exceptionPointers) - { - if (g_minidump_dir.empty()) return; - if (InterlockedExchange(&g_dump_in_progress, 1) != 0) return; - - SYSTEMTIME st{}; - GetLocalTime(&st); - - auto pid = GetCurrentProcessId(); - auto code = exceptionPointers && exceptionPointers->ExceptionRecord - ? exceptionPointers->ExceptionRecord->ExceptionCode - : 0u; - - auto filename = std::format("anm2ed_{:04}{:02}{:02}_{:02}{:02}{:02}_pid{:08x}_code{:08x}.dmp", st.wYear, st.wMonth, - st.wDay, st.wHour, st.wMinute, st.wSecond, pid, code); - auto dumpPath = g_minidump_dir / path::from_utf8(filename); - - auto dumpPathW = dumpPath.wstring(); - HANDLE file = CreateFileW(dumpPathW.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) return; - - MINIDUMP_EXCEPTION_INFORMATION mei{}; - mei.ThreadId = GetCurrentThreadId(); - mei.ExceptionPointers = exceptionPointers; - mei.ClientPointers = FALSE; - - DWORD dumpType = MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | - MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData | - MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithCodeSegs | - MiniDumpWithIndirectlyReferencedMemory; - #ifdef MiniDumpWithModuleHeaders - dumpType |= MiniDumpWithModuleHeaders; - #endif - #ifdef MiniDumpWithTokenInformation - dumpType |= MiniDumpWithTokenInformation; - #endif - #ifdef MiniDumpWithFullAuxiliaryState - dumpType |= MiniDumpWithFullAuxiliaryState; - #endif - #ifdef MiniDumpWithAvxXStateContext - dumpType |= MiniDumpWithAvxXStateContext; - #endif - #ifdef MiniDumpWithIptTrace - dumpType |= MiniDumpWithIptTrace; - #endif - MiniDumpWriteDump(GetCurrentProcess(), pid, file, static_cast(dumpType), - exceptionPointers ? &mei : nullptr, nullptr, nullptr); - - FlushFileBuffers(file); - CloseHandle(file); - } - - LONG WINAPI windows_unhandled_exception_filter(EXCEPTION_POINTERS* exceptionPointers) - { - windows_minidump_write(exceptionPointers); - return EXCEPTION_EXECUTE_HANDLER; - } - - LONG CALLBACK windows_vectored_exception_handler(EXCEPTION_POINTERS* exceptionPointers) - { - windows_minidump_write(exceptionPointers); - return EXCEPTION_CONTINUE_SEARCH; - } - - void windows_minidumps_configure() - { - auto prefDir = sdl::preferences_directory_get(); - if (prefDir.empty()) return; - - std::error_code ec{}; - auto dumpDir = prefDir / "crash"; - std::filesystem::create_directories(dumpDir, ec); - if (ec) return; - - g_minidump_dir = dumpDir; - - if (!g_vectored_handler) g_vectored_handler = AddVectoredExceptionHandler(1, windows_vectored_exception_handler); - SetUnhandledExceptionFilter(windows_unhandled_exception_filter); - - logger.info(std::format("MiniDumpWriteDump enabled: {}", path::to_utf8(dumpDir))); - } -#endif - bool socket_paths_send(Socket& socket, const std::vector& paths) { uint32_t count = htonl(static_cast(paths.size())); @@ -235,10 +144,6 @@ namespace anm2ed logger.info("Initialized SDL"); -#ifdef _WIN32 - windows_minidumps_configure(); -#endif - auto windowProperties = SDL_CreateProperties(); if (windowProperties == 0) diff --git a/src/manager.cpp b/src/manager.cpp index cf07e82..3a4a41a 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -100,7 +100,8 @@ namespace anm2ed void Manager::new_(const std::filesystem::path& path) { open(path, true); } - void Manager::save(int index, const std::filesystem::path& path, anm2::Compatibility compatibility) + void Manager::save(int index, const std::filesystem::path& path, anm2::Compatibility compatibility, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { if (auto document = get(index); document) { @@ -108,21 +109,26 @@ namespace anm2ed ensure_parent_directory_exists(path); document->path = !path.empty() ? path : document->path; document->path.replace_extension(".anm2"); - document->save(document->path, &errorString, compatibility); + document->save(document->path, &errorString, compatibility, isBakeSpecialInterpolatedFramesOnSave, isRoundScale, + isRoundRotation); recent_file_add(document->path); } } - void Manager::save(const std::filesystem::path& path, anm2::Compatibility compatibility) + void Manager::save(const std::filesystem::path& path, anm2::Compatibility compatibility, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { - save(selected, path, compatibility); + save(selected, path, compatibility, isBakeSpecialInterpolatedFramesOnSave, isRoundScale, isRoundRotation); } - void Manager::autosave(Document& document, anm2::Compatibility compatibility) + void Manager::autosave(Document& document, anm2::Compatibility compatibility, + bool isBakeSpecialInterpolatedFramesOnSave, bool isRoundScale, bool isRoundRotation) { std::string errorString{}; auto autosavePath = document.autosave_path_get(); - if (!document.autosave(&errorString, compatibility)) return; + if (!document.autosave(&errorString, compatibility, isBakeSpecialInterpolatedFramesOnSave, isRoundScale, + isRoundRotation)) + return; autosaveFiles.erase(std::remove(autosaveFiles.begin(), autosaveFiles.end(), autosavePath), autosaveFiles.end()); autosaveFiles.insert(autosaveFiles.begin(), autosavePath); diff --git a/src/manager.hpp b/src/manager.hpp index 7514795..3cded74 100644 --- a/src/manager.hpp +++ b/src/manager.hpp @@ -55,6 +55,10 @@ namespace anm2ed imgui::PopupHelper nullPropertiesPopup{ imgui::PopupHelper(LABEL_MANAGER_NULL_PROPERTIES, imgui::POPUP_SMALL_NO_HEIGHT)}; + anm2::Spritesheet::Region makeRegion{}; + int makeRegionSpritesheetId{-1}; + bool isMakeRegionRequested{}; + imgui::PopupHelper progressPopup{ imgui::PopupHelper(LABEL_MANAGER_RENDERING_PROGRESS, imgui::POPUP_SMALL_NO_HEIGHT)}; @@ -66,9 +70,11 @@ namespace anm2ed Document* get(int = -1); Document* open(const std::filesystem::path&, bool = false, bool = true); void new_(const std::filesystem::path&); - void save(int, const std::filesystem::path& = {}, anm2::Compatibility = anm2::ANM2ED); - void save(const std::filesystem::path& = {}, anm2::Compatibility = anm2::ANM2ED); - void autosave(Document&, anm2::Compatibility = anm2::ANM2ED); + void save(int, const std::filesystem::path& = {}, anm2::Compatibility = anm2::ANM2ED, bool = false, bool = true, + bool = true); + void save(const std::filesystem::path& = {}, anm2::Compatibility = anm2::ANM2ED, bool = false, bool = true, + bool = true); + void autosave(Document&, anm2::Compatibility = anm2::ANM2ED, bool = false, bool = true, bool = true); void set(int); void close(int); void layer_properties_open(int = -1); diff --git a/src/resource/icon.hpp b/src/resource/icon.hpp index fe47f2f..7bcb487 100644 --- a/src/resource/icon.hpp +++ b/src/resource/icon.hpp @@ -141,6 +141,18 @@ namespace anm2ed::resource::icon )"; + inline constexpr auto EASE_IN_DATA = R"( + + )"; + + inline constexpr auto EASE_OUT_DATA = R"( + + )"; + + inline constexpr auto EASE_IN_OUT_DATA = R"( + + )"; + inline constexpr auto PIVOT_DATA = R"( )"; @@ -190,6 +202,9 @@ namespace anm2ed::resource::icon X(EVENT, EVENT_DATA, SIZE_NORMAL) \ X(INTERPOLATED, INTERPOLATED_DATA, SIZE_NORMAL) \ X(UNINTERPOLATED, UNINTERPOLATED_DATA, SIZE_NORMAL) \ + X(EASE_IN, EASE_IN_DATA, SIZE_NORMAL) \ + X(EASE_OUT, EASE_OUT_DATA, SIZE_NORMAL) \ + X(EASE_IN_OUT, EASE_IN_OUT_DATA, SIZE_NORMAL) \ X(TRIGGER, TRIGGER_DATA, SIZE_NORMAL) \ X(PLAYHEAD, PLAYHEAD_DATA, SIZE_NORMAL) \ X(PIVOT, PIVOT_DATA, SIZE_NORMAL) \ @@ -217,4 +232,4 @@ namespace anm2ed::resource::icon SVG_LIST #undef X }; -} \ No newline at end of file +} diff --git a/src/resource/strings.hpp b/src/resource/strings.hpp index f97c2dc..0b01f01 100644 --- a/src/resource/strings.hpp +++ b/src/resource/strings.hpp @@ -50,13 +50,17 @@ namespace anm2ed X(BASIC_DUPLICATE, "Duplicate", "Duplicar", "Дублировать", "拷贝", "복제") \ X(BASIC_DURATION, "Duration", "Duracion", "Продолжительность", "时长", "유지 시간") \ X(BASIC_ENABLED, "Enabled", "Activado", "Включено", "开启", "활성화") \ + X(BASIC_EASE_IN, "Ease In", "Aceleración de Entrada", "Плавный вход", "缓入", "천천히 시작") \ + X(BASIC_EASE_OUT, "Ease Out", "Desaceleración de Salida", "Плавный выход", "缓出", "천천히 끝남") \ + X(BASIC_EASE_IN_OUT, "Ease In Out", "Aceleración y Desaceleración", "Плавный вход/выход", "缓入缓出", "천천히 시작/끝") \ X(BASIC_EVENT, "Event", "Evento", "Событие", "事件", "이벤트") \ X(BASIC_FRAME, "Frame", "Frame", "Кадр", "帧", "프레임") \ X(BASIC_FRAMES, "Frames", "Frames", "Кадры", "帧", "프레임") \ X(BASIC_GRID, "Grid", "Cuadricula", "Сетка", "网格", "격자") \ X(BASIC_ID, "ID", "ID", "ID", "ID", "ID") \ X(BASIC_INDEX, "Index", "Indice", "Индекс", "下标", "인덱스") \ - X(BASIC_INTERPOLATED, "Interpolated", "Interpolado", "Интерполировано", "线性插值", "매끄럽게 연결") \ + X(BASIC_INTERPOLATED, "Interpolation", "Interpolación", "Интерполяция", "插值", "보간") \ + X(BASIC_LINEAR, "Linear", "Lineal", "Линейная", "线性", "선형") \ X(BASIC_LAYER_ANIMATION, "Layer", "Capa", "Слой", "动画层", "레이어") \ X(BASIC_MERGE, "Merge", "Combinar", "Соединить", "合并", "병합") \ X(BASIC_MODE, "Mode", "Modo", "Режим", "模式", "모드") \ @@ -188,7 +192,7 @@ namespace anm2ed X(FORMAT_ORIGIN, "Origin: {0}", "Origen: {0}", "Точка отсчета: {0}", "原点: {0}", "원점: {0}") \ X(FORMAT_SPRITESHEET_ID, "Spritesheet ID: {0}", "ID de Spritesheet: {0}", "", "图集 ID: {0}", "스프라이트 시트 ID: {0}") \ X(FORMAT_INDEX, "Index: {0}", "Indice: {0}", "Индекс: {0}", "下标: {0}", "인덱스: {0}") \ - X(FORMAT_INTERPOLATED, "Interpolated: {0}", "Interpolado: {0}", "Интерполировано: {0}", "线性插值: {0}", "매끄럽게 연결: {0}") \ + X(FORMAT_INTERPOLATED, "Interpolation: {0}", "Interpolación: {0}", "Интерполяция: {0}", "插值: {0}", "보간: {0}") \ X(FORMAT_LAYER, "#{0} {1} (Spritesheet: #{2})", "#{0} {1} (Spritesheet: #{2})", "#{0} {1} (Спрайт-лист: #{2})", "#{0} {1} (图集: #{2})", "#{0} {1} (스프라이트 시트: #{2})") \ X(FORMAT_LENGTH, "Length: {0}", "Largo: {0}", "Длина: {0}", "长度: {0}", "길이: {0}") \ X(FORMAT_LOOP, "Loop: {0}", "Loop: {0}", "Цикл: {0}", "循环: {0}", "반복: {0}") \ @@ -226,7 +230,7 @@ namespace anm2ed X(LABEL_ANIMATION_PREVIEW_WINDOW, "Animation Preview###Animation Preview", "Vista Previa de Animacion###Animation Preview", "Предпросмотр анимации###Animation Preview", "动画预放###Animation Preview", "애니메이션 프리뷰###Animation Preview") \ X(LABEL_APPEND_FRAMES, "Append Frames", "Anteponer Frames", "Добавить кадры к концу", "在后面添加帧", "뒷프레임에 추가") \ X(LABEL_APPLICATION_NAME, "Anm2Ed", "Anm2Ed", "Anm2Ed", "Anm2Ed", "Anm2Ed") \ - X(LABEL_APPLICATION_VERSION, "Version 2.3", "Version 2.3", "Версия 2.3", "2.3版本", "버전 2.3") \ + X(LABEL_APPLICATION_VERSION, "Version 2.4", "Version 2.4", "Версия 2.4", "2.4版本", "버전 2.4") \ X(LABEL_AUTHOR, "Author", "Autor", "Автор", "制作者", "작성자") \ X(LABEL_AUTOSAVE, "Autosave", "Autoguardado", "Автосохранение", "自动保存", "자동저장") \ X(LABEL_AXES, "Axes", "Ejes", "Оси", "坐标轴", "가로/세로 축") \ @@ -240,6 +244,7 @@ namespace anm2ed X(LABEL_CLEAR_LIST, "Clear List", "Limpiar Lista", "Стереть список", "清除列表", "기록 삭제") \ X(LABEL_CLOSE, "Close", "Cerrar", "Закрыть", "关闭", "닫기") \ X(LABEL_COMPATIBILITY, "Compatibility", "Compatibilidad", "Совместимость", "兼容性", "호환성") \ + X(LABEL_BAKE_SPECIAL_INTERPOLATED_FRAMES_ON_SAVE, "Bake Special Interpolated Frames On Saving", "Hornear frames con interpolación especial al guardar", "Запекать кадры со специальной интерполяцией при сохранении", "保存时烘焙特殊插值帧", "저장 시 특수 보간 프레임 베이크") \ X(LABEL_CUSTOM_RANGE, "Custom Range", "Rango Personalizado", "Пользовательский диапазон", "自定义范围", "길이 맞춤설정") \ X(LABEL_DELETE, "Delete", "Borrar", "Удалить", "删除", "삭제") \ X(LABEL_DELETE_ANIMATIONS_AFTER, "Delete Animations After", "Borrar Animaciones Despues", "Удалить анимации после", "删除之后的动画", "기존 애니메이션 삭제") \ @@ -290,6 +295,7 @@ namespace anm2ed X(LABEL_ANM2ED_LIMITED, "Anm2Ed Limited", "Anm2Ed Limitado", "Anm2Ed Ограниченный", "Anm2Ed 限制版", "Anm2Ed 제한") \ X(LABEL_DESTINATION, "Destination", "Destino", "Назначение", "目标", "대상") \ X(LABEL_LOCALIZATION, "Localization", "Localizacion", "Локализация", "本地化", "현지화") \ + X(LABEL_MAKE_REGION, "Make Region", "Crear Región", "Создать регион", "创建区域", "영역 만들기") \ X(LABEL_LOOP, "Loop", "Loop", "Цикл", "循环", "반복") \ X(LABEL_MANAGER_ANM2_DRAG_DROP, "Anm2 Drag Drop", "Arrastrar y Soltar Anm2", "Anm2 Drag Drop", "Anm2 拖放", "Anm2 드래그 앤 드롭") \ X(LABEL_REGION_PROPERTIES, "Region Properties", "Propiedades de región", "Свойства региона", "区域属性", "영역 속성") \ @@ -517,9 +523,10 @@ namespace anm2ed X(TOOLTIP_CANCEL_BAKE_FRAMES, "Cancel baking frames.", "Cancelar hacer bake de Frames.", "Отменить запечку кадров.", "取消提前渲染.", "프레임 베이킹을 취소합니다.") \ X(TOOLTIP_CENTER_VIEW, "Centers the view.", "Centra la vista.", "Центрирует вид.", "居中视角.", "미리보기 화면을 가운데에 맞춥니다.") \ X(TOOLTIP_CLOSE_SETTINGS, "Close without updating settings.", "Cerrar sin actualizar las configuraciones.", "Закрыть без обновления настройки.", "关闭但不保存设置.", "설정을 갱신하지 않고 닫습니다.") \ - X(TOOLTIP_COMPATIBILITY_ISAAC, "Sets the output file format to that of The Binding of Isaac: Rebirth's.\nThis removes the following:\n- Sounds\n- Regions\nNOTE: This will not serialize this data and it won't be able to be recovered.", "Establece el formato del archivo de salida al de The Binding of Isaac: Rebirth.\nEsto elimina lo siguiente:\n- Sonidos\n- Regiones\nNOTA: Estos datos no se serializaran y no podran recuperarse.", "Устанавливает формат выходного файла как у The Binding of Isaac: Rebirth.\nЭто удаляет следующее:\n- Звуки\n- Регионы\nПРИМЕЧАНИЕ: Эти данные не будут сериализованы, и их нельзя будет восстановить.", "将输出文件格式设置为 The Binding of Isaac: Rebirth 的格式。\n这会移除以下内容:\n- 声音\n- 区域\n注意:这些数据不会被序列化,且无法恢复。", "출력 파일 형식을 The Binding of Isaac: Rebirth의 형식으로 설정합니다.\n다음 항목이 제거됩니다:\n- 사운드\n- 영역\n참고: 이 데이터는 직렬화되지 않으며 복구할 수 없습니다.") \ - X(TOOLTIP_COMPATIBILITY_ANM2ED, "Sets the output file format to that of this editor.\nAll features will be serialized, including Sounds and Regions.", "Establece el formato del archivo de salida al de este editor.\nTodas las funciones se serializaran, incluyendo Sonidos y Regiones.", "Устанавливает формат выходного файла как у этого редактора.\nВсе возможности будут сериализованы, включая звуки и регионы.", "将输出文件格式设置为本编辑器的格式。\n所有功能都会被序列化,包括声音和区域。", "출력 파일 형식을 이 편집기의 형식으로 설정합니다.\n사운드와 영역을 포함한 모든 기능이 직렬화됩니다.") \ + X(TOOLTIP_COMPATIBILITY_ISAAC, "Sets the output file format to that of The Binding of Isaac: Rebirth's.\nThis removes the following:\n- Sounds\n- Regions\n- Special interpolation modes\nNOTE: This will not serialize this data and it won't be able to be recovered.", "Establece el formato del archivo de salida al de The Binding of Isaac: Rebirth.\nEsto elimina lo siguiente:\n- Sonidos\n- Regiones\n- Modos especiales de interpolación\nNOTA: Estos datos no se serializaran y no podran recuperarse.", "Устанавливает формат выходного файла как у The Binding of Isaac: Rebirth.\nЭто удаляет следующее:\n- Звуки\n- Регионы\n- Специальные режимы интерполяции\nПРИМЕЧАНИЕ: Эти данные не будут сериализованы, и их нельзя будет восстановить.", "将输出文件格式设置为 The Binding of Isaac: Rebirth 的格式。\n这会移除以下内容:\n- 声音\n- 区域\n- 特殊插值模式\n注意:这些数据不会被序列化,且无法恢复。", "출력 파일 형식을 The Binding of Isaac: Rebirth의 형식으로 설정합니다.\n다음 항목이 제거됩니다:\n- 사운드\n- 영역\n- 특수 보간 모드\n참고: 이 데이터는 직렬화되지 않으며 복구할 수 없습니다.") \ + X(TOOLTIP_COMPATIBILITY_ANM2ED, "Sets the output file format to that of this editor.\nAll features will be serialized, including:\n- Sounds\n- Regions\n- Special interpolation modes", "Establece el formato del archivo de salida al de este editor.\nTodas las funciones se serializaran, incluyendo:\n- Sonidos\n- Regiones\n- Modos especiales de interpolación", "Устанавливает формат выходного файла как у этого редактора.\nВсе возможности будут сериализованы, включая:\n- Звуки\n- Регионы\n- Специальные режимы интерполяции", "将输出文件格式设置为本编辑器的格式。\n所有功能都会被序列化,包括:\n- 声音\n- 区域\n- 特殊插值模式", "출력 파일 형식을 이 편집기의 형식으로 설정합니다.\n다음 기능을 포함한 모든 기능이 직렬화됩니다:\n- 사운드\n- 영역\n- 특수 보간 모드") \ X(TOOLTIP_COMPATIBILITY_ANM2ED_LIMITED, "Sets the output file format to that of this editor.\nThis will additionally remove redundant Region-specific information in frames.", "Establece el formato del archivo de salida al de este editor.\nEsto ademas eliminara informacion redundante especifica de Region en los frames.", "Устанавливает формат выходного файла как у этого редактора.\nДополнительно это удалит избыточную информацию, связанную с регионами, в кадрах.", "将输出文件格式设置为本编辑器的格式。\n此外,这还会移除帧中冗余的区域专用信息。", "출력 파일 형식을 이 편집기의 형식으로 설정합니다.\n추가로 프레임 내 중복된 영역 관련 정보를 제거합니다.") \ + X(TOOLTIP_BAKE_SPECIAL_INTERPOLATED_FRAMES_ON_SAVE, "When saving, frames that do not use the None or Linear interpolation modes will automatically be baked in-place when saving the file.\nThe Binding of Isaac: Rebirth does not support extended interpolation features.", "Al guardar, los frames que no usen los modos de interpolación None o Linear se hornearán automáticamente en su lugar al guardar el archivo.\nThe Binding of Isaac: Rebirth no soporta funciones extendidas de interpolación.", "При сохранении кадры, которые не используют режимы интерполяции None или Linear, будут автоматически запекаться на месте при сохранении файла.\nThe Binding of Isaac: Rebirth не поддерживает расширенные возможности интерполяции.", "保存文件时,不使用 None 或 Linear 插值模式的帧将自动在保存时原地烘焙。\nThe Binding of Isaac: Rebirth 不支持扩展插值功能。", "저장할 때 None 또는 Linear 보간 모드를 사용하지 않는 프레임은 파일 저장 시 자동으로 제자리 베이크됩니다.\nThe Binding of Isaac: Rebirth는 확장 보간 기능을 지원하지 않습니다.") \ X(TOOLTIP_COLOR_OFFSET, "Change the color added onto the frame.", "Cambia el color añadido al Frame.", "Изменить цвет, который добавлен на кадр.", "更改覆盖在帧上的颜色.", "프레임에 더해지는 색을 변경합니다.") \ X(TOOLTIP_REGION, "Set the spritesheet region the frame will use.", "Establece la región del spritesheet que usará el frame.", "Установить регион спрайт-листа, который будет использовать кадр.", "设置帧将使用的图集区域.", "프레임이 사용할 스프라이트 시트 영역을 설정합니다.") \ X(TOOLTIP_REGION_PROPERTIES_ORIGIN, "Use a preset origin for the region.", "Usa un origen predefinido para la región.", "Использовать предустановленную точку отсчета для региона.", "为区域使用预设原点。", "영역에 사전 설정된 원점을 사용합니다.") \ @@ -540,7 +547,7 @@ namespace anm2ed X(TOOLTIP_FLIP_Y, "Flip the frame's Y scale to fake a vertical mirror.\nHold Ctrl to also flip the Y position.", "Invierte la escala Y del Frame, para trampear hacer mirroring de el Frame verticalmente.\n(Nota: el formato no soporta mirroring. )", "Отразить масштаб кадра по оси Y, вместо отражения кадра вертикально.\n(Примечание: формат не поддерживает нормальное отражение.)", "通过翻转Y轴的缩放,使此帧看起来像Y轴翻转了.\n(注: 此格式不支持镜像.)", "프레임의 세로 비율을 반전시켜 프레임이 수직으로 뒤집은 것처럼 보이게 합니다.\n(참고: 완전한 뒤집기 기능을 지원하지 않습니다.)") \ X(TOOLTIP_FORMAT, "For outputted images, each image will use this format.\n{0} represents the index of each image.", "Para las imagenes de salida, Cada imagen usara este formato.\n{0} representa el indice de cada imagen.", "Для выведенных изображений, каждое будет использовать этот формат.\n{} представляет индекс каждого изображения.", "用于输出的图像, 每一个图像都会使用这个格式.\n{} 代表每一个图像的下标.", "출력되는 이미지들은 이 형식을 사용합니다.\n{}는 각 이미지의 인덱스를 나타냅니다.") \ X(TOOLTIP_FPS, "Set the FPS of all animations.", "Ajusta los FPS de todas las animaciones.", "Установить сколько кадров в секунде для всех анимаций.", "设置所有动画的FPS(帧数每秒)", "모든 애니메이션의 FPS를 설정합니다.") \ - X(TOOLTIP_FRAME_INTERPOLATION, "Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.", "Alterna la interpolacion de Frames; i. e., combinando sus valores hacia el siguiente Frame basado en tiempo.", "Переключить интерполяцию кадра; т. е. смешать его значения в следующий кадр на основе времени.", "切换帧的线性插值; 也就是利用时间来\"渐变\"两帧之间的数值.", "프레임 보간(시간이 따라 속성 값이 다음 프레임의 값으로 변함) 여부를 정합니다.") \ + X(TOOLTIP_FRAME_INTERPOLATION, "Set how the frame blends its values into the next frame over time.", "Establece como el frame interpola sus valores hacia el siguiente frame con el tiempo.", "Задает, как кадр смешивает свои значения со следующим кадром во времени.", "设置该帧的数值如何随时间过渡到下一帧。", "프레임 속성 값이 시간에 따라 다음 프레임 값으로 어떻게 보간될지 설정합니다.") \ X(TOOLTIP_FRAME_VISIBILITY, "Toggle the frame's visibility.", "Alterna la visibilidad del Frame.", "Переключить видимость кадра.", "切换此帧是否可见.", "프레임을 표시하거나 숨깁니다.") \ X(TOOLTIP_ITEM_ALL_ANIMATIONS, "The item will be placed into all animations.", "El item sera aplicado a todas las animaciones.", "Предмет будет добавлен во все анимации.", "该物体将被放入所有动画中。", "항목이 모든 애니메이션에 배치됩니다.") \ X(TOOLTIP_GRID_COLOR, "Change the grid's color.", "Cambiar el color de la cuadricula.", "Изменить цвет сетки.", "更改网格的颜色.", "격자 색을 변경합니다.") \ diff --git a/src/settings.hpp b/src/settings.hpp index 3b0feec..879a6e2 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -61,6 +61,7 @@ namespace anm2ed \ X(FILE_IS_AUTOSAVE, fileIsAutosave, STRING_UNDEFINED, BOOL, true) \ X(FILE_IS_WARN_OVERWRITE, fileIsWarnOverwrite, STRING_UNDEFINED, BOOL, true) \ + X(FILE_BAKE_SPECIAL_INTERPOLATED_FRAMES_ON_SAVE, fileBakeSpecialInterpolatedFramesOnSave, STRING_UNDEFINED, BOOL, true) \ X(FILE_SNAPSHOT_STACK_SIZE, fileSnapshotStackSize, STRING_UNDEFINED, INT, 50) \ X(FILE_COMPATIBILITY, fileCompatibility, STRING_UNDEFINED, INT, anm2::ANM2ED) \ \ @@ -94,7 +95,7 @@ namespace anm2ed X(CHANGE_IS_COLOR_OFFSET_B, changeIsColorOffsetB, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_COLOR_OFFSET_A, changeIsColorOffsetA, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_VISIBLE_SET, changeIsVisibleSet, STRING_UNDEFINED, BOOL, false) \ - X(CHANGE_IS_INTERPOLATED_SET, changeIsInterpolatedSet, STRING_UNDEFINED, BOOL, false) \ + X(CHANGE_IS_INTERPOLATION_SET, changeIsInterpolationSet, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_FLIP_X_SET, changeIsFlipXSet, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_FLIP_Y_SET, changeIsFlipYSet, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_REGION, changeIsRegion, STRING_UNDEFINED, BOOL, false) \ @@ -109,7 +110,7 @@ namespace anm2ed X(CHANGE_COLOR_OFFSET, changeColorOffset, STRING_UNDEFINED, VEC3, {}) \ X(CHANGE_REGION_ID, changeRegionId, STRING_UNDEFINED, INT, -1) \ X(CHANGE_IS_VISIBLE, changeIsVisible, STRING_UNDEFINED, BOOL, false) \ - X(CHANGE_IS_INTERPOLATED, changeIsInterpolated, STRING_UNDEFINED, BOOL, false) \ + X(CHANGE_INTERPOLATION, changeInterpolation, STRING_UNDEFINED, INT, 0) \ X(CHANGE_IS_FLIP_X, changeIsFlipX, STRING_UNDEFINED, BOOL, false) \ X(CHANGE_IS_FLIP_Y, changeIsFlipY, STRING_UNDEFINED, BOOL, false) \ \