Big refactor, shuffling a lot of files around
This commit is contained in:
206
src/anm2/animation.cpp
Normal file
206
src/anm2/animation.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "animation.h"
|
||||
|
||||
#include "map_.h"
|
||||
#include "math_.h"
|
||||
#include "unordered_map_.h"
|
||||
#include "xml_.h"
|
||||
#include <ranges>
|
||||
|
||||
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[id] = Item(child, LAYER, &id);
|
||||
layerOrder.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
|
||||
for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
|
||||
child = child->NextSiblingElement("NullAnimation"))
|
||||
nullAnimations[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;
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
auto element = document.NewElement("Animation");
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
element->SetAttribute("FrameNum", frameNum);
|
||||
element->SetAttribute("Loop", isLoop);
|
||||
|
||||
rootAnimation.serialize(document, element, ROOT);
|
||||
|
||||
auto layerAnimationsElement = document.NewElement("LayerAnimations");
|
||||
for (auto& i : layerOrder)
|
||||
{
|
||||
Item& layerAnimation = layerAnimations.at(i);
|
||||
layerAnimation.serialize(document, layerAnimationsElement, LAYER, i);
|
||||
}
|
||||
element->InsertEndChild(layerAnimationsElement);
|
||||
|
||||
auto nullAnimationsElement = document.NewElement("NullAnimations");
|
||||
for (auto& [id, nullAnimation] : nullAnimations)
|
||||
nullAnimation.serialize(document, nullAnimationsElement, NULL_, id);
|
||||
element->InsertEndChild(nullAnimationsElement);
|
||||
|
||||
triggers.serialize(document, element, TRIGGER);
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::string Animation::to_string()
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
auto* element = document.NewElement("Animation");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
element->SetAttribute("FrameNum", frameNum);
|
||||
element->SetAttribute("Loop", isLoop);
|
||||
|
||||
rootAnimation.serialize(document, element, ROOT);
|
||||
|
||||
auto layerAnimationsElement = document.NewElement("LayerAnimations");
|
||||
for (auto& i : layerOrder)
|
||||
{
|
||||
Item& layerAnimation = layerAnimations.at(i);
|
||||
layerAnimation.serialize(document, layerAnimationsElement, LAYER, i);
|
||||
}
|
||||
element->InsertEndChild(layerAnimationsElement);
|
||||
|
||||
auto nullAnimationsElement = document.NewElement("NullAnimations");
|
||||
for (auto& [id, nullAnimation] : nullAnimations)
|
||||
nullAnimation.serialize(document, nullAnimationsElement, NULL_, id);
|
||||
element->InsertEndChild(nullAnimationsElement);
|
||||
|
||||
triggers.serialize(document, element, TRIGGER);
|
||||
|
||||
XMLPrinter printer;
|
||||
document.Print(&printer);
|
||||
return std::string(printer.CStr());
|
||||
}
|
||||
|
||||
vec4 Animation::rect(bool isRootTransform)
|
||||
{
|
||||
float minX = std::numeric_limits<float>::infinity();
|
||||
float minY = std::numeric_limits<float>::infinity();
|
||||
float maxX = -std::numeric_limits<float>::infinity();
|
||||
float maxY = -std::numeric_limits<float>::infinity();
|
||||
bool any = false;
|
||||
|
||||
constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
|
||||
|
||||
for (float t = 0.0f; t < (float)frameNum; t += 1.0f)
|
||||
{
|
||||
mat4 transform(1.0f);
|
||||
|
||||
if (isRootTransform)
|
||||
{
|
||||
auto root = rootAnimation.frame_generate(t, anm2::ROOT);
|
||||
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
|
||||
}
|
||||
|
||||
for (auto& [id, layerAnimation] : layerAnimations)
|
||||
{
|
||||
auto frame = layerAnimation.frame_generate(t, anm2::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};
|
||||
}
|
||||
|
||||
}
|
||||
35
src/anm2/animation.h
Normal file
35
src/anm2/animation.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "item.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FRAME_NUM_MIN = 1;
|
||||
constexpr auto FRAME_NUM_MAX = FRAME_DELAY_MAX;
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{FRAME_NUM_MIN};
|
||||
bool isLoop{true};
|
||||
Item rootAnimation;
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers;
|
||||
|
||||
Animation() = default;
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
Item* item_get(Type, int = -1);
|
||||
void item_remove(Type, int = -1);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
int length();
|
||||
glm::vec4 rect(bool);
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
}
|
||||
155
src/anm2/animations.cpp
Normal file
155
src/anm2/animations.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "animations.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Animations::Animations(XMLElement* element)
|
||||
{
|
||||
xml::query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
items.push_back(Animation(child));
|
||||
}
|
||||
|
||||
XMLElement* Animations::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("Animations");
|
||||
element->SetAttribute("DefaultAnimation", defaultAnimation.c_str());
|
||||
for (auto& animation : items)
|
||||
animation.serialize(document, element);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Animations::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document));
|
||||
}
|
||||
|
||||
int Animations::length()
|
||||
{
|
||||
int length{};
|
||||
|
||||
for (auto& animation : items)
|
||||
if (int animationLength = animation.length(); animationLength > length) length = animationLength;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int Animations::merge(int target, std::set<int>& sources, merge::Type type, bool isDeleteAfter)
|
||||
{
|
||||
Animation& animation = items.at(target);
|
||||
|
||||
if (!animation.name.ends_with(MERGED_STRING)) animation.name = animation.name + " " + MERGED_STRING;
|
||||
|
||||
auto merge_item = [&](Item& destination, Item& source)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case merge::APPEND:
|
||||
destination.frames.insert(destination.frames.end(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::PREPEND:
|
||||
destination.frames.insert(destination.frames.begin(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::REPLACE:
|
||||
if (destination.frames.size() < source.frames.size()) destination.frames.resize(source.frames.size());
|
||||
for (int i = 0; i < (int)source.frames.size(); i++)
|
||||
destination.frames[i] = source.frames[i];
|
||||
break;
|
||||
case merge::IGNORE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& i : sources)
|
||||
{
|
||||
if (i == target) continue;
|
||||
if (i < 0 || i >= (int)items.size()) continue;
|
||||
|
||||
auto& source = items.at(i);
|
||||
|
||||
merge_item(animation.rootAnimation, source.rootAnimation);
|
||||
|
||||
for (auto& [id, layerAnimation] : source.layerAnimations)
|
||||
{
|
||||
if (!animation.layerAnimations.contains(id))
|
||||
{
|
||||
animation.layerAnimations[id] = layerAnimation;
|
||||
animation.layerOrder.emplace_back(id);
|
||||
}
|
||||
merge_item(animation.layerAnimations[id], layerAnimation);
|
||||
}
|
||||
|
||||
for (auto& [id, nullAnimation] : source.nullAnimations)
|
||||
{
|
||||
if (!animation.nullAnimations.contains(id)) animation.nullAnimations[id] = nullAnimation;
|
||||
merge_item(animation.nullAnimations[id], nullAnimation);
|
||||
}
|
||||
|
||||
merge_item(animation.triggers, source.triggers);
|
||||
}
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
for (auto& source : std::ranges::reverse_view(sources))
|
||||
{
|
||||
if (source == target) continue;
|
||||
items.erase(items.begin() + source);
|
||||
}
|
||||
}
|
||||
|
||||
int finalIndex = target;
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
int numDeletedBefore = 0;
|
||||
for (auto& idx : sources)
|
||||
{
|
||||
if (idx == target) continue;
|
||||
if (idx >= 0 && idx < target) ++numDeletedBefore;
|
||||
}
|
||||
finalIndex -= numDeletedBefore;
|
||||
}
|
||||
|
||||
return finalIndex;
|
||||
}
|
||||
|
||||
bool Animations::animations_deserialize(const std::string& string, int start, std::set<int>& indices,
|
||||
std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
if (!document.FirstChildElement("Animation"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid animation(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
int count{};
|
||||
for (auto element = document.FirstChildElement("Animation"); element;
|
||||
element = element->NextSiblingElement("Animation"))
|
||||
{
|
||||
auto index = start + count;
|
||||
items.insert(items.begin() + start + count, Animation(element));
|
||||
indices.insert(index);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
src/anm2/animations.h
Normal file
22
src/anm2/animations.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "animation.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto MERGED_STRING = "(Merged)";
|
||||
|
||||
struct Animations
|
||||
{
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
|
||||
Animations() = default;
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&);
|
||||
int length();
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
int merge(int, std::set<int>&, types::merge::Type = types::merge::APPEND, bool = true);
|
||||
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
|
||||
};
|
||||
}
|
||||
376
src/anm2/anm2.cpp
Normal file
376
src/anm2/anm2.cpp
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
#include "time_.h"
|
||||
#include "unordered_map_.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Anm2::Anm2()
|
||||
{
|
||||
info.createdOn = time::get("%d-%B-%Y %I:%M:%S");
|
||||
}
|
||||
|
||||
bool Anm2::serialize(const std::string& path, std::string* errorString)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto* element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
if (document.SaveFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
if (errorString) *errorString = document.ErrorStr();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Anm2::to_string()
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto* element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
XMLPrinter printer;
|
||||
document.Print(&printer);
|
||||
return std::string(printer.CStr());
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::string& path, std::string* errorString)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
if (errorString) *errorString = document.ErrorStr();
|
||||
return;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(path, true);
|
||||
|
||||
const XMLElement* element = document.RootElement();
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) info = Info((XMLElement*)infoElement);
|
||||
if (auto contentElement = element->FirstChildElement("Content")) content = Content((XMLElement*)contentElement);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||
animations = Animations((XMLElement*)animationsElement);
|
||||
}
|
||||
|
||||
uint64_t Anm2::hash()
|
||||
{
|
||||
return std::hash<std::string>{}(to_string());
|
||||
}
|
||||
|
||||
Animation* Anm2::animation_get(Reference reference)
|
||||
{
|
||||
return vector::find(animations.items, reference.animationIndex);
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::animation_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& animation : animations.items)
|
||||
names.push_back(animation.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
Item* Anm2::item_get(Reference reference)
|
||||
{
|
||||
if (Animation* animation = animation_get(reference))
|
||||
{
|
||||
switch (reference.itemType)
|
||||
{
|
||||
case ROOT:
|
||||
return &animation->rootAnimation;
|
||||
case LAYER:
|
||||
return unordered_map::find(animation->layerAnimations, reference.itemID);
|
||||
case NULL_:
|
||||
return map::find(animation->nullAnimations, reference.itemID);
|
||||
case TRIGGER:
|
||||
return &animation->triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Frame* Anm2::frame_get(Reference reference)
|
||||
{
|
||||
Item* item = item_get(reference);
|
||||
if (!item) return nullptr;
|
||||
return vector::find(item->frames, reference.frameIndex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Anm2::spritesheet_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
Spritesheet spritesheet(directory, path);
|
||||
if (!spritesheet.is_valid()) return false;
|
||||
id = map::next_id_get(content.spritesheets);
|
||||
content.spritesheets[id] = std::move(spritesheet);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Anm2::spritesheet_remove(int id)
|
||||
{
|
||||
content.spritesheets.erase(id);
|
||||
}
|
||||
|
||||
Spritesheet* Anm2::spritesheet_get(int id)
|
||||
{
|
||||
return map::find(content.spritesheets, id);
|
||||
}
|
||||
|
||||
std::set<int> Anm2::spritesheets_unused()
|
||||
{
|
||||
return content.spritesheets_unused();
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::spritesheet_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& [id, spritesheet] : content.spritesheets)
|
||||
names.push_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
|
||||
return names;
|
||||
}
|
||||
|
||||
Reference Anm2::layer_add(Reference reference, std::string name, int spritesheetID, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID;
|
||||
auto& layer = content.layers[id];
|
||||
|
||||
layer.name = !name.empty() ? name : layer.name;
|
||||
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0;
|
||||
|
||||
auto add = [&](Animation* animation, int id)
|
||||
{
|
||||
animation->layerAnimations[id] = Item();
|
||||
animation->layerOrder.push_back(id);
|
||||
};
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.layerAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->layerAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
|
||||
Reference Anm2::null_add(Reference reference, std::string name, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.nulls) : reference.itemID;
|
||||
auto& null = content.nulls[id];
|
||||
|
||||
null.name = !name.empty() ? name : null.name;
|
||||
|
||||
auto add = [&](Animation* animation, int id) { animation->nullAnimations[id] = Item(); };
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.nullAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->nullAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
|
||||
void Anm2::event_add(int& id)
|
||||
{
|
||||
content.event_add(id);
|
||||
}
|
||||
|
||||
std::set<int> Anm2::events_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& frame : animation->triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& frame : animation.triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
|
||||
for (auto& id : content.events | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::layers_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.layers | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::nulls_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.nulls | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::event_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& event : content.events | std::views::values)
|
||||
names.push_back(event.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
bool Anm2::sound_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
id = map::next_id_get(content.sounds);
|
||||
content.sounds[id] = Sound(directory, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::sounds_unused()
|
||||
{
|
||||
std::set<int> used;
|
||||
for (auto& event : content.events | std::views::values)
|
||||
used.insert(event.soundID);
|
||||
|
||||
std::set<int> unused;
|
||||
for (auto& id : content.sounds | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::sound_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& [id, sound] : content.sounds)
|
||||
names.push_back(std::format(SOUND_FORMAT, id, sound.path.c_str()));
|
||||
return names;
|
||||
}
|
||||
|
||||
void Anm2::bake(Reference reference, int interval, bool isRoundScale, bool isRoundRotation)
|
||||
{
|
||||
Item* item = item_get(reference);
|
||||
if (!item) return;
|
||||
|
||||
Frame* frame = frame_get(reference);
|
||||
if (!frame) return;
|
||||
|
||||
if (frame->delay == FRAME_DELAY_MIN) return;
|
||||
|
||||
Reference referenceNext = reference;
|
||||
referenceNext.frameIndex = reference.frameIndex + 1;
|
||||
|
||||
Frame* frameNext = frame_get(referenceNext);
|
||||
if (!frameNext) frameNext = frame;
|
||||
|
||||
Frame baseFrame = *frame;
|
||||
Frame baseFrameNext = *frameNext;
|
||||
|
||||
int delay{};
|
||||
int index = reference.frameIndex;
|
||||
|
||||
while (delay < baseFrame.delay)
|
||||
{
|
||||
float interpolation = (float)delay / baseFrame.delay;
|
||||
|
||||
Frame baked = baseFrame;
|
||||
baked.delay = std::min(interval, baseFrame.delay - delay);
|
||||
baked.isInterpolated = (index == reference.frameIndex) ? baseFrame.isInterpolated : false;
|
||||
|
||||
baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation);
|
||||
baked.position = glm::mix(baseFrame.position, baseFrameNext.position, interpolation);
|
||||
baked.scale = glm::mix(baseFrame.scale, baseFrameNext.scale, interpolation);
|
||||
baked.colorOffset = glm::mix(baseFrame.colorOffset, baseFrameNext.colorOffset, interpolation);
|
||||
baked.tint = glm::mix(baseFrame.tint, baseFrameNext.tint, interpolation);
|
||||
|
||||
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
|
||||
if (isRoundRotation) baked.rotation = (int)baked.rotation;
|
||||
|
||||
if (index == reference.frameIndex)
|
||||
item->frames[index] = baked;
|
||||
else
|
||||
item->frames.insert(item->frames.begin() + index, baked);
|
||||
index++;
|
||||
|
||||
delay += baked.delay;
|
||||
}
|
||||
}
|
||||
|
||||
void Anm2::generate_from_grid(Reference reference, ivec2 startPosition, ivec2 size, ivec2 pivot, int columns,
|
||||
int count, int delay)
|
||||
{
|
||||
auto item = item_get(reference);
|
||||
if (!item) return;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto row = i / columns;
|
||||
auto column = i % columns;
|
||||
|
||||
Frame frame{};
|
||||
|
||||
frame.delay = delay;
|
||||
frame.pivot = pivot;
|
||||
frame.size = size;
|
||||
frame.crop = startPosition + ivec2(size.x * column, size.y * row);
|
||||
|
||||
item->frames.emplace_back(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/anm2/anm2.h
Normal file
72
src/anm2/anm2.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "animations.h"
|
||||
#include "content.h"
|
||||
#include "info.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto NO_PATH = "[No Path]";
|
||||
|
||||
struct Reference
|
||||
{
|
||||
int animationIndex{-1};
|
||||
Type itemType{NONE};
|
||||
int itemID{-1};
|
||||
int frameIndex{-1};
|
||||
int frameTime{-1};
|
||||
|
||||
auto operator<=>(const Reference&) const = default;
|
||||
};
|
||||
|
||||
constexpr anm2::Reference REFERENCE_DEFAULT = {-1, anm2::NONE, -1, -1, -1};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
Info info{};
|
||||
Content content{};
|
||||
Animations animations{};
|
||||
|
||||
Anm2();
|
||||
bool serialize(const std::string&, std::string* = nullptr);
|
||||
std::string to_string();
|
||||
Anm2(const std::string&, std::string* = nullptr);
|
||||
uint64_t hash();
|
||||
Animation* animation_get(Reference);
|
||||
std::vector<std::string> animation_names_get();
|
||||
|
||||
Item* item_get(Reference);
|
||||
|
||||
Frame* frame_get(Reference);
|
||||
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
Spritesheet* spritesheet_get(int);
|
||||
void spritesheet_remove(int);
|
||||
std::set<int> spritesheets_unused();
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
|
||||
int layer_add();
|
||||
Reference layer_add(Reference = REFERENCE_DEFAULT, std::string = {}, int = 0,
|
||||
types::locale::Type = types::locale::GLOBAL);
|
||||
std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
|
||||
|
||||
Reference null_add(Reference = REFERENCE_DEFAULT, std::string = {}, types::locale::Type = types::locale::GLOBAL);
|
||||
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
|
||||
|
||||
bool sound_add(const std::string& directory, const std::string& path, int& id);
|
||||
std::vector<std::string> sound_names_get();
|
||||
std::set<int> sounds_unused();
|
||||
|
||||
void event_add(int&);
|
||||
std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::vector<std::string> event_names_get();
|
||||
void bake(Reference, int = 1, bool = true, bool = true);
|
||||
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
};
|
||||
}
|
||||
256
src/anm2/content.cpp
Normal file
256
src/anm2/content.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "content.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Content::Content(XMLElement* element)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (auto spritesheetsElement = element->FirstChildElement("Spritesheets"))
|
||||
for (auto child = spritesheetsElement->FirstChildElement("Spritesheet"); child;
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
spritesheets[id] = Spritesheet(child, id);
|
||||
|
||||
if (auto layersElement = element->FirstChildElement("Layers"))
|
||||
for (auto child = layersElement->FirstChildElement("Layer"); child; child = child->NextSiblingElement("Layer"))
|
||||
layers[id] = Layer(child, id);
|
||||
|
||||
if (auto nullsElement = element->FirstChildElement("Nulls"))
|
||||
for (auto child = nullsElement->FirstChildElement("Null"); child; child = child->NextSiblingElement("Null"))
|
||||
nulls[id] = Null(child, id);
|
||||
|
||||
if (auto eventsElement = element->FirstChildElement("Events"))
|
||||
for (auto child = eventsElement->FirstChildElement("Event"); child; child = child->NextSiblingElement("Event"))
|
||||
events[id] = Event(child, id);
|
||||
}
|
||||
|
||||
void Content::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
auto element = document.NewElement("Content");
|
||||
|
||||
auto spritesheetsElement = document.NewElement("Spritesheets");
|
||||
for (auto& [id, spritesheet] : spritesheets)
|
||||
spritesheet.serialize(document, spritesheetsElement, id);
|
||||
element->InsertEndChild(spritesheetsElement);
|
||||
|
||||
auto layersElement = document.NewElement("Layers");
|
||||
for (auto& [id, layer] : layers)
|
||||
layer.serialize(document, layersElement, id);
|
||||
element->InsertEndChild(layersElement);
|
||||
|
||||
auto nullsElement = document.NewElement("Nulls");
|
||||
for (auto& [id, null] : nulls)
|
||||
null.serialize(document, nullsElement, id);
|
||||
element->InsertEndChild(nullsElement);
|
||||
|
||||
auto eventsElement = document.NewElement("Events");
|
||||
for (auto& [id, event] : events)
|
||||
event.serialize(document, eventsElement, id);
|
||||
element->InsertEndChild(eventsElement);
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
std::set<int> Content::spritesheets_unused()
|
||||
{
|
||||
std::set<int> used;
|
||||
for (auto& layer : layers | std::views::values)
|
||||
if (layer.spritesheetID != -1) used.insert(layer.spritesheetID);
|
||||
|
||||
std::set<int> unused;
|
||||
for (auto& id : spritesheets | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
void Content::layer_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(layers);
|
||||
layers[id] = Layer();
|
||||
}
|
||||
|
||||
void Content::null_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(nulls);
|
||||
nulls[id] = Null();
|
||||
}
|
||||
|
||||
void Content::event_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(events);
|
||||
events[id] = Event();
|
||||
}
|
||||
|
||||
bool Content::spritesheets_deserialize(const std::string& string, const std::string& directory, merge::Type type,
|
||||
std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (!document.FirstChildElement("Spritesheet"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid spritesheet(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
|
||||
for (auto element = document.FirstChildElement("Spritesheet"); element;
|
||||
element = element->NextSiblingElement("Spritesheet"))
|
||||
{
|
||||
auto spritesheet = Spritesheet(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(spritesheets);
|
||||
|
||||
spritesheets[id] = std::move(spritesheet);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::layers_deserialize(const std::string& string, merge::Type type, std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (!document.FirstChildElement("Layer"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid layer(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto element = document.FirstChildElement("Layer"); element; element = element->NextSiblingElement("Layer"))
|
||||
{
|
||||
auto layer = Layer(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(layers);
|
||||
|
||||
layers[id] = layer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::nulls_deserialize(const std::string& string, merge::Type type, std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (!document.FirstChildElement("Null"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid null(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto element = document.FirstChildElement("Null"); element; element = element->NextSiblingElement("Null"))
|
||||
{
|
||||
auto layer = Null(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(nulls);
|
||||
|
||||
nulls[id] = layer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::events_deserialize(const std::string& string, merge::Type type, std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (!document.FirstChildElement("Event"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid event(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto element = document.FirstChildElement("Event"); element; element = element->NextSiblingElement("Event"))
|
||||
{
|
||||
auto layer = Event(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(events);
|
||||
|
||||
events[id] = layer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::sounds_deserialize(const std::string& string, const std::string& directory, merge::Type type,
|
||||
std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (!document.FirstChildElement("Sound"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid sound(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
|
||||
for (auto element = document.FirstChildElement("Sound"); element; element = element->NextSiblingElement("Sound"))
|
||||
{
|
||||
auto sound = Sound(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(sounds);
|
||||
|
||||
sounds[id] = std::move(sound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/anm2/content.h
Normal file
46
src/anm2/content.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "event.h"
|
||||
#include "layer.h"
|
||||
#include "null.h"
|
||||
#include "sound.h"
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
struct Content
|
||||
{
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
Content() = default;
|
||||
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
Content(tinyxml2::XMLElement*);
|
||||
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
std::set<int> spritesheets_unused();
|
||||
void spritesheet_remove(int&);
|
||||
bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void layer_add(int&);
|
||||
bool layers_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void null_add(int&);
|
||||
bool nulls_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void event_add(int&);
|
||||
bool events_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void sound_add(int&);
|
||||
bool sounds_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
};
|
||||
}
|
||||
36
src/anm2/event.cpp
Normal file
36
src/anm2/event.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "event.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Event::Event(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
XMLElement* Event::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Event");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
void Event::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Event::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
20
src/anm2/event.h
Normal file
20
src/anm2/event.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
int soundID{};
|
||||
|
||||
Event() = default;
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
126
src/anm2/frame.cpp
Normal file
126
src/anm2/frame.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "frame.h"
|
||||
|
||||
#include "math_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Frame::Frame(XMLElement* element, Type type)
|
||||
{
|
||||
if (type != TRIGGER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPivot", &pivot.x);
|
||||
element->QueryFloatAttribute("YPivot", &pivot.y);
|
||||
element->QueryFloatAttribute("XCrop", &crop.x);
|
||||
element->QueryFloatAttribute("YCrop", &crop.y);
|
||||
element->QueryFloatAttribute("Width", &size.x);
|
||||
element->QueryFloatAttribute("Height", &size.y);
|
||||
}
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &delay);
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
xml::query_color_attribute(element, "RedTint", tint.r);
|
||||
xml::query_color_attribute(element, "GreenTint", tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
else
|
||||
{
|
||||
element->QueryIntAttribute("EventId", &eventID);
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
}
|
||||
}
|
||||
|
||||
XMLElement* Frame::to_element(XMLDocument& document, Type type)
|
||||
{
|
||||
auto element = document.NewElement(type == TRIGGER ? "Trigger" : "Frame");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ROOT:
|
||||
case NULL_:
|
||||
element->SetAttribute("XPosition", position.x);
|
||||
element->SetAttribute("YPosition", position.y);
|
||||
element->SetAttribute("Delay", delay);
|
||||
element->SetAttribute("Visible", isVisible);
|
||||
element->SetAttribute("XScale", scale.x);
|
||||
element->SetAttribute("YScale", scale.y);
|
||||
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
|
||||
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
|
||||
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
|
||||
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
|
||||
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
|
||||
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);
|
||||
break;
|
||||
case LAYER:
|
||||
element->SetAttribute("XPosition", position.x);
|
||||
element->SetAttribute("YPosition", position.y);
|
||||
element->SetAttribute("XPivot", pivot.x);
|
||||
element->SetAttribute("YPivot", pivot.y);
|
||||
element->SetAttribute("XCrop", crop.x);
|
||||
element->SetAttribute("YCrop", crop.y);
|
||||
element->SetAttribute("Width", size.x);
|
||||
element->SetAttribute("Height", size.y);
|
||||
element->SetAttribute("XScale", scale.x);
|
||||
element->SetAttribute("YScale", scale.y);
|
||||
element->SetAttribute("Delay", delay);
|
||||
element->SetAttribute("Visible", isVisible);
|
||||
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
|
||||
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
|
||||
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
|
||||
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
|
||||
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
|
||||
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);
|
||||
break;
|
||||
case TRIGGER:
|
||||
element->SetAttribute("EventId", eventID);
|
||||
element->SetAttribute("AtFrame", atFrame);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void Frame::serialize(XMLDocument& document, XMLElement* parent, Type type)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, type));
|
||||
}
|
||||
|
||||
std::string Frame::to_string(Type type)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, type));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
void Frame::shorten()
|
||||
{
|
||||
delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
||||
}
|
||||
|
||||
void Frame::extend()
|
||||
{
|
||||
delay = glm::clamp(++delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
||||
}
|
||||
}
|
||||
91
src/anm2/frame.h
Normal file
91
src/anm2/frame.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <glm/glm/vec2.hpp>
|
||||
#include <glm/glm/vec3.hpp>
|
||||
#include <glm/glm/vec4.hpp>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FRAME_DELAY_MIN = 1;
|
||||
constexpr auto FRAME_DELAY_MAX = 100000;
|
||||
|
||||
#define TYPE_LIST \
|
||||
X(NONE, "None", "None") \
|
||||
X(ROOT, "Root", "RootAnimation") \
|
||||
X(LAYER, "Layer", "LayerAnimation") \
|
||||
X(NULL_, "Null", "NullAnimation") \
|
||||
X(TRIGGER, "Trigger", "Triggers")
|
||||
|
||||
enum Type
|
||||
{
|
||||
#define X(symbol, string, animationString) symbol,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr const char* TYPE_STRINGS[] = {
|
||||
#define X(symbol, string, animationString) string,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr const char* TYPE_ANIMATION_STRINGS[] = {
|
||||
#define X(symbol, string, animationString) animationString,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum ChangeType
|
||||
{
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
ADJUST
|
||||
};
|
||||
|
||||
#define MEMBERS \
|
||||
X(isVisible, bool, true) \
|
||||
X(isInterpolated, bool, false) \
|
||||
X(rotation, float, 0.0f) \
|
||||
X(delay, int, FRAME_DELAY_MIN) \
|
||||
X(atFrame, int, -1) \
|
||||
X(eventID, int, -1) \
|
||||
X(pivot, glm::vec2, {}) \
|
||||
X(crop, glm::vec2, {}) \
|
||||
X(position, glm::vec2, {}) \
|
||||
X(size, glm::vec2, {}) \
|
||||
X(scale, glm::vec2, glm::vec2(100.0f)) \
|
||||
X(colorOffset, glm::vec3, types::color::TRANSPARENT) \
|
||||
X(tint, glm::vec4, types::color::WHITE)
|
||||
|
||||
class Frame
|
||||
{
|
||||
public:
|
||||
#define X(name, type, ...) type name = __VA_ARGS__;
|
||||
MEMBERS
|
||||
#undef X
|
||||
|
||||
Frame() = default;
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type);
|
||||
std::string to_string(Type type);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
|
||||
void shorten();
|
||||
void extend();
|
||||
};
|
||||
|
||||
struct FrameChange
|
||||
{
|
||||
#define X(name, type, ...) std::optional<type> name{};
|
||||
MEMBERS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef MEMBERS
|
||||
|
||||
}
|
||||
40
src/anm2/info.cpp
Normal file
40
src/anm2/info.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "info.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Info::Info(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
xml::query_string_attribute(element, "CreatedBy", &createdBy);
|
||||
xml::query_string_attribute(element, "CreatedOn", &createdOn);
|
||||
element->QueryIntAttribute("Fps", &fps);
|
||||
element->QueryIntAttribute("Version", &version);
|
||||
}
|
||||
|
||||
XMLElement* Info::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("Info");
|
||||
element->SetAttribute("CreatedBy", createdBy.c_str());
|
||||
element->SetAttribute("CreatedOn", createdOn.c_str());
|
||||
element->SetAttribute("Fps", fps);
|
||||
element->SetAttribute("Version", version);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Info::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document));
|
||||
}
|
||||
|
||||
std::string Info::to_string()
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
25
src/anm2/info.h
Normal file
25
src/anm2/info.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FPS_MIN = 1;
|
||||
constexpr auto FPS_MAX = 120;
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
std::string createdBy{"robot"};
|
||||
std::string createdOn{};
|
||||
int fps = 30;
|
||||
int version{};
|
||||
|
||||
Info() = default;
|
||||
Info(tinyxml2::XMLElement*);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument& document);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
std::string to_string();
|
||||
};
|
||||
}
|
||||
200
src/anm2/item.cpp
Normal file
200
src/anm2/item.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "item.h"
|
||||
#include <ranges>
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
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_ANIMATION_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);
|
||||
|
||||
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.delay;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
Frame Item::frame_generate(float time, Type type)
|
||||
{
|
||||
Frame frame{};
|
||||
frame.isVisible = false;
|
||||
|
||||
if (frames.empty()) return frame;
|
||||
|
||||
Frame* frameNext = nullptr;
|
||||
int delayCurrent = 0;
|
||||
int delayNext = 0;
|
||||
|
||||
for (auto [i, iFrame] : std::views::enumerate(frames))
|
||||
{
|
||||
if (type == TRIGGER)
|
||||
{
|
||||
if ((int)time == iFrame.atFrame)
|
||||
{
|
||||
frame = iFrame;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame = iFrame;
|
||||
|
||||
delayNext += frame.delay;
|
||||
|
||||
if (time >= delayCurrent && time < delayNext)
|
||||
{
|
||||
if (i + 1 < (int)frames.size())
|
||||
frameNext = &frames[i + 1];
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
delayCurrent += frame.delay;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != TRIGGER && frame.isInterpolated && frameNext && frame.delay > 1)
|
||||
{
|
||||
auto interpolation = (time - delayCurrent) / (delayNext - delayCurrent);
|
||||
|
||||
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(anm2::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.delay) frame.delay = std::max(FRAME_DELAY_MIN, *change.delay);
|
||||
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.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay + *change.delay);
|
||||
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.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay - *change.delay);
|
||||
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<int>& indices,
|
||||
std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
if (!document.FirstChildElement("Frame"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid frame(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
int count{};
|
||||
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 (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
26
src/anm2/item.h
Normal file
26
src/anm2/item.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{true};
|
||||
|
||||
Item() = default;
|
||||
Item(tinyxml2::XMLElement*, Type, int* = nullptr);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
|
||||
std::string to_string(Type, int = -1);
|
||||
int length(Type);
|
||||
Frame frame_generate(float, Type);
|
||||
void frames_change(anm2::FrameChange&, ChangeType, int, int = 0);
|
||||
bool frames_deserialize(const std::string&, Type, int, std::set<int>&, std::string*);
|
||||
};
|
||||
}
|
||||
39
src/anm2/layer.cpp
Normal file
39
src/anm2/layer.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "layer.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Layer::Layer(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("SpritesheetId", &spritesheetID);
|
||||
}
|
||||
|
||||
XMLElement* Layer::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Layer");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
element->SetAttribute("SpritesheetId", spritesheetID);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Layer::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Layer::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
}
|
||||
22
src/anm2/layer.h
Normal file
22
src/anm2/layer.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{};
|
||||
|
||||
Layer() = default;
|
||||
Layer(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
38
src/anm2/null.cpp
Normal file
38
src/anm2/null.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "null.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Null::Null(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryBoolAttribute("ShowRect", &isShowRect);
|
||||
}
|
||||
|
||||
XMLElement* Null::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Null");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
if (isShowRect) element->SetAttribute("ShowRect", isShowRect);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Null::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Null::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
22
src/anm2/null.h
Normal file
22
src/anm2/null.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto NULL_FORMAT = "#{} {}";
|
||||
|
||||
class Null
|
||||
{
|
||||
public:
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
|
||||
Null() = default;
|
||||
Null(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument& document, int id);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
59
src/anm2/sound.cpp
Normal file
59
src/anm2/sound.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "sound.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Sound::Sound(const Sound& other) : path(other.path)
|
||||
{
|
||||
audio = path.empty() ? Audio() : Audio(path.c_str());
|
||||
}
|
||||
|
||||
Sound& Sound::operator=(const Sound& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
path = other.path;
|
||||
audio = path.empty() ? Audio() : Audio(path.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sound::Sound(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
audio = Audio(this->path.c_str());
|
||||
}
|
||||
|
||||
Sound::Sound(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
string::backslash_replace_to_lower(this->path);
|
||||
audio = Audio(this->path.c_str());
|
||||
}
|
||||
|
||||
XMLElement* Sound::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Sound");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Path", path.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
std::string Sound::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
30
src/anm2/sound.h
Normal file
30
src/anm2/sound.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto SOUND_FORMAT = "#{} {}";
|
||||
constexpr auto SOUND_FORMAT_C = "#%d %s";
|
||||
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Audio audio{};
|
||||
|
||||
Sound() = default;
|
||||
Sound(Sound&&) noexcept = default;
|
||||
Sound& operator=(Sound&&) noexcept = default;
|
||||
|
||||
Sound(const Sound&);
|
||||
Sound& operator=(const Sound&);
|
||||
Sound(tinyxml2::XMLElement*, int&);
|
||||
Sound(const std::string&, const std::string&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
70
src/anm2/spritesheet.cpp
Normal file
70
src/anm2/spritesheet.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
// Spritesheet paths from Isaac Rebirth are made with the assumption that paths are case-insensitive
|
||||
// However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyway)
|
||||
// This will handle this case and make the paths OS-agnostic
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
texture = Texture(path);
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
texture = Texture(this->path);
|
||||
}
|
||||
|
||||
XMLElement* Spritesheet::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Spritesheet");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Path", path.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
void Spritesheet::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Spritesheet::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
bool Spritesheet::save(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
return texture.write_png(this->path);
|
||||
}
|
||||
|
||||
void Spritesheet::reload(const std::string& directory)
|
||||
{
|
||||
*this = Spritesheet(directory, this->path);
|
||||
}
|
||||
|
||||
bool Spritesheet::is_valid()
|
||||
{
|
||||
return texture.is_valid();
|
||||
}
|
||||
|
||||
}
|
||||
30
src/anm2/spritesheet.h
Normal file
30
src/anm2/spritesheet.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
|
||||
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
|
||||
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture;
|
||||
|
||||
Spritesheet() = default;
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
Spritesheet(const std::string&, const std::string& = {});
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int id);
|
||||
bool save(const std::string&, const std::string& = {});
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
void reload(const std::string&);
|
||||
bool is_valid();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user