regions and a whole bunch of other shit

This commit is contained in:
2026-01-30 01:13:28 -05:00
parent ca3a0f6691
commit 1b7a49c25d
28 changed files with 988 additions and 107 deletions
+34
View File
@@ -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
View File
@@ -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&);
+2 -2
View File
@@ -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)
+38
View File
@@ -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
View File
@@ -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", &regionID);
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); }
}
}
+2
View File
@@ -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{};
+2
View File
@@ -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);
+91 -1
View File
@@ -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", &region.name);
child->QueryFloatAttribute("CropX", &region.crop.x);
child->QueryFloatAttribute("CropY", &region.crop.y);
child->QueryFloatAttribute("PivotX", &region.pivot.x);
child->QueryFloatAttribute("PivotY", &region.pivot.y);
child->QueryFloatAttribute("Width", &region.size.x);
child->QueryFloatAttribute("Height", &region.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", &region.name);
element->QueryFloatAttribute("CropX", &region.crop.x);
element->QueryFloatAttribute("CropY", &region.crop.y);
element->QueryFloatAttribute("PivotX", &region.pivot.x);
element->QueryFloatAttribute("PivotY", &region.pivot.y);
element->QueryFloatAttribute("Width", &region.size.x);
element->QueryFloatAttribute("Height", &region.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(); }
}
+15
View File
@@ -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&);