#include "animation.hpp" #include "map_.hpp" #include "math_.hpp" #include "unordered_map_.hpp" #include "xml_.hpp" #include using namespace anm2ed::util; using namespace glm; using namespace tinyxml2; namespace anm2ed::anm2 { Animation::Animation(XMLElement* element) { int id{}; xml::query_string_attribute(element, "Name", &name); element->QueryIntAttribute("FrameNum", &frameNum); element->QueryBoolAttribute("Loop", &isLoop); if (auto rootAnimationElement = element->FirstChildElement("RootAnimation")) rootAnimation = Item(rootAnimationElement, ROOT); if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations")) { for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child; child = child->NextSiblingElement("LayerAnimation")) { layerAnimations.emplace(id, Item(child, LAYER, &id)); layerOrder.emplace_back(id); } } if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations")) for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child; child = child->NextSiblingElement("NullAnimation")) nullAnimations.emplace(id, Item(child, NULL_, &id)); if (auto triggersElement = element->FirstChildElement("Triggers")) triggers = Item(triggersElement, TRIGGER); } Item* Animation::item_get(Type type, int id) { switch (type) { case ROOT: return &rootAnimation; case LAYER: return unordered_map::find(layerAnimations, id); case NULL_: return map::find(nullAnimations, id); case TRIGGER: return &triggers; default: return nullptr; } return nullptr; } void Animation::item_remove(Type type, int id) { switch (type) { case LAYER: layerAnimations.erase(id); for (auto [i, value] : std::views::enumerate(layerOrder)) if (value == id) layerOrder.erase(layerOrder.begin() + i); break; case NULL_: nullAnimations.erase(id); break; case ROOT: case TRIGGER: default: break; } } XMLElement* Animation::to_element(XMLDocument& document, Flags flags) { auto element = document.NewElement("Animation"); element->SetAttribute("Name", name.c_str()); element->SetAttribute("FrameNum", frameNum); element->SetAttribute("Loop", isLoop); rootAnimation.serialize(document, element, ROOT, -1, flags); auto layerAnimationsElement = document.NewElement("LayerAnimations"); for (auto& i : layerOrder) { Item& layerAnimation = layerAnimations.at(i); layerAnimation.serialize(document, layerAnimationsElement, LAYER, i, flags); } element->InsertEndChild(layerAnimationsElement); auto nullAnimationsElement = document.NewElement("NullAnimations"); for (auto& [id, nullAnimation] : nullAnimations) nullAnimation.serialize(document, nullAnimationsElement, NULL_, id, flags); element->InsertEndChild(nullAnimationsElement); triggers.serialize(document, element, TRIGGER, -1, flags); return element; } void Animation::serialize(XMLDocument& document, XMLElement* parent, Flags flags) { parent->InsertEndChild(to_element(document, flags)); } std::string Animation::to_string() { XMLDocument document{}; document.InsertEndChild(to_element(document)); return xml::document_to_string(document); } int Animation::length() { int length{}; if (int rootAnimationLength = rootAnimation.length(ROOT); rootAnimationLength > length) length = rootAnimationLength; for (auto& layerAnimation : layerAnimations | std::views::values) if (int layerAnimationLength = layerAnimation.length(LAYER); layerAnimationLength > length) length = layerAnimationLength; for (auto& nullAnimation : nullAnimations | std::views::values) if (int nullAnimationLength = nullAnimation.length(NULL_); nullAnimationLength > length) length = nullAnimationLength; if (int triggersLength = triggers.length(TRIGGER); triggersLength > length) length = triggersLength; return length; } void Animation::fit_length() { frameNum = length(); } vec4 Animation::rect(bool isRootTransform) { constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; float minX = std::numeric_limits::infinity(); float minY = std::numeric_limits::infinity(); float maxX = -std::numeric_limits::infinity(); float maxY = -std::numeric_limits::infinity(); bool any = false; for (float t = 0.0f; t < (float)frameNum; t += 1.0f) { mat4 transform(1.0f); if (isRootTransform) { auto root = rootAnimation.frame_generate(t, ROOT); transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation); } for (auto& [id, layerAnimation] : layerAnimations) { if (!layerAnimation.isVisible) continue; auto frame = layerAnimation.frame_generate(t, LAYER); if (frame.size == vec2() || !frame.isVisible) continue; auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot, math::percent_to_unit(frame.scale), frame.rotation); for (auto& corner : CORNERS) { vec4 world = layerTransform * vec4(corner, 0.0f, 1.0f); minX = std::min(minX, world.x); minY = std::min(minY, world.y); maxX = std::max(maxX, world.x); maxY = std::max(maxY, world.y); any = true; } } } if (!any) return vec4(-1.0f); return {minX, minY, maxX - minX, maxY - minY}; } }