The Update(TM), Part 1

This commit is contained in:
2025-07-27 22:08:57 -04:00
parent 0439221e78
commit fe8bdae9a8
45 changed files with 4450 additions and 3564 deletions

View File

@@ -1,12 +1,10 @@
// Anm2 file format; serializing/deserializing
#include "anm2.h"
using namespace tinyxml2;
static void _anm2_created_on_set(Anm2* self);
// Sets the anm2's date to the system's current date
static void
_anm2_created_on_set(Anm2* self)
static void _anm2_created_on_set(Anm2* self)
{
auto now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
@@ -17,9 +15,7 @@ _anm2_created_on_set(Anm2* self)
self->createdOn = timeString.str();
}
// Serializes the anm2 struct into XML and exports it to the given path
bool
anm2_serialize(Anm2* self, const std::string& path)
bool anm2_serialize(Anm2* self, const std::string& path)
{
XMLDocument document;
XMLError error;
@@ -33,17 +29,11 @@ anm2_serialize(Anm2* self, const std::string& path)
XMLElement* eventsElement;
XMLElement* animationsElement;
if (!self || path.empty())
return false;
if (!self || path.empty()) return false;
// Update creation date on first version
if (self->version == 0)
_anm2_created_on_set(self);
if (self->version == 0) _anm2_created_on_set(self);
// Set anm2 path to argument
self->path = path;
// Update version
self->version++;
// AnimatedActor
@@ -137,7 +127,7 @@ anm2_serialize(Anm2* self, const std::string& path)
animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]);
animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->defaultAnimation.c_str()); // DefaultAnimation
for (const auto & [id, animation] : self->animations)
for (auto& [id, animation] : self->animations)
{
XMLElement* animationElement;
XMLElement* rootAnimationElement;
@@ -155,7 +145,7 @@ anm2_serialize(Anm2* self, const std::string& path)
// RootAnimation
rootAnimationElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION]);
for (const auto & frame : animation.rootAnimation.frames)
for (auto& frame : animation.rootAnimation.frames)
{
XMLElement* frameElement;
@@ -188,8 +178,10 @@ anm2_serialize(Anm2* self, const std::string& path)
// LayerAnimations
layerAnimationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS]);
for (const auto & [layerID, layerAnimation] : animation.layerAnimations)
for (auto& [layerIndex, layerID] : self->layerMap)
{
Anm2Item& layerAnimation = animation.layerAnimations[layerID];
XMLElement* layerAnimationElement;
// LayerAnimation
@@ -304,30 +296,28 @@ anm2_serialize(Anm2* self, const std::string& path)
if (error != XML_SUCCESS)
{
std::cout << STRING_ERROR_ANM2_WRITE << path << std::endl;
log_error(std::format(ANM2_WRITE_ERROR, path));
return false;
}
std::cout << STRING_INFO_ANM2_WRITE << path << std::endl;
log_info(std::format(ANM2_WRITE_INFO, path));
return true;
}
// Loads the .anm2 file and deserializes it into the struct equivalent
bool
anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
{
XMLDocument xmlDocument;
XMLError xmlError;
const XMLElement* xmlElement;
const XMLElement* xmlRoot;
Anm2Animation* animation = NULL;
Anm2Layer* layer = NULL;
Anm2Null* null = NULL;
Anm2Item* item = NULL;
Anm2Event* event = NULL;
Anm2Frame* frame = NULL;
Anm2Spritesheet* spritesheet = NULL;
Anm2Animation* animation = nullptr;
Anm2Layer* layer = nullptr;
Anm2Null* null = nullptr;
Anm2Item* item = nullptr;
Anm2Event* event = nullptr;
Anm2Frame* frame = nullptr;
Anm2Spritesheet* spritesheet = nullptr;
Anm2Element anm2Element = ANM2_ELEMENT_ANIMATED_ACTOR;
Anm2Attribute anm2Attribute = ANM2_ATTRIBUTE_ID;
Anm2Item addItem;
@@ -335,9 +325,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
Anm2Null addNull;
Anm2Event addEvent;
Anm2Spritesheet addSpritesheet;
s32 layerMapIndex = 0;
bool isLayerMapSet = false;
bool isFirstAnimationDone = false;
if (!self || path.empty())
return false;
if (!self || path.empty()) return false;
*self = Anm2{};
@@ -345,13 +337,10 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
if (xmlError != XML_SUCCESS)
{
std::cout << STRING_ERROR_ANM2_READ << path << xmlDocument.ErrorStr() << std::endl;
log_error(std::format(ANM2_READ_ERROR, xmlDocument.ErrorStr()));
return false;
}
// Free the loaded textures used by resources so new ones for the anm2 can be loaded
resources_textures_free(resources);
// Save old working directory and then use anm2's path as directory
// (used for loading textures from anm2 correctly which are relative)
std::filesystem::path workingPath = std::filesystem::current_path();
@@ -365,8 +354,8 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
// Iterate through elements
while (xmlElement)
{
const XMLAttribute* xmlAttribute = NULL;
const XMLElement* xmlChild = NULL;
const XMLAttribute* xmlAttribute = nullptr;
const XMLElement* xmlChild = nullptr;
s32 id = 0;
anm2Element = ANM2_ELEMENT_STRING_TO_ENUM(xmlElement->Name());
@@ -389,6 +378,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
id = map_next_id_get(self->animations);
self->animations[id] = Anm2Animation{};
animation = &self->animations[id];
if (isFirstAnimationDone)
isLayerMapSet = true;
isFirstAnimationDone = true;
break;
case ANM2_ELEMENT_ROOT_ANIMATION: // RootAnimation
item = &animation->rootAnimation;
@@ -455,6 +449,13 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
break;
case ANM2_ATTRIBUTE_LAYER_ID: // LayerId
id = atoi(xmlAttribute->Value());
if (!isLayerMapSet)
{
self->layerMap[layerMapIndex] = id;
layerMapIndex++;
}
animation->layerAnimations[id] = addItem;
item = &animation->layerAnimations[id];
break;
@@ -588,9 +589,7 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
xmlAttribute = xmlAttribute->Next();
}
// Load this anm2's spritesheet textures
if (anm2Element == ANM2_ELEMENT_SPRITESHEET)
resources_texture_init(resources, spritesheet->path , id);
if (anm2Element == ANM2_ELEMENT_SPRITESHEET) resources_texture_init(resources, spritesheet->path , id);
xmlChild = xmlElement->FirstChildElement();
@@ -600,7 +599,6 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
continue;
}
// Iterate through siblings
while (xmlElement)
{
const XMLElement* xmlNext;
@@ -613,12 +611,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
break;
}
// If no siblings, return to parent. If no parent, end parsing
xmlElement = xmlElement->Parent() ? xmlElement->Parent()->ToElement() : NULL;
xmlElement = xmlElement->Parent() ? xmlElement->Parent()->ToElement() : nullptr;
}
}
std::cout << STRING_INFO_ANM2_READ << path << std::endl;
log_info(std::format(ANM2_READ_INFO, path));
// Return to old working directory
std::filesystem::current_path(workingPath);
@@ -626,9 +623,7 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
return true;
}
// Adds a layer to the anm2
void
anm2_layer_add(Anm2* self)
void anm2_layer_add(Anm2* self)
{
s32 id = map_next_id_get(self->layers);
@@ -638,9 +633,7 @@ anm2_layer_add(Anm2* self)
animation.layerAnimations[id] = Anm2Item{};
}
// Removes a layer from the anm2
void
anm2_layer_remove(Anm2* self, s32 id)
void anm2_layer_remove(Anm2* self, s32 id)
{
// Make sure the layer exists
auto it = self->layers.find(id);
@@ -653,9 +646,7 @@ anm2_layer_remove(Anm2* self, s32 id)
animation.layerAnimations.erase(id);
}
// Adds a null to the anm2
void
anm2_null_add(Anm2* self)
void anm2_null_add(Anm2* self)
{
s32 id = map_next_id_get(self->nulls);
@@ -665,9 +656,7 @@ anm2_null_add(Anm2* self)
animation.nullAnimations[id] = Anm2Item{};
}
// Removes the specified null from the anm2
void
anm2_null_remove(Anm2* self, s32 id)
void anm2_null_remove(Anm2* self, s32 id)
{
// Make sure the null exists
auto it = self->nulls.find(id);
@@ -680,26 +669,16 @@ anm2_null_remove(Anm2* self, s32 id)
animation.nullAnimations.erase(id);
}
// Adds an animation to the anm2
s32
anm2_animation_add(Anm2* self)
s32 anm2_animation_add(Anm2* self)
{
s32 id = map_next_id_get(self->animations);
Anm2Animation animation;
// Match layers
for (auto & [layerID, layer] : self->layers)
{
animation.layerAnimations[layerID] = Anm2Item{};
}
for (auto& [layerID, layer] : self->layers)
animation.layerAnimations[layerID] = Anm2Item{};
for (auto & [nullID, null] : self->nulls)
animation.nullAnimations[nullID] = Anm2Item{};
// Match nulls
for (auto & [nullID, null] : self->nulls)
{
animation.nullAnimations[nullID] = Anm2Item{};
}
// Add a root frame
animation.rootAnimation.frames.push_back(Anm2Frame{});
self->animations[id] = animation;
@@ -707,38 +686,29 @@ anm2_animation_add(Anm2* self)
return id;
}
// Removes an animation by id from the anm2
void
anm2_animation_remove(Anm2* self, s32 id)
void anm2_animation_remove(Anm2* self, s32 id)
{
self->animations.erase(id);
}
// Sets the anm2 to default
void
anm2_new(Anm2* self)
void anm2_new(Anm2* self)
{
*self = Anm2{};
_anm2_created_on_set(self);
}
Anm2Animation*
anm2_animation_from_reference(Anm2* self, Anm2Reference* reference)
Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference)
{
auto it = self->animations.find(reference->animationID);
if (it == self->animations.end())
return NULL;
if (it == self->animations.end()) return nullptr;
return &it->second;
}
// Returns the item from a anm2 reference.
Anm2Item*
anm2_item_from_reference(Anm2* self, Anm2Reference* reference)
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference)
{
Anm2Animation* animation = anm2_animation_from_reference(self, reference);
if (!animation)
return NULL;
if (!animation) return nullptr;
switch (reference->itemType)
{
@@ -748,78 +718,108 @@ anm2_item_from_reference(Anm2* self, Anm2Reference* reference)
{
auto it = animation->layerAnimations.find(reference->itemID);
if (it == animation->layerAnimations.end())
return NULL;
return nullptr;
return &it->second;
}
case ANM2_NULL:
{
auto it = animation->nullAnimations.find(reference->itemID);
if (it == animation->nullAnimations.end())
return NULL;
return nullptr;
return &it->second;
}
case ANM2_TRIGGERS:
return &animation->triggers;
default:
return NULL;
return nullptr;
}
}
// Gets the frame from the reference's properties
Anm2Frame*
anm2_frame_from_reference(Anm2* self, Anm2Reference* reference)
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference)
{
Anm2Item* item = anm2_item_from_reference(self, reference);
if (!item)
return NULL;
if (reference->frameIndex < 0 || reference->frameIndex >= (s32)item->frames.size())
return NULL;
if (!item) return nullptr;
if (reference->frameIndex < 0 || reference->frameIndex >= (s32)item->frames.size()) return nullptr;
return &item->frames[reference->frameIndex];
}
// Creates or fetches a frame from a given time.
void
anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time)
s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time)
{
Anm2Animation* animation = anm2_animation_from_reference(self, &reference);
if (!animation)
return;
if (time < 0 || time > animation->frameNum)
return;
if (!animation) return INDEX_NONE;
if (time < 0 || time > animation->frameNum) return INDEX_NONE;
Anm2Item* item = anm2_item_from_reference(self, &reference);
if (!item)
return;
if (!item) return INDEX_NONE;
Anm2Frame* nextFrame = NULL;
s32 delayCurrent = 0;
s32 delayNext = 0;
for (s32 i = 0; i < (s32)item->frames.size(); i++)
for (auto [i, frame] : std::views::enumerate(item->frames))
{
*frame = item->frames[i];
delayNext += frame->delay;
delayNext += frame.delay;
// Use the last parsed frame as the given frame.
if (time >= delayCurrent && time < delayNext)
{
if (i + 1 < (s32)item->frames.size())
nextFrame = &item->frames[i + 1];
else
nextFrame = NULL;
break;
}
return i;
delayCurrent += frame->delay;
delayCurrent += frame.delay;
}
// Interpolate, but only if there's a frame following.
return INDEX_NONE;
}
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time)
{
Anm2Animation* animation = anm2_animation_from_reference(self, &reference);
if (!animation) return;
if (time < 0 || time > animation->frameNum) return;
Anm2Item* item = anm2_item_from_reference(self, &reference);
if (!item) return;
Anm2Frame* nextFrame = nullptr;
s32 delayCurrent = 0;
s32 delayNext = 0;
for (auto [i, iFrame] : std::views::enumerate(item->frames))
{
if (reference.itemType == ANM2_TRIGGERS)
{
if ((s32)time == iFrame.atFrame)
{
*frame = iFrame;
break;
}
}
else
{
*frame = iFrame;
delayNext += frame->delay;
if (time >= delayCurrent && time < delayNext)
{
if (i + 1 < (s32)item->frames.size())
nextFrame = &item->frames[i + 1];
else
nextFrame = nullptr;
break;
}
delayCurrent += frame->delay;
}
}
if (reference.itemType == ANM2_TRIGGERS)
return;
if (frame->isInterpolated && nextFrame)
{
f32 interpolationTime = (time - delayCurrent) / (delayNext - delayCurrent);
@@ -832,62 +832,54 @@ anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32
}
}
// Returns the animation's length, based on the present frames.
s32
anm2_animation_length_get(Anm2* self, s32 animationID)
s32 anm2_animation_length_get(Anm2Animation* self)
{
auto it = self->animations.find(animationID);
if (it == self->animations.end())
return -1;
s32 length = 0;
Anm2Animation* animation = &it->second;
s32 delayHighest = 0;
auto accumulate_max_delay = [&](const std::vector<Anm2Frame>& frames)
{
s32 delaySum = 0;
for (const auto& frame : frames)
{
delaySum += frame.delay;
length = std::max(length, delaySum);
}
};
// Go through all items and see which one has the latest frame; that's the length
// Root
for (auto & frame : animation->rootAnimation.frames)
delayHighest = std::max(delayHighest, frame.delay);
accumulate_max_delay(self->rootAnimation.frames);
// Layer
for (auto & [id, item] : animation->layerAnimations)
for (auto & frame : item.frames)
delayHighest = std::max(delayHighest, frame.delay);
for (const auto& [_, item] : self->layerAnimations)
accumulate_max_delay(item.frames);
// Null
for (auto & [id, item] : animation->nullAnimations)
for (auto & frame : item.frames)
delayHighest = std::max(delayHighest, frame.delay);
for (const auto& [_, item] : self->nullAnimations)
accumulate_max_delay(item.frames);
// Triggers
for (auto & trigger : animation->triggers.frames)
delayHighest = std::max(delayHighest, trigger.atFrame);
for (const auto& frame : self->triggers.frames)
length = std::max(length, frame.atFrame);
return delayHighest;
return length;
}
// Will try adding a frame to the anm2 given the specified reference
Anm2Frame*
anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
void anm2_animation_length_set(Anm2Animation* self)
{
self->frameNum = anm2_animation_length_get(self);
}
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
{
Anm2Animation* animation = anm2_animation_from_reference(self, reference);
Anm2Item* item = anm2_item_from_reference(self, reference);
if (!animation || !item)
return NULL;
if (!animation || !item) return nullptr;
if (item)
{
Anm2Frame frame = Anm2Frame{};
s32 index = -1;
s32 index = INDEX_NONE;
if (reference->itemType == ANM2_TRIGGERS)
{
// Don't add redundant triggers
for (auto & frameCheck : item->frames)
{
if (frameCheck.atFrame == time)
return NULL;
}
for (auto & frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr;
frame.atFrame = time;
index = item->frames.size();
@@ -896,20 +888,15 @@ anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
{
s32 frameDelayCount = 0;
// Get the latest frame delay, to see where this frame might lie
for (auto & frameCheck : item->frames)
frameDelayCount += frameCheck.delay;
// If adding the smallest frame delay would be over the length, don't bother
if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum)
return NULL;
if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) return nullptr;
// Will insert next to frame if frame exists
Anm2Frame* checkFrame = anm2_frame_from_reference(self, reference);
if (checkFrame)
{
// Will shrink frame delay to fit
if (frameDelayCount + checkFrame->delay > animation->frameNum)
frame.delay = animation->frameNum - frameDelayCount;
@@ -924,27 +911,22 @@ anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
return &item->frames[index];
}
return NULL;
return nullptr;
}
// Clears the anm2 reference, totally
void
anm2_reference_clear(Anm2Reference* self)
void anm2_reference_clear(Anm2Reference* self)
{
*self = Anm2Reference{};
}
// Clears the anm2 reference's item
void
anm2_reference_item_clear(Anm2Reference* self)
void anm2_reference_item_clear(Anm2Reference* self)
{
self->itemType = ANM2_NONE;
self->itemID = -1;
self->itemID = ID_NONE;
self->frameIndex = INDEX_NONE;
}
// Clears the anm2 reference's frame
void
anm2_reference_frame_clear(Anm2Reference* self)
void anm2_reference_frame_clear(Anm2Reference* self)
{
self->frameIndex = -1;
self->frameIndex = INDEX_NONE;
}