...Anm2Ed 2.0
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
#include "time_.h"
|
||||
#include "vector_.h"
|
||||
#include "xml_.h"
|
||||
@@ -74,4 +79,255 @@ namespace anm2ed::anm2
|
||||
if (vector::in_bounds(item->frames, frameIndex)) return &item->frames[frameIndex];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Anm2::merge(const Anm2& source, const std::filesystem::path& destinationDirectory,
|
||||
const std::filesystem::path& sourceDirectory)
|
||||
{
|
||||
using util::map::next_id_get;
|
||||
|
||||
auto remap_path = [&](const std::filesystem::path& original) -> std::filesystem::path
|
||||
{
|
||||
if (destinationDirectory.empty()) return original;
|
||||
std::error_code ec{};
|
||||
std::filesystem::path absolute{};
|
||||
bool hasAbsolute = false;
|
||||
|
||||
if (!original.empty())
|
||||
{
|
||||
if (original.is_absolute())
|
||||
{
|
||||
absolute = original;
|
||||
hasAbsolute = true;
|
||||
}
|
||||
else if (!sourceDirectory.empty())
|
||||
{
|
||||
absolute = std::filesystem::weakly_canonical(sourceDirectory / original, ec);
|
||||
if (ec)
|
||||
{
|
||||
ec.clear();
|
||||
absolute = sourceDirectory / original;
|
||||
}
|
||||
hasAbsolute = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAbsolute) return original;
|
||||
|
||||
auto relative = std::filesystem::relative(absolute, destinationDirectory, ec);
|
||||
if (!ec) return relative;
|
||||
ec.clear();
|
||||
try
|
||||
{
|
||||
return std::filesystem::relative(absolute, destinationDirectory);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error&)
|
||||
{
|
||||
return original.empty() ? absolute : original;
|
||||
}
|
||||
return original;
|
||||
};
|
||||
|
||||
auto remap_id = [](const auto& table, int value)
|
||||
{
|
||||
if (value < 0) return value;
|
||||
if (auto it = table.find(value); it != table.end()) return it->second;
|
||||
return value;
|
||||
};
|
||||
|
||||
std::unordered_map<int, int> spritesheetRemap{};
|
||||
std::unordered_map<int, int> layerRemap{};
|
||||
std::unordered_map<int, int> nullRemap{};
|
||||
std::unordered_map<int, int> eventRemap{};
|
||||
std::unordered_map<int, int> soundRemap{};
|
||||
|
||||
// Spritesheets
|
||||
for (auto& [sourceID, sprite] : source.content.spritesheets)
|
||||
{
|
||||
auto sheet = sprite;
|
||||
sheet.path = remap_path(sheet.path);
|
||||
if (!destinationDirectory.empty() && !sheet.path.empty()) sheet.reload(destinationDirectory);
|
||||
|
||||
int destinationID = next_id_get(content.spritesheets);
|
||||
content.spritesheets[destinationID] = std::move(sheet);
|
||||
spritesheetRemap[sourceID] = destinationID;
|
||||
}
|
||||
|
||||
// Sounds
|
||||
for (auto& [sourceID, soundEntry] : source.content.sounds)
|
||||
{
|
||||
auto sound = soundEntry;
|
||||
sound.path = remap_path(sound.path);
|
||||
if (!destinationDirectory.empty() && !sound.path.empty()) sound.reload(destinationDirectory);
|
||||
|
||||
int destinationID = -1;
|
||||
for (auto& [id, existing] : content.sounds)
|
||||
if (existing.path == sound.path)
|
||||
{
|
||||
destinationID = id;
|
||||
existing = sound;
|
||||
break;
|
||||
}
|
||||
|
||||
if (destinationID == -1)
|
||||
{
|
||||
destinationID = next_id_get(content.sounds);
|
||||
content.sounds[destinationID] = sound;
|
||||
}
|
||||
soundRemap[sourceID] = destinationID;
|
||||
}
|
||||
|
||||
auto find_by_name = [](auto& container, const std::string& name) -> int
|
||||
{
|
||||
for (auto& [id, value] : container)
|
||||
if (value.name == name) return id;
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Layers
|
||||
for (auto& [sourceID, sourceLayer] : source.content.layers)
|
||||
{
|
||||
auto layer = sourceLayer;
|
||||
layer.spritesheetID = remap_id(spritesheetRemap, layer.spritesheetID);
|
||||
|
||||
int destinationID = find_by_name(content.layers, layer.name);
|
||||
if (destinationID != -1)
|
||||
content.layers[destinationID] = layer;
|
||||
else
|
||||
{
|
||||
destinationID = next_id_get(content.layers);
|
||||
content.layers[destinationID] = layer;
|
||||
}
|
||||
layerRemap[sourceID] = destinationID;
|
||||
}
|
||||
|
||||
// Nulls
|
||||
for (auto& [sourceID, sourceNull] : source.content.nulls)
|
||||
{
|
||||
auto null = sourceNull;
|
||||
int destinationID = find_by_name(content.nulls, null.name);
|
||||
if (destinationID != -1)
|
||||
content.nulls[destinationID] = null;
|
||||
else
|
||||
{
|
||||
destinationID = next_id_get(content.nulls);
|
||||
content.nulls[destinationID] = null;
|
||||
}
|
||||
nullRemap[sourceID] = destinationID;
|
||||
}
|
||||
|
||||
// Events
|
||||
for (auto& [sourceID, sourceEvent] : source.content.events)
|
||||
{
|
||||
auto event = sourceEvent;
|
||||
event.soundID = remap_id(soundRemap, event.soundID);
|
||||
|
||||
int destinationID = find_by_name(content.events, event.name);
|
||||
if (destinationID != -1)
|
||||
content.events[destinationID] = event;
|
||||
else
|
||||
{
|
||||
destinationID = next_id_get(content.events);
|
||||
content.events[destinationID] = event;
|
||||
}
|
||||
eventRemap[sourceID] = destinationID;
|
||||
}
|
||||
|
||||
auto remap_item = [&](Item& item)
|
||||
{
|
||||
for (auto& frame : item.frames)
|
||||
{
|
||||
frame.soundID = remap_id(soundRemap, frame.soundID);
|
||||
frame.eventID = remap_id(eventRemap, frame.eventID);
|
||||
}
|
||||
};
|
||||
|
||||
auto build_animation = [&](const Animation& incoming) -> Animation
|
||||
{
|
||||
Animation remapped{};
|
||||
remapped.name = incoming.name;
|
||||
remapped.frameNum = incoming.frameNum;
|
||||
remapped.isLoop = incoming.isLoop;
|
||||
remapped.rootAnimation = incoming.rootAnimation;
|
||||
remapped.triggers = incoming.triggers;
|
||||
remap_item(remapped.rootAnimation);
|
||||
remap_item(remapped.triggers);
|
||||
|
||||
for (auto layerID : incoming.layerOrder)
|
||||
{
|
||||
auto mapped = remap_id(layerRemap, layerID);
|
||||
if (mapped >= 0 &&
|
||||
std::find(remapped.layerOrder.begin(), remapped.layerOrder.end(), mapped) == remapped.layerOrder.end())
|
||||
remapped.layerOrder.push_back(mapped);
|
||||
}
|
||||
|
||||
for (auto& [layerID, item] : incoming.layerAnimations)
|
||||
{
|
||||
auto mapped = remap_id(layerRemap, layerID);
|
||||
if (mapped < 0) continue;
|
||||
auto copy = item;
|
||||
remap_item(copy);
|
||||
remapped.layerAnimations[mapped] = std::move(copy);
|
||||
if (std::find(remapped.layerOrder.begin(), remapped.layerOrder.end(), mapped) == remapped.layerOrder.end())
|
||||
remapped.layerOrder.push_back(mapped);
|
||||
}
|
||||
|
||||
for (auto& [nullID, item] : incoming.nullAnimations)
|
||||
{
|
||||
auto mapped = remap_id(nullRemap, nullID);
|
||||
if (mapped < 0) continue;
|
||||
auto copy = item;
|
||||
remap_item(copy);
|
||||
remapped.nullAnimations[mapped] = std::move(copy);
|
||||
}
|
||||
|
||||
remap_item(remapped.triggers);
|
||||
return remapped;
|
||||
};
|
||||
|
||||
auto find_animation = [&](const std::string& name) -> Animation*
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (animation.name == name) return &animation;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto merge_item_map = [&](auto& destination, const auto& incoming)
|
||||
{
|
||||
for (auto& [id, item] : incoming)
|
||||
{
|
||||
if (!item.frames.empty())
|
||||
destination[id] = item;
|
||||
else if (!destination.contains(id))
|
||||
destination[id] = item;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& animation : source.animations.items)
|
||||
{
|
||||
auto processed = build_animation(animation);
|
||||
if (auto destination = find_animation(processed.name))
|
||||
{
|
||||
destination->frameNum = std::max(destination->frameNum, processed.frameNum);
|
||||
destination->isLoop = processed.isLoop;
|
||||
if (!processed.rootAnimation.frames.empty()) destination->rootAnimation = processed.rootAnimation;
|
||||
if (!processed.triggers.frames.empty()) destination->triggers = processed.triggers;
|
||||
|
||||
merge_item_map(destination->layerAnimations, processed.layerAnimations);
|
||||
merge_item_map(destination->nullAnimations, processed.nullAnimations);
|
||||
|
||||
for (auto id : processed.layerOrder)
|
||||
if (std::find(destination->layerOrder.begin(), destination->layerOrder.end(), id) ==
|
||||
destination->layerOrder.end())
|
||||
destination->layerOrder.push_back(id);
|
||||
|
||||
destination->fit_length();
|
||||
}
|
||||
else
|
||||
animations.items.push_back(std::move(processed));
|
||||
}
|
||||
|
||||
if (animations.defaultAnimation.empty() && !source.animations.defaultAnimation.empty()) {
|
||||
animations.defaultAnimation = source.animations.defaultAnimation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user