Render animation fixes, vs code tasks
This commit is contained in:
Vendored
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
"name": "Debug",
|
"name": "Debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/build/anm2ed",
|
"program": "${workspaceFolder}/out/build/linux-debug/anm2ed",
|
||||||
"args": [],
|
"args": [],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"name": "Release",
|
"name": "Release",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/build-release/anm2ed",
|
"program": "${workspaceFolder}/out/build/linux-release/anm2ed",
|
||||||
"args": [],
|
"args": [],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
|
|||||||
Vendored
+18
-2
@@ -1,10 +1,18 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "run-debug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake -S . -B out/build/linux-debug -DCMAKE_BUILD_TYPE=Debug && cmake --build out/build/linux-debug --parallel 8 --target anm2ed && ./out/build/linux-debug/anm2ed",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$gcc"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "build",
|
"label": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cmake --build build --target anm2ed",
|
"command": "cmake -S . -B out/build/linux-debug -DCMAKE_BUILD_TYPE=Debug && cmake --build out/build/linux-debug --parallel 8 --target anm2ed",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -13,10 +21,18 @@
|
|||||||
"$gcc"
|
"$gcc"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "run-release",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake -S . -B out/build/linux-release -DCMAKE_BUILD_TYPE=Release && cmake --build out/build/linux-release --parallel 8 --target anm2ed && ./out/build/linux-release/anm2ed",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$gcc"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "build-release",
|
"label": "build-release",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release && cmake --build build-release --target anm2ed",
|
"command": "cmake -S . -B out/build/linux-release -DCMAKE_BUILD_TYPE=Release && cmake --build out/build/linux-release --parallel 8 --target anm2ed",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
"$gcc"
|
"$gcc"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations.
|
A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations.
|
||||||
|
|
||||||
|
### Clarification
|
||||||
|
This application was partly vibe-coded by an LLM (let's say 70% me/30% AI sloppa). Sorry not sorry, I'm just lazy.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Extended version of the original proprietary Nicalis animation editor
|
- Extended version of the original proprietary Nicalis animation editor
|
||||||
- Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc. You might be familiar with it from [Repentogon](https://repentogon.com/).
|
- Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc. You might be familiar with it from [Repentogon](https://repentogon.com/).
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
/home/anon/sda/Personal/Repos/anm2ed/build/compile_commands.json
|
/home/anon/sda/Personal/Repos/anm2ed/out/build/linux-debug/compile_commands.json
|
||||||
+52
-11
@@ -18,6 +18,13 @@ using namespace glm;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
int remap_id(const std::unordered_map<int, int>& table, int value)
|
||||||
|
{
|
||||||
|
if (value < 0) return value;
|
||||||
|
if (auto it = table.find(value); it != table.end()) return it->second;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
void region_frames_sync(anm2ed::anm2::Anm2& anm2, bool clearInvalid)
|
void region_frames_sync(anm2ed::anm2::Anm2& anm2, bool clearInvalid)
|
||||||
{
|
{
|
||||||
for (auto& animation : anm2.animations.items)
|
for (auto& animation : anm2.animations.items)
|
||||||
@@ -84,12 +91,13 @@ namespace anm2ed::anm2
|
|||||||
|
|
||||||
XMLElement* Anm2::to_element(XMLDocument& document, Flags flags)
|
XMLElement* Anm2::to_element(XMLDocument& document, Flags flags)
|
||||||
{
|
{
|
||||||
region_frames_sync(*this, true);
|
auto normalized = normalized_for_serialize();
|
||||||
|
region_frames_sync(normalized, true);
|
||||||
auto element = document.NewElement("AnimatedActor");
|
auto element = document.NewElement("AnimatedActor");
|
||||||
|
|
||||||
info.serialize(document, element);
|
normalized.info.serialize(document, element);
|
||||||
content.serialize(document, element, flags);
|
normalized.content.serialize(document, element, flags);
|
||||||
animations.serialize(document, element, flags);
|
normalized.animations.serialize(document, element, flags);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@@ -123,6 +131,46 @@ namespace anm2ed::anm2
|
|||||||
|
|
||||||
uint64_t Anm2::hash() { return std::hash<std::string>{}(to_string()); }
|
uint64_t Anm2::hash() { return std::hash<std::string>{}(to_string()); }
|
||||||
|
|
||||||
|
Anm2 Anm2::normalized_for_serialize() const
|
||||||
|
{
|
||||||
|
auto normalized = *this;
|
||||||
|
std::unordered_map<int, int> layerRemap{};
|
||||||
|
|
||||||
|
int normalizedID = 0;
|
||||||
|
for (auto& [layerID, layer] : content.layers)
|
||||||
|
{
|
||||||
|
layerRemap[layerID] = normalizedID;
|
||||||
|
++normalizedID;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.content.layers.clear();
|
||||||
|
for (auto& [layerID, layer] : content.layers)
|
||||||
|
normalized.content.layers[remap_id(layerRemap, layerID)] = layer;
|
||||||
|
|
||||||
|
for (auto& animation : normalized.animations.items)
|
||||||
|
{
|
||||||
|
std::unordered_map<int, Item> layerAnimations{};
|
||||||
|
std::vector<int> layerOrder{};
|
||||||
|
|
||||||
|
for (auto layerID : animation.layerOrder)
|
||||||
|
{
|
||||||
|
auto mappedID = remap_id(layerRemap, layerID);
|
||||||
|
if (mappedID >= 0) layerOrder.push_back(mappedID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [layerID, item] : animation.layerAnimations)
|
||||||
|
{
|
||||||
|
auto mappedID = remap_id(layerRemap, layerID);
|
||||||
|
if (mappedID >= 0) layerAnimations[mappedID] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.layerAnimations = std::move(layerAnimations);
|
||||||
|
animation.layerOrder = std::move(layerOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
Frame* Anm2::frame_get(int animationIndex, Type itemType, int frameIndex, int itemID)
|
Frame* Anm2::frame_get(int animationIndex, Type itemType, int frameIndex, int itemID)
|
||||||
{
|
{
|
||||||
if (auto item = item_get(animationIndex, itemType, itemID); item)
|
if (auto item = item_get(animationIndex, itemType, itemID); item)
|
||||||
@@ -177,13 +225,6 @@ namespace anm2ed::anm2
|
|||||||
return 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> spritesheetRemap{};
|
||||||
std::unordered_map<int, int> layerRemap{};
|
std::unordered_map<int, int> layerRemap{};
|
||||||
std::unordered_map<int, int> nullRemap{};
|
std::unordered_map<int, int> nullRemap{};
|
||||||
|
|||||||
+2
-1
@@ -36,6 +36,7 @@ namespace anm2ed::anm2
|
|||||||
std::string to_string(Flags = 0);
|
std::string to_string(Flags = 0);
|
||||||
Anm2(const std::filesystem::path&, std::string* = nullptr);
|
Anm2(const std::filesystem::path&, std::string* = nullptr);
|
||||||
uint64_t hash();
|
uint64_t hash();
|
||||||
|
Anm2 normalized_for_serialize() const;
|
||||||
|
|
||||||
Spritesheet* spritesheet_get(int);
|
Spritesheet* spritesheet_get(int);
|
||||||
bool spritesheet_add(const std::filesystem::path&, const std::filesystem::path&, int&);
|
bool spritesheet_add(const std::filesystem::path&, const std::filesystem::path&, int&);
|
||||||
@@ -80,7 +81,7 @@ namespace anm2ed::anm2
|
|||||||
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
|
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
|
||||||
|
|
||||||
Item* item_get(int, Type, int = -1);
|
Item* item_get(int, Type, int = -1);
|
||||||
Reference layer_animation_add(Reference = {}, std::string = {}, int = 0,
|
Reference layer_animation_add(Reference = {}, int = -1, std::string = {}, int = 0,
|
||||||
types::destination::Type = types::destination::ALL);
|
types::destination::Type = types::destination::ALL);
|
||||||
Reference null_animation_add(Reference = {}, std::string = {}, bool = false,
|
Reference null_animation_add(Reference = {}, std::string = {}, bool = false,
|
||||||
types::destination::Type = types::destination::ALL);
|
types::destination::Type = types::destination::ALL);
|
||||||
|
|||||||
+19
-5
@@ -30,7 +30,7 @@ namespace anm2ed::anm2
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference Anm2::layer_animation_add(Reference reference, std::string name, int spritesheetID,
|
Reference Anm2::layer_animation_add(Reference reference, int insertBeforeID, std::string name, int spritesheetID,
|
||||||
destination::Type destination)
|
destination::Type destination)
|
||||||
{
|
{
|
||||||
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID;
|
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID;
|
||||||
@@ -39,21 +39,35 @@ namespace anm2ed::anm2
|
|||||||
layer.name = !name.empty() ? name : layer.name;
|
layer.name = !name.empty() ? name : layer.name;
|
||||||
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0;
|
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0;
|
||||||
|
|
||||||
auto add = [&](Animation* animation, int id)
|
auto add = [&](Animation* animation, int id, bool insertBeforeReference)
|
||||||
{
|
{
|
||||||
animation->layerAnimations[id] = Item();
|
animation->layerAnimations[id] = Item();
|
||||||
|
|
||||||
|
if (insertBeforeReference && insertBeforeID != -1)
|
||||||
|
{
|
||||||
|
auto it = std::find(animation->layerOrder.begin(), animation->layerOrder.end(), insertBeforeID);
|
||||||
|
if (it != animation->layerOrder.end())
|
||||||
|
{
|
||||||
|
animation->layerOrder.insert(it, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
animation->layerOrder.push_back(id);
|
animation->layerOrder.push_back(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (destination == destination::ALL)
|
if (destination == destination::ALL)
|
||||||
{
|
{
|
||||||
for (auto& animation : animations.items)
|
for (size_t index = 0; index < animations.items.size(); ++index)
|
||||||
if (!animation.layerAnimations.contains(id)) add(&animation, id);
|
{
|
||||||
|
auto& animation = animations.items[index];
|
||||||
|
if (!animation.layerAnimations.contains(id)) add(&animation, id, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (destination == destination::THIS)
|
else if (destination == destination::THIS)
|
||||||
{
|
{
|
||||||
if (auto animation = animation_get(reference.animationIndex))
|
if (auto animation = animation_get(reference.animationIndex))
|
||||||
if (!animation->layerAnimations.contains(id)) add(animation, id);
|
if (!animation->layerAnimations.contains(id)) add(animation, id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {reference.animationIndex, LAYER, id};
|
return {reference.animationIndex, LAYER, id};
|
||||||
|
|||||||
+3
-2
@@ -104,9 +104,10 @@ namespace anm2ed::anm2
|
|||||||
{
|
{
|
||||||
bool noRegions = has_flag(flags, NO_REGIONS);
|
bool noRegions = has_flag(flags, NO_REGIONS);
|
||||||
bool frameNoRegionValues = has_flag(flags, FRAME_NO_REGION_VALUES);
|
bool frameNoRegionValues = has_flag(flags, FRAME_NO_REGION_VALUES);
|
||||||
bool writeRegionValues = !frameNoRegionValues || noRegions;
|
bool hasValidRegion = !noRegions && regionID != -1;
|
||||||
|
bool writeRegionValues = !frameNoRegionValues || !hasValidRegion;
|
||||||
|
|
||||||
if (!noRegions && regionID != -1) element->SetAttribute("RegionId", regionID);
|
if (hasValidRegion) element->SetAttribute("RegionId", regionID);
|
||||||
element->SetAttribute("XPosition", position.x);
|
element->SetAttribute("XPosition", position.x);
|
||||||
element->SetAttribute("YPosition", position.y);
|
element->SetAttribute("YPosition", position.y);
|
||||||
if (writeRegionValues)
|
if (writeRegionValues)
|
||||||
|
|||||||
@@ -82,6 +82,27 @@ namespace anm2ed::imgui
|
|||||||
directory.clear();
|
directory.clear();
|
||||||
frames.clear();
|
frames.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pixels_unpremultiply_alpha(std::vector<uint8_t>& pixels)
|
||||||
|
{
|
||||||
|
for (size_t index = 0; index + 3 < pixels.size(); index += 4)
|
||||||
|
{
|
||||||
|
auto alpha = pixels[index + 3];
|
||||||
|
if (alpha == 0)
|
||||||
|
{
|
||||||
|
pixels[index + 0] = 0;
|
||||||
|
pixels[index + 1] = 0;
|
||||||
|
pixels[index + 2] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (alpha == 255) continue;
|
||||||
|
|
||||||
|
float alphaUnit = (float)alpha / 255.0f;
|
||||||
|
pixels[index + 0] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 0] / alphaUnit), 0.0f, 255.0f);
|
||||||
|
pixels[index + 1] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 1] / alphaUnit), 0.0f, 255.0f);
|
||||||
|
pixels[index + 2] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 2] / alphaUnit), 0.0f, 255.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationPreview::AnimationPreview() : Canvas(vec2()) {}
|
AnimationPreview::AnimationPreview() : Canvas(vec2()) {}
|
||||||
@@ -232,6 +253,7 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
bind();
|
bind();
|
||||||
auto pixels = pixels_get();
|
auto pixels = pixels_get();
|
||||||
|
if (settings.renderIsRawAnimation) pixels_unpremultiply_alpha(pixels);
|
||||||
auto frameIndex = (int)renderTempFrames.size();
|
auto frameIndex = (int)renderTempFrames.size();
|
||||||
auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex);
|
auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex);
|
||||||
if (Texture::write_pixels_png(framePath, size, pixels.data()))
|
if (Texture::write_pixels_png(framePath, size, pixels.data()))
|
||||||
@@ -240,7 +262,8 @@ namespace anm2ed::imgui
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
|
toasts.push(
|
||||||
|
std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
|
||||||
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH),
|
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH),
|
||||||
std::make_format_args(pathString)));
|
std::make_format_args(pathString)));
|
||||||
if (type != render::PNGS) render_temp_cleanup(renderTempDirectory, renderTempFrames);
|
if (type != render::PNGS) render_temp_cleanup(renderTempDirectory, renderTempFrames);
|
||||||
@@ -513,7 +536,8 @@ namespace anm2ed::imgui
|
|||||||
if (renderTempDirectory.empty())
|
if (renderTempDirectory.empty())
|
||||||
{
|
{
|
||||||
auto pathString = path::to_utf8(settings.renderPath);
|
auto pathString = path::to_utf8(settings.renderPath);
|
||||||
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
|
toasts.push(
|
||||||
|
std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
|
||||||
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH),
|
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH),
|
||||||
std::make_format_args(pathString)));
|
std::make_format_args(pathString)));
|
||||||
manager.isRecording = false;
|
manager.isRecording = false;
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
#include "document.h"
|
#include "document.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "map_.h"
|
#include "map_.h"
|
||||||
#include "math_.h"
|
#include "math_.h"
|
||||||
#include "path_.h"
|
|
||||||
#include "strings.h"
|
#include "strings.h"
|
||||||
#include "toast.h"
|
#include "toast.h"
|
||||||
#include "vector_.h"
|
#include "vector_.h"
|
||||||
|
|||||||
@@ -1780,11 +1780,12 @@ namespace anm2ed::imgui
|
|||||||
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize))
|
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize))
|
||||||
{
|
{
|
||||||
anm2::Reference addReference{};
|
anm2::Reference addReference{};
|
||||||
|
int insertBeforeID = reference.itemType == anm2::LAYER ? reference.itemID : -1;
|
||||||
|
|
||||||
document.snapshot(localize.get(EDIT_ADD_ITEM));
|
document.snapshot(localize.get(EDIT_ADD_ITEM));
|
||||||
if (type == anm2::LAYER)
|
if (type == anm2::LAYER)
|
||||||
addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName,
|
addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, insertBeforeID,
|
||||||
addItemSpritesheetID, (destination::Type)destination);
|
addItemName, addItemSpritesheetID, (destination::Type)destination);
|
||||||
else if (type == anm2::NULL_)
|
else if (type == anm2::NULL_)
|
||||||
addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName,
|
addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName,
|
||||||
addItemIsShowRect, (destination::Type)destination);
|
addItemIsShowRect, (destination::Type)destination);
|
||||||
|
|||||||
+2
-1
@@ -133,7 +133,8 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
case render::GIF:
|
case render::GIF:
|
||||||
command +=
|
command +=
|
||||||
" -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full[p];[s1][p]paletteuse=dither=floyd_steinberg\""
|
" -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full:reserve_transparent=1[p];"
|
||||||
|
"[s1][p]paletteuse=dither=floyd_steinberg:alpha_threshold=128\""
|
||||||
" -loop 0";
|
" -loop 0";
|
||||||
command += std::format(" \"{}\"", pathString);
|
command += std::format(" \"{}\"", pathString);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user