Files
anm2ed/src/anm2/anm2.hpp
T
2026-06-20 15:06:04 -04:00

353 lines
14 KiB
C++

#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include <glm/glm.hpp>
#include <tinyxml2/tinyxml2.h>
#include "icon.hpp"
#include "strings.hpp"
#include "types.hpp"
#define ANM2_ELEMENT_TYPES \
X(UNKNOWN, "") \
X(ANIMATED_ACTOR, "AnimatedActor") \
X(INFO, "Info") \
X(CONTENT, "Content") \
X(SPRITESHEETS, "Spritesheets") \
X(SPRITESHEET, "Spritesheet") \
X(REGION, "Region") \
X(LAYERS, "Layers") \
X(LAYER_ELEMENT, "Layer") \
X(NULLS, "Nulls") \
X(NULL_ELEMENT, "Null") \
X(EVENTS, "Events") \
X(EVENT_ELEMENT, "Event") \
X(SOUNDS, "Sounds") \
X(SOUND_ELEMENT, "Sound") \
X(ANIMATIONS, "Animations") \
X(ANIMATION, "Animation") \
X(ROOT_ANIMATION, "RootAnimation") \
X(LAYER_ANIMATIONS, "LayerAnimations") \
X(LAYER_ANIMATION, "LayerAnimation") \
X(NULL_ANIMATIONS, "NullAnimations") \
X(NULL_ANIMATION, "NullAnimation") \
X(GROUP, "Group") \
X(TRIGGERS, "Triggers") \
X(FRAME, "Frame") \
X(TRIGGER, "Trigger")
namespace anm2ed
{
enum class ElementType
{
#define X(symbol, tag) symbol,
ANM2_ELEMENT_TYPES
#undef X
COUNT
};
enum class Interpolation
{
NONE,
LINEAR,
EASE_IN,
EASE_OUT,
EASE_IN_OUT,
COUNT
};
enum class Origin
{
CUSTOM,
TOP_LEFT,
CENTER,
COUNT
};
enum class ItemType
{
NONE,
ROOT,
LAYER,
NULL_,
TRIGGER,
COUNT
};
inline constexpr int NONE = (int)ItemType::NONE;
inline constexpr int ROOT = (int)ItemType::ROOT;
inline constexpr int LAYER = (int)ItemType::LAYER;
inline constexpr int NULL_ = (int)ItemType::NULL_;
inline constexpr int TRIGGER = (int)ItemType::TRIGGER;
inline constexpr int FRAME_DURATION_MIN = 1;
inline constexpr int FRAME_DURATION_MAX = 1000000;
inline constexpr int FRAME_NUM_MIN = 1;
inline constexpr int FRAME_NUM_MAX = FRAME_DURATION_MAX;
inline constexpr int FPS_MIN = 1;
inline constexpr int FPS_MAX = 120;
enum class SpritesheetMergeOrigin
{
APPEND_RIGHT,
APPEND_BOTTOM,
COUNT
};
inline constexpr int APPEND_RIGHT = (int)SpritesheetMergeOrigin::APPEND_RIGHT;
inline constexpr int APPEND_BOTTOM = (int)SpritesheetMergeOrigin::APPEND_BOTTOM;
inline const glm::vec4 ROOT_COLOR = glm::vec4(0.140f, 0.310f, 0.560f, 1.000f);
inline const glm::vec4 ROOT_COLOR_ACTIVE = glm::vec4(0.240f, 0.520f, 0.880f, 1.000f);
inline const glm::vec4 ROOT_COLOR_HOVERED = glm::vec4(0.320f, 0.640f, 1.000f, 1.000f);
inline const glm::vec4 LAYER_COLOR = glm::vec4(0.640f, 0.320f, 0.110f, 1.000f);
inline const glm::vec4 LAYER_COLOR_ACTIVE = glm::vec4(0.840f, 0.450f, 0.170f, 1.000f);
inline const glm::vec4 LAYER_COLOR_HOVERED = glm::vec4(0.960f, 0.560f, 0.240f, 1.000f);
inline const glm::vec4 NULL_COLOR = glm::vec4(0.140f, 0.430f, 0.200f, 1.000f);
inline const glm::vec4 NULL_COLOR_ACTIVE = glm::vec4(0.250f, 0.650f, 0.350f, 1.000f);
inline const glm::vec4 NULL_COLOR_HOVERED = glm::vec4(0.350f, 0.800f, 0.480f, 1.000f);
inline const glm::vec4 TRIGGER_COLOR = glm::vec4(0.620f, 0.150f, 0.260f, 1.000f);
inline const glm::vec4 TRIGGER_COLOR_ACTIVE = glm::vec4(0.820f, 0.250f, 0.380f, 1.000f);
inline const glm::vec4 TRIGGER_COLOR_HOVERED = glm::vec4(0.950f, 0.330f, 0.490f, 1.000f);
#define ANM2_ITEM_TYPES \
X(NONE, STRING_UNDEFINED, "", resource::icon::NONE, glm::vec4(), glm::vec4(), glm::vec4()) \
X(ROOT, BASIC_ROOT, "RootAnimation", resource::icon::ROOT, ROOT_COLOR, ROOT_COLOR_ACTIVE, ROOT_COLOR_HOVERED) \
X(LAYER, BASIC_LAYER_ANIMATION, "LayerAnimation", resource::icon::LAYER, LAYER_COLOR, LAYER_COLOR_ACTIVE, \
LAYER_COLOR_HOVERED) \
X(NULL_, BASIC_NULL_ANIMATION, "NullAnimation", resource::icon::NULL_, NULL_COLOR, NULL_COLOR_ACTIVE, \
NULL_COLOR_HOVERED) \
X(TRIGGER, BASIC_TRIGGERS, "Triggers", resource::icon::TRIGGERS, TRIGGER_COLOR, TRIGGER_COLOR_ACTIVE, \
TRIGGER_COLOR_HOVERED)
constexpr StringType TYPE_STRINGS[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) string,
ANM2_ITEM_TYPES
#undef X
};
constexpr const char* TYPE_ITEM_STRINGS[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) itemString,
ANM2_ITEM_TYPES
#undef X
};
constexpr resource::icon::Type TYPE_ICONS[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) icon,
ANM2_ITEM_TYPES
#undef X
};
inline const glm::vec4 TYPE_COLOR[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) color,
ANM2_ITEM_TYPES
#undef X
};
inline const glm::vec4 TYPE_COLOR_ACTIVE[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) colorActive,
ANM2_ITEM_TYPES
#undef X
};
inline const glm::vec4 TYPE_COLOR_HOVERED[] = {
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) colorHovered,
ANM2_ITEM_TYPES
#undef X
};
enum class ChangeType
{
ADJUST,
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE
};
enum Flag
{
SERIALIZE_GROUPS = 1 << 0,
SERIALIZE_REGIONS = 1 << 1,
SERIALIZE_SOUNDS = 1 << 2,
SERIALIZE_REDUNDANT_FRAME_REGION_VALUES = 1 << 3,
SERIALIZE_BAKE_SPECIAL_INTERPOLATED_FRAMES = 1 << 4
};
using Flags = int;
constexpr bool has_flag(Flags flags, Flag flag) { return (flags & flag) != 0; }
constexpr Flags SERIALIZE_EDITOR_DEFAULT =
SERIALIZE_GROUPS | SERIALIZE_REGIONS | SERIALIZE_SOUNDS | SERIALIZE_REDUNDANT_FRAME_REGION_VALUES;
constexpr Flags SERIALIZE_DEFAULT = SERIALIZE_EDITOR_DEFAULT | SERIALIZE_BAKE_SPECIAL_INTERPOLATED_FRAMES;
struct Options
{
Flags flags{SERIALIZE_DEFAULT};
};
struct FrameChange
{
std::optional<bool> isVisible{};
std::optional<Interpolation> interpolation{};
std::optional<float> rotation{};
std::optional<int> duration{};
std::optional<int> regionId{};
std::optional<float> pivotX{};
std::optional<float> pivotY{};
std::optional<float> cropX{};
std::optional<float> cropY{};
std::optional<float> positionX{};
std::optional<float> positionY{};
std::optional<float> sizeX{};
std::optional<float> sizeY{};
std::optional<float> scaleX{};
std::optional<float> scaleY{};
std::optional<float> colorOffsetR{};
std::optional<float> colorOffsetG{};
std::optional<float> colorOffsetB{};
std::optional<float> tintR{};
std::optional<float> tintG{};
std::optional<float> tintB{};
std::optional<float> tintA{};
std::optional<bool> isFlipX{};
std::optional<bool> isFlipY{};
};
struct Element
{
ElementType type{ElementType::UNKNOWN};
std::string tag{};
std::vector<Element> children{};
std::string name{};
std::string createdBy{"robot"};
std::string createdOn{};
std::filesystem::path path{};
std::string defaultAnimation{};
int id{-1};
int layerId{-1};
int nullId{-1};
int spritesheetId{};
int fps{30};
int version{};
int frameNum{1};
int duration{1};
int atFrame{-1};
int eventId{-1};
int regionId{-1};
int soundId{-1};
int groupId{-1};
bool isLoop{true};
bool isVisible{true};
bool isShowRect{};
bool isExpanded{true};
Interpolation interpolation{Interpolation::NONE};
Origin origin{Origin::CUSTOM};
float rotation{};
std::vector<int> soundIds{};
glm::vec2 pivot{};
glm::vec2 crop{};
glm::vec2 position{};
glm::vec2 size{};
glm::vec2 scale{100.0f, 100.0f};
glm::vec3 colorOffset{};
glm::vec4 tint{types::color::WHITE};
};
struct Reference
{
int animationIndex{-1};
int itemType{(int)ItemType::NONE};
int itemID{-1};
int frameIndex{-1};
auto operator<=>(const Reference&) const = default;
};
Element element_make(ElementType);
Element element_read(const tinyxml2::XMLElement*);
std::string element_to_string(const Element&, Flags = SERIALIZE_EDITOR_DEFAULT);
std::string element_to_string(const Element&, ElementType, Flags = SERIALIZE_EDITOR_DEFAULT);
tinyxml2::XMLElement* element_to_xml(tinyxml2::XMLDocument&, const Element&, Flags = SERIALIZE_EDITOR_DEFAULT);
tinyxml2::XMLElement* element_to_xml(tinyxml2::XMLDocument&, const Element&, ElementType,
Flags = SERIALIZE_EDITOR_DEFAULT);
Element* element_child_first_get(Element&, ElementType);
const Element* element_child_first_get(const Element&, ElementType);
Element* element_child_id_get(Element&, ElementType, int);
const Element* element_child_id_get(const Element&, ElementType, int);
int element_child_next_id_get(const Element&, ElementType);
int element_child_max_id_get(const Element&, ElementType);
bool element_child_id_erase(Element&, ElementType, int);
Element* element_first_get(Element&, ElementType);
const Element* element_first_get(const Element&, ElementType);
Element* animation_item_get(Element&, ItemType, int = -1);
const Element* animation_item_get(const Element&, ItemType, int = -1);
ElementType item_type_to_track_type_get(ItemType);
Element* track_frame_get(Element&, int);
const Element* track_frame_get(const Element&, int);
Element frame_generate(const Element&, float);
int frame_index_from_at_frame_get(const Element&, int);
int frame_index_from_time_get(const Element&, float);
float frame_time_from_index_get(const Element&, int);
void frame_bake(Element&, int, int, bool, bool);
void frames_generate_from_grid(Element&, glm::ivec2, glm::ivec2, glm::vec2, int, int, int);
bool frames_deserialize(Element&, const std::string&, int, std::set<int>&, std::string* = nullptr);
void frames_sort_by_at_frame(Element&);
void frames_change(Element&, FrameChange, ItemType, ChangeType, const std::set<int>&);
int track_length_get(const Element&);
int animation_length_get(const Element&);
class Anm2
{
public:
bool isValid{true};
Element root{};
Anm2();
Anm2(const std::filesystem::path&, std::string* = nullptr);
bool load(const std::filesystem::path&, std::string* = nullptr);
bool load_string(std::string_view, std::string* = nullptr);
bool save(const std::filesystem::path&, std::string* = nullptr, Options = {}) const;
std::string to_string(Options = {}) const;
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Options = {}) const;
std::uint64_t hash(Options = {}) const;
bool is_special_interpolated_frames() const;
void special_interpolated_frames_bake(int, bool, bool);
void region_frames_sync(bool);
Anm2 normalized_for_serialize() const;
Element* element_get(ElementType);
const Element* element_get(ElementType) const;
Element* element_get(ElementType, int);
const Element* element_get(ElementType, int) const;
Element* element_get(int, ItemType, int = -1);
const Element* element_get(int, ItemType, int = -1) const;
Element* element_get(int, ItemType, int, int);
const Element* element_get(int, ItemType, int, int) const;
std::set<int> element_unused(ElementType) const;
std::set<int> element_unused(ElementType, const Element&) const;
std::set<int> element_unused(ElementType, int) const;
bool deserialize(ElementType, const std::string&, bool, std::string* = nullptr, const std::filesystem::path& = {});
bool regions_deserialize(int, const std::string&, bool, std::string* = nullptr);
Element frame_effective(int, const Element&) const;
glm::vec4 animation_rect(const Element&, bool) const;
int layer_animation_add(int, int = -1, int = -1, std::string = {}, int = 0,
types::destination::Type = types::destination::ALL);
int null_animation_add(int, int = -1, std::string = {}, bool = false,
types::destination::Type = types::destination::ALL);
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
int animations_merge(int, std::set<int>&, types::merge::Type = types::merge::APPEND, bool = true);
};
}