regions and a whole bunch of other shit
This commit is contained in:
@@ -16,6 +16,37 @@ using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace
|
||||
{
|
||||
void region_frames_sync(anm2ed::anm2::Anm2& anm2, bool clearInvalid)
|
||||
{
|
||||
for (auto& animation : anm2.animations.items)
|
||||
{
|
||||
for (auto& [layerId, layerAnimation] : animation.layerAnimations)
|
||||
{
|
||||
if (!anm2.content.layers.contains(layerId)) continue;
|
||||
auto& layer = anm2.content.layers.at(layerId);
|
||||
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
for (auto& frame : layerAnimation.frames)
|
||||
{
|
||||
if (frame.regionID == -1) continue;
|
||||
auto regionIt = spritesheet->regions.find(frame.regionID);
|
||||
if (regionIt == spritesheet->regions.end())
|
||||
{
|
||||
if (clearInvalid) frame.regionID = -1;
|
||||
continue;
|
||||
}
|
||||
frame.crop = regionIt->second.crop;
|
||||
frame.size = regionIt->second.size;
|
||||
frame.pivot = regionIt->second.pivot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Anm2::Anm2() { info.createdOn = time::get("%m/%d/%Y %I:%M:%S %p"); }
|
||||
@@ -47,10 +78,13 @@ namespace anm2ed::anm2
|
||||
if (auto contentElement = element->FirstChildElement("Content")) content = Content((XMLElement*)contentElement);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||
animations = Animations((XMLElement*)animationsElement);
|
||||
|
||||
region_frames_sync(*this, true);
|
||||
}
|
||||
|
||||
XMLElement* Anm2::to_element(XMLDocument& document)
|
||||
{
|
||||
region_frames_sync(*this, true);
|
||||
auto element = document.NewElement("AnimatedActor");
|
||||
|
||||
info.serialize(document, element);
|
||||
|
||||
+5
-1
@@ -44,6 +44,9 @@ namespace anm2ed::anm2
|
||||
std::set<int> spritesheets_unused();
|
||||
bool spritesheets_deserialize(const std::string&, const std::filesystem::path&, types::merge::Type type,
|
||||
std::string*);
|
||||
std::vector<std::string> region_labels_get(Spritesheet&);
|
||||
std::vector<int> region_ids_get(Spritesheet&);
|
||||
std::set<int> regions_unused(Spritesheet&);
|
||||
|
||||
void layer_add(int&);
|
||||
std::set<int> layers_unused();
|
||||
@@ -75,7 +78,8 @@ namespace anm2ed::anm2
|
||||
Item* item_get(int, Type, int = -1);
|
||||
Reference layer_animation_add(Reference = {}, std::string = {}, int = 0,
|
||||
types::destination::Type = types::destination::ALL);
|
||||
Reference null_animation_add(Reference = {}, std::string = {}, bool = false, types::destination::Type = types::destination::ALL);
|
||||
Reference null_animation_add(Reference = {}, std::string = {}, bool = false,
|
||||
types::destination::Type = types::destination::ALL);
|
||||
|
||||
Frame* frame_get(int, Type, int, int = -1);
|
||||
void merge(const Anm2&, const std::filesystem::path&, const std::filesystem::path&);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace anm2ed::anm2
|
||||
std::vector<std::string> Anm2::event_labels_get()
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back("None");
|
||||
labels.emplace_back(localize.get(BASIC_NONE));
|
||||
for (auto& event : content.events | std::views::values)
|
||||
labels.emplace_back(event.name);
|
||||
return labels;
|
||||
@@ -40,7 +40,7 @@ namespace anm2ed::anm2
|
||||
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& frame : animation.triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
if (frame.eventID != -1) used.insert(frame.eventID);
|
||||
|
||||
std::set<int> unused{};
|
||||
for (auto& id : content.events | std::views::keys)
|
||||
|
||||
@@ -55,6 +55,44 @@ namespace anm2ed::anm2
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::region_labels_get(Spritesheet& spritesheet)
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back(localize.get(BASIC_NONE));
|
||||
for (auto& region : spritesheet.regions | std::views::values)
|
||||
labels.emplace_back(region.name);
|
||||
return labels;
|
||||
}
|
||||
|
||||
std::vector<int> Anm2::region_ids_get(Spritesheet& spritesheet)
|
||||
{
|
||||
std::vector<int> ids{};
|
||||
ids.emplace_back(-1);
|
||||
for (auto& id : spritesheet.regions | std::views::keys)
|
||||
ids.emplace_back(id);
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::regions_unused(Spritesheet& spritesheet)
|
||||
{
|
||||
std::set<int> used{};
|
||||
|
||||
for (auto& animation : animations.items)
|
||||
{
|
||||
for (auto& layerAnimation : animation.layerAnimations | std::views::values)
|
||||
{
|
||||
for (auto& frame : layerAnimation.frames)
|
||||
if (frame.regionID != -1) used.insert(frame.regionID);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<int> unused{};
|
||||
for (auto& id : spritesheet.regions | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
bool Anm2::spritesheets_deserialize(const std::string& string, const std::filesystem::path& directory,
|
||||
merge::Type type, std::string* errorString)
|
||||
{
|
||||
|
||||
+56
-34
@@ -10,48 +10,69 @@ namespace anm2ed::anm2
|
||||
{
|
||||
Frame::Frame(XMLElement* element, Type type)
|
||||
{
|
||||
if (type != TRIGGER)
|
||||
switch (type)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
case ROOT:
|
||||
case NULL_:
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &duration);
|
||||
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);
|
||||
break;
|
||||
case LAYER:
|
||||
element->QueryIntAttribute("RegionId", ®ionID);
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
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", &duration);
|
||||
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);
|
||||
|
||||
int soundID{};
|
||||
// Backwards compatibility with old formats
|
||||
if (element->QueryIntAttribute("SoundId", &soundID) == XML_SUCCESS) soundIDs.push_back(soundID);
|
||||
|
||||
for (auto child = element->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &duration);
|
||||
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);
|
||||
break;
|
||||
case TRIGGER:
|
||||
{
|
||||
child->QueryIntAttribute("Id", &soundID);
|
||||
soundIDs.push_back(soundID);
|
||||
}
|
||||
element->QueryIntAttribute("EventId", &eventID);
|
||||
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
int soundID{};
|
||||
// Backwards compatibility with old formats
|
||||
if (element->QueryIntAttribute("SoundId", &soundID) == XML_SUCCESS) soundIDs.push_back(soundID);
|
||||
|
||||
for (auto child = element->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
child->QueryIntAttribute("Id", &soundID);
|
||||
soundIDs.push_back(soundID);
|
||||
}
|
||||
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +101,7 @@ namespace anm2ed::anm2
|
||||
element->SetAttribute("Interpolated", isInterpolated);
|
||||
break;
|
||||
case LAYER:
|
||||
if (regionID != -1) element->SetAttribute("RegionId", regionID);
|
||||
element->SetAttribute("XPosition", position.x);
|
||||
element->SetAttribute("YPosition", position.y);
|
||||
element->SetAttribute("XPivot", pivot.x);
|
||||
@@ -135,4 +157,4 @@ namespace anm2ed::anm2
|
||||
|
||||
void Frame::shorten() { duration = glm::clamp(--duration, FRAME_DURATION_MIN, FRAME_DURATION_MAX); }
|
||||
void Frame::extend() { duration = glm::clamp(++duration, FRAME_DURATION_MIN, FRAME_DURATION_MAX); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace anm2ed::anm2
|
||||
int duration{FRAME_DURATION_MIN};
|
||||
int atFrame{-1};
|
||||
int eventID{-1};
|
||||
int regionID{-1};
|
||||
std::vector<int> soundIDs{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 crop{};
|
||||
@@ -45,6 +46,7 @@ namespace anm2ed::anm2
|
||||
std::optional<bool> isInterpolated{};
|
||||
std::optional<float> rotation{};
|
||||
std::optional<int> duration{};
|
||||
std::optional<int> regionID{};
|
||||
std::optional<float> pivotX{};
|
||||
std::optional<float> pivotY{};
|
||||
std::optional<float> cropX{};
|
||||
|
||||
@@ -186,6 +186,8 @@ namespace anm2ed::anm2
|
||||
|
||||
apply_scalar(frame.size.x, change.sizeX);
|
||||
apply_scalar(frame.size.y, change.sizeY);
|
||||
|
||||
if (change.regionID) frame.regionID = *change.regionID;
|
||||
}
|
||||
|
||||
apply_scalar(frame.position.x, change.positionX);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "path_.h"
|
||||
#include "working_directory.h"
|
||||
#include "xml_.h"
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::types;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
@@ -20,6 +24,21 @@ namespace anm2ed::anm2
|
||||
// This will handle this case and make the paths OS-agnostic
|
||||
path = path::lower_case_backslash_handle(path);
|
||||
texture = Texture(path);
|
||||
|
||||
for (auto child = element->FirstChildElement("Region"); child; child = child->NextSiblingElement("Region"))
|
||||
{
|
||||
Region region{};
|
||||
int id{};
|
||||
child->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(child, "Name", ®ion.name);
|
||||
child->QueryFloatAttribute("CropX", ®ion.crop.x);
|
||||
child->QueryFloatAttribute("CropY", ®ion.crop.y);
|
||||
child->QueryFloatAttribute("PivotX", ®ion.pivot.x);
|
||||
child->QueryFloatAttribute("PivotY", ®ion.pivot.y);
|
||||
child->QueryFloatAttribute("Width", ®ion.size.x);
|
||||
child->QueryFloatAttribute("Height", ®ion.size.y);
|
||||
regions.emplace(id, std::move(region));
|
||||
}
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path)
|
||||
@@ -36,6 +55,20 @@ namespace anm2ed::anm2
|
||||
element->SetAttribute("Id", id);
|
||||
auto pathString = path::to_utf8(path);
|
||||
element->SetAttribute("Path", pathString.c_str());
|
||||
|
||||
for (auto [i, region] : regions)
|
||||
{
|
||||
auto regionElement = element->InsertNewChildElement("Region");
|
||||
regionElement->SetAttribute("Id", i);
|
||||
regionElement->SetAttribute("Name", region.name.c_str());
|
||||
regionElement->SetAttribute("CropX", region.crop.x);
|
||||
regionElement->SetAttribute("CropY", region.crop.y);
|
||||
regionElement->SetAttribute("PivotX", region.pivot.x);
|
||||
regionElement->SetAttribute("PivotY", region.pivot.y);
|
||||
regionElement->SetAttribute("Width", region.size.x);
|
||||
regionElement->SetAttribute("Height", region.size.y);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -51,6 +84,64 @@ namespace anm2ed::anm2
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
std::string Spritesheet::region_to_string(int id)
|
||||
{
|
||||
if (!regions.contains(id)) return {};
|
||||
|
||||
XMLDocument document{};
|
||||
auto element = document.NewElement("Region");
|
||||
auto& region = regions.at(id);
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", region.name.c_str());
|
||||
element->SetAttribute("CropX", region.crop.x);
|
||||
element->SetAttribute("CropY", region.crop.y);
|
||||
element->SetAttribute("PivotX", region.pivot.x);
|
||||
element->SetAttribute("PivotY", region.pivot.y);
|
||||
element->SetAttribute("Width", region.size.x);
|
||||
element->SetAttribute("Height", region.size.y);
|
||||
document.InsertEndChild(element);
|
||||
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
bool Spritesheet::regions_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("Region"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid region(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto element = document.FirstChildElement("Region"); element; element = element->NextSiblingElement("Region"))
|
||||
{
|
||||
Region region{};
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", ®ion.name);
|
||||
element->QueryFloatAttribute("CropX", ®ion.crop.x);
|
||||
element->QueryFloatAttribute("CropY", ®ion.crop.y);
|
||||
element->QueryFloatAttribute("PivotX", ®ion.pivot.x);
|
||||
element->QueryFloatAttribute("PivotY", ®ion.pivot.y);
|
||||
element->QueryFloatAttribute("Width", ®ion.size.x);
|
||||
element->QueryFloatAttribute("Height", ®ion.size.y);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(regions);
|
||||
regions[id] = std::move(region);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Spritesheet::save(const std::filesystem::path& directory, const std::filesystem::path& path)
|
||||
{
|
||||
WorkingDirectory workingDirectory(directory);
|
||||
@@ -59,7 +150,6 @@ namespace anm2ed::anm2
|
||||
}
|
||||
|
||||
void Spritesheet::reload(const std::filesystem::path& directory) { *this = Spritesheet(directory, this->path); }
|
||||
|
||||
bool Spritesheet::is_valid() { return texture.is_valid(); }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "texture.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
struct Region
|
||||
{
|
||||
std::string name{};
|
||||
glm::vec2 crop{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 size{};
|
||||
};
|
||||
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture;
|
||||
|
||||
std::map<int, Region> regions{};
|
||||
|
||||
Spritesheet() = default;
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
Spritesheet(const std::filesystem::path&, const std::filesystem::path& = {});
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int id);
|
||||
std::string region_to_string(int id);
|
||||
bool regions_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
bool save(const std::filesystem::path&, const std::filesystem::path& = {});
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
void reload(const std::filesystem::path&);
|
||||
|
||||
Reference in New Issue
Block a user