Compare commits

..

20 Commits

Author SHA1 Message Date
Shweet
4ce5a225c3 Update README.md 2025-09-14 18:15:09 -04:00
6d191f186d minor spelling mistake 2025-09-14 16:14:21 -04:00
70d4d44c80 update workshop 2025-09-13 20:17:39 -04:00
d35958a289 update readme 2025-09-13 20:04:42 -04:00
b65bcd2316 update readme 2025-09-13 20:01:57 -04:00
cc4501d2f1 add steam workshop 2025-09-13 20:00:50 -04:00
bc8cc37042 add steam workshop 2025-09-13 20:00:08 -04:00
c3bb2bcf17 Fix for auto fullscreen on Windows 2025-09-13 14:07:15 -04:00
9afb845a36 add slight texture inset for layer UVs. 2025-09-13 12:28:37 -04:00
fe32c74967 onionskin can now be 0 2025-09-13 10:39:53 -04:00
c78f2ee17b update gitignore 2025-09-13 10:35:01 -04:00
0a8fa44ee6 update atlas 2025-09-13 10:32:47 -04:00
f7c4bcea52 update windos build 2025-09-12 16:47:16 -07:00
45865bd4d1 refactor to not expose tinyxml2 symbols 2025-09-12 19:17:54 -04:00
c7dc0db9ce namespace fix 2025-09-12 18:57:50 -04:00
3da8928d13 fix triggers not serializing correctly 2025-09-12 18:39:09 -04:00
f8fb3df8d4 Redid layer system, added layer/null windows, more quality of life, polish 2025-09-12 18:06:30 -04:00
6deaaea374 Change clipboard system entirely, refactored anm2 serializing/deserializing, quick shell script for atlas update 2025-09-11 17:18:27 -04:00
b0e52bd444 allow anm2s with variable layers per animation. it'll just load all of them. not 1:1 to the behavior of nicalis editor but it'll make those sorts of files load in an editable fashion 2025-09-09 18:34:35 -04:00
b2cf67a823 Added fit, fixed the layer -1 bug finally, refactored here and there 2025-09-09 17:51:17 -04:00
34 changed files with 2085 additions and 1393 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,11 @@
build/
concept/
release/
packed/
vcpkg_installed/
out/
include/imgui/
include/glm/
include/tinyxml2
workshop/resources
.vs/

View File

@@ -10,26 +10,34 @@ project(anm2ed CXX)
find_package(SDL3 REQUIRED)
find_package(OpenGL REQUIRED)
set(GLAD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp)
file(GLOB SOURCES
${GLAD_SRC}
include/imgui/imgui.cpp
include/imgui/imgui_draw.cpp
include/imgui/imgui_widgets.cpp
include/imgui/imgui_tables.cpp
include/imgui/backends/imgui_impl_sdl3.cpp
include/imgui/backends/imgui_impl_opengl3.cpp
include/tinyxml2/tinyxml2.cpp
src/*.cpp
src/*.h
set(GLAD_SRC
${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp
)
if(NOT MSVC)
set_source_files_properties(${GLAD_SRC} PROPERTIES COMPILE_FLAGS "-Wno-cast-function-type")
endif()
set(IMGUI_SRC
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui_draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui_widgets.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui_tables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/backends/imgui_impl_sdl3.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/backends/imgui_impl_opengl3.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})
set(TINYXML2_SRC
${CMAKE_CURRENT_SOURCE_DIR}/include/tinyxml2/tinyxml2.cpp
)
file(GLOB PROJECT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/*.h
)
add_executable(${PROJECT_NAME}
${GLAD_SRC}
${IMGUI_SRC}
${TINYXML2_SRC}
${PROJECT_SRC}
)
if(WIN32)
enable_language(RC)
@@ -38,7 +46,14 @@ if(WIN32)
endif()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
target_include_directories(${PROJECT_NAME} PRIVATE include include/glad include/imgui include/tinyxml2 src)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/include/glad
${CMAKE_CURRENT_SOURCE_DIR}/include/imgui
${CMAKE_CURRENT_SOURCE_DIR}/include/tinyxml2
${CMAKE_CURRENT_SOURCE_DIR}/src
)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /std:c++latest /EHsc)

View File

@@ -10,6 +10,17 @@
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
},
{
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\build\\${name}",
"installRoot": "${projectDir}\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}

View File

@@ -6,21 +6,26 @@ A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation ed
## Features
- Extended version of the original proprietary Nicalis animation editor
- 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. You might be familiar with it from You might be familiar with it from (Repentogon)
- New features
- Can output .webm or *.png sequence
- Can output .webm, .mp4 or *.png sequence (wih FFmpeg)
- Cutting, copying and pasting
- Additional wizard options
- Robust snapshot (undo/redo) system
- Additional hotkeys/shortcuts
- Additional hotkeys/shortcuts (rebindable!)
- Onionskinning
- Settings that will preserve on exit (stored in %APPDATA% on Windows or ~/.local/share on Linux)
### Note: Difference from Nicalis editor
Layers/nulls are handled differently (in their own window) and are not on the timeline! Add a layer/null in the Layers/Nulls window, and then add a LayerAnimation/NullAnimation in the timeline!
### Note: Rendering Animations
You will need FFmpeg installed! Get it from [here](https://ffmpeg.org/download.html), and point to the downloaded ffmpeg executable within the program!
## Dependencies
Download these from your package manager:
- SDL3
Note, to render animations, you'll need to download [FFmpeg](https://ffmpeg.org/download.html) and specify its install path in the program.
## Build
### Windows
@@ -45,3 +50,6 @@ cd build
cmake ..
make
```
## Happy animating!
![Isaac](https://private-user-images.githubusercontent.com/129694724/482938896-b7f4c7c4-ce38-4062-81e9-bea119c66d1a.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTc4MDY3NTIsIm5iZiI6MTc1NzgwNjQ1MiwicGF0aCI6Ii8xMjk2OTQ3MjQvNDgyOTM4ODk2LWI3ZjRjN2M0LWNlMzgtNDA2Mi04MWU5LWJlYTExOWM2NmQxYS5naWY_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwOTEzJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDkxM1QyMzM0MTJaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0xZmU3YmExYWJhZjg1NGZiNTNjODM0NGYyZGI5MjM2MzIxNGM1YTEyOWM2MjAxNDQwZWJhODRhMzUxYjcyZjQ5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZROJVPS4oIyhl3kt-FQc3DjI5mci32AHwStjG0Sk8TM)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

66
assets/atlas_data_write.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
INPUT="atlas.png"
OUTPUT="../src/PACKED.h"
TMP="atlas.bytes"
# Ensure deps
command -v optipng >/dev/null 2>&1 || { echo "error: optipng not found in PATH" >&2; exit 1; }
command -v xxd >/dev/null 2>&1 || { echo "error: xxd not found in PATH" >&2; exit 1; }
# 1) Optimize PNG in place
optipng -o7 "$INPUT" >/dev/null
# 2) Extract ONLY the bytes from xxd -i (between '= {' and '};')
# Using awk avoids the earlier '{' vs '= {' mismatch bug.
xxd -i "$INPUT" \
| awk '
/= *\{/ {inside=1; next}
inside && /};/ {inside=0; exit}
inside {print}
' > "$TMP"
# Sanity check: make sure we got something
if ! [ -s "$TMP" ]; then
echo "error: failed to extract bytes from xxd output" >&2
exit 1
fi
# 3) Replace ONLY the bytes inside TEXTURE_ATLAS[] initializer
# - Find the exact declaration line for TEXTURE_ATLAS
# - Print that line
# - On the following line with just '{', print it, then insert bytes,
# then skip everything until the matching '};' and print that once.
awk -v tmpfile="$TMP" '
BEGIN { state=0 } # 0=normal, 1=after decl waiting for {, 2=skipping old bytes until };
# Match the TEXTURE_ATLAS declaration line precisely
$0 ~ /^[[:space:]]*const[[:space:]]+u8[[:space:]]+TEXTURE_ATLAS\[\][[:space:]]*=/ {
print; state=1; next
}
# After the decl, the next line with a lone "{" starts the initializer
state==1 && $0 ~ /^[[:space:]]*{[[:space:]]*$/ {
print # print the opening brace line
while ((getline line < tmpfile) > 0) print line # insert fresh bytes
close(tmpfile)
state=2 # now skip old initializer content until we hit the closing "};"
next
}
# While skipping, suppress lines until the closing "};", which we reprint once
state==2 {
if ($0 ~ /^[[:space:]]*};[[:space:]]*$/) {
print # print the closing brace+semicolon
state=0
}
next
}
# Default: pass through unchanged
{ print }
' "$OUTPUT" > "$OUTPUT.tmp" && mv "$OUTPUT.tmp" "$OUTPUT"
rm -f "$TMP"
echo "Updated $OUTPUT with optimized bytes from $INPUT."

View File

@@ -51,15 +51,17 @@ using namespace glm;
#define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f))
#define U8_TO_FLOAT(x) ((x) / 255.0f)
#define PERCENT_TO_UNIT(x) (x / 100.0f)
#define UNIT_TO_PERCENT(x) (x * 100.0f)
#define SECOND 1000.0f
#define TICK_DELAY (SECOND / 30.0)
#define UPDATE_DELAY (SECOND / 120.0)
#define ID_NONE -1
#define INDEX_NONE -1
#define VALUE_NONE -1
#define TIME_NONE -1.0f
#define GL_ID_NONE 0
#if defined(_WIN32)
#ifdef _WIN32
#define POPEN _popen
#define PCLOSE _pclose
#define PWRITE_MODE "wb"
@@ -71,7 +73,6 @@ using namespace glm;
#define PREAD_MODE "r"
#endif
static const GLuint GL_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0};
static const vec4 COLOR_RED = {1.0f, 0.0f, 0.0f, 1.0f};
static const vec4 COLOR_GREEN = {0.0f, 1.0f, 0.0f, 1.0f};
@@ -104,6 +105,15 @@ static inline std::string string_quote(const std::string& string)
return "\"" + string + "\"";
}
static inline std::string string_to_lowercase(std::string string) {
std::transform
(
string.begin(), string.end(), string.begin(),
[](u8 character){ return std::tolower(character); }
);
return string;
}
#define FLOAT_FORMAT_MAX_DECIMALS 2
#define FLOAT_FORMAT_EPSILON 1e-6f
static constexpr f32 FLOAT_FORMAT_POW10[] = {1.f, 10.f, 100.f};
@@ -144,65 +154,6 @@ static inline const char* vec2_format_get(const vec2& value)
return formatString.c_str();
}
static inline std::string path_canonical_resolve
(
const std::string& inputPath,
const std::string& basePath = std::filesystem::current_path().string()
)
{
auto strings_equal_ignore_case = [](std::string a, std::string b) {
auto to_lower = [](unsigned char c) { return static_cast<char>(std::tolower(c)); };
std::transform(a.begin(), a.end(), a.begin(), to_lower);
std::transform(b.begin(), b.end(), b.begin(), to_lower);
return a == b;
};
std::string sanitized = inputPath;
std::replace(sanitized.begin(), sanitized.end(), '\\', '/');
std::filesystem::path normalizedPath = sanitized;
std::filesystem::path absolutePath = normalizedPath.is_absolute()
? normalizedPath
: (std::filesystem::path(basePath) / normalizedPath);
std::error_code error;
if (std::filesystem::exists(absolutePath, error)) {
std::error_code canonicalError;
std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(absolutePath, canonicalError);
return (canonicalError ? absolutePath : canonicalPath).generic_string();
}
std::filesystem::path resolvedPath = absolutePath.root_path();
std::filesystem::path remainingPath = absolutePath.relative_path();
for (const std::filesystem::path& segment : remainingPath) {
std::filesystem::path candidatePath = resolvedPath / segment;
if (std::filesystem::exists(candidatePath, error)) {
resolvedPath = candidatePath;
continue;
}
bool matched = false;
if (std::filesystem::exists(resolvedPath, error) && std::filesystem::is_directory(resolvedPath, error)) {
for (const auto& directoryEntry : std::filesystem::directory_iterator(resolvedPath, error)) {
if (strings_equal_ignore_case(directoryEntry.path().filename().string(), segment.string())) {
resolvedPath = directoryEntry.path();
matched = true;
break;
}
}
}
if (!matched) return sanitized;
}
if (!std::filesystem::exists(resolvedPath, error))
return sanitized;
std::error_code canonicalError;
std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(resolvedPath, canonicalError);
return (canonicalError ? resolvedPath : canonicalPath).generic_string();
};
static inline std::string working_directory_from_file_set(const std::string& path)
{
std::filesystem::path filePath = path;
@@ -237,7 +188,6 @@ static inline bool path_is_valid(const std::filesystem::path& pathCheck)
std::error_code ec;
if (fs::is_directory(pathCheck, ec)) return false;
if (fs::exists(pathCheck, ec) && !fs::is_regular_file(pathCheck, ec)) return false;
fs::path parentDir = pathCheck.has_parent_path() ? pathCheck.parent_path() : fs::path(".");
if (!fs::is_directory(parentDir, ec)) return false;
@@ -248,16 +198,11 @@ static inline bool path_is_valid(const std::filesystem::path& pathCheck)
testStream.close();
if (!existedBefore && isValid)
fs::remove(pathCheck, ec); // cleanup if we created it
fs::remove(pathCheck, ec);
return isValid;
}
static inline const char* enum_to_string(const char* array[], s32 count, s32 index)
{
return (index >= 0 && index < count) ? array[index] : "";
};
static inline s32 string_to_enum(const std::string& string, const char* const* array, s32 n)
{
for (s32 i = 0; i < n; i++)
@@ -287,8 +232,10 @@ static inline s32 map_next_id_get(const std::map<s32, T>& map)
return id;
}
template<typename T>
static inline T* map_find(std::map<s32, T>& map, s32 id)
template <typename Map>
static inline auto map_find(Map& map, typename Map::key_type id)
-> typename Map::mapped_type*
{
if (auto it = map.find(id); it != map.end())
return &it->second;
@@ -343,11 +290,54 @@ static inline void map_insert_shift(std::map<int, T>& map, s32 index, const T& v
map[insertIndex] = value;
}
#define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \
static inline std::string function(s32 index) \
{ \
return enum_to_string(array, count, index); \
};
template <typename T>
void vector_value_erase(std::vector<T>& v, const T& value)
{
v.erase(std::remove(v.begin(), v.end(), value), v.end());
}
template <typename T>
void vector_value_swap(std::vector<T>& v, const T& a, const T& b)
{
for (auto& element : v)
{
if (element == a) element = b;
else if (element == b) element = a;
}
}
static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {})
{
vec2 scaleAbsolute = glm::abs(scale);
vec2 scaleSign = glm::sign(scale);
vec2 pivotScaled = pivot * scaleAbsolute;
vec2 sizeScaled = size * scaleAbsolute;
mat4 model(1.0f);
model = glm::translate(model, vec3(position - pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotScaled, 0.0f));
model = glm::scale(model, vec3(scaleSign, 1.0f));
model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1));
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
model = glm::scale(model, vec3(sizeScaled, 1.0f));
return model;
}
static inline mat4 quad_model_parent_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {})
{
vec2 scaleSign = glm::sign(scale);
vec2 scaleAbsolute = glm::abs(scale);
f32 handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f;
mat4 local(1.0f);
local = glm::translate(local, vec3(pivot, 0.0f));
local = glm::scale(local, vec3(scaleSign, 1.0f));
local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1));
local = glm::translate(local, vec3(-pivot, 0.0f));
local = glm::scale(local, vec3(scaleAbsolute, 1.0f));
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local;
}
#define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \
static inline enumType function(const std::string& string) \

View File

@@ -1,5 +1,3 @@
// Includes packed resources (i.e., textures, shaders)
#pragma once
#include "COMMON.h"
@@ -8,101 +6,108 @@ const u8 TEXTURE_ATLAS[] =
{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xa0,
0x02, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x1e, 0x81, 0x1f, 0x00, 0x00, 0x00,
0x04, 0x03, 0x00, 0x00, 0x00, 0x01, 0x5e, 0x74, 0xbf, 0x00, 0x00, 0x00,
0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b,
0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c,
0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60,
0x60, 0x60, 0x58, 0xe6, 0xdc, 0x65, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52,
0x4e, 0x53, 0x00, 0x00, 0x76, 0x93, 0xcd, 0x38, 0x00, 0x00, 0x04, 0x15,
0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xe5, 0xd3, 0xb1, 0x6a, 0x1c, 0x47,
0x1c, 0xc7, 0xf1, 0x2f, 0xb3, 0xc4, 0x2c, 0xe3, 0x63, 0xaf, 0x4b, 0x91,
0x6a, 0x49, 0xb5, 0xcc, 0x1d, 0xf6, 0x81, 0x1b, 0xb1, 0x36, 0x56, 0x1e,
0x21, 0x75, 0xde, 0xc0, 0x79, 0x8a, 0xc1, 0x95, 0x48, 0x61, 0x5c, 0x24,
0xfd, 0x20, 0x1c, 0x98, 0xfc, 0x77, 0xd1, 0x2d, 0x49, 0x63, 0x14, 0x81,
0xb6, 0x34, 0x41, 0xe4, 0x19, 0x5c, 0x8a, 0xb3, 0x09, 0x02, 0x37, 0xe1,
0x72, 0xf8, 0x92, 0x19, 0x5d, 0xb4, 0x91, 0x6e, 0x94, 0x20, 0xab, 0x48,
0xe1, 0x1f, 0x0c, 0x03, 0xf7, 0xe1, 0xfe, 0x33, 0xf3, 0xdf, 0x19, 0xae,
0x8f, 0x76, 0x5c, 0x44, 0x59, 0x28, 0x3a, 0x34, 0x00, 0x22, 0xe8, 0xd6,
0xb4, 0x00, 0x18, 0x0f, 0xda, 0xa1, 0xff, 0x60, 0x86, 0x72, 0xda, 0x96,
0x5a, 0x3c, 0x2c, 0x44, 0x89, 0xd8, 0x00, 0x63, 0x43, 0x89, 0x46, 0xbb,
0x92, 0x00, 0x22, 0x46, 0xc4, 0xc7, 0x52, 0xe6, 0x27, 0x87, 0xc6, 0xb8,
0xf8, 0x43, 0x66, 0x89, 0xb3, 0xb2, 0x68, 0x23, 0x8e, 0x0a, 0x5f, 0x4d,
0x61, 0x07, 0x65, 0x09, 0xa5, 0x06, 0x50, 0xae, 0x2a, 0x21, 0xaf, 0x94,
0x65, 0x77, 0xd1, 0xac, 0x36, 0xd0, 0x51, 0x69, 0x1b, 0x61, 0x52, 0xf4,
0xec, 0xbe, 0x3f, 0x5e, 0x51, 0xb7, 0xe8, 0xb7, 0x62, 0xa9, 0x0c, 0x11,
0x6a, 0x0d, 0xd9, 0x7a, 0xdd, 0x73, 0x3a, 0x41, 0xf3, 0x06, 0xb4, 0xc7,
0x05, 0xd0, 0x1a, 0x46, 0xeb, 0xd5, 0x49, 0xed, 0x74, 0xab, 0x37, 0x07,
0x8f, 0x50, 0x34, 0xf0, 0xe0, 0xc1, 0xbb, 0x99, 0xb1, 0xca, 0x07, 0x88,
0x07, 0x0c, 0xbb, 0x52, 0x1e, 0x38, 0x39, 0xe9, 0x2f, 0x00, 0x11, 0x6a,
0x91, 0x16, 0x07, 0x3c, 0x98, 0x51, 0xf7, 0x59, 0xab, 0xb7, 0x9a, 0x48,
0xcf, 0x6d, 0x53, 0xb7, 0xc3, 0x7c, 0x29, 0x22, 0x14, 0x22, 0xdd, 0x58,
0xe4, 0x0c, 0x86, 0xc4, 0x2d, 0xdf, 0x85, 0xd7, 0x85, 0xd3, 0xdd, 0xa5,
0x32, 0x7a, 0xbd, 0x74, 0x39, 0x94, 0xba, 0x1f, 0xb6, 0x48, 0x26, 0xd2,
0xd7, 0xeb, 0x45, 0x1b, 0xa0, 0xc6, 0xb4, 0x43, 0x99, 0xe3, 0xf7, 0x76,
0xb2, 0x6e, 0x4e, 0x03, 0x84, 0x6f, 0x73, 0x01, 0x0f, 0x7f, 0xfb, 0xf9,
0xed, 0xe4, 0xfd, 0xfc, 0x34, 0x57, 0xb6, 0xac, 0x94, 0x1b, 0xa0, 0xfd,
0x7c, 0xaf, 0x3e, 0x5d, 0x1e, 0x4f, 0xfe, 0x82, 0x59, 0xa5, 0xed, 0x00,
0x3b, 0xd3, 0x9d, 0xe9, 0xc4, 0x10, 0x4a, 0x51, 0x19, 0x06, 0x50, 0xda,
0xd9, 0xba, 0x59, 0xb7, 0x39, 0xda, 0xd5, 0x7e, 0x3c, 0x2c, 0x9e, 0x75,
0x45, 0xaf, 0xe7, 0x6b, 0x97, 0x73, 0xef, 0xa5, 0x76, 0xbb, 0x03, 0xb0,
0x68, 0x50, 0xf3, 0xa5, 0xbd, 0xcb, 0x8f, 0xcf, 0x0a, 0xb7, 0x3c, 0xde,
0x6e, 0x49, 0x3b, 0xdd, 0x19, 0x8b, 0x8c, 0x12, 0x4d, 0xd4, 0x8e, 0x7a,
0x7e, 0xc6, 0x7f, 0x47, 0xd9, 0x30, 0x92, 0xef, 0x23, 0x8c, 0x58, 0x4e,
0x7d, 0x53, 0xb9, 0x30, 0x80, 0x15, 0x18, 0x4f, 0x1c, 0x22, 0x94, 0x10,
0x07, 0x64, 0x2b, 0x62, 0x36, 0x17, 0xba, 0x54, 0xb2, 0x57, 0x42, 0x0e,
0xd3, 0x55, 0x28, 0x11, 0x41, 0xb7, 0xb5, 0x03, 0xd9, 0x23, 0xc2, 0xe1,
0x8c, 0xfc, 0xed, 0xc3, 0x32, 0x74, 0x36, 0x5c, 0xb4, 0x92, 0x85, 0x2d,
0x01, 0xd4, 0xa2, 0xa7, 0xea, 0x8a, 0xaa, 0x2a, 0xba, 0xca, 0xf4, 0xf8,
0x92, 0x4d, 0xa9, 0xbb, 0x8d, 0xc5, 0x3f, 0x7d, 0x6a, 0x44, 0x44, 0x0c,
0x78, 0xd4, 0x41, 0x2c, 0xc5, 0xbd, 0x03, 0x18, 0xc0, 0xfa, 0x52, 0xdb,
0x58, 0x8a, 0xc3, 0x97, 0x30, 0x94, 0xda, 0xf7, 0x46, 0xc8, 0x4b, 0xd4,
0x1e, 0x8b, 0xd7, 0x5c, 0x2c, 0x5e, 0x5b, 0x69, 0x8d, 0x27, 0x07, 0x65,
0xd5, 0xc4, 0x32, 0x6c, 0xd7, 0x8b, 0x33, 0x1e, 0x4a, 0x8c, 0xd7, 0xcf,
0x60, 0x38, 0xa0, 0x17, 0x1b, 0xfe, 0x11, 0xc1, 0xc1, 0xd0, 0x92, 0xf0,
0x8f, 0x4d, 0xa9, 0xc7, 0x16, 0x86, 0x26, 0x22, 0xde, 0xf8, 0x7d, 0xab,
0xab, 0x7d, 0xfb, 0x28, 0xd1, 0xf6, 0x9b, 0xc4, 0x02, 0x28, 0x20, 0x73,
0xc0, 0x08, 0x78, 0x04, 0xea, 0xd7, 0x4e, 0x9d, 0x83, 0x85, 0x79, 0x80,
0x57, 0xc0, 0x12, 0xb2, 0x43, 0xa7, 0xcf, 0xc1, 0x31, 0x69, 0xa1, 0xee,
0x3c, 0x64, 0x0b, 0x0b, 0x0b, 0x67, 0x44, 0xfa, 0x4c, 0xc4, 0xeb, 0x37,
0x90, 0xf9, 0xa9, 0x87, 0xb1, 0x74, 0x28, 0xdd, 0x89, 0x88, 0xd3, 0x22,
0xa2, 0x7b, 0x28, 0x7e, 0xbf, 0xdf, 0xc2, 0x63, 0x39, 0x23, 0x57, 0xbd,
0x39, 0x07, 0x3f, 0x9e, 0xcf, 0x5d, 0xbd, 0x9c, 0xf7, 0xf0, 0x4b, 0xd3,
0x53, 0x66, 0x9d, 0xb6, 0x9c, 0x4e, 0x50, 0x2e, 0x80, 0x91, 0x03, 0x4b,
0xb6, 0x3f, 0x07, 0xfb, 0xea, 0xb5, 0xb2, 0x54, 0x15, 0xca, 0x8e, 0x60,
0x56, 0xca, 0x01, 0x14, 0xfe, 0x08, 0x45, 0x09, 0xe7, 0xc0, 0x78, 0xbd,
0x3e, 0x2b, 0xe5, 0x10, 0x6a, 0x99, 0x91, 0x8f, 0x2c, 0x10, 0x00, 0x46,
0xcc, 0x66, 0x66, 0x7e, 0x08, 0x5a, 0x7a, 0x72, 0x05, 0x10, 0xd6, 0x20,
0x42, 0x75, 0xff, 0x25, 0x28, 0x81, 0x3c, 0x07, 0x54, 0xd8, 0x95, 0x8d,
0x80, 0x39, 0x05, 0x9a, 0x77, 0xa3, 0xc2, 0x0d, 0x10, 0xd6, 0xa0, 0x0e,
0xb0, 0x7b, 0xd2, 0x43, 0x80, 0x70, 0x72, 0xcb, 0x87, 0x45, 0x59, 0x00,
0x76, 0x12, 0x10, 0xa9, 0x45, 0x5b, 0x50, 0xad, 0xe9, 0xd1, 0x1d, 0x03,
0x28, 0x87, 0xf6, 0x57, 0xc1, 0x78, 0xd0, 0x16, 0x2d, 0x36, 0x01, 0x06,
0xb4, 0xf8, 0x01, 0x36, 0x04, 0x4d, 0x8f, 0x16, 0xb1, 0x01, 0xb4, 0x48,
0x17, 0x46, 0x5c, 0x23, 0x13, 0x09, 0xd0, 0x5e, 0x06, 0x40, 0x49, 0x13,
0xe0, 0xe8, 0x9f, 0x40, 0xcc, 0xd8, 0xa3, 0xa5, 0xe9, 0x03, 0x8c, 0x97,
0xc7, 0x67, 0xa1, 0x57, 0xf1, 0x5a, 0xc7, 0xed, 0xca, 0xd1, 0xa5, 0xc5,
0x8d, 0xff, 0x7b, 0xbb, 0x4d, 0x9f, 0x80, 0x1a, 0xf4, 0x11, 0x09, 0x98,
0xc2, 0xb8, 0x87, 0x6c, 0xb5, 0xdb, 0x33, 0x3e, 0x1b, 0x20, 0x95, 0xb8,
0xf8, 0xb5, 0x59, 0x6f, 0xd2, 0x43, 0x8c, 0x72, 0x0c, 0x10, 0x96, 0xd8,
0x82, 0x85, 0x48, 0xf3, 0x61, 0xc0, 0xc7, 0x02, 0x46, 0xd2, 0xa0, 0x44,
0x5c, 0x12, 0xf4, 0x53, 0xe7, 0xd3, 0xa0, 0x5c, 0x95, 0x84, 0x4a, 0x39,
0x7d, 0x03, 0x90, 0x8b, 0xdc, 0x1c, 0x6e, 0xb3, 0x78, 0xfa, 0x1c, 0xe9,
0x93, 0xa7, 0x7b, 0x95, 0xee, 0xee, 0xff, 0xff, 0xcd, 0x6b, 0x91, 0x36,
0x09, 0x85, 0x48, 0x97, 0x84, 0x4c, 0xa4, 0x4f, 0xbe, 0x0f, 0x16, 0x0d,
0x69, 0xa8, 0x5b, 0xd2, 0x50, 0x74, 0x49, 0x88, 0x3f, 0x6d, 0x3d, 0xe7,
0x54, 0x44, 0x48, 0xc6, 0x88, 0xfc, 0x40, 0x22, 0x9f, 0x0a, 0x88, 0x4d,
0xc0, 0xd7, 0x16, 0x94, 0x67, 0x2b, 0x9f, 0xc4, 0x32, 0x26, 0x51, 0xe9,
0x3b, 0x80, 0x3b, 0xcf, 0xb7, 0xe0, 0xab, 0x2f, 0x01, 0x78, 0xb1, 0x05,
0x4f, 0xbe, 0x00, 0xa0, 0xda, 0x82, 0xef, 0x89, 0xd1, 0x5c, 0xc9, 0x9d,
0x6f, 0x37, 0xb3, 0xbd, 0x0a, 0x9b, 0x55, 0xd5, 0x55, 0xf8, 0x8c, 0x4d,
0x5c, 0x02, 0x92, 0xdb, 0x7a, 0xc2, 0x26, 0xd5, 0x6d, 0x61, 0x28, 0xad,
0x6f, 0x0d, 0x0e, 0x50, 0xf6, 0xa6, 0x00, 0xb7, 0x82, 0xad, 0x73, 0x55,
0xb7, 0x85, 0xeb, 0xdb, 0xae, 0xaf, 0xfb, 0x50, 0xca, 0x0e, 0xf3, 0x65,
0x78, 0x4e, 0xfa, 0x32, 0xe0, 0xd2, 0xc7, 0x80, 0x6a, 0x98, 0x92, 0xdb,
0x7a, 0x01, 0x89, 0x8b, 0x95, 0xbc, 0xd4, 0x78, 0xd2, 0xcf, 0x00, 0x93,
0x7e, 0x38, 0xa0, 0x86, 0xa7, 0x96, 0x78, 0x9c, 0xff, 0xfe, 0x9c, 0xff,
0x04, 0xe2, 0xd8, 0x90, 0xc1, 0x18, 0xe1, 0xf4, 0x9b, 0x00, 0x00, 0x00,
0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c,
0x54, 0x45, 0x00, 0x00, 0x00, 0x76, 0x76, 0x76, 0xff, 0xff, 0xff, 0x60,
0x60, 0x60, 0xff, 0xff, 0xff, 0x3e, 0xd5, 0x47, 0x6d, 0x00, 0x00, 0x00,
0x03, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x00, 0xfa, 0x76, 0xc4, 0xde,
0x00, 0x00, 0x04, 0x62, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x59,
0x5b, 0x6e, 0x24, 0x37, 0x0c, 0x2c, 0x80, 0xbc, 0x40, 0xee, 0x90, 0x03,
0x10, 0x20, 0x0f, 0xc0, 0x80, 0x75, 0xff, 0x33, 0xe5, 0x83, 0xa2, 0x5a,
0x63, 0xcf, 0xae, 0xa7, 0x37, 0xb1, 0x03, 0x6c, 0xac, 0x0f, 0x77, 0x6b,
0x5a, 0xd5, 0x7c, 0x15, 0x49, 0xb5, 0x0c, 0xfc, 0xc2, 0x20, 0x9f, 0xff,
0xae, 0xd9, 0xd7, 0x28, 0x00, 0xe0, 0xb1, 0x9e, 0x04, 0x40, 0x9a, 0x92,
0x76, 0xac, 0x27, 0xf3, 0x78, 0x21, 0xbd, 0x00, 0x01, 0xa0, 0x04, 0x98,
0xd0, 0x46, 0x26, 0x00, 0x04, 0x49, 0xe8, 0x9e, 0x36, 0xc0, 0x95, 0x80,
0x2e, 0x51, 0x64, 0xdf, 0xee, 0x15, 0xa4, 0x92, 0x83, 0x58, 0x2a, 0x29,
0x4b, 0xd8, 0x00, 0x9d, 0x17, 0xf6, 0x02, 0xc9, 0x56, 0x68, 0xe6, 0x6d,
0x4b, 0xbf, 0xa2, 0x67, 0x4c, 0x4d, 0x35, 0xec, 0xb5, 0x89, 0x41, 0x24,
0x9e, 0x01, 0x94, 0xd0, 0xd4, 0xb6, 0x34, 0x35, 0x7b, 0x81, 0x78, 0x90,
0xe5, 0xf6, 0x08, 0x28, 0x40, 0x13, 0xcc, 0x13, 0x50, 0x08, 0x03, 0x20,
0xee, 0x51, 0xee, 0x06, 0x48, 0xfb, 0x8e, 0x61, 0x64, 0x02, 0x9a, 0x4a,
0x1c, 0x00, 0xe1, 0xf2, 0xb8, 0xb8, 0xbb, 0x1b, 0x80, 0x48, 0xad, 0xb6,
0x75, 0x59, 0xcf, 0x04, 0xda, 0x4b, 0x00, 0x12, 0x03, 0x30, 0x77, 0x37,
0x33, 0x21, 0x01, 0xd2, 0xae, 0xc0, 0x75, 0xd4, 0x2e, 0x40, 0xb0, 0x00,
0x40, 0x44, 0xc4, 0x45, 0xa0, 0x4c, 0x40, 0x99, 0x17, 0x60, 0x02, 0x37,
0x5e, 0xd2, 0x15, 0x5f, 0x98, 0x99, 0xd9, 0x13, 0xc0, 0xa2, 0x86, 0xb4,
0xdf, 0xed, 0x22, 0x8d, 0x88, 0x00, 0x10, 0x5a, 0xff, 0xe1, 0xc7, 0xe4,
0x83, 0xe1, 0x8b, 0xc6, 0xd0, 0x5c, 0xde, 0x48, 0x94, 0x07, 0x72, 0x5f,
0xf3, 0xa1, 0xf9, 0xdb, 0xe7, 0x93, 0x17, 0xc1, 0xa6, 0x80, 0x93, 0xa4,
0x5f, 0x9c, 0x92, 0xe5, 0x13, 0xbc, 0x71, 0x2f, 0x0a, 0x00, 0x02, 0x08,
0x02, 0xac, 0xcd, 0x5a, 0x09, 0xba, 0xb3, 0xec, 0x12, 0x45, 0x77, 0x27,
0x81, 0x26, 0x28, 0x40, 0xeb, 0x3c, 0x19, 0x40, 0x3f, 0xb7, 0x2d, 0x4a,
0xe8, 0x1e, 0xa4, 0x0d, 0x40, 0x3a, 0x4f, 0x6c, 0xab, 0xd4, 0xcf, 0x01,
0x68, 0xb9, 0x47, 0x42, 0xcb, 0x9d, 0x15, 0x39, 0x80, 0xc9, 0x93, 0xa1,
0xf9, 0x7e, 0x2e, 0x15, 0x51, 0x15, 0xa6, 0xe5, 0x41, 0x8f, 0x44, 0x42,
0xb3, 0x01, 0x4a, 0x68, 0x0e, 0xcd, 0xf7, 0x73, 0x9a, 0x6a, 0xa6, 0x30,
0xd2, 0x59, 0xae, 0xd5, 0x00, 0xd9, 0x79, 0x32, 0x34, 0xd7, 0x72, 0x96,
0x47, 0x42, 0x52, 0x4d, 0x52, 0x4d, 0x4b, 0x09, 0x8c, 0x4a, 0x57, 0x9e,
0xc0, 0xdc, 0x1d, 0x80, 0x66, 0xd0, 0xa1, 0x09, 0x28, 0x48, 0x24, 0x84,
0x2c, 0xf7, 0x65, 0x34, 0x09, 0x21, 0x13, 0x4e, 0x53, 0x29, 0xb1, 0x9e,
0x97, 0x39, 0x01, 0x48, 0x01, 0x61, 0x00, 0xe9, 0xee, 0xed, 0x56, 0x8d,
0x4a, 0x90, 0x10, 0xa7, 0x81, 0x66, 0x6b, 0x6e, 0xe2, 0xb4, 0x0e, 0x6d,
0x01, 0x50, 0xba, 0xaf, 0xc0, 0x95, 0x66, 0x20, 0x08, 0x67, 0x39, 0x28,
0xc7, 0x3c, 0xfd, 0x19, 0x35, 0x4c, 0x4d, 0xb2, 0xa9, 0x01, 0x53, 0x24,
0xd4, 0x24, 0x05, 0x24, 0xf9, 0xc0, 0xbf, 0x83, 0x5c, 0x64, 0xcf, 0xdd,
0xcf, 0x8c, 0xb9, 0xe6, 0xbf, 0xca, 0xf7, 0xb9, 0xbe, 0xdc, 0x1f, 0xd6,
0x75, 0xa9, 0xa6, 0x49, 0x4d, 0x5e, 0x57, 0x00, 0x40, 0x57, 0xc2, 0x29,
0xa1, 0x47, 0xed, 0xc6, 0x94, 0x9d, 0x7d, 0x5d, 0xc5, 0xcd, 0xde, 0xab,
0xb6, 0xd8, 0xa9, 0xfd, 0x86, 0x5d, 0x11, 0x01, 0x40, 0xcd, 0xed, 0xf4,
0xce, 0x2a, 0xba, 0xec, 0x6a, 0xd7, 0x45, 0x71, 0xcc, 0x4a, 0x00, 0x28,
0x0a, 0x80, 0x0c, 0x93, 0x52, 0x6c, 0x96, 0xae, 0xc2, 0xa5, 0x00, 0xa2,
0x25, 0x6d, 0x0d, 0x82, 0x06, 0x68, 0x16, 0x10, 0x9a, 0x9a, 0x88, 0x82,
0xa6, 0x76, 0xfa, 0xa5, 0x2e, 0x09, 0x87, 0x4a, 0xc5, 0x4a, 0x80, 0x99,
0x9a, 0xba, 0x12, 0x91, 0xa4, 0x76, 0x8c, 0xba, 0x09, 0xc5, 0xa9, 0x92,
0x06, 0x03, 0xcf, 0x01, 0xd9, 0x12, 0x98, 0x38, 0x55, 0x6a, 0x01, 0x4f,
0x55, 0xa2, 0x32, 0xb5, 0x99, 0xd5, 0x2a, 0x69, 0x26, 0xd0, 0x02, 0xde,
0x19, 0x2d, 0x4c, 0xb0, 0x7b, 0x76, 0x8e, 0x77, 0x3a, 0x6f, 0xb4, 0xf2,
0x0d, 0xe9, 0xc6, 0xad, 0xb9, 0xda, 0xf4, 0x14, 0x9c, 0xbe, 0x67, 0xc6,
0x73, 0x4e, 0x29, 0x93, 0xcc, 0x01, 0xe4, 0x05, 0xe0, 0xdb, 0x2d, 0xc2,
0xa6, 0xc6, 0x29, 0x61, 0xab, 0x24, 0x91, 0x3f, 0x22, 0x5f, 0xef, 0x08,
0x94, 0x4c, 0x6a, 0x02, 0xd4, 0xa4, 0xe6, 0x43, 0xe9, 0xbe, 0x4d, 0xef,
0x7f, 0x3a, 0x0e, 0x39, 0xba, 0xb3, 0xf7, 0xb1, 0x8b, 0x2d, 0xfd, 0xd4,
0xa2, 0x1e, 0x15, 0x5b, 0xf7, 0xf4, 0x01, 0x58, 0xff, 0xb0, 0xe6, 0x52,
0x5c, 0x3d, 0xe2, 0xec, 0x19, 0xd0, 0xea, 0x38, 0x09, 0x6b, 0x3d, 0x94,
0x58, 0x8b, 0x62, 0xf7, 0x83, 0x69, 0x03, 0x4c, 0xb0, 0x37, 0x0c, 0x10,
0xa6, 0x5a, 0x2f, 0xf4, 0xae, 0x64, 0x50, 0xb0, 0xb0, 0x38, 0x88, 0x7d,
0x33, 0xb5, 0x27, 0x3c, 0xb5, 0x2b, 0x9f, 0x04, 0xe9, 0x6d, 0xad, 0xda,
0x74, 0x9c, 0x01, 0x24, 0x9c, 0x4e, 0x27, 0x84, 0x4e, 0x76, 0x4a, 0x1b,
0xbb, 0x75, 0x69, 0xd7, 0xd9, 0xde, 0xb5, 0xf5, 0x0e, 0x46, 0x89, 0x01,
0x28, 0x49, 0x46, 0x02, 0x10, 0xea, 0x08, 0xb0, 0x8c, 0xe5, 0x19, 0x4d,
0x9d, 0xbd, 0x59, 0x1b, 0x0c, 0x05, 0xd9, 0xfc, 0x0e, 0xe6, 0x08, 0xd0,
0x1d, 0x87, 0x01, 0x00, 0x70, 0x77, 0x77, 0x87, 0x82, 0xac, 0x55, 0x41,
0x29, 0x23, 0xe0, 0x08, 0xc0, 0x0e, 0x87, 0x01, 0x02, 0x81, 0x92, 0x5e,
0xab, 0xc4, 0xb6, 0x1f, 0x1e, 0x0a, 0xc4, 0xb2, 0xe1, 0x04, 0xa4, 0x7a,
0x0d, 0xab, 0x97, 0x26, 0x79, 0xf1, 0x9d, 0xd7, 0x26, 0x76, 0x01, 0xba,
0x26, 0x74, 0xe0, 0xcb, 0x05, 0x86, 0x38, 0x13, 0xe4, 0x00, 0x2c, 0x1b,
0x00, 0x19, 0x80, 0xb8, 0xd9, 0x63, 0xc9, 0x9c, 0x48, 0x7f, 0x15, 0xdd,
0xf1, 0x3e, 0xbd, 0x24, 0x5f, 0x04, 0x0c, 0x68, 0x95, 0xd3, 0xb1, 0xc0,
0xba, 0xbe, 0x82, 0xf5, 0x03, 0xc0, 0x2a, 0xa7, 0xcc, 0x8f, 0x00, 0x57,
0x5d, 0x99, 0x52, 0xf1, 0x1a, 0x40, 0xb9, 0xdb, 0x74, 0xfe, 0x04, 0x70,
0xd4, 0xc6, 0xb2, 0x01, 0x74, 0x33, 0x69, 0x00, 0xd7, 0xde, 0x6f, 0xae,
0xdb, 0x06, 0xd9, 0x1f, 0x4d, 0x9d, 0x9b, 0x3f, 0x03, 0x6c, 0x22, 0xd4,
0x00, 0xea, 0xc7, 0x80, 0x63, 0x38, 0x73, 0x01, 0xca, 0x2e, 0x95, 0xdc,
0x59, 0xee, 0x57, 0x3e, 0x1c, 0x05, 0x79, 0xdc, 0xba, 0x32, 0xf5, 0xb9,
0xd1, 0xba, 0xea, 0xed, 0xe9, 0xd6, 0x36, 0xfe, 0x63, 0xc0, 0xaa, 0x83,
0x6b, 0xfd, 0x0b, 0x80, 0xfe, 0xd4, 0xc0, 0xec, 0x0a, 0xc4, 0x6d, 0xed,
0x10, 0x1e, 0x36, 0x3b, 0xa7, 0x4a, 0xaf, 0x91, 0xf5, 0xb1, 0x0b, 0xdd,
0x18, 0xfe, 0x66, 0xd8, 0xfb, 0x57, 0x3f, 0x07, 0x8c, 0x05, 0x1f, 0x02,
0x82, 0x13, 0xe2, 0xaf, 0x02, 0xe0, 0x1b, 0xf0, 0x7b, 0x00, 0xd6, 0xde,
0xef, 0x65, 0x80, 0xae, 0xae, 0xfd, 0x32, 0x80, 0xa9, 0x7c, 0xd8, 0x7e,
0x7c, 0x08, 0xe8, 0xcf, 0xbf, 0x97, 0x01, 0xfd, 0xbd, 0x78, 0x9e, 0x07,
0xfc, 0x7b, 0x00, 0x3e, 0x19, 0xff, 0x31, 0xe0, 0xf3, 0x8d, 0xfe, 0xc5,
0x38, 0xdc, 0x8f, 0xf4, 0x6d, 0x2e, 0xdd, 0x66, 0xeb, 0xef, 0x99, 0xd3,
0x73, 0x36, 0xf6, 0x32, 0x60, 0xce, 0x93, 0x5e, 0x07, 0xac, 0xd3, 0x9f,
0x97, 0xfb, 0xc3, 0x9c, 0x6b, 0xdc, 0x00, 0xf4, 0x97, 0xe2, 0x0d, 0x40,
0x9f, 0x59, 0xde, 0x01, 0x88, 0xe1, 0x5d, 0x37, 0xfd, 0x29, 0xe0, 0x7b,
0x7c, 0x8f, 0xaf, 0x19, 0x77, 0x3f, 0xb6, 0xf6, 0x3f, 0x15, 0x5e, 0x1c,
0xca, 0x9b, 0x04, 0x27, 0x70, 0xeb, 0xc0, 0x63, 0x8e, 0x1d, 0x3e, 0x0f,
0xb0, 0x56, 0xfe, 0x99, 0x77, 0x00, 0x7f, 0xdc, 0x05, 0xfc, 0xf5, 0xe9,
0x12, 0x6e, 0x01, 0x3e, 0xdf, 0xad, 0xb7, 0x23, 0x7d, 0x9f, 0x4b, 0xb7,
0xd9, 0x7a, 0x3f, 0x1f, 0xbe, 0xc7, 0xff, 0x75, 0xfc, 0x0d, 0x3b, 0xd4,
0xd5, 0x4b, 0x3b, 0xfe, 0xb6, 0x75, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
const u32 TEXTURE_ATLAS_LENGTH = (u32)std::size(TEXTURE_ATLAS);
@@ -120,8 +125,8 @@ enum AtlasType
ATLAS_INVISIBLE,
ATLAS_SHOW_RECT,
ATLAS_HIDE_RECT,
ATLAS_PLACEHOLDER,
ATLAS_PLACEHOLDER2,
ATLAS_SHOW_UNUSED,
ATLAS_HIDE_UNUSED,
ATLAS_PAN,
ATLAS_MOVE,
ATLAS_ROTATE,

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,11 @@
#define ANM2_FRAME_DELAY_MIN 1
#define ANM2_STRING_MAX 0xFF
#define ANM2_EMPTY_ERROR "No path given for anm2"
#define ANM2_READ_ERROR "Failed to read anm2 from file: {}"
#define ANM2_PARSE_ERROR "Failed to parse anm2: {} ({})"
#define ANM2_FRAME_PARSE_ERROR "Failed to parse frame: {} ({})"
#define ANM2_ANIMATION_PARSE_ERROR "Failed to parse frame: {} ({})"
#define ANM2_READ_INFO "Read anm2 from file: {}"
#define ANM2_WRITE_ERROR "Failed to write anm2 to file: {}"
#define ANM2_WRITE_INFO "Wrote anm2 to file: {}"
@@ -61,7 +65,6 @@ const inline char* ANM2_ELEMENT_STRINGS[] =
};
DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ELEMENT_STRING_TO_ENUM, Anm2Element, ANM2_ELEMENT_STRINGS, ANM2_ELEMENT_COUNT)
DEFINE_ENUM_TO_STRING_FUNCTION(ANM2_ELEMENT_ENUM_TO_STRING, ANM2_ELEMENT_STRINGS, ANM2_ELEMENT_COUNT)
#define ANM2_ATTRIBUTE_LIST \
X(CREATED_BY, "CreatedBy") \
@@ -118,7 +121,6 @@ static const char* ANM2_ATTRIBUTE_STRINGS[] =
};
DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ATTRIBUTE_STRING_TO_ENUM, Anm2Attribute, ANM2_ATTRIBUTE_STRINGS, ANM2_ATTRIBUTE_COUNT)
DEFINE_ENUM_TO_STRING_FUNCTION(ANM2_ATTRIBUTE_ENUM_TO_STRING, ANM2_ATTRIBUTE_STRINGS, ANM2_ATTRIBUTE_COUNT)
enum Anm2Type
{
@@ -140,7 +142,7 @@ struct Anm2Spritesheet
struct Anm2Layer
{
std::string name = "New Layer";
s32 spritesheetID = ID_NONE;
s32 spritesheetID{};
};
struct Anm2Null
@@ -198,7 +200,8 @@ struct Anm2Animation
std::string name = "New Animation";
bool isLoop = true;
Anm2Item rootAnimation;
std::map<s32, Anm2Item> layerAnimations;
std::unordered_map<s32, Anm2Item> layerAnimations;
std::vector<s32> layerOrder;
std::map<s32, Anm2Item> nullAnimations;
Anm2Item triggers;
};
@@ -213,7 +216,6 @@ struct Anm2
std::map<s32, Anm2Null> nulls;
std::map<s32, Anm2Event> events;
std::map<s32, Anm2Animation> animations;
std::map<s32, s32> layerMap; // index, id
s32 defaultAnimationID = ID_NONE;
s32 fps = ANM2_FPS_DEFAULT;
s32 version{};
@@ -225,27 +227,10 @@ struct Anm2Reference
Anm2Type itemType = ANM2_NONE;
s32 itemID = ID_NONE;
s32 frameIndex = INDEX_NONE;
f32 time = VALUE_NONE;
auto operator<=>(const Anm2Reference&) const = default;
};
struct Anm2AnimationWithID
{
s32 id;
Anm2Animation animation;
};
struct Anm2EventWithID
{
s32 id;
Anm2Event event;
};
struct Anm2FrameWithReference
{
Anm2Reference reference;
Anm2Frame frame;
};
enum Anm2MergeType
{
ANM2_MERGE_APPEND_FRAMES,
@@ -267,33 +252,42 @@ enum OnionskinDrawOrder
ONIONSKIN_ABOVE
};
void anm2_layer_add(Anm2* self);
void anm2_layer_remove(Anm2* self, s32 id);
void anm2_null_add(Anm2* self);
void anm2_null_remove(Anm2* self, s32 id);
bool anm2_serialize(Anm2* self, const std::string& path);
bool anm2_deserialize(Anm2* self, const std::string& path);
void anm2_new(Anm2* self);
void anm2_free(Anm2* self);
void anm2_created_on_set(Anm2* self);
s32 anm2_animation_add(Anm2* self);
void anm2_animation_remove(Anm2* self, s32 id);
Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, 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, Anm2Frame* frame, Anm2Reference* reference, s32 time = 0.0f);
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);
void anm2_reference_frame_clear(Anm2Reference* self);
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml);
bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true);
bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml);
bool anm2_serialize(Anm2* self, const std::string& path);
s32 anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, s32 id = ID_NONE);
s32 anm2_animation_length_get(Anm2Animation* self);
s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time);
s32 anm2_layer_add(Anm2* self);
s32 anm2_null_add(Anm2* self);
vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference* reference, bool isRootTransform);
void anm2_animation_layer_animation_add(Anm2Animation* animation, s32 id);
void anm2_animation_layer_animation_remove(Anm2Animation* animation, s32 id);
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_null_animation_add(Anm2Animation* animation, s32 id);
void anm2_animation_null_animation_remove(Anm2Animation* animation, s32 id);
void anm2_animation_remove(Anm2* self, s32 id);
void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string);
void anm2_created_on_set(Anm2* self);
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);
void anm2_scale(Anm2* self, f32 scale);
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_frame_remove(Anm2* self, Anm2Reference* reference);
void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string);
void anm2_free(Anm2* self);
void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay);
void anm2_spritesheet_texture_pixels_upload(Anm2* self);
void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count);
void anm2_layer_remove(Anm2* self, s32 id);
void anm2_new(Anm2* self);
void anm2_null_remove(Anm2* self, s32 id);
void anm2_reference_clear(Anm2Reference* self);
void anm2_reference_frame_clear(Anm2Reference* self);
void anm2_reference_item_clear(Anm2Reference* self);
void anm2_scale(Anm2* self, f32 scale);
void anm2_spritesheet_texture_pixels_download(Anm2* self);
void anm2_spritesheet_texture_pixels_upload(Anm2* self);

View File

@@ -197,6 +197,7 @@ void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform,
glUseProgram(0);
}
void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color)
{
vec4 originNDC = transform * vec4(0.0f, 0.0f, 0.0f, 1.0f);
@@ -241,36 +242,3 @@ void canvas_free(Canvas* self)
glDeleteBuffers(1, &self->textureVBO);
glDeleteBuffers(1, &self->textureEBO);
}
mat4 canvas_model_get(vec2 size, vec2 position, vec2 pivot, vec2 scale, f32 rotation)
{
vec2 scaleAbsolute = glm::abs(scale);
vec2 scaleSign = glm::sign(scale);
vec2 pivotScaled = pivot * scaleAbsolute;
vec2 sizeScaled = size * scaleAbsolute;
mat4 model(1.0f);
model = glm::translate(model, vec3(position - pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotScaled, 0.0f));
model = glm::scale(model, vec3(scaleSign, 1.0f));
model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1));
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
model = glm::scale(model, vec3(sizeScaled, 1.0f));
return model;
}
mat4 canvas_parent_model_get(vec2 position, vec2 pivot, vec2 scale, f32 rotation)
{
vec2 scaleSign = glm::sign(scale);
vec2 scaleAbsolute = glm::abs(scale);
f32 handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f;
mat4 local(1.0f);
local = glm::translate(local, vec3(pivot, 0.0f));
local = glm::scale(local, vec3(scaleSign, 1.0f));
local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1));
local = glm::translate(local, vec3(-pivot, 0.0f));
local = glm::scale(local, vec3(scaleAbsolute, 1.0f));
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local;
}

View File

@@ -65,6 +65,11 @@ struct Canvas
0, 1, uvMin.x, uvMax.y \
}
#define ATLAS_UV_MIN(type) (ATLAS_POSITION(type) / TEXTURE_ATLAS_SIZE)
#define ATLAS_UV_MAX(type) ((ATLAS_POSITION(type) + ATLAS_SIZE(type)) / TEXTURE_ATLAS_SIZE)
#define ATLAS_UV_ARGS(type) ATLAS_UV_MIN(type), ATLAS_UV_MAX(type)
#define ATLAS_UV_VERTICES(type) UV_VERTICES(ATLAS_UV_MIN(type), ATLAS_UV_MAX(type))
mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin);
void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color);
void canvas_bind(Canvas* self);
@@ -77,8 +82,6 @@ void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform,
void canvas_framebuffer_resize_check(Canvas* self);
void canvas_unbind(void);
void canvas_viewport_set(Canvas* self);
mat4 canvas_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {});
mat4 canvas_parent_model_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {});
void canvas_texture_draw
(

View File

@@ -1,94 +1,105 @@
#include "clipboard.h"
static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2)
{
switch (self->type)
{
case CLIPBOARD_FRAME:
{
Anm2FrameWithReference* frameWithReference = std::get_if<Anm2FrameWithReference>(&self->data);
if (!frameWithReference) break;
anm2_frame_erase(anm2, &frameWithReference->reference);
break;
}
case CLIPBOARD_ANIMATION:
{
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);
break;
}
}
break;
}
default:
break;
}
}
static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* location, Anm2* anm2)
{
switch (self->type)
{
case CLIPBOARD_FRAME:
{
Anm2FrameWithReference* frameWithReference = std::get_if<Anm2FrameWithReference>(&self->data);
Anm2Reference* reference = std::get_if<Anm2Reference>(location);
if (!frameWithReference || !reference) break;
if (frameWithReference->reference.itemType != reference->itemType) break;
Anm2Animation* animation = anm2_animation_from_reference(anm2, reference);
Anm2Item* anm2Item = anm2_item_from_reference(anm2, reference);
if (!animation || !anm2Item) break;
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, animationWithID->animation);
break;
}
default:
break;
}
}
void clipboard_copy(Clipboard* self)
{
self->item = self->hoveredItem;
std::string clipboardText{};
auto clipboard_text_set = [&]()
{
if (!SDL_SetClipboardText(clipboardText.c_str()))
log_warning(std::format(CLIPBOARD_TEXT_SET_WARNING, SDL_GetError()));
};
switch (self->type)
{
case CLIPBOARD_FRAME:
{
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location);
if (!reference) break;
Anm2Frame* frame = anm2_frame_from_reference(self->anm2, reference);
if (!frame) break;
anm2_frame_serialize_to_string(frame, reference->itemType, &clipboardText);
clipboard_text_set();
break;
}
case CLIPBOARD_ANIMATION:
{
s32* id = std::get_if<s32>(&self->location);
if (!id) break;
Anm2Animation* animation = map_find(self->anm2->animations, *id);
if (!animation) break;
anm2_animation_serialize_to_string(animation, &clipboardText);
clipboard_text_set();
break;
}
break;
default:
break;
}
}
void clipboard_cut(Clipboard* self)
{
self->item = self->hoveredItem;
_clipboard_item_remove(&self->item, self->anm2);
clipboard_copy(self);
switch (self->type)
{
case CLIPBOARD_FRAME:
{
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location);
if (!reference) break;
anm2_frame_remove(self->anm2, reference);
break;
}
case CLIPBOARD_ANIMATION:
{
s32* id = std::get_if<s32>(&self->location);
if (!id) break;
anm2_animation_remove(self->anm2, *id);
break;
}
default:
break;
}
}
void clipboard_paste(Clipboard* self)
bool clipboard_paste(Clipboard* self)
{
_clipboard_item_paste(&self->item, &self->location, self->anm2);
auto clipboard_string = [&]()
{
char* clipboardText = SDL_GetClipboardText();
std::string clipboardString = std::string(clipboardText);
SDL_free(clipboardText);
return clipboardString;
};
switch (self->type)
{
case CLIPBOARD_FRAME:
{
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location);
if (!reference) break;
Anm2Frame frame;
if (anm2_frame_deserialize_from_xml(&frame, clipboard_string()))
anm2_frame_add(self->anm2, &frame, reference);
else return false;
break;
}
case CLIPBOARD_ANIMATION:
{
s32* id = std::get_if<s32>(&self->location);
if (!id) break;
Anm2Animation animation;
if (anm2_animation_deserialize_from_xml(&animation, clipboard_string()))
anm2_animation_add(self->anm2, &animation, *id);
else return false;
break;
}
default:
break;
}
return true;
}
void clipboard_init(Clipboard* self, Anm2* anm2)
@@ -96,3 +107,7 @@ void clipboard_init(Clipboard* self, Anm2* anm2)
self->anm2 = anm2;
}
bool clipboard_is_value(void)
{
return SDL_HasClipboardText();
}

View File

@@ -2,21 +2,13 @@
#include "anm2.h"
enum ClipboardItemType
#define CLIPBOARD_TEXT_SET_WARNING "Unable to set clipboard text! ({})"
enum ClipboardType
{
CLIPBOARD_NONE,
CLIPBOARD_FRAME,
CLIPBOARD_ANIMATION,
};
struct ClipboardItem
{
std::variant<std::monostate, Anm2FrameWithReference, Anm2AnimationWithID, Anm2EventWithID> data = std::monostate();
ClipboardItemType type = CLIPBOARD_NONE;
ClipboardItem() = default;
ClipboardItem(const Anm2FrameWithReference& frame) : data(frame), type(CLIPBOARD_FRAME) {}
ClipboardItem(const Anm2AnimationWithID& animation) : data(animation), type(CLIPBOARD_ANIMATION) {}
CLIPBOARD_ANIMATION
};
using ClipboardLocation = std::variant<std::monostate, Anm2Reference, s32>;
@@ -24,12 +16,12 @@ using ClipboardLocation = std::variant<std::monostate, Anm2Reference, s32>;
struct Clipboard
{
Anm2* anm2 = nullptr;
ClipboardItem item;
ClipboardItem hoveredItem;
ClipboardType type;
ClipboardLocation location;
};
bool clipboard_is_value(void);
void clipboard_copy(Clipboard* self);
void clipboard_cut(Clipboard* self);
void clipboard_paste(Clipboard* self);
bool clipboard_paste(Clipboard* self);
void clipboard_init(Clipboard* self, Anm2* anm2);

View File

@@ -30,7 +30,7 @@ void editor_draw(Editor* self)
{
Texture& texture = spritesheet->texture;
mat4 spritesheetTransform = transform * canvas_model_get(texture.size);
mat4 spritesheetTransform = transform * quad_model_get(texture.size);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, spritesheetTransform);
if (self->settings->editorIsBorder)
@@ -40,10 +40,10 @@ void editor_draw(Editor* self)
if (frame)
{
mat4 cropTransform = transform * canvas_model_get(frame->size, frame->crop);
mat4 cropTransform = transform * quad_model_get(frame->size, frame->crop);
canvas_rect_draw(&self->canvas, shaderLine, cropTransform, EDITOR_FRAME_COLOR);
mat4 pivotTransform = transform * canvas_model_get(CANVAS_PIVOT_SIZE, frame->crop + frame->pivot, CANVAS_PIVOT_SIZE * 0.5f);
mat4 pivotTransform = transform * quad_model_get(CANVAS_PIVOT_SIZE, frame->crop + frame->pivot, CANVAS_PIVOT_SIZE * 0.5f);
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, EDITOR_PIVOT_COLOR);
}

View File

@@ -1,10 +1,5 @@
#include "ffmpeg.h"
static std::string ffmpeg_log_path_get(void)
{
return preferences_path_get() + FFMPEG_LOG_PATH;
}
bool
ffmpeg_render
(
@@ -35,13 +30,8 @@ ffmpeg_render
break;
}
// ffmpeg output will be piped into the log
std::string logOutput = " 2>> \"" + ffmpeg_log_path_get() + "\"";
#if _WIN32
command = string_quote(command) + logOutput;
#else
command += logOutput;
command = string_quote(command);
#endif
log_command(command);

View File

@@ -4,8 +4,6 @@
#include "texture.h"
#define FFMPEG_POPEN_ERROR "popen() (for FFmpeg) failed!\n{}"
#define FFMPEG_LOG_BUFFER_SIZE 256
#define FFMPEG_LOG_PATH "ffmpeg.txt"
static constexpr const char* FFMPEG_GIF_FORMAT =
"\"{0}\" -y "

View File

@@ -38,11 +38,12 @@ void generate_preview_draw(GeneratePreview* self)
const s32 column = index % columns;
vec2 crop = startPosition + vec2(size.x * column, size.y * row);
vec2 uvMin = crop / vec2(texture.size);
vec2 uvMax = (crop + size) / vec2(texture.size);
vec2 textureSize = vec2(texture.size);
vec2 uvMin = (crop + vec2(0.5f)) / textureSize;
vec2 uvMax = (crop + size - vec2(0.5f)) / textureSize;
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 generateTransform = transform * canvas_model_get(size, {}, pivot);
mat4 generateTransform = transform * quad_model_get(size, {}, pivot);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -59,19 +59,22 @@
#define IMGUI_TIMELINE_FRAME_MULTIPLE 5
#define IMGUI_TIMELINE_MERGE
#define IMGUI_TOOL_COLOR_PICKER_DURATION 0.25f
#define IMGUI_OPTION_POPUP_ROW_COUNT 2
#define IMGUI_CONFIRM_POPUP_ROW_COUNT 2
#define IMGUI_CHORD_REPEAT_TIME 0.25f
#define IMGUI_ACTION_FRAME_CROP "Frame Crop"
#define IMGUI_ACTION_FRAME_SWAP "Frame Swap"
#define IMGUI_ACTION_FRAME_TRANSFORM "Frame Transform"
#define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap"
#define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame"
#define IMGUI_ACTION_TRIGGER_MOVE "Trigger At Frame"
#define IMGUI_ACTION_FRAME_DELAY "Frame Delay"
#define IMGUI_ACTION_MOVE_PLAYHEAD "Move Playhead"
#define IMGUI_ACTION_DRAW "Draw"
#define IMGUI_ACTION_ERASE "Erase"
#define IMGUI_ACTION_RELOAD_SPRITESHEET "Reload Spritesheet(s)"
#define IMGUI_ACTION_MOVE "Move"
#define IMGUI_ACTION_SCALE "Scale"
#define IMGUI_ACTION_ROTATE "Rotate"
#define IMGUI_ACTION_CROP "Crop"
#define IMGUI_ACTION_ADD_SPRITESHEET "Add Spritesheet"
#define IMGUI_ACTION_REPLACE_SPRITESHEET "Replace Spritesheet"
#define IMGUI_ACTION_OPEN_FILE "Open File"
@@ -92,6 +95,9 @@
#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation."
#define IMGUI_LOG_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}"
#define IMGUI_LOG_DRAG_DROP_ERROR "Invalid file for dragging/dropping!"
#define IMGUI_LOG_ANIMATION_PASTE_ERROR "Failed to parse clipboard text as an animation."
#define IMGUI_LOG_FRAME_PASTE_ERROR "Failed to parse clipboard text as a frame."
#define IMGUI_LOG_RELOAD_SPRITESHEET "Reloaded spritesheet(s)."
#define IMGUI_NONE "None"
#define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}"
@@ -106,13 +112,16 @@
#define IMGUI_SPRITESHEET_FORMAT "#{} {}"
#define IMGUI_SPRITESHEET_ID_FORMAT "#{}"
#define IMGUI_TIMELINE_ITEM_CHILD_FORMAT "#{} {}"
#define IMGUI_LAYER_FORMAT "#{} {}"
#define IMGUI_NULL_FORMAT "#{} {}"
#define IMGUI_TIMELINE_FRAME_LABEL_FORMAT "## {}"
#define IMGUI_SELECTABLE_INPUT_INT_FORMAT "#{}"
#define IMGUI_TIMELINE_ANIMATION_NONE "Select an animation to show timeline..."
#define IMGUI_HOTKEY_CHANGE "Input new hotkey..."
#define IMGUI_LABEL_SHORTCUT_FORMAT "({})"
#define IMGUI_TOOLTIP_SHORTCUT_FORMAT "\n(Shortcut: {})"
#define IMGUI_LABEL_HOTKEY_FORMAT " ({})"
#define IMGUI_TOOLTIP_HOTKEY_FORMAT "\n(Hotkey: {})"
#define IMGUI_INVISIBLE_FORMAT "## {}"
#define IMGUI_RENDERING_FFMPEG_INFO_THRESHOLD 0.95f
#define IMGUI_TRIGGERS_FONT_SCALE 2.0
@@ -222,7 +231,7 @@ static void imgui_log_push(Imgui* self, const std::string& text)
log_imgui(text);
}
static inline void imgui_file_new(Imgui* self)
static inline void imgui_anm2_new(Imgui* self)
{
anm2_reference_clear(self->reference);
anm2_free(self->anm2);
@@ -245,6 +254,14 @@ static inline void imgui_file_save(Imgui* self)
}
}
static inline void imgui_file_new(Imgui* self)
{
std::string path = self->anm2->path;
imgui_anm2_new(self);
self->anm2->path = path;
imgui_file_save(self);
}
static inline void imgui_file_save_as(Imgui* self)
{
dialog_anm2_save(self->dialog);
@@ -334,7 +351,15 @@ static inline void imgui_copy(Imgui* self)
static inline void imgui_paste(Imgui* self)
{
clipboard_paste(self->clipboard);
if (!clipboard_paste(self->clipboard))
{
switch (self->clipboard->type)
{
case CLIPBOARD_FRAME: imgui_log_push(self, IMGUI_LOG_FRAME_PASTE_ERROR); break;
case CLIPBOARD_ANIMATION: imgui_log_push(self, IMGUI_LOG_ANIMATION_PASTE_ERROR); break;
default: break;
}
}
}
static inline void imgui_onionskin_toggle(Imgui* self)
@@ -520,10 +545,17 @@ static inline ImGuiKeyChord imgui_chord_from_string_get(const std::string& str)
return chord;
}
static void imgui_contextual_actions_enable(Imgui* self) { self->isContextualActionsEnabled = true; }
static void imgui_contextual_actions_disable(Imgui* self){ self->isContextualActionsEnabled = false; }
static inline void imgui_keyboard_nav_enable(void) { ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; }
static inline void imgui_keyboard_nav_disable(void) { ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; }
static inline void imgui_contextual_actions_enable(Imgui* self) { self->isContextualActionsEnabled = true; }
static inline void imgui_contextual_actions_disable(Imgui* self){ self->isContextualActionsEnabled = false; }
static inline bool imgui_is_popup_open(const std::string& label) { return ImGui::IsPopupOpen(label.c_str()); }
static inline bool imgui_is_any_popup_open(void)
{
return ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId);
}
static inline void imgui_open_popup(const std::string& label) { ImGui::OpenPopup(label.c_str()); }
static inline void imgui_pending_popup_process(Imgui* self)
{
@@ -584,7 +616,7 @@ enum ImguiItemType
IMGUI_DOCKSPACE,
IMGUI_CHILD,
IMGUI_TABLE,
IMGUI_OPTION_POPUP,
IMGUI_CONFIRM_POPUP,
IMGUI_SELECTABLE,
IMGUI_BUTTON,
IMGUI_RADIO_BUTTON,
@@ -611,6 +643,7 @@ struct ImguiItemOverride
AtlasType atlas = ATLAS_NONE;
bool isMnemonicDisabled{};
s32 value{};
s32 rowCount{};
};
struct ImguiItem;
@@ -656,7 +689,7 @@ struct ImguiItem
bool isSelected = false;
bool isUseItemActivated = false;
bool isSizeToText = false;
bool isShortcutInLabel = false;
bool isHotkeyInLabel = false;
bool isSameLine = false;
bool isSeparator = false;
s32 id = 0;
@@ -673,6 +706,23 @@ struct ImguiItem
s32 windowFlags{};
s32 rowCount = 0;
bool is_border() const { return border != 0; }
bool is_row() const { return rowCount != 0; }
bool is_hotkey() const { return hotkey != HOTKEY_NONE; }
bool is_chord() const { return chord != IMGUI_CHORD_NONE || is_hotkey(); }
bool is_drag_drop() const { return !dragDrop.empty(); }
bool is_focus_window() const { return !focusWindow.empty(); }
bool is_popup() const { return !popup.empty(); }
bool is_function() const { return function; }
bool is_size() const { return size != ImVec2(); }
bool is_popup_size() const { return popupSize != ImVec2(); }
bool is_tooltip() const { return !tooltip.empty(); }
bool is_undoable() const { return !snapshotAction.empty(); }
bool is_mnemonic() const { return mnemonicKey != ImGuiKey_None; }
bool is_range() const { return min != 0 || max != 0; }
const char* drag_drop_get() const { return dragDrop.c_str(); }
const char* text_get() const { return text.c_str(); }
void construct()
{
static s32 idNew = 0;
@@ -717,6 +767,7 @@ struct ImguiItem
if (override.size != ImVec2{}) out.size = override.size;
if (override.max != 0) out.max = override.max;
if (override.value != 0) out.value = override.value;
if (override.rowCount != 0) out.rowCount = override.rowCount;
if (override.atlas != ATLAS_NONE) out.atlas = override.atlas;
if (override.isMnemonicDisabled) out.isMnemonicDisabled = override.isMnemonicDisabled;
return out;
@@ -728,24 +779,21 @@ struct ImguiItem
return chord;
}
bool is_border() const { return border != 0; }
bool is_row() const { return rowCount != 0; }
bool is_hotkey() const { return hotkey != HOTKEY_NONE; }
bool is_chord() const { return chord != IMGUI_CHORD_NONE || is_hotkey(); }
bool is_drag_drop() const { return !dragDrop.empty(); }
bool is_focus_window() const { return !focusWindow.empty(); }
bool is_popup() const { return !popup.empty(); }
bool is_function() const { return function; }
bool is_size() const { return size != ImVec2(); }
bool is_popup_size() const { return popupSize != ImVec2(); }
bool is_tooltip() const { return !tooltip.empty(); }
bool is_undoable() const { return !snapshotAction.empty(); }
bool is_mnemonic() const { return mnemonicKey != ImGuiKey_None; }
bool is_range() const { return min != 0 || max != 0; }
const char* label_get() const { return label.c_str(); }
const char* drag_drop_get() const { return dragDrop.c_str(); }
const char* tooltip_get() const { return tooltip.c_str(); }
const char* text_get() const { return text.c_str(); }
std::string label_get() const
{
std::string newLabel = label;
if (isHotkeyInLabel)
newLabel += std::format(IMGUI_LABEL_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get()));
return newLabel;
}
std::string tooltip_get() const
{
std::string newTooltip = tooltip;
if (is_chord())
newTooltip += std::format(IMGUI_TOOLTIP_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get()));
return newTooltip;
}
};
#define IMGUI_ITEM(NAME, ...) const inline ImguiItem NAME = []{ ImguiItem self; __VA_ARGS__; self.construct(); return self; }()
@@ -797,7 +845,7 @@ IMGUI_ITEM(IMGUI_NEW,
self.function = imgui_file_new,
self.hotkey = HOTKEY_NEW,
self.isSizeToText = true,
self.isShortcutInLabel = true
self.isHotkeyInLabel = true
);
IMGUI_ITEM(IMGUI_OPEN,
@@ -806,7 +854,7 @@ IMGUI_ITEM(IMGUI_OPEN,
self.function = imgui_file_open,
self.hotkey = HOTKEY_OPEN,
self.isSizeToText = true,
self.isShortcutInLabel = true
self.isHotkeyInLabel = true
);
IMGUI_ITEM(IMGUI_SAVE,
@@ -815,7 +863,7 @@ IMGUI_ITEM(IMGUI_SAVE,
self.function = imgui_file_save,
self.hotkey = HOTKEY_SAVE,
self.isSizeToText = true,
self.isShortcutInLabel = true
self.isHotkeyInLabel = true
);
IMGUI_ITEM(IMGUI_SAVE_AS,
@@ -824,7 +872,7 @@ IMGUI_ITEM(IMGUI_SAVE_AS,
self.function = imgui_file_save_as,
self.hotkey = HOTKEY_SAVE_AS,
self.isSizeToText = true,
self.isShortcutInLabel = true
self.isHotkeyInLabel = true
);
IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION,
@@ -841,7 +889,7 @@ IMGUI_ITEM(IMGUI_EXIT,
self.function = imgui_quit,
self.hotkey = HOTKEY_EXIT,
self.isSizeToText = true,
self.isShortcutInLabel = true
self.isHotkeyInLabel = true
);
IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION,
@@ -854,6 +902,11 @@ IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION,
self.text = "Unsaved changes will be lost!\nAre you sure you open a new file?"
);
IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION,
self.label = "No Anm2 Path",
self.text = "You will need to load or make a new .anm2 file first!\n"
);
IMGUI_ITEM(IMGUI_WIZARD,
self.label = "&Wizard",
self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.",
@@ -962,7 +1015,7 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE,
self.label = "Generate",
self.tooltip = "Generate an animation with the used settings.",
self.snapshotAction = "Generate Animation from Grid",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT,
self.isSameLine = true
);
@@ -1042,7 +1095,7 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL,
);
IMGUI_ITEM(IMGUI_SCALE_ANM2,
self.label = "&Scale Anm2",
self.label = "S&cale Anm2",
self.tooltip = "Scale up all size and position-related frame properties in the anm2.",
self.popup = "Scale Anm2",
self.popupType = IMGUI_POPUP_CENTER_WINDOW,
@@ -1069,7 +1122,7 @@ IMGUI_ITEM(IMGUI_SCALE_ANM2_SCALE,
self.label = "Scale",
self.tooltip = "Scale the anm2 with the value specified.",
self.snapshotAction = "Scale Anm2",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT,
self.isSameLine = true
);
@@ -1077,12 +1130,20 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION,
self.label = "&Render Animation",
self.tooltip = "Renders the current animation preview; output options can be customized.",
self.popup = "Render Animation",
self.popupSize = {600, 125}
self.popupSize = {600, 150},
self.popupType = IMGUI_POPUP_CENTER_WINDOW
);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD,
self.label = "## Render Animation Child",
self.size = {600, 125}
self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_RENDER_ANIMATION.popupSize.y - IMGUI_FOOTER_CHILD.size.y},
self.flags = true
);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD,
self.label = "## Render Animation Footer Child",
self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_FOOTER_CHILD.size.y},
self.flags = true
);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE,
@@ -1115,8 +1176,7 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT,
self.label = "Output",
self.tooltip = "Select the rendered animation output.\nIt can either be one animated image or a sequence of frames.",
self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)},
self.value = RENDER_PNG,
self.isSeparator = true
self.value = RENDER_PNG
);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT,
@@ -1132,9 +1192,16 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM,
self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {300, 60},
self.isSameLine = true,
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT
);
IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CHILD,
self.label = "##Rendering Child",
self.size = {400.0f, 65.0f},
self.flags = true
);
IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_INFO, self.label = "Recording frames. Once done, the program may halt\nas FFmpeg renders the animation. Please be patient!");
IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL,
self.label = "Cancel",
self.tooltip = "Cancel rendering the animation.",
@@ -1218,6 +1285,77 @@ IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS,
self.isSizeToText = true
);
IMGUI_ITEM(IMGUI_LAYERS,
self.label = "Layers",
self.flags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_LAYERS_CHILD, self.label = "## Layers Child", self.flags = true);
IMGUI_ITEM(IMGUI_LAYER,
self.label = "## Layer Item",
self.dragDrop = "## Layer Drag Drop",
self.atlas = ATLAS_LAYER,
self.idOffset = 3000
);
IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID,
self.label = "## Spritesheet ID",
self.tooltip = "Change the spritesheet ID this layer uses.",
self.atlas = ATLAS_SPRITESHEET,
self.size = {50, 0}
);
#define IMGUI_LAYERS_OPTIONS_ROW_COUNT 2
IMGUI_ITEM(IMGUI_LAYER_ADD,
self.label = "Add",
self.tooltip = "Adds a new layer.",
self.snapshotAction = "Add Layer",
self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_LAYER_REMOVE,
self.label = "Remove",
self.tooltip = "Removes the selected layer.\nThis will remove all layer animations that use this layer from all animations.",
self.snapshotAction = "Remove Layer",
self.chord = ImGuiKey_Delete,
self.focusWindow = IMGUI_LAYERS.label,
self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT
);
IMGUI_ITEM(IMGUI_NULLS,
self.label = "Nulls",
self.flags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_NULLS_CHILD, self.label = "## Nulls Child", self.flags = true);
IMGUI_ITEM(IMGUI_NULL,
self.label = "## Null Item",
self.dragDrop = "## Null Drag Drop",
self.atlas = ATLAS_NULL,
self.idOffset = 4000
);
#define IMGUI_NULLS_OPTIONS_ROW_COUNT 2
IMGUI_ITEM(IMGUI_NULL_ADD,
self.label = "Add",
self.tooltip = "Adds a null layer.",
self.snapshotAction = "Add Null",
self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_NULL_REMOVE,
self.label = "Remove",
self.tooltip = "Removes the selected null.\nThis will remove all null animations that use this null from all animations.",
self.snapshotAction = "Remove Null",
self.chord = ImGuiKey_Delete,
self.focusWindow = IMGUI_NULLS.label,
self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT
);
IMGUI_ITEM(IMGUI_ANIMATIONS,
self.label = "Animations",
self.flags = ImGuiWindowFlags_NoScrollbar |
@@ -1227,7 +1365,6 @@ IMGUI_ITEM(IMGUI_ANIMATIONS_CHILD, self.label = "## Animations Child", self.flag
IMGUI_ITEM(IMGUI_ANIMATION,
self.label = "## Animation Item",
self.snapshotAction = "Select Animation",
self.dragDrop = "## Animation Drag Drop",
self.atlas = ATLAS_ANIMATION,
self.idOffset = 2000
@@ -1314,7 +1451,7 @@ IMGUI_ITEM(IMGUI_MERGE_CONFIRM,
self.label = "Merge",
self.tooltip = "Merge the selected animations with the options set.",
self.snapshotAction = "Merge Animations",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT,
self.isSameLine = true
);
@@ -1400,15 +1537,15 @@ IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD,
#define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3
IMGUI_ITEM(IMGUI_SPRITESHEET_ADD,
self.label = "Add",
self.tooltip = "Select an image to add as a spritesheet.",
self.tooltip = "Select a .png image to add as a spritesheet.",
self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_SPRITESHEETS_RELOAD,
self.label = "Reload",
self.tooltip = "Reload the selected spritesheet.",
self.snapshotAction = "Reload Spritesheet",
self.tooltip = "Reload the selected spritesheet(s).",
self.snapshotAction = "Reload Spritesheet(s)",
self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT,
self.isSameLine = true
);
@@ -1450,7 +1587,9 @@ const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {230, 85};
IMGUI_ITEM(IMGUI_CANVAS_GRID_CHILD,
self.label = "## Canvas Grid Child",
self.size = IMGUI_CANVAS_CHILD_SIZE,
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_CANVAS_GRID,
@@ -1485,7 +1624,9 @@ IMGUI_ITEM(IMGUI_CANVAS_GRID_OFFSET,
IMGUI_ITEM(IMGUI_CANVAS_VIEW_CHILD,
self.label = "## View Child",
self.size = IMGUI_CANVAS_CHILD_SIZE,
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_CANVAS_ZOOM,
@@ -1497,12 +1638,12 @@ IMGUI_ITEM(IMGUI_CANVAS_ZOOM,
self.value = CANVAS_ZOOM_DEFAULT
);
IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD,
self.label = "## Animation Preview Visual Child",
self.size = IMGUI_CANVAS_CHILD_SIZE,
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_CANVAS_BACKGROUND_COLOR,
@@ -1526,7 +1667,9 @@ IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY,
IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD,
self.label = "## Animation Preview Helper Child",
self.size = IMGUI_CANVAS_CHILD_SIZE,
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_CANVAS_AXES,
@@ -1558,10 +1701,10 @@ IMGUI_ITEM(IMGUI_CANVAS_PIVOTS,
self.value = SETTINGS_PREVIEW_IS_PIVOTS_DEFAULT
);
IMGUI_ITEM(IMGUI_CANVAS_TARGETS,
self.label = "Targets",
self.tooltip = "Toggles drawing the targets (the colored root/null icons).",
self.value = SETTINGS_PREVIEW_IS_TARGETS_DEFAULT
IMGUI_ITEM(IMGUI_CANVAS_ICONS,
self.label = "Icons",
self.tooltip = "Toggles drawing the the colored root/null icons.",
self.value = SETTINGS_PREVIEW_IS_ICONS_DEFAULT
);
IMGUI_ITEM(IMGUI_CANVAS_ALT_ICONS,
@@ -1581,12 +1724,22 @@ IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW,
self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse
);
#define IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT 2
IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW,
self.label = "Center View",
self.tooltip = "Centers the current view on the animation preview.",
self.hotkey = HOTKEY_CENTER_VIEW,
self.focusWindow = IMGUI_ANIMATION_PREVIEW.label,
self.size = {-FLT_MIN, 0}
self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_FIT,
self.label = "Fit",
self.tooltip = "Adjust the view/pan based on the size of the animation, to fit the canvas' size.",
self.hotkey = HOTKEY_FIT,
self.focusWindow = IMGUI_ANIMATION_PREVIEW.label,
self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT
);
IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR,
@@ -1594,12 +1747,22 @@ IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR,
self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse
);
#define IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT 2
IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW,
self.label = "Center View",
self.tooltip = "Centers the current view on the spritesheet editor.",
self.hotkey = HOTKEY_CENTER_VIEW,
self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label,
self.size = {-FLT_MIN, 0}
self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_FIT,
self.label = "Fit",
self.tooltip = "Adjust the view/pan based on the size of the spritesheet, to fit the canvas' size.",
self.hotkey = HOTKEY_FIT,
self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label,
self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT
);
IMGUI_ITEM(IMGUI_FRAME_PROPERTIES, self.label = "Frame Properties");
@@ -1672,12 +1835,12 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET,
self.value = 0
);
const ImVec2 IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE = {75, 0};
#define IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT 2
IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_X,
self.label = "Flip X",
self.tooltip = "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly.)",
self.snapshotAction = "Frame Flip X",
self.size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE,
self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT,
self.isSameLine = true
);
@@ -1685,7 +1848,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_Y,
self.label = "Flip Y",
self.tooltip = "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly.)",
self.snapshotAction = "Frame Flip Y",
self.size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE
self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT
);
IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_VISIBLE,
@@ -1700,9 +1863,16 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_INTERPOLATED,
self.label = "Interpolation",
self.tooltip = "Toggles the interpolation of the selected frame.",
self.snapshotAction = "Frame Interpolation",
self.isSameLine = true,
self.value = true
);
IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROUND,
self.label = "Round",
self.tooltip = "Values will be rounded to the nearest integer.",
self.value = SETTINGS_PROPERTIES_IS_ROUND_DEFAULT
);
IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_EVENT,
self.label = "Event",
self.tooltip = "Change the event the trigger uses.",
@@ -1727,7 +1897,7 @@ IMGUI_ITEM(IMGUI_TOOL_PAN,
IMGUI_ITEM(IMGUI_TOOL_MOVE,
self.label = "## Move",
self.tooltip = "Use the move tool.\nWill move the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)",
self.tooltip = "Use the move tool.\nWhen in animation preview, will move the position of the frame.\nWhen in spritesheet editor, will move the pivot instead.\nUse mouse or directional keys to change the value.",
self.function = imgui_tool_move_set,
self.hotkey = HOTKEY_MOVE,
self.atlas = ATLAS_MOVE
@@ -1751,7 +1921,7 @@ IMGUI_ITEM(IMGUI_TOOL_SCALE,
IMGUI_ITEM(IMGUI_TOOL_CROP,
self.label = "## Crop",
self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\n(Spritesheet Editor only.)",
self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\nAlternatively, you can use the arrow keys and Ctrl/Shift to move the size/position, respectively.\n(Spritesheet Editor only.)",
self.function = imgui_tool_crop_set,
self.hotkey = HOTKEY_CROP,
self.atlas = ATLAS_CROP
@@ -1907,7 +2077,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SELECTABLE,
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE,
self.label = "Root",
self.tooltip = "The root item of an animation.\nChanging its properties will transform the rest of the animation.",
self.snapshotAction = "Root Item Select",
self.atlas = ATLAS_ROOT,
self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE
);
@@ -1915,7 +2084,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE,
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE,
self.label = "## Layer Selectable",
self.tooltip = "A layer item.\nA graphical item within the animation.",
self.snapshotAction = "Layer Item Select",
self.dragDrop = "## Layer Drag Drop",
self.atlas = ATLAS_LAYER,
self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE
@@ -1924,7 +2092,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE,
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE,
self.label = "## Null Selectable",
self.tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.",
self.snapshotAction = "Null Item Select",
self.dragDrop = "## Null Drag Drop",
self.atlas = ATLAS_NULL,
self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE
@@ -1933,7 +2100,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE,
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE,
self.label = "Triggers",
self.tooltip = "The animation's triggers.\nWill fire based on an event.",
self.snapshotAction = "Triggers Item Select",
self.atlas = ATLAS_TRIGGERS,
self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE
);
@@ -1947,6 +2113,20 @@ const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT]
&IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE
};
IMGUI_ITEM(IMGUI_TIMELINE_SHOW_UNUSED,
self.label = "## Show Unused",
self.tooltip = "Layers/nulls without any frames will be hidden.",
self.snapshotAction = "Hide Unused",
self.atlas = ATLAS_SHOW_UNUSED
);
IMGUI_ITEM(IMGUI_TIMELINE_HIDE_UNUSED,
self.label = "## Hide Unused",
self.tooltip = "Layers/nulls without any frames will be shown.",
self.snapshotAction = "Show Unused",
self.atlas = ATLAS_HIDE_UNUSED
);
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_VISIBLE,
self.label = "## Visible",
self.tooltip = "The item is visible.\nPress to set to invisible.",
@@ -1975,12 +2155,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_HIDE_RECT,
self.atlas = ATLAS_HIDE_RECT
);
IMGUI_ITEM(IMGUI_TIMELINE_SPRITESHEET_ID,
self.label = "## Spritesheet ID",
self.tooltip = "Change the spritesheet ID this item uses.",
self.atlas = ATLAS_SPRITESHEET,
self.size = {32, 0}
);
IMGUI_ITEM(IMGUI_TIMELINE_FRAMES_CHILD,
self.label = "## Timeline Frames Child",
@@ -1998,7 +2172,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_FRAME, self.label = "## Frame");
static const vec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.25f};
IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME,
self.label = "## Root Frame",
self.snapshotAction = "Root Frame Select",
self.color = {{0.14f, 0.27f, 0.39f, 1.0f}, {0.28f, 0.54f, 0.78f, 1.0f}, {0.36f, 0.70f, 0.95f, 1.0f}, IMGUI_FRAME_BORDER_COLOR},
self.size = IMGUI_TIMELINE_FRAME_SIZE,
self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET,
@@ -2007,7 +2180,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME,
IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME,
self.label = "## Layer Frame",
self.snapshotAction = "Layer Frame Select",
self.dragDrop = "## Layer Frame Drag Drop",
self.color = {{0.45f, 0.18f, 0.07f, 1.0f}, {0.78f, 0.32f, 0.12f, 1.0f}, {0.95f, 0.40f, 0.15f, 1.0f}, IMGUI_FRAME_BORDER_COLOR},
self.size = IMGUI_TIMELINE_FRAME_SIZE,
@@ -2017,7 +2189,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME,
IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME,
self.label = "## Null Frame",
self.snapshotAction = "Null Frame Select",
self.dragDrop = "## Null Frame Drag Drop",
self.color = {{0.17f, 0.33f, 0.17f, 1.0f}, {0.34f, 0.68f, 0.34f, 1.0f}, {0.44f, 0.88f, 0.44f, 1.0f}, IMGUI_FRAME_BORDER_COLOR},
self.size = IMGUI_TIMELINE_FRAME_SIZE,
@@ -2027,7 +2198,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME,
IMGUI_ITEM(IMGUI_TIMELINE_TRIGGERS_FRAME,
self.label = "## Triggers Frame",
self.snapshotAction = "Trigger Select",
self.color = {{0.36f, 0.14f, 0.24f, 1.0f}, {0.72f, 0.28f, 0.48f, 1.0f}, {0.92f, 0.36f, 0.60f, 1.0f}, IMGUI_FRAME_BORDER_COLOR},
self.size = IMGUI_TIMELINE_FRAME_SIZE,
self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET,
@@ -2046,43 +2216,76 @@ const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT]
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FOOTER_CHILD,
self.label = "## Item Footer Child",
self.size = {IMGUI_TIMELINE_ITEM_CHILD.size.x, IMGUI_FOOTER_CHILD.size.y},
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
IMGUI_ITEM(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD,
self.label = "## Options Footer Child",
self.size = {0, IMGUI_FOOTER_CHILD.size.y},
self.flags = true
self.flags = true,
self.windowFlags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
);
#define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM,
self.label = "Add",
self.tooltip = "Adds an item (layer or null) to the animation.",
self.popup = "## Add Item Popup",
self.popupType = IMGUI_POPUP_BY_ITEM,
self.tooltip = "Adds an item (layer or null) to the animation.\nMake sure to add a Layer/Null first in the Layers or Nulls windows.",
self.popup = "Add Item",
self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {300, 350},
self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD,
self.label = "## Add Item Type Child",
self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 35},
self.flags = true
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_LAYER,
self.label = "Layer",
self.tooltip = "Adds a layer item.\nA layer item is a primary graphical item, using a spritesheet.",
self.snapshotAction = "Add Layer",
self.isSizeToText = true
self.isSizeToText = true,
self.value = ANM2_LAYER,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_NULL,
self.label = "Null",
self.tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by the game engine.",
self.snapshotAction = "Add Null",
self.isSizeToText = true
self.tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by a game engine.",
self.isSizeToText = true,
self.value = ANM2_NULL
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD,
self.label = "## Add Item Items",
self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 250},
self.flags = true
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_OPTIONS_CHILD,
self.label = "## Add Item Options Child",
self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 35},
self.flags = true
);
IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_ADD,
self.label = "Add",
self.tooltip = "Add the selected item.",
self.snapshotAction = "Add Animation",
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_TIMELINE_REMOVE_ITEM,
self.label = "Remove",
self.tooltip = "Removes the selected item (layer or null) from the animation.",
self.snapshotAction = "Remove Item",
self.focusWindow = IMGUI_TIMELINE.label,
self.chord = ImGuiKey_Delete,
self.focusWindow = IMGUI_TIMELINE_ITEMS_CHILD.label,
self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT
);
@@ -2161,7 +2364,7 @@ IMGUI_ITEM(IMGUI_BAKE_CONFIRM,
self.label = "Bake",
self.tooltip = "Bake the selected frame with the options selected.",
self.snapshotAction = "Bake Frames",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT,
self.isSameLine = true
);
@@ -2221,13 +2424,13 @@ IMGUI_ITEM(IMGUI_ONIONSKIN_ENABLED,
self.isSeparator = true
);
IMGUI_ITEM(IMGUI_ONIONSKIN_BEFORE, self.label = "-- Before-- ");
IMGUI_ITEM(IMGUI_ONIONSKIN_BEFORE, self.label = "-- Before -- ");
IMGUI_ITEM(IMGUI_ONIONSKIN_AFTER, self.label = "-- After -- ");
IMGUI_ITEM(IMGUI_ONIONSKIN_COUNT,
self.label = "Count",
self.tooltip = "Set the number of previewed frames appearing.",
self.min = 1,
self.min = 0,
self.max = 100,
self.value = SETTINGS_ONIONSKIN_BEFORE_COUNT_DEFAULT,
self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT,
@@ -2303,17 +2506,17 @@ IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT,
self.step = 0
);
#define IMGUI_OPTION_POPUP_ROW_COUNT 2
#define IMGUI_CONFIRM_POPUP_ROW_COUNT 2
IMGUI_ITEM(IMGUI_POPUP_OK,
self.label = "OK",
self.tooltip = "Confirm the action.",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT
);
IMGUI_ITEM(IMGUI_POPUP_CANCEL,
self.label = "Cancel",
self.tooltip = "Cancel the action.",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT
self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT
);
IMGUI_ITEM(IMGUI_LOG_WINDOW,

View File

@@ -4,7 +4,7 @@ static bool _anm2_rescale(const std::string& file, f32 scale)
{
Anm2 anm2;
if (!anm2_deserialize(&anm2, file)) return false;
if (!anm2_deserialize(&anm2, file, false)) return false;
anm2_scale(&anm2, scale);
return anm2_serialize(&anm2, file);
}
@@ -35,9 +35,19 @@ main(s32 argc, char* argv[])
return EXIT_FAILURE;
}
else if (std::string(argv[1]) == ARGUMENT_TEST && argv[2])
{
if (anm2_deserialize(&state.anm2, std::string(argv[2]), false)) return EXIT_SUCCESS;
return EXIT_FAILURE;
}
else if (std::string(argv[1]) == ARGUMENT_TEST_GL && argv[2])
{
if (!sdl_init(&state, true)) return EXIT_FAILURE;
if (anm2_deserialize(&state.anm2, std::string(argv[2]))) return EXIT_SUCCESS;
return EXIT_FAILURE;
}
else
if (argv[1])
state.argument = argv[1];
if (argv[1]) state.argument = argv[1];
}
init(&state);

View File

@@ -2,6 +2,9 @@
#include <SDL3/SDL_main.h>
#define ARGUMENT_TEST "--test"
#define ARGUMENT_TEST_GL "--test-gl"
#define ARGUMENT_RESCALE "--rescale"
#define ARGUMENT_RESCALE_ARGUMENT_ERROR "--rescale: specify both anm2 and scale arguments"
#define ARGUMENT_RESCALE_ANM2_ERROR "Unable to rescale anm2 {} by value {}. Make sure the file is valid."

View File

@@ -40,7 +40,7 @@ void preview_tick(Preview* self)
glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data());
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
texture_from_rgba_init(&frameTexture, size, TEXTURE_CHANNELS, framebufferPixels.data());
texture_from_rgba_init(&frameTexture, size, framebufferPixels.data());
self->renderFrames.push_back(frameTexture);
}
@@ -105,7 +105,7 @@ void preview_draw(Preview* self)
auto root_draw = [&](Anm2Frame root, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {})
{
mat4 model = canvas_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, PERCENT_TO_UNIT(root.scale), root.rotation);
mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, PERCENT_TO_UNIT(root.scale), root.rotation);
mat4 rootTransform = transform * model;
vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_ROOT_COLOR;
AtlasType atlas = self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET;
@@ -122,7 +122,7 @@ void preview_draw(Preview* self)
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time);
if (!frame.isVisible) return;
mat4 model = canvas_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 layerTransform = transform * (rootModel * model);
vec3 frameColorOffset = frame.offsetRGB + colorOffset;
vec4 frameTint = frame.tintRGBA;
@@ -134,8 +134,9 @@ void preview_draw(Preview* self)
Texture& texture = spritesheet->texture;
if (texture.isInvalid) return;
vec2 uvMin = frame.crop / vec2(texture.size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size);
vec2 inset = 0.5f / vec2(texture.size);
vec2 uvMin = frame.crop / vec2(texture.size) + inset;
vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset;
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frameTint, frameColorOffset);
@@ -149,7 +150,7 @@ void preview_draw(Preview* self)
{
vec4 pivotColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_PIVOT_COLOR;
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
mat4 pivotModel = canvas_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 pivotTransform = transform * (rootModel * pivotModel);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, pivotColor);
}
@@ -173,7 +174,7 @@ void preview_draw(Preview* self)
vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE;
AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET;
mat4 model = canvas_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 model = quad_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 nullTransform = transform * (rootModel * model);
f32 vertices[] = ATLAS_UV_VERTICES(atlas);
@@ -182,7 +183,7 @@ void preview_draw(Preview* self)
if (null.isShowRect)
{
mat4 rectModel = canvas_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 rectModel = quad_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 rectTransform = transform * (rootModel * rectModel);
canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color);
}
@@ -194,15 +195,15 @@ void preview_draw(Preview* self)
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time);
mat4 rootModel = self->settings->previewIsRootTransform ?
canvas_parent_model_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f);
quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f);
if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible)
if (self->settings->previewIsIcons && animation->rootAnimation.isVisible && root.isVisible)
root_draw(root, colorOffset, alphaOffset, isOnionskin);
for (auto [i, id] : self->anm2->layerMap)
for (auto id : animation->layerOrder)
layer_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin);
if (self->settings->previewIsTargets)
if (self->settings->previewIsIcons)
for (auto& [id, _] : animation->nullAnimations)
null_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin);
};

View File

@@ -42,8 +42,6 @@ struct Preview
f32 time{};
};
void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings);
void preview_draw(Preview* self);
void preview_tick(Preview* self);

View File

@@ -2,7 +2,7 @@
void resources_init(Resources* self)
{
texture_from_encoded_data_init(&self->atlas, TEXTURE_ATLAS_SIZE, TEXTURE_CHANNELS, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH);
texture_from_encoded_data_init(&self->atlas, TEXTURE_ATLAS_SIZE, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH);
for (s32 i = 0; i < SHADER_COUNT; i++)
shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment);

View File

@@ -20,12 +20,19 @@
#define SETTINGS_PATH "settings.ini"
#define SETTINGS_TEMPORARY_EXTENSION ".tmp"
#ifdef _WIN32
#define SETTINGS_FFMPEG_PATH_VALUE_DEFAULT "C:\\ffmpeg\\bin\\ffmpeg.exe"
#else
#define SETTINGS_FFMPEG_PATH_VALUE_DEFAULT "/usr/bin/ffmpeg"
#endif
#define SETTINGS_LIST \
/* name, symbol, type, defaultValue */ \
X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1280, 720}) \
X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1600, 900}) \
X(isVsync, IS_VSYNC, TYPE_BOOL, true) \
\
X(hotkeyCenterView, HOTKEY_CENTER_VIEW, TYPE_STRING, "Home") \
X(hotkeyFit, HOTKEY_FIT, TYPE_STRING, "F") \
X(hotkeyZoomIn, HOTKEY_ZOOM_IN, TYPE_STRING, "Ctrl++") \
X(hotkeyZoomOut, HOTKEY_ZOOM_OUT, TYPE_STRING, "Ctrl+-") \
X(hotkeyPlayPause, HOTKEY_PLAY_PAUSE, TYPE_STRING, "Space") \
@@ -84,7 +91,7 @@
X(previewIsRootTransform, PREVIEW_IS_ROOT_TRANSFORM, TYPE_BOOL, false) \
X(previewIsTriggers, PREVIEW_IS_TRIGGERS, TYPE_BOOL, true) \
X(previewIsPivots, PREVIEW_IS_PIVOTS, TYPE_BOOL, false) \
X(previewIsTargets, PREVIEW_IS_TARGETS, TYPE_BOOL, true) \
X(previewIsIcons, PREVIEW_IS_ICONS, TYPE_BOOL, true) \
X(previewIsBorder, PREVIEW_IS_BORDER, TYPE_BOOL, false) \
X(previewIsAltIcons, PREVIEW_IS_ALT_ICONS, TYPE_BOOL, false) \
X(previewOverlayTransparency,PREVIEW_OVERLAY_TRANSPARENCY,TYPE_FLOAT, 255.0f) \
@@ -96,6 +103,8 @@
X(previewAxesColor, PREVIEW_AXES_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \
X(previewBackgroundColor, PREVIEW_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \
\
X(propertiesIsRound, PROPERTIES_IS_ROUND, TYPE_BOOL, true) \
\
X(generateStartPosition, GENERATE_START_POSITION, TYPE_IVEC2, {}) \
X(generateSize, GENERATE_SIZE, TYPE_IVEC2, {64,64}) \
X(generatePivot, GENERATE_PIVOT, TYPE_IVEC2, {32,32}) \
@@ -121,10 +130,13 @@
X(bakeIsRoundScale, BAKE_IS_ROUND_SCALE, TYPE_BOOL, true) \
X(bakeIsRoundRotation, BAKE_IS_ROUND_ROTATION, TYPE_BOOL, true) \
\
X(timelineAddItemType, TIMELINE_ADD_ITEM_TYPE, TYPE_INT, ANM2_LAYER) \
X(timelineIsShowUnused, TIMELINE_IS_SHOW_UNUSED, TYPE_BOOL, true) \
\
X(onionskinIsEnabled, ONIONSKIN_IS_ENABLED, TYPE_BOOL, false) \
X(onionskinDrawOrder, ONIONSKIN_DRAW_ORDER, TYPE_INT, ONIONSKIN_BELOW) \
X(onionskinBeforeCount, ONIONSKIN_BEFORE_COUNT, TYPE_INT, 1) \
X(onionskinAfterCount, ONIONSKIN_AFTER_COUNT, TYPE_INT, 1) \
X(onionskinBeforeCount, ONIONSKIN_BEFORE_COUNT, TYPE_INT, 0) \
X(onionskinAfterCount, ONIONSKIN_AFTER_COUNT, TYPE_INT, 0) \
X(onionskinBeforeColorOffset,ONIONSKIN_BEFORE_COLOR_OFFSET,TYPE_VEC3, COLOR_RED) \
X(onionskinAfterColorOffset, ONIONSKIN_AFTER_COLOR_OFFSET,TYPE_VEC3, COLOR_BLUE) \
\
@@ -134,7 +146,7 @@
X(renderType, RENDER_TYPE, TYPE_INT, RENDER_PNG) \
X(renderPath, RENDER_PATH, TYPE_STRING, ".") \
X(renderFormat, RENDER_FORMAT, TYPE_STRING, "{}.png") \
X(ffmpegPath, FFMPEG_PATH, TYPE_STRING, "")
X(ffmpegPath, FFMPEG_PATH, TYPE_STRING, SETTINGS_FFMPEG_PATH_VALUE_DEFAULT)
#define X(name, symbol, type, ...) \
const inline DATATYPE_TO_CTYPE(type) SETTINGS_##symbol##_DEFAULT = __VA_ARGS__;
@@ -169,6 +181,7 @@ constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES);
#define HOTKEY_LIST \
X(NONE, "None") \
X(CENTER_VIEW, "Center View") \
X(FIT, "Fit") \
X(ZOOM_IN, "Zoom In") \
X(ZOOM_OUT, "Zoom Out") \
X(PLAY_PAUSE, "Play/Pause") \
@@ -213,6 +226,7 @@ const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] =
{
nullptr,
&Settings::hotkeyCenterView,
&Settings::hotkeyFit,
&Settings::hotkeyZoomIn,
&Settings::hotkeyZoomOut,
&Settings::hotkeyPlayPause,
@@ -251,66 +265,86 @@ Collapsed=0
[Window][Tools]
Pos=8,40
Size=39,612
Size=38,516
Collapsed=0
DockId=0x0000000B,0
[Window][Animations]
Pos=1288,301
Size=304,351
Pos=1289,307
Size=303,249
Collapsed=0
DockId=0x0000000A,0
[Window][Events]
Pos=1005,353
Size=281,299
Pos=957,264
Size=330,292
Collapsed=0
DockId=0x00000008,0
DockId=0x00000008,2
[Window][Spritesheets]
Pos=1288,40
Size=304,259
Pos=1289,40
Size=303,265
Collapsed=0
DockId=0x00000009,0
[Window][Animation Preview]
Pos=49,40
Size=954,612
Pos=48,40
Size=907,516
Collapsed=0
DockId=0x0000000C,0
[Window][Spritesheet Editor]
Pos=49,40
Size=954,612
Pos=48,40
Size=907,516
Collapsed=0
DockId=0x0000000C,1
[Window][Timeline]
Pos=8,654
Size=1584,238
Pos=8,558
Size=1584,334
Collapsed=0
DockId=0x00000004,0
[Window][Frame Properties]
Pos=1005,40
Size=281,311
Pos=957,40
Size=330,222
Collapsed=0
DockId=0x00000007,0
[Window][Onionskin]
Pos=957,264
Size=330,292
Collapsed=0
DockId=0x00000008,3
[Window][Layers]
Pos=957,264
Size=330,292
Collapsed=0
DockId=0x00000008,0
[Window][Nulls]
Pos=957,264
Size=330,292
Collapsed=0
DockId=0x00000008,1
[Docking][Data]
DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1584,852 Split=Y
DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,612 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1278,1016 Split=X Selected=0x024430EF
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=995,654 Split=X Selected=0x024430EF
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=39,654 Selected=0x18A5FDB9
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=954,654 CentralNode=1 Selected=0x024430EF
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=281,654 Split=Y Selected=0x754E368F
DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,311 Selected=0x754E368F
DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,299 Selected=0x8A65D963
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=304,1016 Split=Y Selected=0x4EFD0020
DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,259 Selected=0x4EFD0020
DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,351 Selected=0xC1986EE2
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,238 Selected=0x4F89F0DC
DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,680 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1017,1016 Split=X Selected=0x024430EF
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1264,654 Split=X Selected=0x024430EF
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=38,654 Selected=0x18A5FDB9
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=1224,654 CentralNode=1 Selected=0x024430EF
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=330,654 Split=Y Selected=0x754E368F
DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,293 Selected=0x754E368F
DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,385 Selected=0xCD8384B1
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=303,1016 Split=Y Selected=0x4EFD0020
DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,349 Selected=0x4EFD0020
DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,329 Selected=0xC1986EE2
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,334 Selected=0x4F89F0DC
)";
void settings_save(Settings* self);

View File

@@ -22,18 +22,16 @@ static void _draw(State* self)
SDL_GL_SwapWindow(self->window);
}
void init(State* self)
bool sdl_init(State* self, bool isTestMode = false)
{
settings_init(&self->settings);
if (!SDL_Init(SDL_INIT_VIDEO))
{
log_error(std::format(STATE_SDL_INIT_ERROR, SDL_GetError()));
quit(self);
return;
return false;
}
log_info(STATE_SDL_INIT_INFO);
if (!isTestMode) log_info(STATE_SDL_INIT_INFO);
// Todo, when sdl3 mixer is released officially
/*
@@ -61,13 +59,35 @@ void init(State* self)
log_info(STATE_MIX_INIT_INFO);
*/
self->window = SDL_CreateWindow
(
WINDOW_TITLE,
self->settings.windowSize.x,
self->settings.windowSize.y,
WINDOW_FLAGS
);
if (isTestMode)
{
self->window = SDL_CreateWindow
(
WINDOW_TITLE,
WINDOW_TEST_MODE_SIZE.x, WINDOW_TEST_MODE_SIZE.y,
WINDOW_TEST_MODE_FLAGS
);
}
else
{
ivec2 windowSize = self->settings.windowSize;
// Fix for auto-fullscreen on Windows
if (SDL_DisplayID* displayIDs = SDL_GetDisplays(nullptr))
if (displayIDs[0])
if (const SDL_DisplayMode* displayMode = SDL_GetDesktopDisplayMode(displayIDs[0]))
if (windowSize.x == displayMode->w && windowSize.y == displayMode->h)
windowSize -= ivec2(1, 1);
self->window = SDL_CreateWindow
(
WINDOW_TITLE,
windowSize.x,
windowSize.y,
WINDOW_FLAGS
);
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, STATE_GL_VERSION_MAJOR);
@@ -79,17 +99,17 @@ void init(State* self)
{
log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError()));
quit(self);
return;
return false;
}
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
log_error(std::format(STATE_GLAD_INIT_ERROR));
quit(self);
return;
return false;
}
log_info(std::format(STATE_GL_CONTEXT_INIT_INFO, (const char*)glGetString(GL_VERSION)));
if (!isTestMode) log_info(std::format(STATE_GL_CONTEXT_INIT_INFO, (const char*)glGetString(GL_VERSION)));
window_vsync_set(self->settings.isVsync);
@@ -100,6 +120,17 @@ void init(State* self)
glDisable(GL_DEPTH_TEST);
glDisable(GL_LINE_SMOOTH);
return true;
}
void init(State* self)
{
log_info(STATE_INIT_INFO);
settings_init(&self->settings);
if (!sdl_init(self)) return;
if (!self->argument.empty())
{
anm2_deserialize(&self->anm2, self->argument);

View File

@@ -2,7 +2,7 @@
#include "imgui.h"
#define STATE_INIT_INFO "Initializing..."
#define STATE_INIT_INFO "Initializing anm2ed (Version 1.1)"
#define STATE_SDL_INIT_ERROR "Failed to initialize SDL! {}"
#define STATE_SDL_INIT_INFO "Initialized SDL"
#define STATE_MIX_INIT_WARNING "Unable to initialize SDL_mixer! {}"
@@ -51,6 +51,7 @@ struct State
bool isRunning = true;
};
bool sdl_init(State* self, bool isTestMode);
void init(State* state);
void loop(State* state);
void quit(State* state);

View File

@@ -17,8 +17,7 @@
static void _texture_gl_set(Texture* self, const u8* data)
{
if (self->id == GL_ID_NONE)
glGenTextures(1, &self->id);
if (self->id == GL_ID_NONE) glGenTextures(1, &self->id);
glBindTexture(GL_TEXTURE_2D, self->id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
@@ -35,6 +34,7 @@ std::vector<u8> texture_download(const Texture* self)
glBindTexture(GL_TEXTURE_2D, self->id);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
return pixels;
@@ -42,16 +42,12 @@ std::vector<u8> texture_download(const Texture* self)
bool texture_from_path_init(Texture* self, const std::string& path)
{
u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS);
if (!data)
{
data = stbi_load(path_canonical_resolve(path).c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
if (!data)
{
log_error(std::format(TEXTURE_INIT_ERROR, path));
return false;
}
log_error(std::format(TEXTURE_INIT_ERROR, path));
return false;
}
self->isInvalid = false;
@@ -63,13 +59,12 @@ bool texture_from_path_init(Texture* self, const std::string& path)
return true;
}
bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, const u8* data, u32 length)
bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length)
{
*self = Texture{};
self->size = size;
self->channels = channels;
u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS);
if (!textureData)
{
@@ -82,11 +77,11 @@ bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, con
return true;
}
bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* data)
bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data)
{
*self = Texture{};
self->size = size;
self->channels = channels;
self->isInvalid = false;
_texture_gl_set(self, data);
@@ -96,13 +91,8 @@ 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 isSuccess = stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
if (!isSuccess)
{
isSuccess = stbi_write_png(path_canonical_resolve(path).c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
if (!isSuccess) log_info(std::format(TEXTURE_SAVE_ERROR, path));
}
log_info(std::format(TEXTURE_SAVE_INFO, path));
if (!isSuccess) log_error(std::format(TEXTURE_SAVE_ERROR, path));
else log_info(std::format(TEXTURE_SAVE_INFO, path));
return isSuccess;
}

View File

@@ -12,16 +12,14 @@ struct Texture
{
GLuint id = GL_ID_NONE;
ivec2 size{};
s32 channels = TEXTURE_CHANNELS;
bool isInvalid = true;
};
bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, const u8* data, u32 length);
bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length);
bool texture_from_gl_write(Texture* self, const std::string& path);
bool texture_from_path_init(Texture* self, const std::string& path);
bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* data);
bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data);
bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size);
bool texture_pixel_set(Texture* self, ivec2 position, vec4 color);
void texture_free(Texture* self);
std::vector<u8> texture_download(const Texture* self);
Texture texture_copy(Texture* self);

View File

@@ -5,6 +5,9 @@
#define WINDOW_TITLE "Anm2Ed"
#define WINDOW_TITLE_FORMAT "Anm2Ed ({})"
#define WINDOW_FLAGS SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL
#define WINDOW_TEST_MODE_FLAGS WINDOW_FLAGS | SDL_WINDOW_HIDDEN
static const ivec2 WINDOW_TEST_MODE_SIZE = {1, 1};
void window_title_from_path_set(SDL_Window* self, const std::string& path);
bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color);

View File

@@ -1,6 +1,5 @@
{
"dependencies": [
"sdl3",
"glew"
"sdl3"
]
}

48
workshop/metadata.xml Normal file
View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<name>Anm2Ed (Animation Editor)</name>
<directory>anm2ed</directory>
<id>3567731616</id>
<description>
[h1]NOTE: THIS IS NOT A MOD! SUBSCRIBING TO THIS ISN'T NECESSARY (though it's appreciated)! It's recommended you head to the download page below![/h1]
[h2]Anm2Ed[/h2]
[img]https://files.catbox.moe/3bj6za.png[/img]
A reimplementation of [i]The Binding of Isaac: Rebirth[/i]'s proprietary animation editor. Manipulates the XML-based &quot;.anm2&quot; format, used for in-game tweened animations.
[h2]Features[/h2]
[list]
[*] Extended version of the original proprietary Nicalis animation editor
[*] Smooth [url=https://github.com/ocornut/imgui]Dear ImGui[/url] interface; docking, dragging and dropping, etc. You might be familiar with it from [url=https://steamcommunity.com/sharedfiles/filedetails/?id=3127536138]Repentogon[/url].
[*] New to this editor:
[list]
[*] Can output .webm. .mp4 or *.png sequence (with FFmpeg)
[*] Cutting, copying and pasting frames/animations
[*] Additional wizard options
[*] Robust snapshot (undo/redo) system
[*] Additional hotkeys/shortcuts (rebindable!)
[*] Onionskinning
[*] Settings that will preserve on exit (stored in %APPDATA% on Windows or ~/.local/share on Linux)
[/list]
[/list]
[h3]Note: Difference from Nicalis editor[/h3]
Layers/nulls are handled differently (in their own window) and are not on the timeline! Add a layer/null in the Layers/Nulls window, and then add a LayerAnimation/NullAnimation in the timeline!
[h3]Note on Rendering Animations[/h3]
You will need FFmpeg installed! Get it from [url=https://ffmpeg.org/download.html]here[/url] and point to the downloaded ffmpeg executable within the program!
[h2]Download (Windows)[/h2]
https://github.com/ShweetsStuff/anm2ed/releases
Extract with 7z and run .exe. Check that you have the latest [url=https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170]Microsoft C++ redistributable[/url] (if you play a lot of games on Steam, you likely will have it).
Alternatively, if you have subscribed to the mod, you can find the latest release inside the &quot;resources&quot; folder. Once downloaded, you can put it wherever you want.
[h3]Happy animating![/h3]
[img]https://files.catbox.moe/4auc1c.gif[/img]
</description>
<version>1.1</version>
<visibility>Public</visibility>
</metadata>

BIN
workshop/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB