The Omega Update(TM) Part 4 (Massive Refactor, Change All Frame Properties)

This commit is contained in:
2025-08-14 00:25:35 -04:00
parent a48c72357a
commit ea3498692a
25 changed files with 3364 additions and 3127 deletions

View File

@@ -5,14 +5,14 @@
A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations.
## Features
- Most things present in the original IsaacAnimationEditor.exe, except some stuff like drawing (why not use an art program!)
- Extended version of the original proprietary Nicalis animation editor
- Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc.
- Keybinds/keyboard control for common actions(see [src/input.h](https://github.com/ShweetsStuff/anm2ed/blob/master/src/input.h))
### Known Issues
- Root Transform doesn't work for scale/rotation (matrix math is hard; if you can help me fix it I will give you $100.)
- Some .anm2 files used in Rebirth might not render correctly due to the ordering of layers; just drag and drop to fix the ordering and save, they will work fine afterwards.
- New features
- Can output .webm or *.png sequence
- Cutting, copying and pasting
- Robust snapshot (undo/redo) system
- Additional hotkeys/shortcuts
- Settings that will preserve on exit
## Dependencies
Download these from your package manager:
@@ -20,6 +20,8 @@ Download these from your package manager:
- SDL3
- GLEW
Note, to render animations, you'll need to download [FFmpeg](https://ffmpeg.org/download.html) and specify its install path in the program.
## Build
After cloning and enter the repository's directory, make sure to initialize the submodules:

View File

@@ -10,20 +10,20 @@
#include <algorithm>
#include <chrono>
#include <cstring>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <format>
#include <functional>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <print>
#include <optional>
#include <print>
#include <ranges>
#include <string>
#include <vector>
#include <unordered_set>
#include <vector>
typedef uint8_t u8;
typedef uint16_t u16;
@@ -123,11 +123,12 @@ static inline bool string_to_bool(const std::string& string)
return lower == "true";
}
static inline void working_directory_from_file_set(const std::string& path)
static inline std::string working_directory_from_file_set(const std::string& path)
{
std::filesystem::path filePath = path;
std::filesystem::path parentPath = filePath.parent_path();
std::filesystem::current_path(parentPath);
return parentPath;
};
static inline std::string path_extension_change(const std::string& path, const std::string& extension)
@@ -178,6 +179,13 @@ static inline s32 string_to_enum(const std::string& string, const char* const* a
return -1;
};
template <typename T>
T& dummy_value()
{
static T value{};
return value;
}
template<typename T>
static inline s32 map_next_id_get(const std::map<s32, T>& map)
{
@@ -302,6 +310,7 @@ enum DataType
TYPE_STRING,
TYPE_IVEC2,
TYPE_VEC2,
TYPE_VEC3,
TYPE_VEC4
};

View File

@@ -1,5 +1,3 @@
// Anm2 file format; serializing/deserializing
#include "anm2.h"
using namespace tinyxml2;
@@ -781,6 +779,7 @@ Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference)
}
}
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference)
{
Anm2Item* item = anm2_item_from_reference(self, reference);
@@ -903,7 +902,7 @@ s32 anm2_animation_length_get(Anm2Animation* self)
accumulate_max_delay(item.frames);
for (const auto& frame : self->triggers.frames)
length = std::max(length, frame.atFrame);
length = std::max(length, frame.atFrame + 1);
return length;
}
@@ -913,7 +912,7 @@ void anm2_animation_length_set(Anm2Animation* self)
self->frameNum = anm2_animation_length_get(self);
}
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time)
{
Anm2Animation* animation = anm2_animation_from_reference(self, reference);
Anm2Item* item = anm2_item_from_reference(self, reference);
@@ -923,46 +922,43 @@ Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time)
if (item)
{
Anm2Frame frame = Anm2Frame{};
s32 index = INDEX_NONE;
Anm2Frame frameAdd = frame ? *frame : Anm2Frame{};
s32 index = reference->frameIndex + 1;
if (reference->itemType == ANM2_TRIGGERS)
{
for (auto& frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr;
s32 index = time;
frame.atFrame = time;
for (auto& frameCheck : item->frames)
{
if (frameCheck.atFrame == time)
{
index++;
break;
}
}
frameAdd.atFrame = index;
index = item->frames.size();
return &item->frames.emplace_back(frameAdd);
}
else
{
s32 frameDelayCount = 0;
for (auto& frameCheck : item->frames)
frameDelayCount += frameCheck.delay;
if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) return nullptr;
Anm2Frame* checkFrame = anm2_frame_from_reference(self, reference);
if (checkFrame)
{
if (frameDelayCount + checkFrame->delay > animation->frameNum)
frame.delay = animation->frameNum - frameDelayCount;
index = reference->frameIndex + 1;
}
else
index = (s32)item->frames.size();
item->frames.insert(item->frames.begin() + index, frameAdd);
return &item->frames[index];
}
item->frames.insert(item->frames.begin() + index, frame);
return &item->frames[index];
}
return nullptr;
}
void anm2_frame_erase(Anm2* self, Anm2Reference* reference)
{
Anm2Item* item = anm2_item_from_reference(self, reference);
if (!item) return;
item->frames.erase(item->frames.begin() + reference->frameIndex);
}
void anm2_reference_clear(Anm2Reference* self)
{
*self = Anm2Reference{};
@@ -970,9 +966,7 @@ void anm2_reference_clear(Anm2Reference* self)
void anm2_reference_item_clear(Anm2Reference* self)
{
self->itemType = ANM2_NONE;
self->itemID = ID_NONE;
self->frameIndex = INDEX_NONE;
*self = {self->animationID};
}
void anm2_reference_frame_clear(Anm2Reference* self)
@@ -980,20 +974,70 @@ void anm2_reference_frame_clear(Anm2Reference* self)
self->frameIndex = INDEX_NONE;
}
void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count)
{
Anm2Item* item = anm2_item_from_reference(self, reference);
if (!item) return;
if (start < 0 || count <= 0) return;
const s32 size = (s32)item->frames.size();
if (size == 0 || start >= size) return;
const s32 end = std::min(start + count, size);
for (s32 i = start; i < end; ++i)
{
Anm2Frame& dest = item->frames[i];
// Booleans always just set if provided
if (change.isVisible) dest.isVisible = *change.isVisible;
if (change.isInterpolated) dest.isInterpolated = *change.isInterpolated;
switch (type)
{
case ANM2_CHANGE_SET:
if (change.rotation) dest.rotation = *change.rotation;
if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, *change.delay);
if (change.crop) dest.crop = *change.crop;
if (change.pivot) dest.pivot = *change.pivot;
if (change.position) dest.position = *change.position;
if (change.size) dest.size = *change.size;
if (change.scale) dest.scale = *change.scale;
if (change.offsetRGB) dest.offsetRGB = glm::clamp(*change.offsetRGB, 0.0f, 1.0f);
if (change.tintRGBA) dest.tintRGBA = glm::clamp(*change.tintRGBA, 0.0f, 1.0f);
break;
case ANM2_CHANGE_ADD:
if (change.rotation) dest.rotation += *change.rotation;
if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay + *change.delay);
if (change.crop) dest.crop += *change.crop;
if (change.pivot) dest.pivot += *change.pivot;
if (change.position) dest.position += *change.position;
if (change.size) dest.size += *change.size;
if (change.scale) dest.scale += *change.scale;
if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB + *change.offsetRGB, 0.0f, 1.0f);
if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA + *change.tintRGBA, 0.0f, 1.0f);
break;
case ANM2_CHANGE_SUBTRACT:
if (change.rotation) dest.rotation -= *change.rotation;
if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay - *change.delay);
if (change.crop) dest.crop -= *change.crop;
if (change.pivot) dest.pivot -= *change.pivot;
if (change.position) dest.position -= *change.position;
if (change.size) dest.size -= *change.size;
if (change.scale) dest.scale -= *change.scale;
if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB - *change.offsetRGB, 0.0f, 1.0f);
if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA - *change.tintRGBA, 0.0f, 1.0f);
break;
}
}
}
void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type)
{
Anm2Animation newAnimation = self->animations[animationID];
newAnimation.rootAnimation.frames.clear();
for (auto& [id, layerAnimation] : newAnimation.layerAnimations)
layerAnimation.frames.clear();
for (auto& [id, nullAnimation] : newAnimation.nullAnimations)
nullAnimation.frames.clear();
newAnimation.triggers.frames.clear();
auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem)
{
switch (type)
@@ -1017,6 +1061,8 @@ void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& m
for (auto mergeID : mergeIDs)
{
if (animationID == mergeID) continue;
const Anm2Animation& mergeAnimation = self->animations[mergeID];
merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation);
@@ -1047,7 +1093,7 @@ void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool is
referenceNext.frameIndex = reference->frameIndex + 1;
Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext);
if (!frameNext) return;
if (!frameNext) frameNext = frame;
const Anm2Frame baseFrame = *frame;
const Anm2Frame baseFrameNext = *frameNext;

View File

@@ -7,6 +7,7 @@
#define ANM2_TINT_CONVERT(x) ((f32)x / 255.0f)
#define ANM2_FPS_MIN 0
#define ANM2_FPS_DEFAULT 30
#define ANM2_FPS_MAX 120
#define ANM2_FRAME_NUM_MIN 1
#define ANM2_FRAME_NUM_MAX 1000000
@@ -148,8 +149,8 @@ struct Anm2Event
struct Anm2Frame
{
bool isInterpolated = false;
bool isVisible = true;
bool isInterpolated = false;
f32 rotation = 1.0f;
s32 delay = ANM2_FRAME_DELAY_MIN;
s32 atFrame = INDEX_NONE;
@@ -163,6 +164,21 @@ struct Anm2Frame
vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f};
};
struct Anm2FrameChange
{
std::optional<bool> isVisible;
std::optional<bool> isInterpolated;
std::optional<f32> rotation;
std::optional<s32> delay;
std::optional<vec2> crop;
std::optional<vec2> pivot;
std::optional<vec2> position;
std::optional<vec2> size;
std::optional<vec2> scale;
std::optional<vec3> offsetRGB;
std::optional<vec4> tintRGBA;
};
struct Anm2Item
{
bool isVisible = true;
@@ -192,7 +208,7 @@ struct Anm2
std::map<s32, Anm2Animation> animations;
std::map<s32, s32> layerMap; // index, id
s32 defaultAnimationID{};
s32 fps = 30;
s32 fps = ANM2_FPS_DEFAULT;
s32 version{};
};
@@ -231,6 +247,13 @@ enum Anm2MergeType
ANM2_MERGE_IGNORE
};
enum Anm2ChangeType
{
ANM2_CHANGE_ADD,
ANM2_CHANGE_SUBTRACT,
ANM2_CHANGE_SET
};
void anm2_layer_add(Anm2* self);
void anm2_layer_remove(Anm2* self, s32 id);
void anm2_null_add(Anm2* self);
@@ -246,7 +269,8 @@ Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* referenc
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference);
s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time);
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time);
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time);
void anm2_frame_erase(Anm2* self, Anm2Reference* reference);
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_reference_clear(Anm2Reference* self);
void anm2_reference_item_clear(Anm2Reference* self);
@@ -255,3 +279,4 @@ s32 anm2_animation_length_get(Anm2Animation* self);
void anm2_animation_length_set(Anm2Animation* self);
void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type);
void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation);
void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count);

View File

@@ -1,5 +1,3 @@
// Common rendering area
#include "canvas.h"
static void _canvas_texture_free(Canvas* self)
@@ -9,7 +7,7 @@ static void _canvas_texture_free(Canvas* self)
if (self->texture != 0) glDeleteTextures(1, &self->texture);
}
static void _canvas_texture_init(Canvas* self, vec2& size)
static void _canvas_texture_init(Canvas* self, const vec2& size)
{
_canvas_texture_free(self);
@@ -35,7 +33,7 @@ static void _canvas_texture_init(Canvas* self, vec2& size)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void canvas_init(Canvas* self)
void canvas_init(Canvas* self, const vec2& size)
{
// Axis
glGenVertexArrays(1, &self->axisVAO);
@@ -91,6 +89,8 @@ void canvas_init(Canvas* self)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32)));
glBindVertexArray(0);
_canvas_texture_init(self, size);
}
mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin)
@@ -278,29 +278,3 @@ mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 r
return transform * model;
}
/*
mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale, vec2 pivotAlt, f32 rotationAlt)
{
vec2 scaleAbsolute = abs(scale);
vec2 signScale = glm::sign(scale);
vec2 pivotScaled = pivot * scaleAbsolute;
vec2 pivotAltScaled = pivotAlt * scaleAbsolute;
vec2 sizeScaled = size * scaleAbsolute;
mat4 model = glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotScaled, 0.0f));
model = glm::scale(model, vec3(signScale, 1.0f)); // Flip
model = glm::rotate(model, radians(rotation), vec3(0,0,1));
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotAltScaled, 0.0f));
model = glm::rotate(model, radians(rotationAlt), vec3(0,0,1));
model = glm::translate(model, vec3(-pivotAltScaled, 0.0f));
model = glm::scale(model, vec3(sizeScaled, 1.0f));
return transform * model;
}
*/

View File

@@ -4,14 +4,16 @@
#define CANVAS_ZOOM_MIN 1.0f
#define CANVAS_ZOOM_MAX 2000.0f
#define CANVAS_ZOOM_DEFAULT 100.0f
#define CANVAS_ZOOM_STEP 10.0f
#define CANVAS_ZOOM_MOD 10.0f
#define CANVAS_GRID_MIN 1
#define CANVAS_GRID_MAX 1000
#define CANVAS_GRID_DEFAULT 32
#define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f)
static const vec2 CANVAS_GRID_SIZE = {3200, 1600};
static const vec2 CANVAS_PIVOT_SIZE = {4, 4};
static const vec2 CANVAS_PIVOT_SIZE = {8, 8};
const f32 CANVAS_AXIS_VERTICES[] =
{
@@ -38,7 +40,7 @@ struct Canvas
vec2 size{};
};
void canvas_init(Canvas* self);
void canvas_init(Canvas* self, const vec2& size);
mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin);
void canvas_clear(vec4& color);
void canvas_bind(Canvas* self);

View File

@@ -6,44 +6,26 @@ static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2)
{
case CLIPBOARD_FRAME:
{
Anm2FrameWithReference frameWithReference = std::get<Anm2FrameWithReference>(self->data);
Anm2Animation* animation = anm2_animation_from_reference(anm2, &frameWithReference.reference);
Anm2FrameWithReference* frameWithReference = std::get_if<Anm2FrameWithReference>(&self->data);
if (!animation) break;
std::vector<Anm2Frame>* frames = nullptr;
switch (frameWithReference.reference.itemType)
{
case ANM2_ROOT:
frames = &animation->rootAnimation.frames;
break;
case ANM2_LAYER:
frames = &animation->layerAnimations[frameWithReference.reference.itemID].frames;
break;
case ANM2_NULL:
frames = &animation->nullAnimations[frameWithReference.reference.itemID].frames;
break;
case ANM2_TRIGGERS:
frames = &animation->triggers.frames;
break;
default:
break;
}
if (frames)
frames->erase(frames->begin() + frameWithReference.reference.frameIndex);
if (!frameWithReference) break;
anm2_frame_erase(anm2, &frameWithReference->reference);
break;
}
case CLIPBOARD_ANIMATION:
{
Anm2AnimationWithID animationWithID = std::get<Anm2AnimationWithID>(self->data);
Anm2AnimationWithID* animationWithID = std::get_if<Anm2AnimationWithID>(&self->data);
if (!animationWithID) break;
for (auto & [id, animation] : anm2->animations)
{
if (id == animationWithID.id)
anm2->animations.erase(animationWithID.id);
if (id == animationWithID->id)
{
anm2->animations.erase(animationWithID->id);
break;
}
}
break;
}
@@ -69,24 +51,26 @@ static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* locati
if (!animation || !anm2Item) break;
s32 insertIndex = (reference->itemType == ANM2_TRIGGERS) ?
reference->frameIndex : std::max(reference->frameIndex, (s32)anm2Item->frames.size());
anm2_frame_add(anm2, &frameWithReference->frame, reference, reference->frameIndex);
anm2Item->frames.insert(anm2Item->frames.begin() + insertIndex, frameWithReference->frame);
anm2_animation_length_set(animation);
break;
}
case CLIPBOARD_ANIMATION:
{
Anm2AnimationWithID* animationWithID = std::get_if<Anm2AnimationWithID>(&self->data);
if (!animationWithID) break;
s32 index = 0;
if (std::holds_alternative<s32>(*location))
index = std::get<s32>(*location);
else
break;
index = std::clamp(index, 0, (s32)anm2->animations.size());
map_insert_shift(anm2->animations, index, std::get<Anm2AnimationWithID>(self->data).animation);
map_insert_shift(anm2->animations, index, animationWithID->animation);
break;
}
default:

View File

@@ -15,12 +15,8 @@ struct ClipboardItem
ClipboardItemType type = CLIPBOARD_NONE;
ClipboardItem() = default;
ClipboardItem(const Anm2FrameWithReference& frame)
: data(frame), type(CLIPBOARD_FRAME) {}
ClipboardItem(const Anm2AnimationWithID& anim)
: data(anim), type(CLIPBOARD_ANIMATION) {}
ClipboardItem(const Anm2FrameWithReference& frame) : data(frame), type(CLIPBOARD_FRAME) {}
ClipboardItem(const Anm2AnimationWithID& animation) : data(animation), type(CLIPBOARD_ANIMATION) {}
};
using ClipboardLocation = std::variant<std::monostate, Anm2Reference, s32>;

View File

@@ -1,5 +1,3 @@
// File dialog; saving/writing to files
#include "dialog.h"
static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter)

View File

@@ -1,5 +1,3 @@
// Handles the rendering of the spritesheet editor
#include "editor.h"
void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings)
@@ -9,7 +7,7 @@ void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources*
self->resources = resources;
self->settings = settings;
canvas_init(&self->canvas);
canvas_init(&self->canvas, vec2());
}
void editor_draw(Editor* self)

101
src/generate_preview.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include "generate_preview.h"
void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings)
{
self->anm2 = anm2;
self->reference = reference;
self->resources = resources;
self->settings = settings;
canvas_init(&self->canvas, GENERATE_PREVIEW_SIZE);
}
void generate_preview_draw(GeneratePreview* self)
{
/* TODO
f32& zoom = self->settings->previewZoom;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER);
canvas_bind(&self->canvas);
canvas_viewport_set(&self->canvas);
canvas_clear(self->settings->previewBackgroundColor);
self->time = std::clamp(self->time, 0.0f, 1.0f);
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
s32& animationID = self->reference->animationID;
if (animation)
{
Anm2Frame root;
mat4 rootModel = mat4(1.0f);
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time);
if (self->settings->previewIsRootTransform)
rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale));
// Root
if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible)
{
mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, root.rotation, PERCENT_TO_UNIT(root.scale));
mat4 rootTransform = transform * model;
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_TARGET);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, PREVIEW_ROOT_COLOR);
}
// Layers
for (auto [i, id] : self->anm2->layerMap)
{
Anm2Frame frame;
Anm2Item& layerAnimation = animation->layerAnimations[id];
if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0)
continue;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, self->time);
if (!frame.isVisible)
continue;
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (!texture || texture->isInvalid)
continue;
vec2 uvMin = frame.crop / vec2(texture->size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB);
if (self->settings->previewIsBorder)
canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR);
if (self->settings->previewIsPivots)
{
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 pivotTransform = transform * (rootModel * pivotModel);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR);
}
}
s32& animationOverlayID = self->animationOverlayID;
Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID);
canvas_unbind();
*/
}
void generate_preview_free(GeneratePreview* self)
{
canvas_free(&self->canvas);
}

22
src/generate_preview.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "anm2.h"
#include "resources.h"
#include "settings.h"
#include "canvas.h"
const vec2 GENERATE_PREVIEW_SIZE = {325, 215};
struct GeneratePreview
{
Anm2* anm2 = nullptr;
Anm2Reference* reference = nullptr;
Resources* resources = nullptr;
Settings* settings = nullptr;
Canvas canvas;
f32 time{};
};
void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings);
void generate_preview_draw(GeneratePreview* self);
void generate_preview_free(GeneratePreview* self);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,3 @@
// Main function
#include "main.h"
s32

View File

@@ -1,5 +1,3 @@
// Handles the render of the animation preview
#include "preview.h"
static void _preview_render_textures_free(Preview* self)
@@ -17,11 +15,12 @@ void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources
self->resources = resources;
self->settings = settings;
canvas_init(&self->canvas);
canvas_init(&self->canvas, vec2());
}
void preview_tick(Preview* self)
{
f32& time = self->time;
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
if (animation)
@@ -45,29 +44,38 @@ void preview_tick(Preview* self)
self->renderFrames.push_back(frameTexture);
}
self->time += (f32)self->anm2->fps / TICK_RATE;
time += (f32)self->anm2->fps / TICK_RATE;
if (self->time >= (f32)animation->frameNum - 1)
if (time >= (f32)animation->frameNum - 1)
{
if (self->isRender)
{
self->isRender = false;
self->isRenderFinished = true;
self->time = 0.0f;
time = 0.0f;
self->isPlaying = false;
}
else
{
if (self->settings->playbackIsLoop)
self->time = 0.0f;
time = 0.0f;
else
{
time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
self->isPlaying = false;
}
}
}
}
self->time = std::clamp(self->time, 0.0f, (f32)animation->frameNum - 1);
if (self->settings->playbackIsClampPlayhead)
time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
else
time = std::max(time, 0.0f);
}
}
void preview_draw(Preview* self)
@@ -89,8 +97,8 @@ void preview_draw(Preview* self)
if (self->settings->previewIsGrid)
canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor);
if (self->settings->previewIsAxis)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxisColor);
if (self->settings->previewIsAxes)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor);
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
s32& animationID = self->reference->animationID;

View File

@@ -54,6 +54,13 @@ static void _settings_setting_load(Settings* self, const std::string& line)
if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; }
if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; }
}
else if (entry.type == TYPE_VEC3)
{
vec3* v = (vec3*)target;
if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; }
if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; }
if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; }
}
else if (entry.type == TYPE_VEC4)
{
vec4* v = (vec4*)target;
@@ -105,6 +112,14 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE
out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n";
break;
}
case TYPE_VEC3:
{
vec3* data = (vec3*)(selfPointer + entry.offset);
out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n";
out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n";
out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n";
break;
}
case TYPE_VEC4:
{
vec4* data = (vec4*)(selfPointer + entry.offset);

View File

@@ -1,5 +1,6 @@
#pragma once
#include "anm2.h"
#include "render.h"
#include "tool.h"
@@ -22,20 +23,53 @@ struct Settings
{
ivec2 windowSize = {1080, 720};
bool playbackIsLoop = true;
bool previewIsAxis = true;
bool playbackIsClampPlayhead = true;
bool changeIsCrop = false;
bool changeIsSize = false;
bool changeIsPosition = false;
bool changeIsPivot = false;
bool changeIsScale = false;
bool changeIsRotation = false;
bool changeIsDelay = false;
bool changeIsTint = false;
bool changeIsColorOffset = false;
bool changeIsVisibleSet = false;
bool changeIsInterpolatedSet = false;
bool changeIsFromSelectedFrame = false;
vec2 changeCrop{};
vec2 changeSize{};
vec2 changePosition{};
vec2 changePivot{};
vec2 changeScale{};
f32 changeRotation{};
s32 changeDelay{};
vec4 changeTint{};
vec3 changeColorOffset{};
bool changeIsVisible{};
bool changeIsInterpolated{};
s32 changeNumberFrames = 1;
bool previewIsAxes = true;
bool previewIsGrid = true;
bool previewIsRootTransform = false;
bool previewIsTriggers = true;
bool previewIsPivots = false;
bool previewIsTargets = true;
bool previewIsBorder = false;
f32 previewOverlayTransparency = 255.0f;
f32 previewZoom = 200.0;
vec2 previewPan = {0.0, 0.0};
ivec2 previewGridSize = {32, 32};
ivec2 previewGridSize = {32, 3};
ivec2 previewGridOffset{};
vec4 previewGridColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewAxisColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0};
ivec2 generateStartPosition = {0, 0};
ivec2 generateFrameSize = {64, 64};
ivec2 generatePivot = {32, 32};
s32 generateRows = 4;
s32 generateColumns = 4;
s32 generateFrameCount = 16;
s32 generateDelay = 1;
bool editorIsGrid = true;
bool editorIsGridSnap = true;
bool editorIsBorder = true;
@@ -45,9 +79,14 @@ struct Settings
ivec2 editorGridOffset = {32, 32};
vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125};
vec4 editorBackgroundColor = {0.113, 0.184, 0.286, 1.0};
ToolType tool = TOOL_PAN;
s32 mergeType = ANM2_MERGE_APPEND_FRAMES;
bool mergeIsDeleteAnimationsAfter = false;
s32 bakeInterval = 1;
bool bakeIsRoundScale = true;
bool bakeIsRoundRotation = true;
s32 tool = TOOL_PAN;
vec4 toolColor = {1.0, 1.0, 1.0, 1.0};
RenderType renderType = RENDER_PNG;
s32 renderType = RENDER_PNG;
std::string renderPath = ".";
std::string renderFormat = "{}.png";
std::string ffmpegPath = "/usr/bin/ffmpeg";
@@ -57,9 +96,35 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{
{"window", TYPE_IVEC2, offsetof(Settings, windowSize)},
{"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)},
{"previewIsAxis", TYPE_BOOL, offsetof(Settings, previewIsAxis)},
{"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)},
{"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)},
{"changeIsSize", TYPE_BOOL, offsetof(Settings, changeIsSize)},
{"changeIsPosition", TYPE_BOOL, offsetof(Settings, changeIsPosition)},
{"changeIsPivot", TYPE_BOOL, offsetof(Settings, changeIsPivot)},
{"changeIsScale", TYPE_BOOL, offsetof(Settings, changeIsScale)},
{"changeIsRotation", TYPE_BOOL, offsetof(Settings, changeIsRotation)},
{"changeIsDelay", TYPE_BOOL, offsetof(Settings, changeIsDelay)},
{"changeIsTint", TYPE_BOOL, offsetof(Settings, changeIsTint)},
{"changeIsColorOffset", TYPE_BOOL, offsetof(Settings, changeIsColorOffset)},
{"changeIsVisibleSet", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolatedSet", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeIsFromSelectedFrame", TYPE_BOOL, offsetof(Settings, changeIsFromSelectedFrame)},
{"changeCrop", TYPE_VEC2, offsetof(Settings, changeCrop)},
{"changeSize", TYPE_VEC2, offsetof(Settings, changeSize)},
{"changePosition", TYPE_VEC2, offsetof(Settings, changePosition)},
{"changePivot", TYPE_VEC2, offsetof(Settings, changePivot)},
{"changeScale", TYPE_VEC2, offsetof(Settings, changeScale)},
{"changeRotation", TYPE_FLOAT, offsetof(Settings, changeRotation)},
{"changeDelay", TYPE_INT, offsetof(Settings, changeDelay)},
{"changeTint", TYPE_VEC4, offsetof(Settings, changeTint)},
{"changeColorOffset", TYPE_VEC3, offsetof(Settings, changeColorOffset)},
{"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)},
{"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)},
{"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)},
{"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)},
{"previewIsTriggers", TYPE_BOOL, offsetof(Settings, previewIsTriggers)},
{"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)},
{"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)},
{"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)},
@@ -69,8 +134,15 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)},
{"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)},
{"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)},
{"previewAxisColor", TYPE_VEC4, offsetof(Settings, previewAxisColor)},
{"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)},
{"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)},
{"generateStartPosition", TYPE_VEC2, offsetof(Settings, generateStartPosition)},
{"generateFrameSize", TYPE_VEC2, offsetof(Settings, generateFrameSize)},
{"generatePivot", TYPE_VEC2, offsetof(Settings, generatePivot)},
{"generateRows", TYPE_INT, offsetof(Settings, generateRows)},
{"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)},
{"generateFrameCount", TYPE_INT, offsetof(Settings, generateFrameCount)},
{"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)},
{"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)},
{"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)},
{"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)},
@@ -80,6 +152,11 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)},
{"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)},
{"editorBackgroundColor", TYPE_VEC4, offsetof(Settings, editorBackgroundColor)},
{"mergeType", TYPE_INT, offsetof(Settings, mergeType)},
{"mergeIsDeleteAnimationsAfter", TYPE_BOOL, offsetof(Settings, mergeIsDeleteAnimationsAfter)},
{"bakeInterval", TYPE_INT, offsetof(Settings, bakeInterval)},
{"bakeRoundScale", TYPE_BOOL, offsetof(Settings, bakeIsRoundScale)},
{"bakeRoundRotation", TYPE_BOOL, offsetof(Settings, bakeIsRoundRotation)},
{"tool", TYPE_INT, offsetof(Settings, tool)},
{"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)},
{"renderType", TYPE_INT, offsetof(Settings, renderType)},

View File

@@ -41,7 +41,7 @@ void snapshots_reset(Snapshots* self)
self->action.clear();
}
void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot)
void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot)
{
_snapshot_stack_push(&self->undoStack, snapshot);
self->redoStack.top = 0;

View File

@@ -2,6 +2,7 @@
#include "anm2.h"
#include "preview.h"
#include "texture.h"
#define SNAPSHOT_STACK_MAX 1000
#define SNAPSHOT_ACTION "Action"
@@ -18,6 +19,8 @@ struct SnapshotStack
{
Snapshot snapshots[SNAPSHOT_STACK_MAX];
s32 top = 0;
bool is_empty() const { return top == 0; }
};
struct Snapshots
@@ -30,7 +33,7 @@ struct Snapshots
SnapshotStack redoStack;
};
void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot);
void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot);
void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview);
void snapshots_undo(Snapshots* self);
void snapshots_redo(Snapshots* self);

View File

@@ -17,8 +17,6 @@ static void _update(State* self)
static void _draw(State* self)
{
editor_draw(&self->editor);
preview_draw(&self->preview);
imgui_draw();
SDL_GL_SwapWindow(self->window);
@@ -147,6 +145,8 @@ void loop(State* self)
_update(self);
_draw(self);
SDL_Delay(STATE_DELAY_MIN);
}
void quit(State* self)

View File

@@ -13,6 +13,8 @@
#define STATE_QUIT_INFO "Exiting..."
#define STATE_GL_LINE_WIDTH 2.0f
#define STATE_DELAY_MIN 1
#define STATE_MIX_FLAGS (MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_WAV)
#define STATE_MIX_SAMPLE_RATE 44100
#define STATE_MIX_FORMAT MIX_DEFAULT_FORMAT

View File

@@ -114,3 +114,33 @@ bool texture_pixel_set(Texture* self, ivec2 position, vec4 color)
return true;
}
Texture texture_copy(Texture* self)
{
Texture copy = *self;
_texture_gl_set(&copy, nullptr);
GLuint fboSource;
GLuint fboDestination;
glGenFramebuffers(1, &fboSource);
glGenFramebuffers(1, &fboDestination);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboSource);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->id, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboDestination);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copy.id, 0);
glBlitFramebuffer
(
0, 0, self->size.x, self->size.y,
0, 0, self->size.x, self->size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fboSource);
glDeleteFramebuffers(1, &fboDestination);
return copy;
}

View File

@@ -23,3 +23,4 @@ bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size
bool texture_pixel_set(Texture* self, ivec2 position, vec4 color);
void texture_free(Texture* self);
std::vector<u8> texture_download(const Texture* self);
Texture texture_copy(Texture* self);

View File

@@ -21,8 +21,8 @@ enum ToolType
TOOL_COUNT,
};
const SDL_SystemCursor MOUSE_CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT;
const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] =
const SDL_SystemCursor CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT;
const SDL_SystemCursor TOOL_CURSORS[TOOL_COUNT] =
{
SDL_SYSTEM_CURSOR_POINTER,
SDL_SYSTEM_CURSOR_MOVE,
@@ -35,18 +35,3 @@ const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] =
SDL_SYSTEM_CURSOR_DEFAULT,
SDL_SYSTEM_CURSOR_DEFAULT
};
const bool TOOL_MOUSE_CURSOR_IS_CONSTANT[TOOL_COUNT] =
{
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false
};