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. 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 ## Features
- Extended version of the original proprietary Nicalis animation editor
- Most things present in the original IsaacAnimationEditor.exe, except some stuff like drawing (why not use an art program!)
- Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc. - 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)) - New features
- Can output .webm or *.png sequence
### Known Issues - Cutting, copying and pasting
- Root Transform doesn't work for scale/rotation (matrix math is hard; if you can help me fix it I will give you $100.) - Robust snapshot (undo/redo) system
- 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. - Additional hotkeys/shortcuts
- Settings that will preserve on exit
## Dependencies ## Dependencies
Download these from your package manager: Download these from your package manager:
@@ -20,6 +20,8 @@ Download these from your package manager:
- SDL3 - SDL3
- GLEW - 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 ## Build
After cloning and enter the repository's directory, make sure to initialize the submodules: After cloning and enter the repository's directory, make sure to initialize the submodules:

View File

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

View File

@@ -1,5 +1,3 @@
// Anm2 file format; serializing/deserializing
#include "anm2.h" #include "anm2.h"
using namespace tinyxml2; 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) Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference)
{ {
Anm2Item* item = anm2_item_from_reference(self, 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); accumulate_max_delay(item.frames);
for (const auto& frame : self->triggers.frames) for (const auto& frame : self->triggers.frames)
length = std::max(length, frame.atFrame); length = std::max(length, frame.atFrame + 1);
return length; return length;
} }
@@ -913,7 +912,7 @@ void anm2_animation_length_set(Anm2Animation* self)
self->frameNum = anm2_animation_length_get(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); Anm2Animation* animation = anm2_animation_from_reference(self, reference);
Anm2Item* item = anm2_item_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) if (item)
{ {
Anm2Frame frame = Anm2Frame{}; Anm2Frame frameAdd = frame ? *frame : Anm2Frame{};
s32 index = INDEX_NONE; s32 index = reference->frameIndex + 1;
if (reference->itemType == ANM2_TRIGGERS) 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(); index = item->frames.size();
return &item->frames.emplace_back(frameAdd);
} }
else else
{ {
s32 frameDelayCount = 0; item->frames.insert(item->frames.begin() + index, frameAdd);
return &item->frames[index];
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, frame);
return &item->frames[index];
} }
return nullptr; 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) void anm2_reference_clear(Anm2Reference* self)
{ {
*self = Anm2Reference{}; *self = Anm2Reference{};
@@ -970,9 +966,7 @@ void anm2_reference_clear(Anm2Reference* self)
void anm2_reference_item_clear(Anm2Reference* self) void anm2_reference_item_clear(Anm2Reference* self)
{ {
self->itemType = ANM2_NONE; *self = {self->animationID};
self->itemID = ID_NONE;
self->frameIndex = INDEX_NONE;
} }
void anm2_reference_frame_clear(Anm2Reference* self) void anm2_reference_frame_clear(Anm2Reference* self)
@@ -980,20 +974,70 @@ void anm2_reference_frame_clear(Anm2Reference* self)
self->frameIndex = INDEX_NONE; 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) void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type)
{ {
Anm2Animation newAnimation = self->animations[animationID]; 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) auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem)
{ {
switch (type) switch (type)
@@ -1017,6 +1061,8 @@ void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& m
for (auto mergeID : mergeIDs) for (auto mergeID : mergeIDs)
{ {
if (animationID == mergeID) continue;
const Anm2Animation& mergeAnimation = self->animations[mergeID]; const Anm2Animation& mergeAnimation = self->animations[mergeID];
merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); 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; referenceNext.frameIndex = reference->frameIndex + 1;
Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext); Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext);
if (!frameNext) return; if (!frameNext) frameNext = frame;
const Anm2Frame baseFrame = *frame; const Anm2Frame baseFrame = *frame;
const Anm2Frame baseFrameNext = *frameNext; const Anm2Frame baseFrameNext = *frameNext;

View File

@@ -7,6 +7,7 @@
#define ANM2_TINT_CONVERT(x) ((f32)x / 255.0f) #define ANM2_TINT_CONVERT(x) ((f32)x / 255.0f)
#define ANM2_FPS_MIN 0 #define ANM2_FPS_MIN 0
#define ANM2_FPS_DEFAULT 30
#define ANM2_FPS_MAX 120 #define ANM2_FPS_MAX 120
#define ANM2_FRAME_NUM_MIN 1 #define ANM2_FRAME_NUM_MIN 1
#define ANM2_FRAME_NUM_MAX 1000000 #define ANM2_FRAME_NUM_MAX 1000000
@@ -148,8 +149,8 @@ struct Anm2Event
struct Anm2Frame struct Anm2Frame
{ {
bool isInterpolated = false;
bool isVisible = true; bool isVisible = true;
bool isInterpolated = false;
f32 rotation = 1.0f; f32 rotation = 1.0f;
s32 delay = ANM2_FRAME_DELAY_MIN; s32 delay = ANM2_FRAME_DELAY_MIN;
s32 atFrame = INDEX_NONE; s32 atFrame = INDEX_NONE;
@@ -163,6 +164,21 @@ struct Anm2Frame
vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; 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 struct Anm2Item
{ {
bool isVisible = true; bool isVisible = true;
@@ -192,7 +208,7 @@ struct Anm2
std::map<s32, Anm2Animation> animations; std::map<s32, Anm2Animation> animations;
std::map<s32, s32> layerMap; // index, id std::map<s32, s32> layerMap; // index, id
s32 defaultAnimationID{}; s32 defaultAnimationID{};
s32 fps = 30; s32 fps = ANM2_FPS_DEFAULT;
s32 version{}; s32 version{};
}; };
@@ -231,6 +247,13 @@ enum Anm2MergeType
ANM2_MERGE_IGNORE ANM2_MERGE_IGNORE
}; };
enum Anm2ChangeType
{
ANM2_CHANGE_ADD,
ANM2_CHANGE_SUBTRACT,
ANM2_CHANGE_SET
};
void anm2_layer_add(Anm2* self); void anm2_layer_add(Anm2* self);
void anm2_layer_remove(Anm2* self, s32 id); void anm2_layer_remove(Anm2* self, s32 id);
void anm2_null_add(Anm2* self); 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); Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Frame* anm2_frame_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); 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_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_reference_clear(Anm2Reference* self); void anm2_reference_clear(Anm2Reference* self);
void anm2_reference_item_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_length_set(Anm2Animation* self);
void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type); 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_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" #include "canvas.h"
static void _canvas_texture_free(Canvas* self) 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); 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); _canvas_texture_free(self);
@@ -35,7 +33,7 @@ static void _canvas_texture_init(Canvas* self, vec2& size)
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
void canvas_init(Canvas* self) void canvas_init(Canvas* self, const vec2& size)
{ {
// Axis // Axis
glGenVertexArrays(1, &self->axisVAO); 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))); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32)));
glBindVertexArray(0); glBindVertexArray(0);
_canvas_texture_init(self, size);
} }
mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin) 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; 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_MIN 1.0f
#define CANVAS_ZOOM_MAX 2000.0f #define CANVAS_ZOOM_MAX 2000.0f
#define CANVAS_ZOOM_DEFAULT 100.0f
#define CANVAS_ZOOM_STEP 10.0f #define CANVAS_ZOOM_STEP 10.0f
#define CANVAS_ZOOM_MOD 10.0f #define CANVAS_ZOOM_MOD 10.0f
#define CANVAS_GRID_MIN 1 #define CANVAS_GRID_MIN 1
#define CANVAS_GRID_MAX 1000 #define CANVAS_GRID_MAX 1000
#define CANVAS_GRID_DEFAULT 32
#define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f) #define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f)
static const vec2 CANVAS_GRID_SIZE = {3200, 1600}; 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[] = const f32 CANVAS_AXIS_VERTICES[] =
{ {
@@ -38,7 +40,7 @@ struct Canvas
vec2 size{}; 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); mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin);
void canvas_clear(vec4& color); void canvas_clear(vec4& color);
void canvas_bind(Canvas* self); void canvas_bind(Canvas* self);

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
// File dialog; saving/writing to files
#include "dialog.h" #include "dialog.h"
static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter) 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" #include "editor.h"
void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) 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->resources = resources;
self->settings = settings; self->settings = settings;
canvas_init(&self->canvas); canvas_init(&self->canvas, vec2());
} }
void editor_draw(Editor* self) 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" #include "main.h"
s32 s32

View File

@@ -1,5 +1,3 @@
// Handles the render of the animation preview
#include "preview.h" #include "preview.h"
static void _preview_render_textures_free(Preview* self) 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->resources = resources;
self->settings = settings; self->settings = settings;
canvas_init(&self->canvas); canvas_init(&self->canvas, vec2());
} }
void preview_tick(Preview* self) void preview_tick(Preview* self)
{ {
f32& time = self->time;
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
if (animation) if (animation)
@@ -45,29 +44,38 @@ void preview_tick(Preview* self)
self->renderFrames.push_back(frameTexture); 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) if (self->isRender)
{ {
self->isRender = false; self->isRender = false;
self->isRenderFinished = true; self->isRenderFinished = true;
self->time = 0.0f; time = 0.0f;
self->isPlaying = false; self->isPlaying = false;
} }
else else
{ {
if (self->settings->playbackIsLoop) if (self->settings->playbackIsLoop)
self->time = 0.0f; time = 0.0f;
else else
{
time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
self->isPlaying = false; 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) void preview_draw(Preview* self)
@@ -89,8 +97,8 @@ void preview_draw(Preview* self)
if (self->settings->previewIsGrid) if (self->settings->previewIsGrid)
canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor); canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor);
if (self->settings->previewIsAxis) if (self->settings->previewIsAxes)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxisColor); canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor);
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
s32& animationID = self->reference->animationID; 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 + "X"))) { v->x = std::atoi(value); return; }
if ((value = match_key(key + "Y"))) { v->y = 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) else if (entry.type == TYPE_VEC4)
{ {
vec4* v = (vec4*)target; 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"; out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n";
break; 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: case TYPE_VEC4:
{ {
vec4* data = (vec4*)(selfPointer + entry.offset); vec4* data = (vec4*)(selfPointer + entry.offset);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "anm2.h"
#include "render.h" #include "render.h"
#include "tool.h" #include "tool.h"
@@ -22,20 +23,53 @@ struct Settings
{ {
ivec2 windowSize = {1080, 720}; ivec2 windowSize = {1080, 720};
bool playbackIsLoop = true; 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 previewIsGrid = true;
bool previewIsRootTransform = false; bool previewIsRootTransform = false;
bool previewIsTriggers = true;
bool previewIsPivots = false; bool previewIsPivots = false;
bool previewIsTargets = true; bool previewIsTargets = true;
bool previewIsBorder = false; bool previewIsBorder = false;
f32 previewOverlayTransparency = 255.0f; f32 previewOverlayTransparency = 255.0f;
f32 previewZoom = 200.0; f32 previewZoom = 200.0;
vec2 previewPan = {0.0, 0.0}; vec2 previewPan = {0.0, 0.0};
ivec2 previewGridSize = {32, 32}; ivec2 previewGridSize = {32, 3};
ivec2 previewGridOffset{}; ivec2 previewGridOffset{};
vec4 previewGridColor = {1.0, 1.0, 1.0, 0.125}; 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}; 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 editorIsGrid = true;
bool editorIsGridSnap = true; bool editorIsGridSnap = true;
bool editorIsBorder = true; bool editorIsBorder = true;
@@ -45,9 +79,14 @@ struct Settings
ivec2 editorGridOffset = {32, 32}; ivec2 editorGridOffset = {32, 32};
vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125}; vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125};
vec4 editorBackgroundColor = {0.113, 0.184, 0.286, 1.0}; 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}; vec4 toolColor = {1.0, 1.0, 1.0, 1.0};
RenderType renderType = RENDER_PNG; s32 renderType = RENDER_PNG;
std::string renderPath = "."; std::string renderPath = ".";
std::string renderFormat = "{}.png"; std::string renderFormat = "{}.png";
std::string ffmpegPath = "/usr/bin/ffmpeg"; std::string ffmpegPath = "/usr/bin/ffmpeg";
@@ -57,9 +96,35 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{ {
{"window", TYPE_IVEC2, offsetof(Settings, windowSize)}, {"window", TYPE_IVEC2, offsetof(Settings, windowSize)},
{"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, {"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)}, {"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)},
{"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, {"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)},
{"previewIsTriggers", TYPE_BOOL, offsetof(Settings, previewIsTriggers)},
{"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)}, {"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)},
{"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)}, {"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)},
{"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)}, {"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)},
@@ -69,8 +134,15 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)}, {"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)},
{"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)}, {"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)},
{"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)}, {"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)},
{"previewAxisColor", TYPE_VEC4, offsetof(Settings, previewAxisColor)}, {"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)},
{"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)}, {"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)}, {"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)},
{"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, {"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)},
{"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)}, {"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)},
@@ -80,6 +152,11 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)}, {"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)},
{"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)}, {"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)},
{"editorBackgroundColor", TYPE_VEC4, offsetof(Settings, editorBackgroundColor)}, {"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)}, {"tool", TYPE_INT, offsetof(Settings, tool)},
{"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)}, {"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)},
{"renderType", TYPE_INT, offsetof(Settings, renderType)}, {"renderType", TYPE_INT, offsetof(Settings, renderType)},

View File

@@ -41,7 +41,7 @@ void snapshots_reset(Snapshots* self)
self->action.clear(); 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); _snapshot_stack_push(&self->undoStack, snapshot);
self->redoStack.top = 0; self->redoStack.top = 0;

View File

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

View File

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

View File

@@ -13,6 +13,8 @@
#define STATE_QUIT_INFO "Exiting..." #define STATE_QUIT_INFO "Exiting..."
#define STATE_GL_LINE_WIDTH 2.0f #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_FLAGS (MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_WAV)
#define STATE_MIX_SAMPLE_RATE 44100 #define STATE_MIX_SAMPLE_RATE 44100
#define STATE_MIX_FORMAT MIX_DEFAULT_FORMAT #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; 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); bool texture_pixel_set(Texture* self, ivec2 position, vec4 color);
void texture_free(Texture* self); void texture_free(Texture* self);
std::vector<u8> texture_download(const 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, TOOL_COUNT,
}; };
const SDL_SystemCursor MOUSE_CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT; const SDL_SystemCursor CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT;
const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] = const SDL_SystemCursor TOOL_CURSORS[TOOL_COUNT] =
{ {
SDL_SYSTEM_CURSOR_POINTER, SDL_SYSTEM_CURSOR_POINTER,
SDL_SYSTEM_CURSOR_MOVE, SDL_SYSTEM_CURSOR_MOVE,
@@ -35,18 +35,3 @@ const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] =
SDL_SYSTEM_CURSOR_DEFAULT, SDL_SYSTEM_CURSOR_DEFAULT,
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
};