The Omega Update(TM) Part 4 (Massive Refactor, Change All Frame Properties)
This commit is contained in:
		
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							@@ -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:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/COMMON.h
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/COMMON.h
									
									
									
									
									
								
							@@ -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
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										134
									
								
								src/anm2.cpp
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/anm2.cpp
									
									
									
									
									
								
							@@ -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;
 | 
			
		||||
			
 | 
			
		||||
			for (auto& frameCheck : item->frames) 
 | 
			
		||||
			{
 | 
			
		||||
				if (frameCheck.atFrame == time)
 | 
			
		||||
				{
 | 
			
		||||
					index++;
 | 
			
		||||
					break;	
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			frame.atFrame = time;
 | 
			
		||||
			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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								src/anm2.h
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								src/anm2.h
									
									
									
									
									
								
							@@ -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);
 | 
			
		||||
@@ -254,4 +278,5 @@ void anm2_reference_frame_clear(Anm2Reference* self);
 | 
			
		||||
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_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);
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -277,30 +277,4 @@ mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 r
 | 
			
		||||
    model = glm::scale(model, vec3(sizeScaled, 1.0f));
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
            if (!animation) break;
 | 
			
		||||
 | 
			
		||||
            std::vector<Anm2Frame>* frames = nullptr;
 | 
			
		||||
            Anm2FrameWithReference* frameWithReference = std::get_if<Anm2FrameWithReference>(&self->data);
 | 
			
		||||
            
 | 
			
		||||
            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 (!frameWithReference) break;
 | 
			
		||||
 | 
			
		||||
            if (frames)
 | 
			
		||||
                frames->erase(frames->begin() + frameWithReference.reference.frameIndex);
 | 
			
		||||
            
 | 
			
		||||
            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()); 
 | 
			
		||||
            
 | 
			
		||||
            anm2Item->frames.insert(anm2Item->frames.begin() + insertIndex, frameWithReference->frame);
 | 
			
		||||
            
 | 
			
		||||
            anm2_animation_length_set(animation);
 | 
			
		||||
            anm2_frame_add(anm2, &frameWithReference->frame, reference, reference->frameIndex); 
 | 
			
		||||
 | 
			
		||||
            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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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>;
 | 
			
		||||
 
 | 
			
		||||
@@ -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) 
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										101
									
								
								src/generate_preview.cpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										22
									
								
								src/generate_preview.h
									
									
									
									
									
										Normal 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);
 | 
			
		||||
							
								
								
									
										2579
									
								
								src/imgui.cpp
									
									
									
									
									
								
							
							
						
						
									
										2579
									
								
								src/imgui.cpp
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										3270
									
								
								src/imgui.h
									
									
									
									
									
								
							
							
						
						
									
										3270
									
								
								src/imgui.h
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
// Main function
 | 
			
		||||
 | 
			
		||||
#include "main.h"
 | 
			
		||||
 | 
			
		||||
s32
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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)},
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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(©, 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;
 | 
			
		||||
}
 | 
			
		||||
@@ -22,4 +22,5 @@ bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* d
 | 
			
		||||
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);
 | 
			
		||||
std::vector<u8> texture_download(const Texture* self);
 | 
			
		||||
Texture texture_copy(Texture* self);
 | 
			
		||||
							
								
								
									
										21
									
								
								src/tool.h
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/tool.h
									
									
									
									
									
								
							@@ -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,
 | 
			
		||||
@@ -34,19 +34,4 @@ const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] =
 | 
			
		||||
    SDL_SYSTEM_CURSOR_CROSSHAIR,
 | 
			
		||||
    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
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user