#include "item.h" #include #include #include "vector_.h" #include "xml_.h" using namespace anm2ed::util; using namespace tinyxml2; using namespace glm; namespace anm2ed::anm2 { Item::Item(XMLElement* element, Type type, int* id) { if (type == LAYER && id) element->QueryIntAttribute("LayerId", id); if (type == NULL_ && id) element->QueryIntAttribute("NullId", id); element->QueryBoolAttribute("Visible", &isVisible); for (auto child = type == TRIGGER ? element->FirstChildElement("Trigger") : element->FirstChildElement("Frame"); child; child = type == TRIGGER ? child->NextSiblingElement("Trigger") : child->NextSiblingElement("Frame")) frames.push_back(Frame(child, type)); } XMLElement* Item::to_element(XMLDocument& document, Type type, int id) { auto element = document.NewElement(TYPE_ITEM_STRINGS[type]); if (type == LAYER) element->SetAttribute("LayerId", id); if (type == NULL_) element->SetAttribute("NullId", id); if (type == LAYER || type == NULL_) element->SetAttribute("Visible", isVisible); if (type == TRIGGER) frames_sort_by_at_frame(); for (auto& frame : frames) frame.serialize(document, element, type); return element; } void Item::serialize(XMLDocument& document, XMLElement* parent, Type type, int id) { parent->InsertEndChild(to_element(document, type, id)); } std::string Item::to_string(Type type, int id) { XMLDocument document{}; document.InsertEndChild(to_element(document, type, id)); return xml::document_to_string(document); } int Item::length(Type type) { int length{}; if (type == TRIGGER) for (auto& frame : frames) length = frame.atFrame > length ? frame.atFrame : length; else for (auto& frame : frames) length += frame.duration; return length; } void Item::frames_sort_by_at_frame() { std::sort(frames.begin(), frames.end(), [](const Frame& a, const Frame& b) { return a.atFrame < b.atFrame; }); } Frame Item::frame_generate(float time, Type type) { Frame frame{}; frame.isVisible = false; if (frames.empty()) return frame; Frame* frameNext = nullptr; int durationCurrent = 0; int durationNext = 0; for (auto [i, iFrame] : std::views::enumerate(frames)) { if (type == TRIGGER) { if ((int)time == iFrame.atFrame) { frame = iFrame; break; } } else { frame = iFrame; durationNext += frame.duration; if (time >= durationCurrent && time < durationNext) { if (i + 1 < (int)frames.size()) frameNext = &frames[i + 1]; else frameNext = nullptr; break; } durationCurrent += frame.duration; } } if (type != TRIGGER && frame.isInterpolated && frameNext && frame.duration > 1) { auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent); frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation); frame.position = glm::mix(frame.position, frameNext->position, interpolation); frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation); frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation); frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation); } return frame; } void Item::frames_change(FrameChange& change, ChangeType type, int start, int numberFrames) { auto useStart = numberFrames > -1 ? start : 0; auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size(); end = glm::clamp(end, start, (int)frames.size()); for (int i = useStart; i < end; i++) { Frame& frame = frames[i]; if (change.isVisible) frame.isVisible = *change.isVisible; if (change.isInterpolated) frame.isInterpolated = *change.isInterpolated; switch (type) { case ADJUST: if (change.rotation) frame.rotation = *change.rotation; if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, *change.duration); if (change.crop) frame.crop = *change.crop; if (change.pivot) frame.pivot = *change.pivot; if (change.position) frame.position = *change.position; if (change.size) frame.size = *change.size; if (change.scale) frame.scale = *change.scale; if (change.colorOffset) frame.colorOffset = glm::clamp(*change.colorOffset, 0.0f, 1.0f); if (change.tint) frame.tint = glm::clamp(*change.tint, 0.0f, 1.0f); break; case ADD: if (change.rotation) frame.rotation += *change.rotation; if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration + *change.duration); if (change.crop) frame.crop += *change.crop; if (change.pivot) frame.pivot += *change.pivot; if (change.position) frame.position += *change.position; if (change.size) frame.size += *change.size; if (change.scale) frame.scale += *change.scale; if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset + *change.colorOffset, 0.0f, 1.0f); if (change.tint) frame.tint = glm::clamp(frame.tint + *change.tint, 0.0f, 1.0f); break; case SUBTRACT: if (change.rotation) frame.rotation -= *change.rotation; if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration - *change.duration); if (change.crop) frame.crop -= *change.crop; if (change.pivot) frame.pivot -= *change.pivot; if (change.position) frame.position -= *change.position; if (change.size) frame.size -= *change.size; if (change.scale) frame.scale -= *change.scale; if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset - *change.colorOffset, 0.0f, 1.0f); if (change.tint) frame.tint = glm::clamp(frame.tint - *change.tint, 0.0f, 1.0f); break; } } } bool Item::frames_deserialize(const std::string& string, Type type, int start, std::set& indices, std::string* errorString) { XMLDocument document{}; if (document.Parse(string.c_str()) == XML_SUCCESS) { int count{}; if (document.FirstChildElement("Frame") && type != anm2::TRIGGER) { start = std::clamp(start, 0, (int)frames.size()); for (auto element = document.FirstChildElement("Frame"); element; element = element->NextSiblingElement("Frame")) { auto index = start + count; frames.insert(frames.begin() + start + count, Frame(element, type)); indices.insert(index); count++; } return true; } else if (document.FirstChildElement("Trigger") && type == anm2::TRIGGER) { auto has_conflict = [&](int value) { for (auto& trigger : frames) if (trigger.atFrame == value) return true; return false; }; for (auto element = document.FirstChildElement("Trigger"); element; element = element->NextSiblingElement("Trigger")) { Frame trigger(element, type); trigger.atFrame = start + count; while (has_conflict(trigger.atFrame)) trigger.atFrame++; frames.push_back(trigger); indices.insert(trigger.atFrame); count++; } frames_sort_by_at_frame(); return true; } else { if (errorString) *errorString = type == anm2::TRIGGER ? "No valid trigger(s)." : "No valid frame(s)."; return false; } } else if (errorString) *errorString = document.ErrorStr(); return false; } void Item::frames_bake(int index, int interval, bool isRoundScale, bool isRoundRotation) { if (!vector::in_bounds(frames, index)) return; Frame& frame = frames[index]; if (frame.duration == FRAME_DURATION_MIN) return; Frame frameNext = vector::in_bounds(frames, index + 1) ? frames[index + 1] : frame; int duration{}; int i = index; while (duration < frame.duration) { Frame baked = frame; float interpolation = (float)duration / frame.duration; baked.duration = std::min(interval, frame.duration - duration); baked.isInterpolated = (i == index) ? frame.isInterpolated : false; baked.rotation = glm::mix(frame.rotation, frameNext.rotation, interpolation); baked.position = glm::mix(frame.position, frameNext.position, interpolation); baked.scale = glm::mix(frame.scale, frameNext.scale, interpolation); baked.colorOffset = glm::mix(frame.colorOffset, frameNext.colorOffset, interpolation); baked.tint = glm::mix(frame.tint, frameNext.tint, interpolation); if (isRoundScale) baked.scale = vec2(ivec2(baked.scale)); if (isRoundRotation) baked.rotation = (int)baked.rotation; if (i == index) frames[i] = baked; else frames.insert(frames.begin() + i, baked); i++; duration += baked.duration; } } void Item::frames_generate_from_grid(ivec2 startPosition, ivec2 size, ivec2 pivot, int columns, int count, int duration) { for (int i = 0; i < count; i++) { Frame frame{}; frame.duration = duration; frame.pivot = pivot; frame.size = size; frame.crop = startPosition + ivec2(size.x * (i % columns), size.y * (i / columns)); frames.emplace_back(frame); } } int Item::frame_index_from_at_frame_get(int atFrame) { for (auto [i, frame] : std::views::enumerate(frames)) if (frame.atFrame == atFrame) return i; return -1; } }