Refactor + render animation tweaks + updated frame properties + bug fixes

This commit is contained in:
2025-12-17 23:02:00 -05:00
parent b4b4fe3714
commit 119bbc4081
63 changed files with 1964 additions and 1701 deletions
+5 -4
View File
@@ -4,10 +4,11 @@
#include <filesystem>
#include <unordered_map>
#include "filesystem_.h"
#include "file_.h"
#include "map_.h"
#include "time_.h"
#include "vector_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace tinyxml2;
@@ -23,7 +24,7 @@ namespace anm2ed::anm2
{
XMLDocument document;
filesystem::File file(path, "rb");
File file(path, "rb");
if (!file)
{
if (errorString) *errorString = localize.get(ERROR_FILE_NOT_FOUND);
@@ -38,7 +39,7 @@ namespace anm2ed::anm2
return;
}
filesystem::WorkingDirectory workingDirectory(path, true);
WorkingDirectory workingDirectory(path, WorkingDirectory::FILE);
const XMLElement* element = document.RootElement();
@@ -65,7 +66,7 @@ namespace anm2ed::anm2
XMLDocument document;
document.InsertFirstChild(to_element(document));
filesystem::File file(path, "wb");
File file(path, "wb");
if (!file)
{
if (errorString) *errorString = localize.get(ERROR_FILE_NOT_FOUND);
+4 -3
View File
@@ -1,7 +1,8 @@
#include "anm2.h"
#include "filesystem_.h"
#include "map_.h"
#include "path_.h"
#include "working_directory.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
@@ -22,7 +23,7 @@ namespace anm2ed::anm2
labels.emplace_back(localize.get(BASIC_NONE));
for (auto& [id, sound] : content.sounds)
{
auto pathString = filesystem::path_to_utf8(sound.path);
auto pathString = path::to_utf8(sound.path);
labels.emplace_back(std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathString)));
}
return labels;
@@ -57,7 +58,7 @@ namespace anm2ed::anm2
return false;
}
filesystem::WorkingDirectory workingDirectory(directory);
WorkingDirectory workingDirectory(directory);
for (auto element = document.FirstChildElement("Sound"); element; element = element->NextSiblingElement("Sound"))
{
+4 -3
View File
@@ -2,8 +2,9 @@
#include <ranges>
#include "filesystem_.h"
#include "map_.h"
#include "path_.h"
#include "working_directory.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
@@ -41,7 +42,7 @@ namespace anm2ed::anm2
labels.emplace_back(localize.get(BASIC_NONE));
for (auto& [id, spritesheet] : content.spritesheets)
{
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
labels.emplace_back(std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathString)));
}
return labels;
@@ -62,7 +63,7 @@ namespace anm2ed::anm2
return false;
}
filesystem::WorkingDirectory workingDirectory(directory);
WorkingDirectory workingDirectory(directory);
for (auto element = document.FirstChildElement("Spritesheet"); element;
element = element->NextSiblingElement("Spritesheet"))
-2
View File
@@ -117,7 +117,5 @@ 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); }
}
+25 -4
View File
@@ -35,6 +35,8 @@ namespace anm2ed::anm2
MEMBERS
#undef X
#undef MEMBERS
Frame() = default;
Frame(tinyxml2::XMLElement*, Type);
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type);
@@ -46,10 +48,29 @@ namespace anm2ed::anm2
struct FrameChange
{
#define X(name, type, ...) std::optional<type> name{};
MEMBERS
#undef X
std::optional<bool> isVisible{};
std::optional<bool> isInterpolated{};
std::optional<float> rotation{};
std::optional<int> duration{};
std::optional<int> atFrame{};
std::optional<int> eventID{};
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{};
};
#undef MEMBERS
}
+55 -59
View File
@@ -132,6 +132,39 @@ namespace anm2ed::anm2
auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size();
end = glm::clamp(end, start, (int)frames.size());
const auto clamp_identity = [](auto value) { return value; };
const auto clamp01 = [](auto value) { return glm::clamp(value, 0.0f, 1.0f); };
const auto clamp_duration = [](int value) { return std::max(FRAME_DURATION_MIN, value); };
auto apply_scalar_with_clamp = [&](auto& target, const auto& optionalValue, auto clampFunc)
{
if (!optionalValue) return;
auto value = *optionalValue;
switch (type)
{
case ADJUST:
target = clampFunc(value);
break;
case ADD:
target = clampFunc(target + value);
break;
case SUBTRACT:
target = clampFunc(target - value);
break;
case MULTIPLY:
target = clampFunc(target * value);
break;
case DIVIDE:
if (value == decltype(value){}) return;
target = clampFunc(target / value);
break;
}
};
auto apply_scalar = [&](auto& target, const auto& optionalValue)
{ apply_scalar_with_clamp(target, optionalValue, clamp_identity); };
for (int i = useStart; i < end; i++)
{
Frame& frame = frames[i];
@@ -139,69 +172,32 @@ namespace anm2ed::anm2
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.duration) frame.duration = std::max(FRAME_DURATION_MIN, *change.duration);
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;
apply_scalar(frame.rotation, change.rotation);
apply_scalar_with_clamp(frame.duration, change.duration, clamp_duration);
case ADD:
if (change.rotation) frame.rotation += *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration + *change.duration);
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;
apply_scalar(frame.crop.x, change.cropX);
apply_scalar(frame.crop.y, change.cropY);
case SUBTRACT:
if (change.rotation) frame.rotation -= *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration - *change.duration);
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;
apply_scalar(frame.pivot.x, change.pivotX);
apply_scalar(frame.pivot.y, change.pivotY);
case MULTIPLY:
if (change.rotation) frame.rotation *= *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration * *change.duration);
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;
apply_scalar(frame.position.x, change.positionX);
apply_scalar(frame.position.y, change.positionY);
case DIVIDE:
if (change.rotation && *change.rotation != 0.0f) frame.rotation /= *change.rotation;
if (change.duration && *change.duration != 0)
frame.duration = std::max(FRAME_DURATION_MIN, frame.duration / *change.duration);
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;
}
apply_scalar(frame.size.x, change.sizeX);
apply_scalar(frame.size.y, change.sizeY);
apply_scalar(frame.scale.x, change.scaleX);
apply_scalar(frame.scale.y, change.scaleY);
apply_scalar_with_clamp(frame.colorOffset.x, change.colorOffsetR, clamp01);
apply_scalar_with_clamp(frame.colorOffset.y, change.colorOffsetG, clamp01);
apply_scalar_with_clamp(frame.colorOffset.z, change.colorOffsetB, clamp01);
apply_scalar_with_clamp(frame.tint.x, change.tintR, clamp01);
apply_scalar_with_clamp(frame.tint.y, change.tintG, clamp01);
apply_scalar_with_clamp(frame.tint.z, change.tintB, clamp01);
apply_scalar_with_clamp(frame.tint.w, change.tintA, clamp01);
}
}
+7 -20
View File
@@ -1,6 +1,7 @@
#include "sound.h"
#include "filesystem_.h"
#include "path_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace anm2ed::resource;
@@ -21,23 +22,11 @@ namespace anm2ed::anm2
return *this;
}
namespace
{
std::filesystem::path make_relative_or_keep(const std::filesystem::path& input)
{
if (input.empty()) return input;
std::error_code ec{};
auto relative = std::filesystem::relative(input, ec);
if (!ec) return relative;
return input;
}
}
Sound::Sound(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
this->path = filesystem::path_lower_case_backslash_handle(this->path);
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
this->path = path::lower_case_backslash_handle(this->path);
audio = Audio(this->path);
}
@@ -46,7 +35,7 @@ namespace anm2ed::anm2
if (!element) return;
element->QueryIntAttribute("Id", &id);
xml::query_path_attribute(element, "Path", &path);
path = filesystem::path_lower_case_backslash_handle(path);
path = path::lower_case_backslash_handle(path);
audio = Audio(path);
}
@@ -54,7 +43,7 @@ namespace anm2ed::anm2
{
auto element = document.NewElement("Sound");
element->SetAttribute("Id", id);
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
element->SetAttribute("Path", pathString.c_str());
return element;
}
@@ -72,8 +61,6 @@ namespace anm2ed::anm2
}
void Sound::reload(const std::filesystem::path& directory) { *this = Sound(directory, this->path); }
bool Sound::is_valid() { return audio.is_valid(); }
void Sound::play() { audio.play(); }
}
+9 -20
View File
@@ -1,6 +1,7 @@
#include "spritesheet.h"
#include "filesystem_.h"
#include "path_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace anm2ed::resource;
@@ -17,27 +18,15 @@ namespace anm2ed::anm2
// 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
path = filesystem::path_lower_case_backslash_handle(path);
path = path::lower_case_backslash_handle(path);
texture = Texture(path);
}
namespace
{
std::filesystem::path make_relative_or_keep(const std::filesystem::path& input)
{
if (input.empty()) return input;
std::error_code ec{};
auto relative = std::filesystem::relative(input, ec);
if (!ec) return relative;
return input;
}
}
Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
this->path = filesystem::path_lower_case_backslash_handle(this->path);
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
this->path = path::lower_case_backslash_handle(this->path);
texture = Texture(this->path);
}
@@ -45,7 +34,7 @@ namespace anm2ed::anm2
{
auto element = document.NewElement("Spritesheet");
element->SetAttribute("Id", id);
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
element->SetAttribute("Path", pathString.c_str());
return element;
}
@@ -64,8 +53,8 @@ namespace anm2ed::anm2
bool Spritesheet::save(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
return texture.write_png(this->path);
}