I N T E R P O L A T I O N
This commit is contained in:
+27
-2
@@ -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)
|
||||
|
||||
+3
-1
@@ -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<int>&, 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,
|
||||
|
||||
@@ -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> 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}};
|
||||
}
|
||||
|
||||
+48
-4
@@ -1,5 +1,7 @@
|
||||
#include "frame.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#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:
|
||||
|
||||
+11
-2
@@ -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<bool> isVisible{};
|
||||
std::optional<bool> isInterpolated{};
|
||||
std::optional<Frame::Interpolation> interpolation{};
|
||||
std::optional<float> rotation{};
|
||||
std::optional<int> duration{};
|
||||
std::optional<int> regionID{};
|
||||
|
||||
+30
-5
@@ -1,5 +1,6 @@
|
||||
#include "item.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ranges>
|
||||
|
||||
#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);
|
||||
|
||||
Reference in New Issue
Block a user